Tunel se podiže, ikona ključa se pojavljuje, a svaki zahtjev u pregledniku i dalje ističe. U VpnService aplikaciji ta kombinacija obično znači da je petlja čitanja živa, a izlazna polovina pokvarena, a ne da je server odbio rukovanje. Android je već kreirao interfejs, premjestio saobraćaj aplikacije u njega i dao vašem procesu datotečni deskriptor. Sve nakon tog deskriptora je vaš kod, a jedan propušteni poziv dovoljan je da proguta cijeli tok.

Šta vam sistem daje, a šta ne

establish() vraća ParcelFileDescriptor za tun interfejs. Sistem upisuje odlazne IP pakete u njega i očekuje da ih vaš proces pročita, odluči kuda idu i pošalje ih kroz običan socket. Ispod nema kernel prosljeđivanja, pa kada niko ne čita, paketi se odbacuju i brojači interfejsa se nikada ne pomjeraju.

Počnite brojanjem umjesto nagađanjem. Povećajte brojač pri svakom uspješnom čitanju i zabilježite odredište i protokol prvih nekoliko paketa. Ako brojač ostane na nuli dok se saobraćaj generiše, greška je uzvodno od vašeg koda za prosljeđivanje, što sužava pretragu na konfiguraciju builder-a i režim čitanja. Ako se brojač pomjera, a preglednik i dalje ne radi, greška je u putu slanja ili na povratnom putu.

Blokirajuće čitanje odlučuje da li se petlja pomjera

setBlocking(true) stavlja deskriptor u blokirajući režim i postoji od API 21. Na starijim izdanjima rješenje je mali JNI pomoćnik koji briše O_NONBLOCK pomoću fcntl na sirovom deskriptoru. Režim je važan jer odlučuje šta čitanju bez paketa na čekanju smije da radi.

U neblokirajućem režimu to čitanje se vraća odmah s negativnom vrijednošću, a petlja koja ignorira rezultat vrti jezgro bez ijednog prenesenog bajta saobraćaja. U blokirajućem režimu čitanje čeka, što nit za prosljeđivanje i želi, ali nit tada više ne može biti prekinuta. Zatvaranje deskriptora je podržan način da čitanje ne uspije kako bi petlja mogla izaći. Ubijanje procesa ili prekidanje niti umjesto toga može ostaviti sljedeće pokretanje s deskriptorom koji više ne odgovara interfejsu.

Čitajte jedan paket po pozivu u bafer veličine MTU interfejsa, a nulu ili negativan povratak tretirajte kao grešku, a ne kao prazan paket.

protect() prije prvog slanja, ne poslije

Svaki socket koji nosi inkapsulirani paket mora se prvo zaštititi. Nezaštićeni socket sluša ista pravila rutiranja koja ste upravo postavili, pa se njegovi paketi vraćaju u tunel i vaša vlastita petlja čitanja ih ponovo preuzima. Tunel tada beskonačno prosljeđuje vlastiti izlaz, a spolja to izgleda kao veza koja se otvara i nikada ne odgovara.

Redoslijed je protect, pa connect ili bind, pa send. Poziv protect nakon connect može ostaviti raniju odluku o ruti, pa socket nastavlja koristiti pogrešan put iako je poziv vratio true. Provjerite boolean koji vraća i glasno ne uspijte kada je false, umjesto da uđete u send koji ne može raditi.

Greška dozvole iz sendto na socketu koji nikada nije dotakao protect() uobičajen je potpis ove greške.

UDP primjer ne nosi HTTPS

Primjer prosljeđivanja koji većina tutorijala kopira obrađuje UDP i ništa drugo. TCP treba rukovanje, redne brojeve, potvrde i ponovno slanje, a petlja koja kopira payload između tun deskriptora i socketa ne pruža ništa od toga. Zato UDP DNS upit može proći dok svaki HTTPS zahtjev zastaje. Ili pokrenite TCP stog u korisničkom prostoru ili veze predajte lokalnom proxyju koji prekida TCP na uređaju i prosljeđuje tok.

Testirajte podjelu prije nego što bilo šta prepišete: pošaljite jedan UDP upit i otvorite jednu TCP vezu prema istom hostu. Ako UDP put radi, a TCP put visi na prvom SYN, problem je u kodu za prosljeđivanje, a ne u serveru. To jedno poređenje štedi mnogo vremena provedenog uz serverske dnevnike koji ne sadrže ništa korisno.

Lista ruta i filteri po aplikacijama ograničavaju šta vidite

addAddress, addRoute i addDns definiraju šta sistem šalje u tunel. Odredište izvan liste ruta nikada ne stiže do vašeg deskriptora, pa nikakva količina logike prosljeđivanja to neće popraviti. Bilježite builder pozive koje pravite i uporedite ih s adresama koje aplikacija stvarno pokušava doseći.

Izbor po aplikacijama ima svoj prag: addAllowedApplication i addDisallowedApplication traže API 21, a na starijim izdanjima te metode jednostavno nema. Dizajn koji filtrira po paketu tamo uopšte ne može raditi.

Ponovno uspostavljanje ostavlja zastarjele deskriptore

Ponovni poziv establish() vraća novi deskriptor i zatvara prethodni interfejs. Stari deskriptor je sada nevažeći, pa nit koja još blokira na njemu dobija grešku, a socketi stvoreni za stari put možda se moraju zamijeniti. onRevoke je povratni poziv za tunel koji su korisnik ili sistem oduzeli. Držanje životnog ciklusa kod jednog vlasnika, koji zaustavlja čitača prije ponovnog uspostavljanja, sprječava većinu stanja povezan-ali-mrtav.

Čitajte svaku grešku prema koraku kojem pripada. Reset veze iz recvfrom znači da se udaljena strana zatvorila dok je vaš kod još čitao. Greška loše adrese upućuje na parsiranje zaglavlja ili grešku pomaka, a ne na mrežu.

Redoslijed rada: blokirajući režim uključen, jedan paket po čitanju, zaglavlje parsirano, socket zaštićen, payload poslan, čisto zatvaranje pri predaji i lista ruta koja pokriva odredište.

Povezani članci