Una ruta de salida de este puesto de trabajo es un proxy SOCKS, y OpenVPN detrás de él informa de un tiempo de espera de conexión sin nada útil adjunto. Un navegador en la misma máquina funciona a través de ese proxy, las credenciales son correctas y el puerto responde. Tres cosas difieren con la suficiente frecuencia como para revisarlas primero: la versión de SOCKS que habla el proxy, el transporte que eligió el cliente y dónde ocurre la resolución de nombres. Confirma cada una antes de decidir que el cortafuegos tiene la culpa.

Ajusta la versión de SOCKS antes del cortafuegos

El OpenVPN comunitario habla SOCKS5 para socks-proxy. Apúntalo a un proxy que solo ofrece SOCKS4 y la conexión TCP se abre, y luego se bloquea, porque el cliente espera una respuesta a un saludo que el otro extremo nunca analiza. Desde fuera, ese bloqueo parece exactamente un puerto filtrado, por eso la pregunta de la versión va antes de cualquier regla de cortafuegos. Una consulta con SOCKS lo resuelve con un solo comando.

curl --socks5-hostname 127.0.0.1:9050 gw.lab.example

Una petición completada demuestra dos cosas a la vez: el proxy aceptó la petición SOCKS y resolvió el nombre. No hace falta ningún esquema de URL, porque curl trata un host desnudo como HTTP, lo que basta para esta prueba.

Esa consulta también descarta el fallo que oculta el registro del cliente. Un proxy puede permitir el navegador y aun así rechazar el destino VPN, y en ese caso el rechazo ocurre en CONNECT, antes de cualquier autenticación VPN. OpenVPN solo informa de un tiempo de espera, así que la respuesta del propio proxy es la evidencia que necesitas.

El transporte tiene que ser TCP en ambos lados

SOCKS5 transporta aquí un flujo TCP, así que el túnel mismo necesita proto tcp-client. Un perfil que siga configurado para UDP no funcionará por esta ruta, porque el cliente no usa el modo de asociación UDP del protocolo. Cambiar el perfil es un cambio real y no una formalidad: el extremo remoto tiene que escuchar en TCP en el mismo puerto. La carga pasa entonces a ser TCP dentro de TCP, donde un segmento perdido se retransmite dos veces y el ritmo se degrada, algo que las sesiones interactivas notan mucho más que las transferencias grandes.

proto tcp-client
remote gw.lab.example 8443
socks-proxy 127.0.0.1 9050

La directiva toma el host del proxy, un puerto opcional y un archivo de credenciales opcional. Ponlos en el propio perfil para que una copia se comporte igual en otro sitio.

Confina toda la cadena a un solo contenedor

Hacer la cadena en el host significa tomar su ruta predeterminada y dejar el resolutor en un estado que nadie podrá explicar después. Un namespace o un contenedor lo acota en su lugar, de modo que el host conserva su propia ruta mientras el salto SOCKS y el túnel viven juntos. Dentro no levanta nada sin un dispositivo de túnel, los permisos para configurarlo y un proxy accesible. Un servicio de Compose lo consigue con cap_add: NET_ADMIN y un /dev/net/tun montado por bind.

cap_add:
  - NET_ADMIN
devices:
  - /dev/net/tun:/dev/net/tun

Ese par es más estrecho que ejecutar el contenedor con privilegios, y es todo el acceso al dispositivo que hace falta. Un cliente de Tor o un contenedor de proxy aparte pueden proporcionar el escucha. En cualquier caso, átalo a loopback y llégalo desde el namespace a través del host en vez de exponer el proxy a la red.

La resolución de nombres es donde falla el aislamiento

Un túnel que transporta paquetes mientras pregunta al resolutor del host no ha aislado nada. Dos cosas hacen real el aislamiento: el namespace tiene un resolutor propio, y sus consultas viajan por el proxy o por el túnel, nunca por la interfaz física. Usa dos nombres para la prueba: uno que solo exista detrás del túnel y uno público que debería seguir la misma ruta.

docker compose exec vpn cat /etc/resolv.conf
docker compose exec vpn getent hosts internal.lab.example

Si el nombre del lado remoto se resuelve y el público no, la cadena es selectiva y no está rota, y la diferencia te dice qué ruta tomó cada consulta. Si ambos se resuelven desde el host pero ninguno dentro del contenedor, el contenedor sigue tomando prestado el resolutor del host. Anota el archivo de resolutor que encontraste, porque ese es el ajuste que hay que cambiar.

Demuestra cada salto en orden

La lista es corta. Una petición SOCKS5 obtiene respuesta, el túnel alcanza su escucha a través del proxy, y los paquetes que salen del namespace llevan la dirección que el lado remoto espera. Confirma cada uno por separado en lugar de apilar más configuración. Cuando el cliente dice conectado y nada se mueve, la rotura está casi siempre en el segundo o tercer hecho.

curl --socks5-hostname 127.0.0.1:9050 gw.lab.example
docker compose exec vpn ip route get 203.0.113.10
docker compose exec vpn getent hosts internal.lab.example

Apunta la combinación que funcionó: versión de SOCKS, transporte, resolutor y ajustes del namespace. Resolver esta cadena una segunda vez de memoria cuesta más que la nota. Guarda esos cuatro hechos en el tique y no en un hilo de chat.

Una cadena que informa de conexión y no pasa nada suele haber fallado en el salto que aún no has demostrado. Prueba ese salto antes de añadir otra capa.

Artículos relacionados