WireGuardExt enruta solo el tráfico del navegador por un túnel WireGuard. Lo consigue con tres piezas:
- Extensión MV3 — configura el proxy del navegador (
chrome.proxy.settings) para apuntar a un SOCKS5 local, y ofrece la UI. - Native messaging host (Go) — recibe órdenes de la extensión por stdin/stdout, arranca/para wireproxy en el mismo proceso.
- wireproxy — cliente WireGuard userspace (sin root, sin TUN del kernel) que expone el SOCKS5 y cifra el tráfico hacia el peer remoto.
Navegador ──(tráfico web)──▶ SOCKS5 (127.0.0.1:8869)
│
▼
wireproxy (userspace)
│ crypto Noise (WireGuard)
▼
UDP al endpoint del peer ──▶ Internet
El resto de aplicaciones del sistema no pasa por el proxy del navegador, por lo que sus conexiones quedan sin modificar.
- El usuario selecciona un perfil en el popup y pulsa Conectar.
- La extensión envía
{command:"start", profile:{...}}al host por Native Messaging. - El host construye la
Configurationde wireproxy en memoria (sin ficheros temporales). - El host llama a
wireproxy.StartWireguard(conf, logLevel)→ arranca el túnel y la rutina SOCKS5. - El host responde
{type:"started", socksPort:8869}. - La extensión aplica
chrome.proxy.settings→fixed_serverscon esquemasocks5apuntando a127.0.0.1:8869. - A partir de aquí, todo el tráfico HTTP/HTTPS del navegador pasa por el túnel.
La desconexión invierte el proceso: {command:"stop"} → el host detiene wireproxy → la extensión restaura el proxy a direct.
La motivación inicial era ejecutar wireproxy directamente dentro de la extensión, compilado a WebAssembly. No es viable por un bloqueante arquitectónico:
- WireGuard transporta su criptografía Noise en paquetes UDP hacia el endpoint del peer. Es intrínseco al protocolo.
- Las WebExtensions MV3 no exponen sockets UDP ni TCP crudos. Solo
fetch(HTTP request/response) yWebSocket. chrome.sockets.udppertenecía a Chrome Apps, una plataforma deprecada; no está disponible en extensiones MV3. (Chromium issue 40068590)- Compilar Go/WireGuard a WASM no sortea el sandbox del navegador: el runtime caería al intentar abrir el socket UDP. (WebAssembly/design#1251)
El único caso real de wireguard-go corriendo en WASM dentro de un navegador es el NetBird Browser Client, pero resuelve un caso de uso distinto (red P2P corporativa con señalización propia) y no es aplicable como proxy VPN hacia un endpoint WireGuard arbitrario. (NetBird docs)
Conclusión: se necesita un proceso nativo con acceso a sockets. Native Messaging es el mecanismo estándar de las extensiones para interactuar con procesos nativos, y wireproxy al ser userspace no requiere root ni interfaz de red del kernel. La combinación cumple el objetivo: VPN solo para el navegador, sin privilegios.
wireproxy expone una API pública en Go que permite integrarlo en el mismo proceso del host (no como subproceso):
StartWireguard(conf *Configuration, logLevel int) (*VirtualTun, error)— arranca el túnel.Configuration{Device *DeviceConfig, Routines []RoutineSpawner}— config en memoria.Socks5Config{BindAddress, Username, Password}implementaRoutineSpawnery levanta el listener SOCKS5.
Esto da un único binario y control directo del ciclo de vida.
Socks5Config.SpawnRoutine usa server.ListenAndServe que es bloqueante y no expone el net.Listener (además llama log.Fatal si falla el accept, lo que mataría el host entero). Para tener un Stop real, el host no usa las rutinas de wireproxy. En su lugar:
- Crea él mismo el
net.ListenerTCP (net.Listen("tcp", "127.0.0.1:port")). - Construye un
socks5.Servercon el dial y resolver delVirtualTun(vt.Tnet.DialContext,vtcomo resolver). - Lo sirve en una goroutine con
server.Serve(ln). - En
Stop: cierra el listener (detiene el accept) y llamavt.Dev.Close()(cierra los binds UDP y detiene las goroutines de wireguard-go).
Así la desconexión es limpia: el navegador deja de enrutar por el SOCKS5 y el túnel WireGuard se cierra de verdad.
En MV3, el service worker puede ser terminado por el navegador en cualquier momento. Para mantener la conectividad:
- wireproxy corre dentro del host, no dentro del SW. Si el SW muere, el host (y el túnel) siguen vivos mientras el navegador mantenga el port nativo o el proceso host siga ejecutándose.
- Al revivir el SW (
onStartup/ carga), este reconsultastatusal host. Si wireproxy seguía activo, reaplica el proxy hacia el puerto SOCKS5 que sigue escuchando. Si no estaba activo, limpia el estado. - El proxy del navegador (
chrome.proxy.settings) persiste independientemente del SW, así que no se pierde la configuración de enrutamiento.
- Private keys: se guardan en
chrome.storage.local(cifrado a nivel de perfil de Chrome) y solo se envían al host en el comandostart. No se loguean. - Native Messaging: el manifest restringe
allowed_originsal ID concreto de la extensión. Ningún otro origen puede comunicarse con el host. - SOCKS5 sin credenciales: escucha en
127.0.0.1, por lo que solo procesos locales pueden usarlo. No se expone a la red.
- Una VPN activa a la vez. Cambiar de perfil detiene el túnel actual (cierra listener + device) y arranca el nuevo.
- No hay hot-swap en wireproxy: el cambio implica reiniciar el túnel.
- DNS: con esquema
socks5, la resolución DNS se hace a través del túnel (evita fugas). El DNS configurado en el perfil (Interface.DNS) lo usa wireproxy internamente.