Fixarea unui singur program la un VPN pe Linux este în primul rând o problemă de rutare, iar regula de firewall decide doar ce se întâmplă după ce ruta este aleasă. Dacă potrivești UID-ul programului în OUTPUT înainte ca tunelul să dețină ruta implicită, regula aruncă exact traficul pe care voiai să-l protejezi. Aceeași regulă nu vede nici procesul resolverului care răspunde interogărilor DNS ale programului. Ordonează corect și un singur cont fără privilegii devine un selector de încredere pentru tunelul divizat.

Dă contului o rută înainte de verdict

Potrivirea proprietarului răspunde la o singură întrebare: un socket local care aparține acestui UID a construit pachetul? Nu alege interfață și nu poate muta traficul pe care tabela principală îl trimite deja prin NIC-ul fizic. De aceea o regulă UID fără rută potrivită blochează programul în loc să îl redirecționeze.

Trimite contul mai întâi prin propria tabelă și apoi lasă firewallul să confirme rezultatul. Funcționează pe un host al cărui iproute2 înțelege uidrange, ceea ce ip rule help raportează într-o singură linie. Tabela 100 ține o rută implicită prin tunel, iar regula trimite doar UID 1000 către acea tabelă. Tot restul rămâne în tabela principală și continuă să folosească legătura fizică.

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

Un al doilea cont care are nevoie de alt tunel primește tabela 101 și propria regulă, pentru că două profile nu pot deține ambele ruta implicită în aceeași tabelă. Profilurile NetworkManager sunt la nivel de sistem, așa că această parte rămâne manuală, iar regulile dispar la repornire dacă ceva nu le restaurează. Scrie tabelele într-un script de pornire lângă definiția tunelului, ca o reconectare să nu poată lăsa un cont pe drumul greșit.

De ce excepția loopback ucide tunelul

Majoritatea versiunilor copiate ale regulii aruncă fiecare pachet care nu iese prin loopback. Tunelul nu este loopback, așa că acea formă aruncă și traficul tunelului, iar rezultatul arată ca un VPN mort și nu ca o eroare de firewall.

Când rulează OUTPUT, nucleul a ales deja o interfață de ieșire, așa că -o este comparat cu răspunsul rutei și nu cu antetul pachetului. Potrivirea proprietarului este consultată doar în OUTPUT și POSTROUTING, iar pachetele redirecționate nu poartă niciun socket pe care să îl citească. Această combinație înseamnă că excepția trebuie să numească interfața tunelului și să nege acea potrivire, în loc să nege loopback.

Loopback merită în continuare propriul ACCEPT deasupra DROP, pentru că primul pachet DNS al programului merge la stubul local prin lo. Pune cele două reguli în această ordine și contul ajunge la stub, apoi la tunel, și nicăieri altundeva.

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 este un set separat de reguli, iar o pereche doar pentru v4 lasă ip6tables neatins. Un program care preferă înregistrări AAAA ar ieși atunci prin legătura fizică fără să atingă vreodată DROP. Oglindește ambele reguli în ip6tables, sau dezactivează IPv6 pe host când tunelul nu poartă nicio adresă v6. Regulile adăugate manual dispar și ele la repornire, așa că salvează-le cu netfilter-persistent save sau reconstruiește aceeași logică drept tabel nftables încărcat de unitatea de pornire.

Răspunsurile DNS vin din alt proces

Interogarea programului către 127.0.0.53 trece de ACCEPT-ul loopback, și acolo se oprește potrivirea UID. Resolverul stub redirecționează în amonte din propriul cont de serviciu, așa că o regulă legată de vpnapp nu vede niciodată a doua interogare. Dacă resolverul alege un server LAN pentru legătură, răspunsurile se întorc prin NIC-ul fizic în timp ce pachetele programului rămân în tunel. Rezultatul vizibil este un tunel funcțional cu nume stricate, sau nume la care răspunde un server pe care nu l-ai ales.

Repară rezolvarea numelor pe legătura tunelului, nu în aplicație. Dă profilului tunelului un server DNS și un domeniu de rutare, apoi confirmă cu resolvectl query și resolvectl status tun0 că resolverul îl folosește pentru numele care contează. În interiorul unui spațiu de nume de rețea problema dispare, pentru că resolverul spațiului cunoaște doar tunelul.

Verifică regula din contul însuși

Întreabă nucleul ce rută primește un UID, apoi fă o cerere ca acel cont și compară adresa de ieșire cu cea a contului tău. Dacă ambele raportează aceeași adresă, regula potrivește altceva decât traficul pe care l-ai testat. Prima comandă de mai jos arată căutarea rutei fără a trimite un pachet, ceea ce separă o eroare de rutare de una de firewall.

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

Contoarele arată dacă o regulă s-a activat vreodată. Rulează iptables -vnL OUTPUT, citește numărul de pachete de lângă fiecare regulă și tratează un zero lângă DROP după trafic real ca pe o eroare de potrivire.

Traficul redirecționat rămâne în afara acestui design. Un container sau un VM din spatele unui pod nu are niciun socket local deținut de vpnapp, așa că potrivirea proprietarului nu se activează niciodată și pachetele urmează în schimb politica FORWARD. Dacă acea încărcare trebuie acoperită, testeaz-o separat și scrie o politică legată de pod sau de adresa containerului. Designul rezistă când tabela, excepția de interfață, resolverul și regulile v6 indică toate în aceeași direcție; omite oricare dintre ele și contul fie scapă, fie pierde conectivitatea.

Articole similare