O paquete do contedor nunca chega a tun0, e as capacidades adicionais no contedor non o cambiarán. Entre o convidado e o túnel hai dúas consultas de ruta: unha dentro do espazo de nomes de rede do convidado, outra no host despois de que a trama aterre na ponte, e a segunda consulta unha táboa distinta con regras de coincidencia distintas. A miña disposición de referencia é un host Linux cun OpenVPN cliente que mantén 10.8.0.10 en tun0, unha ponte Docker en 172.18.0.0/16, e un enderezo privado ao que só pode chegar o lado do túnel. O obxectivo segue estreito: esa subrede convidada sae polo túnel mentres o host conserva o seu uplink habitual.

Executar o cliente VPN dentro do contedor é outro deseño, non un atallo. O contedor pasa a posuír o seu túnel e as súas credenciais, e o traballo trasládase a un Dockerfile en lugar de quedar en iptables.

O reenvío escolle unha ruta por destino, e despois NAT reescribe a orixe

Con net.ipv4.ip_forward=1 configurado, un paquete reenviado encamíñase só por destino, na táboa principal. A orixe non xoga ningún papel nesa decisión. Só despois de escoller a ruta o núcleo executa POSTROUTING, e é aí onde está a regra propia de Docker: -s 172.18.0.0/16 ! -o br-appnet -j MASQUERADE. Todo o que sae da ponte cara a outra interface tradúcese, e o enderezo no que se converte depende da interface á que a ruta o enviou.

Esa regra explica a maioría dos síntomas polos que me preguntan. Se a táboa principal non ten ruta para o destino privado, o paquete sae polo uplink co enderezo físico do host. O seu provedor descarta o destino, ou o sitio remoto descarta unha orixe RFC 1918 que nunca agardaba, e o contedor esgota o tempo mentres o túnel segue levantado e san. Unha captura en tun0 non mostra nada, e esa é a pista. Se a ruta si apunta a tun0, MASQUERADE reescribe a orixe a 10.8.0.10 e o mesmo contedor funciona sen que vostede toque nada.

Pida a consulta que nomea a orixe do convidado. Sen from e iif o host responde polo seu propio socket e informa dun camiño que o paquete reenviado nunca tomará:

ip route get 192.168.11.222 from 172.18.0.5 iif br-appnet

Se esa resposta nomea o uplink, o túnel non está na imaxe e ningún axuste do lado do túnel axudará.

Leve o convidado ao enderezo propio do túnel

O cambio mínimo que funciona é unha reescritura explícita da orixe á saída. Insírtea en lugar de engadila: Docker xa está á cabeza de POSTROUTING, así que unha regra engadida con -A avalíase detrás da de Docker e nunca se dispara.

iptables -t nat -I POSTROUTING 1 -s 172.18.0.0/16 -o tun0 -j SNAT --to-source 10.8.0.10

Dúas condicións deciden se isto se mantén. O enderezo debe ser un que o cliente realmente teña, así que fíxeo cunha entrada por cliente que conteña ifconfig-push 10.8.0.10 255.255.255.0; unha regra que nomea un enderezo que nunca se asignou produce descartes silenciosos, non un erro. E o lado remoto debe aceptar esa orixe e encamiñar as respostas de volta, por iso unha lista de permitidos alí nomea o enderezo do túnel e non a subrede convidada. Cando o enderezo do cliente é dinámico, -j MASQUERADE con -o tun0 segue o que teña a interface, a costa dunha política remota contra a que non pode escribir un valor fixo.

O filtrado é a outra metade. Docker pon a política FORWARD en drop e baleira as cadeas que posúe en cada reinicio do demonio, así que pon a política local en DOCKER-USER, que sobrevive:

iptables -I DOCKER-USER -i br-appnet -o tun0 -j ACCEPT
iptables -I DOCKER-USER -i tun0 -o br-appnet -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

A regra de volta importa: unha resposta que chega a tun0 para unha conexión aberta pola ponte non se acepta só porque a dirección de saída o fixese.

Manteña o host directo cunha regra de orixe

Cando toda a ponte debe usar o túnel pero nada máis do host debe facelo, salte NAT e dea a esa orixe a súa propia táboa. Os enderezos convidados quedan entón visibles de extremo a extremo.

echo "200 vpnbox" >> /etc/iproute2/rt_tables
ip rule add from 172.18.0.0/16 lookup vpnbox pref 1000
ip route add default dev tun0 table vpnbox

As regras consúltanse antes da táboa principal, e from coincide coa orixe. Os paquetes externos propios do túnel levan a orixe física do host, así que nunca coinciden con from 172.18.0.0/16 e o túnel non pode encamiñarse a si mesmo de volta. Esa autoexclusión é o que fai estable a disposición.

O camiño de volta agora necesita axuda. O lado remoto ve unha 172.18.0.x sen traducir e non ten motivo para saber onde vive. No servidor, un ficheiro por cliente necesita iroute 172.18.0.0 255.255.0.0 para que a táboa interna do servidor entregue ese prefixo a este cliente, e un route 172.18.0.0 255.255.0.0 empuxado para que os demais clientes saiban que existe.

Dúas comprobacións antes de confiar nel. Execute ip route show default: con redirect-gateway def1 a táboa ten 0.0.0.0/1 e 128.0.0.0/1 vía tun0, e o host non é directo en absoluto, porque eses medios predeterminados gañan sen substituír default. As regras engadidas con ip rule add tamén desaparecen ao reiniciar, así que lléveas nun script up.

O tamaño do paquete é o segundo fallo que atopará

Unha interface de ponte a 1500 bytes dá ao túnel máis do que pode levar unha vez engadidas as cabeceiras UDP e IP externas. Os intercambios pequenos pasan, así que DNS resolve e o aperto de mans TLS complétase, e despois unha subida maior colga sen ningún erro en ningures. O emisor segue reintentando o mesmo tamaño de segmento porque a mensaxe ICMP de necesidade de fragmentación, que lle diría que reduza, nunca chega.

Arranxeo na interface, no MSS ou en ambos. O grampado non necesita a colaboración do convidado:

ip link set dev tun0 mtu 1400
iptables -t mangle -A FORWARD -o tun0 -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu

O grampado só reescribe TCP. UDP, QUIC incluído, necesita en cambio unha interface convidada máis baixa, con --opt com.docker.network.driver.mtu=1400 ao crear a rede ou a clave de compose correspondente.

DNS e as comprobacións que paga a pena repetir

A resolución de nomes segue o mesmo camiño. O resolvedor integrado de Docker en 127.0.0.11 reenvía aos servidores configurados do host, así que un contedor herda o que use o host. Cando a zona privada só a responde un resolvedor alcanzable polo túnel, apunte o convidado a ese enderezo, e proba con getent hosts dentro do contedor en lugar de no host.

  • Execute a petición que falla dentro do convidado; unha proba no lado do host usa outro enderezo de orixe e esconde o problema.
  • Lea os contadores das regras NAT e de filtrado que engadiu. Unha regra sen coincidencias é un erro de encamiñamento, non de política.
  • Confirme que o enderezo do túnel na súa regra SNAT é un que o cliente realmente ten.
  • Baixe a MTU do convidado ou grampe o MSS antes de culpar o servidor dun colgado.

Percorra esa lista e detéñase no primeiro fallo. Esa capa é a que hai que arranxar; calquera cousa engadida máis abaixo só esconde a fronteira que rompe.

Artigos relacionados