Android VpnService sa pripojí, ale do siete sa nedostane žiadny paket
Tunel beží a stránky stále zlyhávajú: skontrolujte režim čítania, ochranu socketu, podporu TCP a zoznam trás, skôr než obviníte server VPN.
Tunel naskočí, ikona kľúča sa objaví a každá požiadavka v prehliadači napriek tomu vyprší. V aplikácii s VpnService táto kombinácia zvyčajne znamená, že čítacia slučka žije a výstupná polovica je pokazená, nie že server odmietol handshake. Android už rozhranie vytvoril, presunul doň prevádzku aplikácie a odovzdal vášmu procesu deskriptor súboru. Všetko za týmto deskriptorom je váš kód a jedno chýbajúce volanie stačí, aby pohltilo celý tok.
Čo vám systém dáva a čo nie
establish() vracia ParcelFileDescriptor pre rozhranie tun. Systém doň zapisuje odchádzajúce IP pakety a očakáva, že ich váš proces prečíta, rozhodne, kam idú, a odošle ich cez obyčajný socket. Pod tým nie je žiadne presmerovanie v jadre, takže keď nikto nečíta, pakety sa zahodia a počítadlá rozhrania sa nikdy nepohnú.
Začnite počítaním namiesto hádania. Zvyšujte počítadlo pri každom úspešnom čítaní a zaznamenajte cieľ a protokol prvých paketov. Ak počítadlo zostane na nule, kým sa generuje prevádzka, porucha je pred vaším kódom na presmerovanie, čo zužuje hľadanie na konfiguráciu buildera a režim čítania. Ak sa počítadlo hýbe a prehliadač stále zlyháva, porucha je v ceste odosielania alebo na ceste späť.
Blokujúce čítanie rozhoduje, či sa slučka hýbe
setBlocking(true) prepne deskriptor do blokujúceho režimu a existuje od API 21. V starších verziách je riešením malý pomocník JNI, ktorý vymaže O_NONBLOCK pomocou fcntl na surovom deskriptore. Režim je dôležitý, pretože určuje, čo smie čítanie bez čakajúceho paketu robiť.
V neblokujúcom režime sa také čítanie vráti okamžite so zápornou hodnotou a slučka, ktorá výsledok ignoruje, točí jadro bez prenesenia jediného bajtu prevádzky. V blokujúcom režime čítanie čaká, čo vlákno na presmerovanie chce, ale vlákno sa potom už nedá prerušiť. Zatvorenie deskriptora je podporovaný spôsob, ako nechať čítanie zlyhať, aby slučka mohla skončiť. Zabiť proces alebo prerušiť vlákno môže naopak nechať ďalší štart s deskriptorom, ktorý už rozhraniu nezodpovedá.
Čítajte jeden paket na volanie do vyrovnávacej pamäte veľkosti MTU rozhrania a nulu alebo záporný výsledok berte ako chybu, nie ako prázdny paket.
protect() pred prvým odoslaním, nie po ňom
Každý socket, ktorý nesie zapuzdrený paket, musí byť najprv chránený. Nechránený socket poslúcha rovnaké pravidlá smerovania, ktoré ste práve nastavili, takže jeho pakety sa vracajú do tunela a vaša vlastná čítacia slučka ich znovu zachytí. Tunel potom presmerúva vlastný výstup donekonečna a zvonku to vyzerá ako spojenie, ktoré sa otvorí a nikdy neodpovie.
Poradie je protect, potom connect alebo bind, potom send. Volanie protect po connect môže nechať predchádzajúce rozhodnutie o trase, takže socket ďalej používa nesprávnu cestu, aj keď volanie vrátilo true. Skontrolujte boolean, ktorý vracia, a nahlas zlyhajte, keď je false, namiesto toho, aby ste vošli do odoslania, ktoré nemôže fungovať.
Chyba oprávnenia zo sendto na sockete, ktorý sa nikdy nedotkol protect(), je obvyklý podpis tejto chyby.
Ukážka s UDP nenarieša HTTPS
Ukážka presmerovania, ktorú kopíruje väčšina návodov, zvláda UDP a nič iné. TCP potrebuje handshake, poradové čísla, potvrdenia a opakované odosielanie a slučka, ktorá kopíruje dáta medzi deskriptorom tun a socketom, neponúka nič z toho. Preto UDP dotaz DNS prejde, kým každá požiadavka HTTPS uviazne. Buď spustite TCP stack v používateľskom priestore, alebo odovzdajte spojenia lokálnemu proxy, ktoré ukončí TCP na zariadení a presmeruje tok.
Rozdiel otestujte, kým čokoľvek prepíšete: pošlite jeden UDP dotaz a otvorte jedno TCP spojenie k rovnakému hostiteľovi. Ak cesta UDP funguje a cesta TCP visí na prvom SYN, problém je v kóde na presmerovanie, nie v serveri. Toto jediné porovnanie ušetrí veľa času stráveného čítaním serverových logov, ktoré neobsahujú nič užitočné.
Zoznam trás a filtre podľa aplikácií obmedzujú, čo vidíte
addAddress, addRoute a addDns určujú, čo systém posiela do tunela. Cieľ mimo zoznamu trás sa k vášmu deskriptoru nikdy nedostane, takže to nespraví žiadne množstvo logiky presmerovania. Zaznamenávajte volania buildera a porovnávajte ich s adresami, na ktoré sa aplikácia skutočne snaží dostať.
Výber podľa aplikácií má svoj prah: addAllowedApplication a addDisallowedApplication vyžadujú API 21 a v starších verziách tieto metódy jednoducho chýbajú. Návrh, ktorý filtruje podľa balíka, tam vôbec nemôže bežať.
Opätovné nadviazanie necháva zastarané deskriptory
Ďalšie volanie establish() vráti nový deskriptor a zavrie predchádzajúce rozhranie. Starý deskriptor je teraz neplatný, takže vlákno, ktoré na ňom ešte blokuje, dostane chybu a sockety vytvorené pre starú cestu môže byť potrebné nahradiť. onRevoke je spätné volanie pre tunel, ktorý používateľ alebo systém odobral. Držanie životného cyklu u jedného vlastníka, ktorý pred novým nadviazaním zastaví čítačku, zabráni väčšine stavov pripojené-ale-mŕtve.
Každú chybu čítajte podľa kroku, ku ktorému patrí. Reset spojenia z recvfrom znamená, že vzdialená strana zavrela, kým váš kód ešte čítal. Chyba nesprávnej adresy ukazuje na parsovanie hlavičky alebo chybu posunu, nie na sieť.
Poradie práce: blokujúci režim zapnutý, jeden paket na čítanie, hlavička spracovaná, socket chránený, dáta odoslané, čisté zatvorenie pri odovzdaní a zoznam trás, ktorý pokrýva cieľ.