Corrija o tráfego do Docker e do WSL que ignora a VPN do host
Contêineres e WSL mantêm suas próprias rotas, resolvedor e caminho de encaminhamento, então um túnel de host funcional os deixa com DNS desatualizado ou sem saída. Corrija cada camada.
Passei a maior parte de uma manhã desta primavera perseguindo um contêiner que não conseguia resolver um único nome interno, enquanto o navegador ao lado abria o wiki sem reclamar. O túnel estava ativo, o navegador provava isso, e o contêiner ficava em outro espaço de nomes de rede com outra tabela de rotas e outro resolvedor. O que segue é a ordem que verifico agora: primeiro as tabelas de rotas, depois a resolução, em seguida o encaminhamento e, por fim, a plataforma do host. Pare no primeiro passo que já falha, porque adicionar regras mais abaixo na pilha esconde a fronteira em vez de movê-la.
Compare as três tabelas de rotas antes de alterar uma regra
Um contêiner na ponte padrão do Docker tem uma única rota padrão apontando para o gateway docker0 e não faz ideia de que o tun0 existe. O host tem o tun0, as rotas empurradas pela VPN e talvez uma regra de política que envia o tráfego de teste por uma interface diferente da que você espera. O WSL adiciona uma terceira tabela atrás do próprio adaptador virtual. Imprima as três antes de editar qualquer uma.
| Context | Command | What a wrong answer looks like |
|---|---|---|
| Host | ip route get 10.8.0.20 | Leaves a physical interface instead of tun0 |
| Container | ip route | Only 172.17.0.0/16 via 172.17.0.1 |
| Container DNS | cat /etc/resolv.conf | A public resolver, or an upstream the tunnel never pushed |
| WSL | ip addr; ip route | Only the WSL subnet, no Windows VPN adapter |
Se a única rota do contêiner é a ponte e o tun0 nunca aparece no espaço de nomes dele, nada abaixo ajudará: nenhuma mudança de servidor de nomes cria uma rota, e nenhuma rota cria a identidade de túnel que o par aceitará. A ordem importa porque uma falha de DNS e uma falha de rota parecem idênticas de dentro de um shell.
Aponte o resolvedor para o túnel em vez de para um público
No host, resolvectl status lista os servidores DNS por link, e readlink -f /etc/resolv.conf diz se o arquivo é o stub do systemd, um arquivo do NetworkManager ou algo que o cliente de um fornecedor reescreveu. Dentro de um contêiner numa rede definida pelo usuário, /etc/resolv.conf nomeia 127.0.0.11, o resolvedor embutido do Docker, que encaminha para o upstream com que o daemon foi configurado. Já um contêiner na ponte padrão recebeu uma cópia do arquivo do host quando foi criado, e a mantém.
É essa cópia que faz um túnel que sobe depois de o contêiner iniciar deixar o contêiner consultando o upstream antigo. Ou passe o resolvedor da VPN explicitamente com docker run --dns 10.8.0.53, ou defina "dns": ["10.8.0.53"] em /etc/docker/daemon.json e reinicie o daemon, ou recrie o contêiner quando o túnel estiver ativo. Recriar costuma sair mais barato do que depurar um resolvedor que estava correto uma hora atrás.
Trocar para um resolvedor público é o reparo que mantém o sintoma. Ele resolve a internet e falha em toda zona privada, e pode contornar em silêncio a filtragem que o túnel existe para impor. Se tentar escrever dentro do /etc/resolv.conf de um contêiner em execução, espere Device or resource busy: o Docker monta esse arquivo com bind, então a edição exige primeiro uma desmontagem. Definir --dns na criação é a sequência que sobrevive a um reinício.
Encaminhe e traduza quando os contêineres tomam o túnel emprestado
Com a ponte padrão, o host é um roteador entre o docker0 e o tun0, e por padrão não faz nenhum dos dois trabalhos. Ative o encaminhamento e traduza o endereço de origem para que o par veja um endereço de túnel do host em vez de um para o qual não tem rota de volta (substitua o tun0 pelo nome que o seu cliente dá à interface):
sysctl -w net.ipv4.ip_forward=1
iptables -t nat -A POSTROUTING -o tun0 -j MASQUERADE
iptables -A FORWARD -i docker0 -o tun0 -j ACCEPT
iptables -A FORWARD -i tun0 -o docker0 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPTAs regras FORWARD importam em hosts cuja política de encaminhamento é DROP. O Docker insere as próprias cadeias, então teste com um pacote em vez de ler iptables -S: a partir do contêiner, ip route get 10.8.0.20 deveria nomear o gateway da ponte, e nc -vz 10.8.0.20 443 deveria conectar ou expirar no túnel em vez de no resolvedor.
Sem MASQUERADE o par precisa de uma rota de volta para 172.17.0.0/16, que a maioria das VPNs de acesso remoto não empurra. Aqui também um cliente baseado em políticas quebra o plano: se ele captura apenas o tráfego de UIDs ou cgroups específicos, as suas regras de encaminhamento nunca veem o pacote dentro do túnel, e a correção pertence à política de roteamento do cliente e não ao iptables. Regras adicionadas à mão desaparecem no reinício, então coloque-as numa unidade systemd ou numa tabela netfilter-persistent e teste de novo após um reinício, já que um túnel que sobe tarde pode varrê-las.
Coloque o túnel dentro do contêiner quando o objetivo é o isolamento
Se um contêiner precisa sair por um nó específico, execute o cliente nesse contêiner. Ele precisa de --cap-add NET_ADMIN e de --device /dev/net/tun, e então possui seu próprio tun0, rotas e firewall. Se outros contêineres passam por ele, ele também precisa de encaminhamento e de uma regra MASQUERADE dentro do contêiner, e esses pares precisam de uma rota ou proxy explícitos apontando para ele; a ponte padrão do Docker não adiciona essa rota por você.
A rede do host, docker run --network host, é a versão barata: o contêiner entra no espaço de nomes do host e vê as rotas do túnel diretamente. Ela também abre mão do isolamento de rede, convida a colisões de portas e, na prática, é uma opção do Linux. Uma porta publicada não é um substituto. -p 8080:80 torna um serviço acessível a partir do host; não torna os endereços internos 10.x acessíveis de dentro do contêiner, e confundir as duas coisas é como um problema de roteamento se transforma numa porta de banco de dados exposta.
Distinga uma falha de nome de uma sub-rede sobreposta
A ponte padrão do Docker é 172.17.0.0/16, e muitos pools de VPN corporativas ficam dentro de 172.16.0.0/12. Quando o túnel possui essa faixa, o tráfego destinado a um contêiner pode ser roteado para o túnel. Os nomes falham, o erro parece DNS, e o problema são os endereços. Verifique o destino, não o resolvedor: ip route get 172.17.0.5 no host, depois compare o prefixo da ponte com o pool da VPN e com docker network inspect bridge --format '{{range .IPAM.Config}}{{.Subnet}}{{end}}'.
O reparo é mover uma das faixas: ou outro pool de endereços padrão para o Docker em daemon.json ou outro pool no concentrador. Adicionar outro servidor de nomes não faz nada aqui, e ssh -b também não: vincular o cliente a uma interface muda o endereço de origem, não uma rota de destino que aponta para dentro do túnel. Divisão rápida de trabalho: um nome que resolve no host mas não no contêiner é problema de resolvedor, enquanto uma conexão IP direta que trava no contêiner mas funciona no host é problema de rota ou de cadeia de encaminhamento.
O WSL e o Docker Desktop não seguem as regras do Linux
No seu modo NAT padrão, o WSL fica atrás do próprio adaptador virtual e não recebe as rotas internas nem os sufixos DNS da VPN do Windows, e é por isso que o navegador funciona e ping dentro da distribuição não. A rede espelhada, disponível em builds recentes do Windows 11, compartilha em vez disso as interfaces do host, mas precisa ser ativada em .wslconfig e a distribuição reiniciada com wsl --shutdown. Confirme o modo ativo com wslinfo --networking-mode antes de aplicar uma correção, porque a resposta do NAT está errada para o modo espelhado e vice-versa.
O Docker Desktop acrescenta outra camada. Os contêineres Linux no Windows ficam atrás de uma implementação de NAT historicamente chamada vpnkit, e o caminho deles difere do de um host Linux nativo. Os contêineres Windows se conectam por um switch virtual do Hyper-V, e um cliente corporativo pode bloquear o tráfego desse switch, então contêineres Linux na mesma máquina podem alcançar a rede enquanto os contêineres Windows não têm nada. Um switch externo vinculado ao adaptador físico é a solução habitual, e muda a identidade de rede do contêiner de formas que a política pode não permitir.
Antes de escrever outra regra, percorra as quatro camadas em ordem e pare onde uma delas estiver errada:
- Dentro do contêiner:
ip routealcança o prefixo privado, e/etc/resolv.confnomeia um upstream que o resolve? - No host: o encaminhamento está ativo, o NAT está vinculado à interface do túnel, e a cadeia de encaminhamento aceita a ponte para o túnel?
- No par: ele conhece um caminho de volta, ou você conta com o NAT para esconder a sub-rede do contêiner?
- Na plataforma: o WSL está em modo NAT ou espelhado, e o contêiner é Linux ou Windows?
Rode cada teste de novo num contêiner ou shell novos. Um processo de longa duração com resposta em cache ou conexão estabelecida continuará falhando depois que a configuração estiver correta, e essa falha parece exatamente um reparo que não funcionou.