Så binder du en Linux-app till ett VPN med UID-regler
En UID-regel ser bara lokala sockets, och det vanliga loopback-undantaget dödar tunneln. Rutta kontot först och filtrera och verifiera sedan varje steg.
Att binda ett enda program till ett VPN på Linux är mest ett routingproblem, och brandväggsregeln avgör bara vad som händer efter att rutten valts. Om du matchar programmets UID i OUTPUT innan tunneln äger standardrutten, släpper regeln just den trafik du ville skydda. Samma regel ser inte heller resolverprocessen som svarar på programmets DNS-frågor. Få ordningen rätt och ett enda konto utan privilegier blir en pålitlig väljare för den delade tunneln.
Ge kontot en rutt före en dom
Ägar-matchningen besvarar en fråga: byggde en lokal socket som tillhör detta UID paketet? Den väljer inte gränssnitt och kan inte flytta trafik som huvudtabellen redan skickar ut genom det fysiska NIC-kortet. Därför blockerar en UID-regel utan matchande rutt programmet i stället för att omdirigera det.
Skicka kontot genom sin egen tabell först och låt sedan brandväggen bekräfta resultatet. Detta fungerar på en värd vars iproute2 förstår uidrange, vilket ip rule help rapporterar på en rad. Tabell 100 håller en standardrutt genom tunneln, och regeln skickar bara UID 1000 till den tabellen. Allt annat stannar på huvudtabellen och fortsätter använda den fysiska länken.
ip rule add uidrange 1000-1000 table 100 ip route add default dev tun0 table 100
Ett andra konto som behöver en annan tunnel får tabell 101 och sin egen regel, eftersom två profiler inte kan äga standardrutten i samma tabell samtidigt. NetworkManager-profiler är systemomfattande, så den här delen förblir manuell, och reglerna försvinner vid omstart om inte något återställer dem. Skriv tabellerna i ett startskript bredvid tunneldefinitionen så att en återanslutning inte kan lämna ett konto på fel väg.
Varför loopback-undantaget dödar tunneln
De flesta kopierade versioner av regeln släpper varje paket som inte går ut genom loopback. Tunneln är inte loopback, så den formen släpper också tunneltrafiken, och resultatet ser ut som ett dött VPN och inte som ett brandväggsfel.
När OUTPUT körs har kärnan redan valt ett utgångsgränssnitt, så -o jämförs med ruttens svar och inte med paketets huvud. Ägar-matchningen konsulteras bara i OUTPUT och POSTROUTING, och vidarebefordrade paket bär ingen socket som den kan läsa. Den kombinationen betyder att undantaget måste namnge tunnelns gränssnitt och negera den matchningen, i stället för att negera loopback.
Loopback förtjänar fortfarande sin egen ACCEPT ovanför DROP, eftersom det första DNS-paketet från programmet går till den lokala stubben via lo. Sätt de två reglerna i den här ordningen, och kontot når stubben, sedan tunneln, och ingenting annat.
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 är en egen regeluppsättning, och ett par bara för v4 lämnar ip6tables orört. Ett program som föredrar AAAA-poster skulle då gå ut genom den fysiska länken utan att någonsin nå DROP. Spegla båda reglerna i ip6tables, eller stäng av IPv6 på värden när tunneln inte bär någon v6-adress. Regler som lagts till manuellt försvinner också vid omstart, så spara dem med netfilter-persistent save eller bygg samma logik igen som en nftables-tabell som startenheten läser in.
DNS-svar kommer från en annan process
Programmets fråga till 127.0.0.53 passerar loopback-ACCEPT, och det är där UID-matchningen stannar. Stub-resolvern vidarebefordrar uppåt från sitt eget tjänstekonto, så en regel knuten till vpnapp ser aldrig den andra frågan. Om resolvern väljer en LAN-server för länken kommer svaren tillbaka över det fysiska NIC-kortet medan programmets egna paket stannar i tunneln. Det synliga resultatet är en fungerande tunnel med trasiga namn, eller namn som besvaras av en server du inte valde.
Rätta namnmatchningen på tunnelns länk, inte i applikationen. Ge tunnelprofilen en DNS-server och en routingdomän och bekräfta sedan med resolvectl query och resolvectl status tun0 att resolvern använder den för namnen som betyder något. Inne i ett nätverksnamnrymd försvinner problemet, eftersom namnrymdens resolver bara känner till tunneln.
Kontrollera regeln från själva kontot
Fråga kärnan vilken rutt ett UID får, gör sedan en begäran som det kontot och jämför utgångsadressen med din egen kontos. Om båda rapporterar samma adress matchar regeln något annat än trafiken du testade. Det första kommandot nedan visar ruttuppslaget utan att skicka ett paket, vilket skiljer ett routingfel från ett brandväggsfel.
ip route get 1.1.1.1 uid 1000 sudo -u vpnapp curl -s https://ifconfig.co
Räknare visar om en regel någonsin utlöstes. Kör iptables -vnL OUTPUT, läs paketantalet bredvid varje regel och behandla en nolla bredvid DROP efter verklig trafik som ett matchningsfel.
Vidarebefordrad trafik ligger utanför den här designen. En container eller VM bakom en brygga har ingen lokal socket som ägs av vpnapp, så ägar-matchningen utlöses aldrig och de paketen följer i stället FORWARD-policyn. Om den arbetsbelastningen ska täckas, testa den separat och skriv en policy knuten till bryggan eller containerns adress. Designen håller när tabellen, gränssnittsundantaget, resolvern och v6-reglerna alla pekar åt samma håll; utelämna någon av dem och kontot läcker eller tappar anslutningen.