Android VPN-routning: policyregler före tabellen main
Android väljer en routingtabell med uid- och merkeregler före varje destinationsuppslag, så en rutt som läggs till i main kanske aldrig tillfrågas.
En rutt som läggs till i tabellen main på Android ser korrekt ut i varje lista och bär ändå inte trafiken. Orsaken är att tabellen main bara är ett alternativ bland flera, och den tillfrågas inte först. Androids nätverkstjänst bygger policyregler som väljer en routingtabell utifrån information som destinationsadressen inte innehåller — appens uid, en markering på paketet, gränssnittet det kom in på — och destinationsuppslaget sker efteråt, inuti den tabell som reglerna valde.
Tre beslut i fast ordning
Ett paket på väg ut fattar tre separata beslut. Först avgör en policyregel vilken routingtabell som gäller. Sedan avgör en rutt i den tabellen nästa hopp och det utgående gränssnittet. Till sist avgör netfilter-krokar om paketet överlever, ändras eller omdirigeras.
Ordningen spelar roll eftersom varje steg bara kan svara på sin egen fråga. En brandväggsregel som accepterar ett paket väljer ingen tabell, och en rutt som läggs till i en tabell som ingen regel väljer är verkningslös. Mycket av förvirringen kring Android-VPN kommer från att man rättar en av de tre och inte ser någon ändring, eftersom felet låg i en annan.
| Stage | Selector | What it cannot decide |
|---|---|---|
| Policy rule | uid range, mark, incoming interface | Which route exists inside the table |
| Route lookup | Destination prefix, longest match | Whether the packet is allowed out |
| Netfilter hook | Chain position, match conditions | Which table was selected |
Markeringar är ett packat fält, inte en etikett du äger
Android packar flera fakta i samma markeringsvärde: vilket nätverk som valdes, om en app uttryckligen valde ett nätverk, om en socket är skyddad från VPN och bitar kopplade till behörigheter. AOSP:s fwmark-huvud definierar den layouten för aktuellt träd, och layouten är en plattformsintern detalj som ändras mellan utgåvor.
Därför är det farligt på Android att skriva ett helt markeringsvärde med iptables -j MARK --set-mark. Det lägger inte till en tagg; det ersätter det nätverksval som systemet redan gjort. Paketet matchar då andra regler, vilket kan se ut som att VPN började fungera, eller som att en orelaterad app tappade anslutningen, beroende på vilka bitar som skrevs över.
Om du måste röra markeringar, läs först det aktuella värdet och använd en mask så att bara de bitar du förstår ändras:
ip rule show
ip route show table all | head -40
ip route get 10.20.4.11En anslutningsmarkering är ett eget objekt från en paketmarkering, och ingen av dem är en rutt. Att återställa markeringen på svarspaket är ett annat steg än att välja tabell på väg ut, och därför ger enkelriktade rättelser anslutningar som öppnas och sedan hänger.
Vem äger reglerna och tabellerna
Androids nätverkstjänst netd skapar reglerna och tabellerna per nätverk. AOSP RouteController är platsen där dessa regler sätts samman för VPN, fysiska nätverk och uid-intervall, och dess livscykel är knuten till nätverkshändelser i stället för en konfigurationsfil.
Två konsekvenser följer. För det första är prioriteringar och tabellnummer som du hittar på en telefon interna byggdetaljer, inte ett gränssnitt: nummer som ser likadana ut på en annan Android-version kan betyda något helt annat. För det andra byggs tillståndet om när nätverk ändras, så en regel som infogas i dag från ett root-skal kan vara borta efter nästa Wi-Fi-byte.
Den försvinnningen rapporteras ofta som att systemet ignorerar en rutt. Det som faktiskt hände är att regeln som valde tabellen inte längre finns, och rutten i tabellen nu är onåbar ur paketets perspektiv. Kontrollera regellistan, inte ruttlistan, när en fungerande konfiguration slutar fungera efter en återanslutning.
Frågor om krokordningen har inget universellt svar
Frågan om iptables körs före VPN-routningen går inte att besvara utan att namnge paketets väg. Ett lokalt genererat paket passerar output-kroken, där en markeringsändring utlöser en ny routingkontroll — ett andra uppslag, inte en efterhandsredigering av det första. Ett paket som kommer från en tether-klient passerar prerouting-vägen och vidarebefordras i stället, och kedjorna som ser det är andra.
Samma regel kan alltså vara effektiv för en apps trafik och osynlig för tether-trafik, och en räknare som står kvar på noll berättar vilken väg paketet tog, inte om regeln är fel. Använd räknare för att fastställa vägen innan du ändrar ett matchningsvillkor.
Det här är också steget där ett policyroutingfel blir svårt att se. Routning till en tabell som finns men inte innehåller destinationen ger ett nätverk onåbart som ser ut som en brandväggsdrop, medan en brandväggsdrop ger en timeout som ser ut som ett routing-svart hål.
Diagnostisera inne i systemet, eller låt det vara
För en auktoriserad granskning, fastställ fyra fakta innan du rör något: appens uid och profil, nätverket Android valde för den, om VPN över huvud taget inkluderar appen, och om problemet finns för båda adressfamiljerna. Ett undantag per app förklarar väldigt många rapporter om att VPN inte fungerar utan något routingfel.
Läs sedan reglerna, maskerna och tabellerna som faktiskt gäller, och håll ett nätverksbyte inom observationsfönstret. Om beteendet ändras över en återanslutning tittar du på en hanterad livscykel, inte på en saknad rutt.
För vanliga krav, använd de stödda kontrollerna: inkludering och undantag per app i VPN-programmet, och plattformens eget nätverksval för allt som behöver en bestämd väg. Om en privilegierad integration verkligen måste samarbeta med routingmaskineriet, rikta in dig på implementeringen av exakt den Android-version du stöder och underhåll dess livscykel genom komponenten som äger den. Att spola plattformskedjor eller infoga en regel med hög prioritet gör en diagnos till en andra nätverkshanterare, och telefonen kommer att bete sig därefter.