O pacote do contêiner nunca chega a tun0 , e capacidades extras no contêiner não mudarão isso. Entre o convidado e o túnel há duas consultas de rota: uma dentro do espaço de nomes de rede do convidado, uma no host depois que o quadro pousa na ponte, e a segunda consulta outra tabela com regras de correspondência diferentes. Minha disposição de referência é um host Linux com um OpenVPN cliente que mantém 10.8.0.10 em tun0 , uma ponte Docker em 172.18.0.0/16 , e um endereço privado que só o lado do túnel alcança. O objetivo continua estreito: essa sub-rede convidada sai pelo túnel enquanto o host mantém o uplink habitual.

Rodar o cliente VPN dentro do contêiner é outro projeto , não um atalho. O contêiner então possui o próprio túnel e as próprias credenciais, e o trabalho vai para um Dockerfile em vez de ficar no iptables.

O encaminhamento escolhe uma rota por destino, depois o NAT reescreve a origem

Com net.ipv4.ip_forward=1 definido, um pacote encaminhado é roteado só por destino, na tabela principal. A origem não desempenha papel nessa decisão. Só depois de escolher a rota o kernel executa POSTROUTING , e é aí que fica a regra do próprio Docker: -s 172.18.0.0/16 ! -o br-appnet -j MASQUERADE . Tudo o que deixa a ponte para outra interface é traduzido, e o endereço em que se transforma depende da interface para a qual a rota o enviou.

Essa regra explica a maioria dos sintomas que me perguntam. Se a tabela principal não tem rota para o destino privado, o pacote sai pelo uplink carregando o endereço físico do host. O provedor descarta o destino, ou o site remoto descarta uma origem RFC 1918 que nunca esperou, e o contêiner estoura o tempo enquanto o túnel continua de pé e saudável. Uma captura em tun0 não mostra nada, e esse é o sinal. Se a rota aponta mesmo para tun0 , o MASQUERADE reescreve a origem para 10.8.0.10 , e o mesmo contêiner funciona sem que você toque em nada.

Peça a consulta que nomeia a origem do convidado. Sem from e iif o host responde pelo próprio socket e relata um caminho que o pacote encaminhado nunca vai tomar:

ip route get 192.168.11.222 from 172.18.0.5 iif br-appnet

Se essa resposta nomeia o uplink, o túnel não está no quadro e nenhum ajuste do lado do túnel ajudará.

Leve o convidado para o endereço próprio do túnel

A menor mudança que funciona é uma reescrita explícita da origem na saída. Insira-a em vez de acrescentá-la: o Docker já está à frente de POSTROUTING , então uma regra adicionada com -A é avaliada atrás da do Docker e nunca dispara.

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

Duas condições decidem se isso se sustenta. O endereço precisa ser um que o cliente realmente tenha, então fixe-o com uma entrada por cliente que contenha ifconfig-push 10.8.0.10 255.255.255.0 ; uma regra que nomeia um endereço nunca atribuído produz descartes silenciosos, não um erro. E o lado remoto precisa aceitar essa origem e rotear as respostas de volta, por isso uma lista de permitidos ali nomeia o endereço do túnel e não a sub-rede convidada. Quando o endereço do cliente é dinâmico, -j MASQUERADE com -o tun0 segue o que a interface tiver, ao custo de uma política remota contra a qual você não pode escrever um valor fixo.

A filtragem é a outra metade. O Docker põe a política FORWARD em drop e limpa as cadeias que possui a cada reinício do daemon, então coloque a política local em 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 retorno importa: uma resposta que chega em tun0 para uma conexão aberta pela ponte não é aceita só porque a direção de saída foi.

Mantenha o host direto com uma regra de origem

Quando a ponte inteira deve usar o túnel mas nada mais no host deve, pule o NAT e dê a essa origem a própria tabela. Os endereços convidados ficam então visíveis de ponta a ponta.

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 são consultadas antes da tabela principal, e from corresponde à origem. Os pacotes externos do próprio túnel carregam a origem física do host, então nunca correspondem a from 172.18.0.0/16 , e o túnel não pode rotear a si mesmo de volta em si mesmo. Essa autoexclusão é o que torna a disposição estável.

O caminho de volta agora precisa de ajuda. O lado remoto vê um 172.18.0.x sem tradução e não tem motivo para saber onde ele mora. No servidor, um arquivo por cliente precisa de iroute 172.18.0.0 255.255.0.0 para que a tabela interna do servidor entregue esse prefixo a este cliente, e de um route 172.18.0.0 255.255.0.0 empurrado para que os outros clientes saibam que ele existe.

Duas verificações antes de confiar nisso. Execute ip route show default : com redirect-gateway def1 a tabela tem 0.0.0.0/1 e 128.0.0.0/1 via tun0 , e o host não é direto de jeito nenhum, porque esses meios padrão vencem sem substituir default . Regras adicionadas com ip rule add também desaparecem ao reiniciar, então carregue-as num script up.

O tamanho do pacote é a segunda falha que você encontra

Uma interface de ponte a 1500 bytes dá ao túnel mais do que ele consegue carregar depois que os cabeçalhos UDP e IP externos entram. As trocas pequenas passam, então o DNS resolve e o handshake TLS termina, e depois um envio maior trava sem erro nenhum em lugar algum. O emissor continua repetindo o mesmo tamanho de segmento porque a mensagem ICMP de necessidade de fragmentação, que lhe diria para reduzir, nunca chega.

Corrija na interface, no MSS ou nos dois. O grampeamento não precisa de cooperação 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 grampeamento reescreve só o TCP. UDP, QUIC incluído, precisa em vez disso de uma interface convidada mais baixa, com --opt com.docker.network.driver.mtu=1400 na criação da rede ou a chave compose correspondente.

DNS e as verificações que valem repetir

A resolução de nomes segue o mesmo caminho. O resolvedor embutido do Docker em 127.0.0.11 encaminha para os servidores configurados do host, então um contêiner herda o que o host usa. Quando a zona privada só é respondida por um resolvedor alcançável pelo túnel, aponte o convidado para esse endereço, e teste com getent hosts dentro do contêiner em vez de no host.

  • Execute a requisição que falha dentro do convidado; um teste do lado do host usa outro endereço de origem e esconde o problema.
  • Leia os contadores nas regras de NAT e de filtragem que você adicionou. Uma regra sem nenhuma correspondência é um erro de roteamento, não de política.
  • Confirme que o endereço do túnel na sua regra SNAT é um que o cliente realmente tem.
  • Baixe a MTU do convidado ou grampeie o MSS antes de culpar o servidor por uma trava.

Percorra essa lista e pare na primeira falha. É essa camada que precisa de conserto; qualquer coisa adicionada mais abaixo só esconde a fronteira que quebra.

Artigos relacionados