Az alagút felépül, megjelenik a kulcs ikon, és a böngésző minden kérése továbbra is időtúllépéssel zárul. Egy VpnService-alkalmazásban ez a kombináció általában azt jelenti, hogy az olvasási ciklus él, a kimeneti fél pedig törött, nem pedig azt, hogy a kiszolgáló elutasította a kézfogást. Az Android már létrehozta az interfészt, átvitte bele az alkalmazás forgalmát, és átadott a folyamatának egy fájlleírót. Minden, ami e leíró után történik, a saját kódja, és egyetlen hiányzó hívás elég a teljes adatfolyam elnyeléséhez.

Mit ad a rendszer, és mit nem

Az establish() egy ParcelFileDescriptor-t ad vissza egy tun interfészhez. A rendszer kimenő IP-csomagokat ír bele, és elvárja, hogy a folyamata olvassa őket, eldöntse, hová mennek, és hagyományos socketen keresztül küldje el. Alatta nincs kernel-szintű továbbítás, ezért ha senki nem olvas, a csomagok eldobódnak, és az interfész számlálói soha nem mozdulnak.

Számolással kezdje találgatás helyett. Növeljen egy számlálót minden sikeres olvasásnál, és naplózza az első csomagok célját és protokollját. Ha a számláló nullán marad, miközben forgalom keletkezik, a hiba a továbbító kódja fölött van, ami a keresést a builder konfigurációjára és az olvasási módra szűkíti. Ha a számláló mozog, és a böngésző mégis hibázik, a hiba a küldési útvonalon vagy a visszaúton van.

A blokkoló olvasás dönti el, mozog-e a ciklus

A setBlocking(true) blokkoló módba teszi a leírót, és API 21 óta létezik. Régebbi kiadásokon a megoldás egy kis JNI-segéd, amely fcntl-lel törli az O_NONBLOCK-ot a nyers leírón. A mód azért fontos, mert eldönti, mit tehet egy olvasás várakozó csomag nélkül.

Nem blokkoló módban ez az olvasás azonnal negatív értékkel tér vissza, és az eredményt figyelmen kívül hagyó ciklus magot pörget forgalom mozgatása nélkül. Blokkoló módban az olvasás vár, amit egy továbbító szál akar, de a szál ekkor már nem szakítható meg. A leíró lezárása a támogatott módja annak, hogy az olvasás meghiúsuljon, és a ciklus kiléphessen. A folyamat megölése vagy a szál megszakítása ehelyett a következő indítást olyan leíróval hagyhatja, amely már nem illik az interfészhez.

Olvasson egy csomagot hívásonként az interfész MTU-jának méretére szabott pufferbe, és a nullát vagy a negatív visszatérést hibának tekintse, ne üres csomagnak.

protect() az első küldés előtt, nem utána

Minden socketet, amely beágyazott csomagot szállít, először védeni kell. A védtelen socket ugyanazokat az útválasztási szabályokat követi, amelyeket épp beállított, ezért csomagjai visszamennek az alagútba, és a saját olvasási ciklusa újra felveszi őket. Az alagút ekkor végtelenül továbbítja a saját kimenetét, és kívülről úgy néz ki, mint egy kapcsolat, amely megnyílik és soha nem válaszol.

A sorrend: protect, majd connect vagy bind, majd send. A protect hívása connect után hagyhatja a korábbi útvonal-döntést, így a socket továbbra is a rossz utat használja, pedig a hívás true-t adott vissza. Ellenőrizze a visszaadott logikai értéket, és hangosan hibázzon, ha false, ahelyett hogy olyan küldésbe kezdene, amely nem működhet.

Az engedélyhiba a sendto-tól olyan socketen, amely soha nem érintette a protect()-et, ennek a hibának a szokásos jele.

Az UDP-példa nem visz HTTPS-t

A továbbítási példa, amelyet a legtöbb oktatóanyag másol, csak UDP-vel foglalkozik. A TCP-hez kézfogás, sorszámok, nyugtázások és újraküldés kell, és egy ciklus, amely hasznos terhet másol a tun leíró és egy socket között, ezek közül egyiket sem nyújtja. Ezért egy UDP-s DNS-lekérdezés átmehet, miközben minden HTTPS-kérés elakad. Vagy futtasson TCP-vermet felhasználói térben, vagy adja a kapcsolatokat egy helyi proxy-nak, amely az eszközön lezárja a TCP-t és továbbítja az adatfolyamot.

Tesztelje a szétválasztást, mielőtt bármit újraír: küldjön egy UDP-lekérdezést, és nyisson egy TCP-kapcsolatot ugyanahhoz a hoszthoz. Ha az UDP-út működik, a TCP-út pedig az első SYN-nél akad, a hiba a továbbító kódban van, nem a kiszolgálóban. Ez az egyetlen összehasonlítás sok időt takarít meg, amelyet haszontalan kiszolgálónaplók olvasására fordítanak.

Az útvonallista és az alkalmazásonkénti szűrők korlátozzák, mit lát

Az addAddress, addRoute és addDns határozza meg, mit küld a rendszer az alagútba. Az útvonallistán kívüli cél soha nem ér el a leírójához, ezért semmilyen mennyiségű továbbítási logika nem javítja meg. Naplózza a builder-hívásokat, és hasonlítsa össze azokkal a címekkel, amelyeket az alkalmazás valóban el akar érni.

Az alkalmazásonkénti kiválasztásnak saját padlója van: az addAllowedApplication és az addDisallowedApplication API 21-et igényel, régebbi kiadásokon pedig ezek a metódusok egyszerűen hiányoznak. A csomag szerint szűrő tervezés ott egyáltalán nem futhat.

Az újraépítés elavult leírókat hagy maga után

Az establish() ismételt hívása új leírót ad, és lezárja az előző interfészt. A régi leíró most érvénytelen, ezért a rajta még blokkoló szál hibát kap, és a régi útvonalhoz létrehozott socketeket esetleg le kell cserélni. Az onRevoke az a visszahívás, amely egy elvont alagúthoz tartozik, amelyet a felhasználó vagy a rendszer elvett. Az életciklus egyetlen tulajdonosnál tartása, amely az újraépítés előtt leállítja az olvasót, megelőzi a csatlakozott-de-halott állapotok többségét.

Minden hibát a hozzá tartozó lépés szerint olvasson. A recvfrom-ból jövő kapcsolat-visszaállítás azt jelenti, hogy a távoli fél lezárta, miközben a kódja még olvasott. A rossz címre vonatkozó hiba fejléc-elemzésre vagy eltolási hibára utal, nem a hálózatra.

A munka sorrendje: blokkoló mód be, egy csomag olvasásonként, fejléc elemezve, socket védve, hasznos teher elküldve, tiszta lezárás átadáskor és a célt lefedő útvonallista.

Kapcsolódó cikkek