Empilhar um segundo cliente OpenVPN num único host Linux falha num de dois pontos. Ou o cliente interno não consegue alcançar o próprio servidor porque o túnel externo capturou esse transporte, ou o segundo cliente reescreve a tabela de rotas e o primeiro túnel deixa de funcionar. Ambos os problemas estão no encaminhamento, não na cifragem, e ambos se resolvem antes de o novo túnel transportar qualquer tráfego de aplicação. Os endereços aqui são ilustrativos: tun0 é o túnel externo, 203.0.113.9 é o servidor interno e TCP 9999 é o fluxo que deve usar o caminho interno.

Dois modos de falha, uma tabela de rotas

Um cliente interno abre um socket UDP para o seu servidor, e esse socket segue a tabela de rotas atual como qualquer outro. Se o túnel externo já detém a rota predefinida, os pacotes de handshake entram no túnel externo. Isso só funciona quando o servidor externo consegue alcançar o servidor interno e o outro lado aceita transportar o fluxo; caso contrário o handshake pára e o cliente tenta novamente.

A segunda falha vem do push do servidor. O push habitual é redirect-gateway def1, que instala 0.0.0.0/1 e 128.0.0.0/1 em vez de substituir a rota predefinida. Dois clientes que instalam cada um o seu próprio par /1 colidem por isso, e o par mais recente ganha para quase todos os destinos. Procure esses dois prefixos na tabela quando um túnel morre depois de o segundo ligar, porque a própria rota predefinida pode nunca ter mudado.

Marque o fluxo e dê-lhe uma tabela

Encaminhe o tráfego selecionado marcando-o na tabela mangle e fazendo corresponder a marca numa regra de política. A regra aponta para uma tabela dedicada cuja rota predefinida é o dispositivo interno, de modo que só os pacotes marcados tomam o caminho interno.

iptables -t mangle -A PREROUTING -p tcp -m multiport --dport 9999 -j MARK --set-mark 0x9999
ip rule add fwmark 0x9999 table 200 pref 1000
ip route add default dev tun1 table 200

A maioria das falhas neste conjunto de regras vem de dois erros. O valor da marca definido pela firewall e o valor correspondido pela regra têm de ser idênticos, porque uma divergência não produz erro nem efeito. E o PREROUTING só vê pacotes que chegam de fora, portanto uma ligação iniciada neste host precisa de uma regra OUTPUT com a mesma correspondência antes de ser marcada.

Retire a propriedade das rotas ao segundo cliente

Duas opções impedem o segundo cliente de editar a tabela, e atuam em pontos diferentes. route-nopull ignora as opções de rota enviadas por push, por isso nada é instalado. route-noexec aceita as opções enviadas no ambiente do cliente mas deixa a alteração da tabela para um hook, e é isto que quer quando precisa de ver o que o servidor ofereceu.

O hook corre com os valores enviados no seu ambiente, e deve acrescentar apenas as entradas que aprovou. Torne-o idempotente: limpe a tabela que possui antes de a reconstruir, e não assuma que a execução anterior deixou um estado limpo. Uma reconexão invoca o hook novamente, e um hook que acrescenta sem limpar acumula rotas duplicadas.

Escolha um único proprietário e escreva-o. Um perfil que define ambas as opções, ou um hook que compete com um script manual, produz uma tabela que ninguém consegue explicar mais tarde.

Fixe ambos os endereços dos servidores

O servidor interno precisa de uma rota de host através do dispositivo externo antes de o cliente interno ligar. É essa entrada que mantém o transporte interno dentro do túnel externo em vez de o deixar seguir a rota predefinida.

ip route add 203.0.113.9/32 dev tun0
ip route get 203.0.113.9

Acrescente também uma rota de host correspondente para o servidor externo na tabela principal, senão uma reconexão pode encaminhar o transporte do próprio cliente externo para o seu próprio túnel. A ordem não é opcional: acrescente estas entradas depois de existir a interface relevante, ou seja, um hook ou script que corre após a ligação. Uma rota que aponta para um dispositivo desligado é removida ou ignorada, por isso uma entrada escrita no arranque não sobrevive à sequência que assumiu.

Verifique a pesquisa em vez de ler a tabela inteira. A saída da pesquisa de rota indica o dispositivo e o endereço de origem que o kernel vai usar, e é exatamente o que o cliente recebe quando abre o seu socket.

Teste a cadeia e torne a falha visível

Capture na interface externa enquanto o cliente interno liga. O handshake interno deve aparecer como carga cifrada dentro do túnel externo; se aparecer como UDP simples para 203.0.113.9, falta a rota de host e a cadeia nunca foi construída.

Uma cadeia que volta silenciosamente ao uplink físico é o pior resultado, porque o estado da interface não anuncia nada e o tráfego interno sai do endereço de origem errado. Se o segundo cliente consegue voltar a ligar diretamente quando o túnel externo cai, desative esse caminho ou ligue-o à interface externa para que a falha fique visível.

Termine com dois fluxos e um dump de regras: uma ligação marcada e uma não marcada devem seguir caminhos diferentes, e ip rule show deve listar a preferência que pretendia. Se ambos os fluxos seguem o mesmo caminho, a marca não está a ser aplicada onde pensa.

Artigos relacionados