Fixar un só programa a unha VPN en Linux é sobre todo un problema de encamiñamento, e a regra da devasa só decide o que ocorre despois de escoller a ruta. Se fas coincidir o UID do programa en OUTPUT antes de que o túnel teña a ruta predeterminada, a regra descarta xusto o tráfico que querías protexer. A mesma regra tampouco ve o proceso do resolver que responde ás consultas DNS do programa. Ordena ben as cousas e unha soa conta sen privilexios convértese nun selector fiable do túnel dividido.

Dálle á conta unha ruta antes do veredicto

A coincidencia de propietario responde unha pregunta: construíu o paquete un socket local pertencente a este UID? Non escolle interface e non pode mover o tráfico que a táboa principal xa envía pola NIC física. Por iso unha regra UID sen ruta correspondente bloquea o programa en vez de redirixilo.

Envía a conta primeiro pola súa propia táboa e logo deixa que a devasa confirme o resultado. Isto funciona nun host cuxo iproute2 entende uidrange, o que ip rule help informa nunha liña. A táboa 100 contén unha ruta predeterminada polo túnel, e a regra envía só o UID 1000 a esa táboa. Todo o demais segue na táboa principal e segue usando a ligazón física.

ip rule add uidrange 1000-1000 table 100
ip route add default dev tun0 table 100

Unha segunda conta que necesita outro túnel recibe a táboa 101 e a súa propia regra, porque dous perfís non poden posuír ambos a ruta predeterminada na mesma táboa. Os perfís de NetworkManager son de todo o sistema, así que esta parte segue sendo manual, e as regras desaparecen ao reiniciar salvo que algo as restaure. Escribe as táboas nun script de arranque xunto á definición do túnel para que unha reconexión non poida deixar unha conta no camiño equivocado.

Por que a excepción de loopback mata o túnel

A maioría das versións copiadas da regra descartan cada paquete que non sale por loopback. O túnel non é loopback, así que esa forma tamén descarta o tráfico do túnel, e o resultado parece un VPN morto máis que un erro da devasa.

Cando se executa OUTPUT, o núcleo xa escolleu unha interface de saída, así que -o compárase coa resposta da ruta e non coa cabeceira do paquete. A coincidencia de propietario só se consulta en OUTPUT e POSTROUTING, e os paquetes reenviados non levan ningún socket para que a lea. Ese conxunto significa que a excepción debe nomear a interface do túnel e negar esa coincidencia, en vez de negar loopback.

Loopback aínda merece o seu propio ACCEPT por riba do DROP, porque o primeiro paquete DNS do programa vai ao stub local a través de lo. Pon as dúas regras nesta orde e a conta chega ao stub, despois ao túnel, e a ningún outro sitio.

iptables -I OUTPUT 1 -o lo -m owner --uid-owner vpnapp -j ACCEPT
iptables -A OUTPUT -m owner --uid-owner vpnapp ! -o tun0 -j DROP

IPv6 é un conxunto de regras á parte, e un par só para v4 deixa ip6tables intacto. Un programa que prefire rexistros AAAA sairía entón pola ligazón física sen chegar nunca ao DROP. Reflicte ambas as regras en ip6tables, ou desactiva IPv6 no host cando o túnel non leve ningunha dirección v6. As regras engadidas a man tamén desaparecen ao reiniciar, así que gárdaas con netfilter-persistent save ou reconstrúe a mesma lóxica como unha táboa nftables cargada pola unidade de arranque.

As respostas DNS veñen doutro proceso

A consulta do programa a 127.0.0.53 pasa o ACCEPT de loopback, e aí é onde se detén a coincidencia de UID. O resolver stub reenvía cara arriba desde a súa propia conta de servizo, así que unha regra vinculada a vpnapp nunca ve a segunda consulta. Se o resolver escolle un servidor LAN para a ligazón, as respostas volven pola NIC física mentres os paquetes propios do programa quedan no túnel. O resultado visible é un túnel que funciona con nomes rotos, ou nomes respondidos por un servidor que non escolliches.

Corrixe a resolución de nomes na ligazón do túnel, non na aplicación. Dálle ao perfil do túnel un servidor DNS e un dominio de encamiñamento, e despois confirma con resolvectl query e resolvectl status tun0 que o resolver o usa para os nomes que importan. Dentro dun espazo de nomes de rede o problema desaparece, porque o resolver do espazo só coñece o túnel.

Comproba a regra desde a propia conta

Pregúntalle ao núcleo que ruta recibe un UID, despois fai unha petición como esa conta e compara o enderezo de saída co da túa propia conta. Se ambos informan do mesmo enderezo, a regra coincide con algo distinto do tráfico que probaches. O primeiro comando de abaixo mostra a consulta de ruta sen enviar un paquete, o que separa un erro de encamiñamento dun erro da devasa.

ip route get 1.1.1.1 uid 1000
sudo -u vpnapp curl -s https://ifconfig.co

Os contadores mostran se unha regra se disparou algunha vez. Executa iptables -vnL OUTPUT, le o reconto de paquetes xunto a cada regra e trata un cero xunto ao DROP despois de tráfico real como un erro de coincidencia.

O tráfico reenviado queda fóra deste deseño. Un contedor ou unha VM detrás dunha ponte non ten socket local propiedade de vpnapp, así que a coincidencia de propietario nunca se dispara e eses paquetes seguen a política FORWARD. Se esa carga debe cubrirse, próbaa por separado e escribe unha política vinculada á ponte ou ao enderezo do contedor. O deseño aguanta cando a táboa, a excepción de interface, o resolver e as regras v6 apuntan todos na mesma dirección; deixa fóra calquera deles e a conta ou filtra ou perde conectividade.

Artigos relacionados