Aplicação dentro de um espaço de nomes VPN não é acessível pela LAN
Um espaço de nomes de rede dá a um programa o seu próprio túnel e o seu próprio loopback. Publique apenas a sua porta com um salto socat ou um par veth em vez do host.
O seu serviço funciona na máquina, o túnel está de pé e, mesmo assim, o navegador na sua secretária continua à espera de resposta. Nada está avariado no sentido habitual: a aplicação escuta dentro de um espaço de nomes de rede, e essa pilha é invisível para a LAN. O seu loopback pertence ao espaço de nomes, por isso um cliente noutra máquina não tem qualquer endereço que chegue à porta. Um salto deliberado, ou um par veth, resolve isso sem mover o resto do host.
Porque nada responde no endereço da LAN
Um espaço de nomes é uma pilha de rede completa: interfaces, rotas, regras de firewall e um loopback privado. Um processo lançado através de ip netns exec vê essa pilha e nada mais, por isso o endereço a que se liga só é resolvido dentro do espaço de nomes. O próprio 127.0.0.1 do host é um loopback completamente diferente.
É por isso que o curl no host falha enquanto o mesmo comando dentro do espaço de nomes tem sucesso. A aplicação está saudável; o endereço simplesmente não existe na pilha do host.
Publicar uma só porta com um salto socat
socat faz a ponte entre as duas pilhas no espaço do utilizador, o que basta quando é uma única porta TCP que tem de atravessar. O lado do host escuta na LAN e, para cada ligação aceite, inicia um segundo socat dentro do espaço de nomes que se liga ao loopback da aplicação. O processo interno chega à aplicação exatamente como um cliente local faria. Nada no encaminhamento do espaço de nomes muda, por isso a aplicação continua a usar o túnel como única saída.
socat TCP-LISTEN:9100,reuseaddr,fork EXEC:'ip netns exec vpn socat STDIO tcp-connect:127.0.0.1:9100',nofork
O processo externo precisa de root ou da capacidade de entrar no espaço de nomes. Se a VPN cair, a aplicação perde o caminho e a porta publicada falha fechada, que é o comportamento que se quer de uma jaula.
Usar um par veth quando atravessa mais de uma porta
Um par veth é a resposta certa quando têm de atravessar várias portas ou protocolos, ou quando outros hosts precisam de iniciar ligações. Uma extremidade fica no host, a outra passa para o espaço de nomes, e um /30 pequeno dá aos dois lados um endereço estável. Depois uma rota do host envia o endereço do espaço de nomes pelo par.
ip link add veth0 type veth peer name veth1
ip link set veth1 netns vpn
ip addr add 10.77.0.1/30 dev veth0
ip link set veth0 up
ip netns exec vpn ip addr add 10.77.0.2/30 dev veth1
ip netns exec vpn ip link set veth1 up
Não acrescente uma rota predefinida através do host a menos que queira quebrar a jaula. O túnel deve continuar a ser a saída, por isso pelo par só é acessível o endereço do serviço publicado. Dê também ao espaço de nomes o seu próprio ficheiro de resolução, porque ip netns exec lê /etc/netns/vpn/resolv.conf e sem ele a resolução de nomes dentro da jaula fica vazia.
Testar a partir de outra máquina, não do host
Faça o teste a partir de uma segunda máquina, porque o host consegue chegar a um listener local por caminhos que não dizem nada sobre a LAN. Verifique primeiro a que a aplicação se ligou realmente dentro do espaço de nomes e só depois ligue-se de fora ao endereço publicado. Ligar-se a todos os endereços ali não alarga a jaula, porque o curinga continua a pertencer ao espaço de nomes e a LAN não o consegue ver.
ip netns exec vpn ss -lntp
Se a ligação a partir do host funcionar e a da LAN não, a peça em falta é o salto ou a rota, não o endereço de ligação. Inicie o OpenVPN dentro do espaço de nomes em vez de mover para lá um dispositivo tun ativo, porque mover a interface desliga-a e pode limpar rotas que depois não conseguirá reparar de fora. Mantenha os comandos de recuperação na consola, não dentro da jaula.