Cómo enrutar el tráfico del host a través de un contenedor Docker con VPN
Un contenedor VPN es un espacio de nombres de red, no un proxy. Elija un netns compartido, un auténtico oyente SOCKS o una puerta de enlace enrutada, y luego verifique la ruta de salida.
Su contenedor de túnel funciona, docker compose exec vpn curl -s https://ifconfig.me responde con la dirección del proveedor y, aun así, el navegador de su escritorio sale por su ISP. Esa brecha es esperable. Un contenedor que ejecuta un cliente VPN mantiene un espacio de nombres de red, rutas y una interfaz de túnel, y nada que hable HTTP o SOCKS; publicar un puerto en él no puede crear un oyente que nunca existió.
Dónde se ejecuta el cliente decide el diseño. Un contenedor hermano puede entrar en el espacio de nombres del túnel, una aplicación de escritorio no puede, y un router necesita rutas en lugar de un campo de proxy. Si se equivoca en la correspondencia, el síntoma es el mismo: conexión rechazada o tráfico que toma en silencio la ruta antigua.
Ajuste la disposición al lugar donde se ejecuta el cliente
Haga una pregunta antes de editar Compose: ¿se puede iniciar el cliente dentro del espacio de nombres de red del contenedor VPN? Cualquier cosa que ya se ejecute como contenedor Linux puede. Un navegador, un IDE o una CLI de su escritorio no puede, porque su ajuste de proxy espera un servicio de protocolo, no un espacio de nombres.
| Client | Arrangement | What must already exist |
|---|---|---|
| Another Compose service | Shares the vpn namespace via network_mode: service:vpn | A working tunnel and correct start order |
| Host browser, IDE or CLI | Proxy listener inside the namespace, published to host loopback | A configured SOCKS or HTTP proxy and its access rules |
| Router, VM or phone | The container as a layer-3 gateway | Forwarding, NAT and a return route |
Estos tres no se mezclan. Un campo de proxy HTTP apuntado a la dirección de puente del contenedor no llega a nada salvo que un proxy escuche allí, y un oyente de proxy no enruta el tráfico de un router. Decida primero y luego siga la sección correspondiente.
Comparta el espacio de nombres del túnel con un contenedor hermano
Esta es la opción más barata porque no interviene ningún protocolo de proxy. La aplicación se une al espacio de nombres del servicio vpn y hereda sus interfaces, rutas y espacio de puertos:
services:
vpn:
image: your-vpn-client
cap_add:
- NET_ADMIN
healthcheck:
test: ["CMD-SHELL", "ip link show tun0 >/dev/null"]
interval: 10s
retries: 5
app:
image: your-app
network_mode: service:vpn
depends_on:
vpn:
condition: service_healthyTres restricciones se derivan de que el espacio de nombres pertenezca a otro servicio. La aplicación no recibe una clave networks propia, porque no tiene espacio de nombres al que engancharse. La publicación de puertos corresponde a vpn, ya que allí vive el espacio de nombres. Y recrear vpn destruye el espacio de nombres al que se unió la aplicación, así que docker compose up -d --force-recreate vpn le deja recrear también app; un reinicio simple no siempre basta. La referencia de servicios de Compose cubre esta disposición y su restricción de publicación de puertos.
No dé por hecho que el resolutor viene incluido. /etc/resolv.conf es un archivo por contenedor y no forma parte del espacio de nombres de red, así que la aplicación todavía puede consultar el resolutor del host mientras sus paquetes salen por el túnel. Léalo dentro del contenedor y defina dns: en la aplicación cuando los nombres privados deban resolverse por el túnel.
docker compose exec app ip route
docker compose exec app cat /etc/resolv.conf
docker compose exec app curl -s https://ifconfig.meSi la lista de rutas contiene la interfaz del túnel y la dirección es la del proveedor, la aplicación está dentro del túnel. Si falta la interfaz, app se inició antes de que vpn tuviera túnel, así que recree app en cuanto vpn informe que está sano.
Ponga un oyente de proxy real en el espacio de nombres para los clientes del host
Un navegador de escritorio no puede unirse a un espacio de nombres, así que el túnel debe exponerse como servicio. Ejecute un proxy SOCKS o HTTP real dentro del espacio de nombres vpn y publique solo su puerto:
services:
vpn:
ports:
- "127.0.0.1:1080:1080"
proxy:
image: your-proxy
network_mode: service:vpn
command: ["--socks", "0.0.0.0:1080"]La dirección del lado del host es la mitad importante de esa correspondencia. 127.0.0.1:1080 a la izquierda mantiene el oyente fuera de su LAN; un "1080:1080" desnudo publica un proxy abierto a cada dispositivo que pueda alcanzar su máquina.
El lado del contenedor tiene su propia trampa. Los puertos publicados se entregan a la dirección del contenedor en el puente, no a su loopback, así que un proxy iniciado con --socks 127.0.0.1:1080 dentro del espacio de nombres acepta conexiones de los hermanos y rechaza todo lo que llega por el puerto publicado. Átelo a 0.0.0.0 y confíe en el enlace de loopback del lado del host para el aislamiento. La misma regla se aplica a la dirección de escucha en un archivo de configuración de proxy.
Luego decida dónde se resuelven los nombres. En curl, --socks5 resuelve el nombre de host localmente y --socks5-hostname envía el nombre al proxy; los navegadores exponen el mismo conmutador con otra etiqueta. La resolución local filtra la consulta incluso cuando la página carga por el túnel.
Trate el contenedor como puerta de enlace solo con una ruta de vuelta
Los routers, las VM y los teléfonos no usarán un campo de proxy HTTP, y algunas aplicaciones ignoran por completo las variables de proxy. Enrutar su tráfico significa capa 3: el contenedor reenvía paquetes y traduce direcciones en la interfaz del túnel.
sysctl -w net.ipv4.ip_forward=1
iptables -t nat -A POSTROUTING -o tun0 -j MASQUERADEDos cosas se pasan por alto con facilidad. El cliente necesita una ruta que apunte su puerta de enlace predeterminada a este host, y las respuestas deben encontrar el camino de vuelta, ya sea por una ruta estática en el router de nivel superior o por NAT en la interfaz que mira a los clientes. Sin una de las dos, el túnel saca la petición y la respuesta desaparece.
También tiene el mayor radio de impacto. Un host que actúa como puerta de enlace para toda la LAN envía cada cliente mal configurado por su cuenta VPN, y una caída del túnel o bien deja la LAN a oscuras o bien vuelve al ISP según el kill switch.
Verifique la ruta de salida y luego revierta en orden
Compruebe en este orden y deténgase en el primer fallo: dirección de salida dentro del espacio de nombres, dirección de salida desde el host a través del proxy, y luego resolución de nombres para un host privado.
docker compose exec vpn curl -s https://ifconfig.me
curl -s -x socks5h://127.0.0.1:1080 https://ifconfig.me
curl -s -x socks5h://127.0.0.1:1080 http://internal.example/healthLas dos primeras direcciones deben coincidir con el proveedor, y la tercera debe resolverse a través del proxy en lugar de fallar con un error de nombre. Una primera línea coincidente y una segunda que falla significan que el oyente o el puerto publicado están mal, no el túnel.
Revertir es lo contrario de la instalación. Elimine las variables de entorno de proxy del perfil del shell y de cualquier unidad systemd, y luego baje la pila para que el espacio de nombres compartido desaparezca con su propietario. Borre el puerto publicado antes de borrar el servicio de proxy, o el host conserva un oyente de loopback que nadie lee.
Antes de continuar, confirme cuatro cosas: el cliente que le importa llega realmente al túnel, solo el propietario del espacio de nombres publica puertos, los nombres privados se resuelven donde usted quería, y nada escucha en una dirección de host que no sea loopback. Cada una de ellas falla en silencio en producción, y cada una es una comprobación de una línea más arriba.