Prečo váš tunel Android VpnService prestane smerovať prevádzku
Štyri presvedčenia o VpnService, ktoré v produkcii pukajú: jeden tunel naraz, rozsah trás, zoznamy podľa aplikácií a poradie vypnutia, s kontrolou pre každé.
Štyri predpoklady o Android VpnService ma raz stáli väčšinu týždňa a ani jeden nebol o sieťovom protokole. Aplikácia hlásila pripojené, ikona kľúča sedela v stavovom riadku a prevádzka napriek tomu odchádzala bežnou trasou. Stále som predpokladal, že tunel je môj, kým ho nezastavím, že trasy, ktoré pridám, sú trasy, ktoré dostanem, a že volanie stopSelf vezme rozhranie so sebou. Služba sa správala presne tak, ako je zdokumentované. Pokazená bola moja mentálna predstava.
Mýtus 1: tunel vydrží, kým ho nezastavíte
Android drží jedno rozhranie VPN naraz. Zriaďte druhé a platforma ho nezaradí do frontu: deaktivuje vaše a zavolá onRevoke() vo vašej službe. Ak váš kód zbúra všetko len pri stlačení tlačidla, prevzatie nechá za sebou deskriptor a čítacie vlákno a ďalší štart zdedí oboje.
Referenčná dokumentácia VpnService výslovne hovorí, že prepare() vracia null, keď aplikácia už má súhlas, a Intent, keď ho používateľ ešte musí schváliť. Ten súhlas dorazí len vtedy, keď zámer doručíte cez startActivityForResult(). Preskočte odovzdanie a establish() vráti null, čo sa číta ako tiché zlyhanie, nie ako problém s oprávneniami.
Always-on VPN pridáva vlastníka, ktorého neovládate. Systém môže tunel vrátiť po štarte alebo po páde a predbehne aplikáciu VPN, ktorú používateľ spustil ručne. Stavte onRevoke() tak, aby zatváral rovnaké prostriedky ako vaše bežné vypnutie, a berte ho ako hlavnú cestu zastavenia, nie ako okrajový prípad. Používatelia striedajú aplikácie VPN; váš kód to musí prežiť.
Mýtus 2: addRoute zachytí presne to, čo ste vymenovali
Trasy rozhodujú, ktoré pakety sa dostanú k vášmu deskriptoru, a chyba v rozsahu vytvorí šialený prípad, keď časť prevádzky ide tunelom a časť nie. addRoute("0.0.0.0", 0) vezme celý IPv4, čo znamená, že každý paket teraz prichádza k vám a musí byť poslaný ďalej alebo zahodený. /32 pre jedného hosta nevezme takmer nič a všetko ostatné sa správa, ako keby váš VPN nikdy nebol nainštalovaný.
Rodiny sa počítajú oddelene. Trasa len pre IPv4 necháva IPv6 na podkladovej sieti, takže host s dvoma zásobníkmi môže stále prekladať a pripájať sa mimo tunela, hoci vo vnútri vyzerá dokonale pripojený.
DNS ide tou istou logikou. Servery pridané cez addDnsServer() idú aplikáciám, ktoré používajú prekladač tunela, kým aplikácia s vlastným šifrovaným DNS koncovým bodom alebo s Private DNS nastaveným na meno hosta ich úplne ignoruje. Keď mená zlyhávajú jednej aplikácii a fungujú inej, skontrolujte cestu prekladača, skôr než sa dotknete čo i len jednej trasy.
Mýtus 3: pravidlá podľa aplikácií sú prepínač, ktorý môžete obrátiť na ktorúkoľvek stranu
addDisallowedApplication() a addAllowedApplication() sú dva režimy, nie dva filtre. Tvorca prijíma jeden model na tunel, takže aplikácia, ktorá chce všetko okrem troch balíkov, použije zakázaný zoznam a povolený nechá prázdny. Tie dva sa navzájom vylučujú a ľudia to zistia za behu po tom, čo obrazovka nastavení už ponúkla oba prepínače.
Viditeľnosť balíkov tiež hryzie. Na Androide 11 a novšom môžete pomenovať len balíky, ktoré vidíte, takže zoznam výnimiek postavený z dotazu, ktorý vaša aplikácia nesmie spustiť, ticho vynechá položky. Porovnajte zoznam s tým, čo správca balíkov skutočne vracia, a až potom mu dôverujte.
Pracovné profily znovu delia publikum. Tunel spustený v profile pokrýva ten profil a jeho aplikácie, kým osobná strana si drží vlastnú konektivitu. Je to rozhodnutie o rozsahu urobené raz a patrí do rozhrania, nie do tiketu podpory.
Mýtus 4: stopSelf ukončí tunel
Neukončí. Rozhranie žije v deskriptore, ktorý vracia establish(), a objekt služby môže zomrieť, kým deskriptor zostáva otvorený. Najprv zatvorte ParcelFileDescriptor, pustite referenciu, pripojte sa k čítaciemu a zapisovaciemu vláknu alebo ich prerušte, a potom zavolajte stopForeground(true) a stopSelf() v tomto poradí.
Zabudnutý deskriptor je obvyklý dôvod, prečo sa zdá, že služba prežije svoju vlastnú požiadavku na zastavenie, a vysvetľuje zastaranú trasu, ktorá žije dlhšie ako aplikácia, ktorá ju pridala.
Opačná chyba je rovnako častá: zavrieť deskriptor, kým čítacie vlákno číta, a potom nechať to vlákno vyrovnať sa s výnimkou, pre ktorú nikto nenapísal obsluhu. Držte poradie vypnutia na jednom mieste a nechajte doň volať aj akciu používateľa, aj onRevoke().
Čo skontrolovať, keď tunel vyzerá mŕtvy
Chráňte dlho žijúce sockety pomocou protect(), kým tunel vstane. Bez toho sa vlastná doprava tunela smeruje späť do tunela a spojenie sa zasekne spôsobom, ktorý vyzerá presne ako porucha servera. Testujte proti adrese, ktorá existuje len za vzdialenou sieťou, nie proti verejnej stránke, ktorá odpovedá z vyrovnávacej pamäte alebo z podkladovej cesty.
Zaznamenajte výsledok establish(), trasy, vylúčené balíky a dôvod vypnutia. Tie štyri riadky oddeľujú problém so súhlasom od problému s rozsahom a od problému so životným cyklom bez zachytávania paketov.