Miért hagyja abba az Android VpnService-alagút a forgalom továbbítását
Négy VpnService-hiedelem, amely éles környezetben megbukik: egyszerre egy alagút, az útvonalak hatóköre, alkalmazásonkénti listák és a leállítás sorrendje, mindegyikhez egy ellenőrzéssel.
Négy feltevés az Android VpnService-ről egyszer majdnem egy hétbe került, és egyik sem a hálózati protokollról szólt. Az alkalmazás azt mondta, kapcsolódva, a kulcs ikon ott ült az állapotsorban, és a forgalom mégis a normál útvonalon ment ki. Folyton azt feltételeztem, hogy az alagút az enyém, amíg le nem állítom, hogy a hozzáadott útvonalak azok, amelyeket kapok, és hogy a stopSelf hívása magával viszi az interfészt. A szolgáltatás pontosan úgy viselkedett, ahogy dokumentálva van. A hibás rész az én mentális modellem volt.
Tévhit 1: az alagút addig áll, amíg le nem állítod
Az Android egyszerre egy VPN-interfészt tart. Hozz létre egy másodikat, és a platform nem sorolja be, hanem deaktiválja a tiédet, és meghívja a onRevoke()-t a szolgáltatásodban. Ha a kódod csak gombnyomásra bontja le, egy átvétel hátrahagy egy leírót és egy olvasószálat, és a következő indulás mindkettőt megörökli.
A VpnService-referencia kimondja, hogy a prepare() null-t ad vissza, ha az alkalmazásnak már van hozzájárulása, és egy Intent-t, ha a felhasználónak még jóvá kell hagynia. Ez a jóváhagyás csak akkor érkezik meg, ha a szándékot a startActivityForResult()-on keresztül továbbítod. Hagyd ki az átadást, és a establish() null-t ad vissza, ami csendes kudarcnak olvasható, nem engedélyproblémának.
Az Always-on VPN egy olyan tulajdonost ad hozzá, amelyet nem irányítasz. A rendszer visszahozhatja az alagutat indítás vagy összeomlás után, és megelőzi a felhasználó által kézzel indított VPN-alkalmazást. Úgy építsd fel a onRevoke()-t, hogy ugyanazokat az erőforrásokat zárja be, mint a normál leállításod, és kezeld elsődleges leállítási útvonalként, ne szélsőséges esetként. A felhasználók VPN-alkalmazást váltanak; a kódodnak ezt túl kell élnie.
Tévhit 2: az addRoute pontosan azt fogja meg, amit felsoroltál
Az útvonalak döntik el, mely csomagok érik el a leíródat, és egy hatókörhiba azt az őrjítő esetet hozza létre, amikor a forgalom egy része az alagúton megy, egy része nem. A addRoute("0.0.0.0", 0) az összes IPv4-et elveszi, ami azt jelenti, hogy most minden csomag hozzád érkezik, és továbbítani vagy eldobni kell. Egyetlen hoszt /32-es útvonala szinte semmit nem vesz el, és minden más úgy viselkedik, mintha a VPN-edet soha nem telepítették volna.
A családokat külön számolják. Egy csak IPv4-es útvonal az IPv6-ot az alatta lévő hálózaton hagyja, így egy kettős veremű hoszt még mindig fel tud oldani és kapcsolódni az alagúton kívül, miközben belül teljesen kapcsolódottnak látszik.
A DNS ugyanezt a logikát követi. A addDnsServer()-tal hozzáadott szerverek azokhoz az alkalmazásokhoz mennek, amelyek az alagút feloldóját használják, míg a saját titkosított DNS-végponttal vagy hosztnévre állított Private DNS-sel rendelkező alkalmazás teljesen figyelmen kívül hagyja őket. Amikor a nevek az egyik alkalmazásnál meghiúsulnak, a másiknál működnek, ellenőrizd a feloldó útvonalát, mielőtt akár egyetlen útvonalhoz hozzányúlnál.
Tévhit 3: az alkalmazásonkénti szabályok egy kapcsoló, amelyet bármelyik irányba át lehet billenteni
A addDisallowedApplication() és a addAllowedApplication() két mód, nem két szűrő. Az építő alagútonként egy modellt fogad el, ezért egy három csomagon kívül mindent kérő alkalmazás a tiltott listát használja, és az engedélyezett listát üresen hagyja. A kettő kölcsönösen kizárja egymást, és az emberek futásidőben jönnek rá, miután a beállítások képernyő már mindkét kapcsolót felkínálta.
A csomagláthatóság is harap. Android 11-től csak olyan csomagokat nevezhetsz meg, amelyeket látsz, ezért egy olyan lekérdezésből épített kizárási lista, amelyet az alkalmazásod nem futtathat, csendben kihagyja a bejegyzéseket. Hasonlítsd össze a listát azzal, amit a csomagkezelő valóban visszaad, mielőtt megbízol benne.
A munkaprofilok újra kettéosztják a közönséget. A profilon belül indított alagút lefedi azt a profilt és alkalmazásait, míg a személyes oldal megtartja a saját kapcsolatát. Ez egy egyszer meghozott hatókör-döntés, és a helye a felületen van, nem egy támogatási jegyben.
Tévhit 4: a stopSelf lezárja az alagutat
Nem. Az interfész a establish() által visszaadott leíróban él, és a szolgáltatásobjektum meghalhat, miközben a leíró nyitva marad. Először zárd be a ParcelFileDescriptor-t, engedd el a hivatkozást, csatlakozz az olvasó- és írószálakhoz vagy szakítsd meg őket, majd hívd meg a stopForeground(true)-t és a stopSelf()-t ebben a sorrendben.
Az elfelejtett leíró a szokásos oka annak, hogy egy szolgáltatás látszólag túléli a saját leállítási kérését, és ez magyarázza azt az elavult útvonalat, amely tovább él, mint az őt hozzáadó alkalmazás.
A fordított hiba ugyanilyen gyakori: bezárni a leírót, miközben egy olvasószál éppen olvas, majd magára hagyni azt a szálat egy olyan kivétellel, amelyre senki nem írt kezelést. Tartsd a leállítási sorrendet egy helyen, és hívd be oda a felhasználói műveletet és a onRevoke()-t is.
Mit érdemes ellenőrizni, amikor az alagút halottnak tűnik
Védd meg a hosszú életű socketeket a protect()-tal, mielőtt az alagút feljön. Enélkül az alagút saját szállítása visszairányítódik az alagútba, és a kapcsolat úgy akad el, hogy pontosan szerverhibának látszik. Olyan cím ellen tesztelj, amely csak a távoli hálózat mögött létezik, ne egy nyilvános oldal ellen, amely a gyorsítótárból vagy az alatta lévő útvonalról válaszol.
Naplózd a establish() eredményét, az útvonalakat, a kizárt csomagokat és a leállítás okát. Ez a négy sor elválasztja a hozzájárulási problémát a hatókör-problémától és az életciklus-problémától csomagrögzítés nélkül.