Kodėl jūsų Android VpnService tunelis nustoja maršrutizuoti srautą
Keturi VpnService įsitikinimai, kurie lūžta produkcijoje: vienas tunelis vienu metu, maršrutų apimtis, sąrašai pagal programas ir išjungimo tvarka, su patikra kiekvienam.
Keturių prielaidų apie Android VpnService man kartą kainavo didžiąją savaitės dalį, ir nė viena nebuvo apie tinklo protokolą. Programėlė sakė prisijungta, rakto piktograma sėdėjo būsenos juostoje, o srautas vis tiek išeidavo įprastu maršrutu. Aš vis maniau, kad tunelis yra mano, kol jo nesustabdysiu, kad maršrutai, kuriuos pridedu, yra maršrutai, kuriuos gaunu, ir kad stopSelf iškvietimas nusineš sąsają. Paslauga elgėsi tiksliai taip, kaip dokumentuota. Sulūžęs buvo mano mentalinis modelis.
Mitas 1: tunelis laikosi, kol jūs jį sustabdote
Android vienu metu laiko vieną VPN sąsają. Įveskite antrą, ir platforma nededą jos į eilę: ji išjungia jūsiškę ir iškvies onRevoke() jūsų paslaugoje. Jei jūsų kodas išardo viską tik paspaudus mygtuką, perėmimas palieka deskriptorių ir skaitymo giją, o kitas paleidimas paveldi abu.
VpnService nuoroda aiškiai sako, kad prepare() grąžina null, kai programėlė jau turi sutikimą, ir Intent, kai vartotojas dar turi jį patvirtinti. Tas sutikimas ateina tik jei ketinimą perduodate per startActivityForResult(). Praleiskite perdavimą ir establish() grąžins null, o tai skaitoma kaip tyli nesėkmė, o ne leidimų problema.
Always-on VPN prideda savininką, kurio jūs nekontroliuojate. Sistema gali grąžinti tunelį po įkrovimo ar po avarijos, ir ji aplenkia VPN programėlę, kurią vartotojas paleido rankiniu būdu. Kurkite onRevoke() taip, kad jis uždarytų tuos pačius išteklius kaip įprastas išjungimas, ir traktuokite jį kaip pagrindinį sustabdymo kelią, o ne kraštinį atvejį. Vartotojai keičia VPN programėles; jūsų kodas turi tai atlaikyti.
Mitas 2: addRoute pagauna būtent tai, ką išvardijote
Maršrutai nusprendžia, kurie paketai pasiekia jūsų deskriptorių, o apimties klaida sukuria erziningą atvejį, kai dalis srauto eina per tunelį, o dalis ne. addRoute("0.0.0.0", 0) paima visą IPv4, o tai reiškia, kad dabar kiekvienas paketas ateina pas jus ir turi būti persiųstas arba numestas. /32 vienam mazgui beveik nieko nepaima, o visa kita elgiasi taip, tarsi jūsų VPN niekada nebūtų įdiegtas.
Šeimos skaičiuojamos atskirai. Tik IPv4 maršrutas palieka IPv6 pagrindiniame tinkle, todėl dvigubo kamino mazgas vis dar gali išspręsti ir prisijungti už tunelio, nors viduje atrodo visiškai prijungtas.
DNS laikosi tos pačios logikos. Serveriai, pridėti su addDnsServer(), patenka į programas, kurios naudoja tunelio sprendėją, o programa su savo šifruotu DNS galiniu tašku arba Private DNS, nustatytu pagal mazgo vardą, jų visiškai nepaiso. Kai vardai nepavyksta vienai programai ir pavyksta kitai, patikrinkite sprendėjo kelią prieš liedami nors vieną maršrutą.
Mitas 3: taisyklės pagal programas yra jungiklis, kurį galite perjungti bet kuria kryptimi
addDisallowedApplication() ir addAllowedApplication() yra du režimai, o ne du filtrai. Kūrėjas priima vieną modelį vienam tuneliui, todėl programa, kuri nori visko, išskyrus tris paketus, naudoja uždraustą sąrašą ir palieka leidžiamą sąrašą tuščią. Du yra vienas kitą išskiriantys, ir žmonės tai sužino vykdymo metu po to, kai nustatymų ekranas jau pasiūlė abu jungiklius.
Paketų matomumas taip pat kanda. Nuo Android 11 galite įvardinti tik tuos paketus, kuriuos matote, todėl išskyrimo sąrašas, sudarytas iš užklausos, kurios jūsų programai neleidžiama vykdyti, tyliai praleis įrašus. Palyginkite sąrašą su tuo, ką paketų tvarkyklė iš tikrųjų grąžina, prieš juo pasitikėdami.
Darbo profiliai vėl padalija auditoriją. Profilio viduje paleistas tunelis apima tą profilį ir jo programas, o asmeninė pusė išlaiko savo ryšį. Tai vieną kartą priimtas apimties sprendimas, ir jam vieta sąsajoje, o ne palaikymo biliete.
Mitas 4: stopSelf užbaigia tunelį
Ne. Sąsaja gyvena deskriptoriuje, kurį grąžina establish(), ir paslaugos objektas gali mirti, kol tas deskriptorius lieka atidarytas. Pirmiausia uždarykite ParcelFileDescriptor, atsisakykite nuorodos, prijunkite arba pertraukite skaitymo ir rašymo gijas, tada iškvieskite stopForeground(true) ir stopSelf() ta tvarka.
Pamirštas deskriptorius yra įprasta priežastis, kodėl paslauga atrodo išgyvenanti savo paties sustabdymo prašymą, ir tai paaiškina pasenusį maršrutą, kuris gyvena ilgiau nei jį pridėjusi programa.
Atvirkštinė klaida taip pat dažna: uždaryti deskriptorių, kai skaitymo gija yra skaitymo viduryje, ir tada palikti tą giją tvarkytis su išimtimi, kuriai niekas neparašė apdorojimo. Laikykite išjungimo seką vienoje vietoje ir leiskite tiek vartotojo veiksmui, tiek onRevoke() į ją kviestis.
Ką patikrinti, kai tunelis atrodo miręs
Apsaugokite ilgaamžius lizdus su protect() prieš tuneliui pakylant. Be to tunelio transportas vėl nukreipiamas į tunelį ir ryšys sustingsta taip, kad atrodo lygiai kaip serverio gedimas. Testuokite su adresu, kuris egzistuoja tik už nuotolinio tinklo, o ne su vieša svetaine, kuri atsako iš podėlio arba iš pagrindinio kelio.
Užregistruokite establish() rezultatą, maršrutus, išskirtus paketus ir išjungimo priežastį. Tos keturios eilutės atskiria sutikimo problemą nuo apimties problemos ir nuo gyvavimo ciklo problemos be paketų fiksavimo.