Tunel se podiže, ikona ključa se pojavljuje, a svaki zahtjev u pregledniku i dalje istječe. U VpnService aplikaciji ta kombinacija obično znači da je petlja čitanja živa, a izlazna polovica pokvarena, a ne da je poslužitelj odbio rukovanje. Android je već stvorio sučelje, premjestio promet aplikacije u njega i predao vašem procesu datotečni deskriptor. Sve nakon tog deskriptora je vaš kod, a jedan propušteni poziv dovoljan je da proguta cijeli tok.

Što vam sustav daje, a što ne

establish() vraća ParcelFileDescriptor za tun sučelje. Sustav upisuje odlazne IP pakete u njega i očekuje da ih vaš proces pročita, odluči kamo idu i pošalje ih kroz običan socket. Ispod nema prosljeđivanja u jezgri, pa kada nitko ne čita, paketi se odbacuju i brojači sučelja se nikada ne pomiču.

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 promet stvara, greška je uzvodno od vašeg koda za prosljeđivanje, što sužava pretragu na konfiguraciju builder-a i način čitanja. Ako se brojač pomiče, a preglednik i dalje ne radi, greška je u putu slanja ili na povratnom putu.

Blokirajuće čitanje odlučuje pomiče li se petlja

setBlocking(true) stavlja deskriptor u blokirajući način 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. Način je važan jer odlučuje što čitanju bez paketa na čekanju smije raditi.

U neblokirajućem načinu to se čitanje vraća odmah s negativnom vrijednošću, a petlja koja ignorira rezultat vrti jezgru bez ijednog prenesenog bajta prometa. U blokirajućem načinu čitanje čeka, što nit za prosljeđivanje i želi, ali nit se tada više ne može prekinuti. Zatvaranje deskriptora podržan je 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 sučelju.

Čitajte jedan paket po pozivu u međuspremnik veličine MTU sučelja, 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 usmjeravanja koja ste upravo postavili, pa se njegovi paketi vraćaju u tunel i vaša vlastita petlja čitanja ih ponovno preuzima. Tunel tada beskonačno prosljeđuje vlastiti izlaz, a izvana 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 podbacite kada je false, umjesto da uđete u send koji ne može raditi.

Greška dopuštenja iz sendto na socketu koji nikada nije dotaknuo protect() uobičajen je potpis ove pogreške.

UDP primjer ne nosi HTTPS

Primjer prosljeđivanja koji većina vodiča kopira obrađuje UDP i ništa drugo. TCP treba rukovanje, redne brojeve, potvrde i ponovno slanje, a petlja koja kopira teret 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 bilo što 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 poslužitelju. To jedno poređenje štedi mnogo vremena provedenog uz poslužiteljske dnevnike koji ne sadrže ništa korisno.

Popis ruta i filtri po aplikacijama ograničavaju što vidite

addAddress, addRoute i addDns definiraju što sustav šalje u tunel. Odredište izvan popisa 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 radite i usporedite ih s adresama koje aplikacija doista pokušava dosegnuti.

Odabir 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će ne može raditi.

Ponovno uspostavljanje ostavlja zastarjele deskriptore

Ponovni poziv establish() vraća novi deskriptor i zatvara prethodno sučelje. Stari deskriptor sada je nevažeći, pa nit koja još blokira na njemu dobiva grešku, a socketi stvoreni za stari put možda se moraju zamijeniti. onRevoke je povratni poziv za tunel koji su korisnik ili sustav oduzeli. Držanje životnog ciklusa kod jednog vlasnika, koji zaustavlja čitač 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 raščlambu zaglavlja ili grešku pomaka, a ne na mrežu.

Redoslijed rada: blokirajući način uključen, jedan paket po čitanju, zaglavlje raščlanjeno, socket zaštićen, teret poslan, čisto zatvaranje pri predaji i popis ruta koji pokriva odredište.

Povezani članci