Android VpnService: zastoj čitanja TUN nasuprot ENOBUFS greškama pisanja
Čitanje koje se nikada ne vraća i pisanje koje pada uz ENOBUFS imaju suprotne uzroke. Izmjerite oglašeni prozor i duljinu paketa da odaberete popravak.
Dva se simptoma pojavljuju u istoj VpnService petlji i pripisuju istoj stvari: čitač koji se nikada ne vraća i pisanje koje pada uz ENOBUFS. To nisu varijacije jedne greške. Zaustavljeno čitanje je neslaganje o kontroli toka s peerom; neuspjelo pisanje je paket koji sučelje neće prihvatiti. Ako ih tretirate kao jedan problem, potrošit ćete dan na podešavanje međuspremnika koji nikada nisu bili uključeni. Podjela ispod je kako odlučujem koju polovicu petlje prvo instrumentirati.
Jedna petlja, dvije neovisne točke otkaza
Vaša usluga čita IP paket iz TUN datotečnog deskriptora, pregledava ga i piše odgovor natrag u isti deskriptor. Sve što se pokvari na tom putu pojavljuje se na jednom od ta dva poziva, zato se oba prijavljuju kao pokvaren tunel. Pozivi imaju različite ugovore. Čitanje čeka da vam jezgra preda paket, a pisanje predaje jezgri paket koji ona smije odbiti.
Čitanja su lakša polovica za razmišljanje. Čitanje koje ne vraća ništa ili čeka promet koji nikada ne dolazi ili čeka jer je peer prestao slati.
Zaustavljeno čitanje je neslaganje o kontroli toka
Čitanje TUN uređaja blokira dok paket ne bude dostupan. To je projektirano ponašanje i zato nit parkirana u read izgleda normalno na praznoj vezi. To postaje zastoj kada vaš vlastiti TCP stog nastavi primati podatke od lokalne aplikacije ali nikada ne oglasi dovoljan prozor da udaljeni peer pošalje još. Peer popuni prozor koji ste oglasili, čeka ACK i ažuriranje prozora, i stane. Tada vaš čitač nema što vratiti, a aplikacija vidi vezu koja je otvorena i tiha.
Česti su uzroci stog koji piše u socket bez praćenja oglašenog prozora i onaj koji zaboravi poslati ažuriranje prozora nakon što isprazni svoj međuspremnik. Izvana izgledaju identično: bajtovi izlaze, ništa se ne vraća, nikakva se greška ne podiže. Blokiranje čitačke niti na parsiranju, DNS razlučivanju ili diskovnom I/O proizvodi isti oblik simptoma iz nepovezanog razloga, zato potvrdite da je nit zaista unutar read prije nego okrivite kontrolu toka.
Provjerite peerov red za slanje i prozor koji je posljednji vidio. Ako peer drži podatke jer je vaš prozor nula, popravak je u vašem ACK putu, ne u TUN čitanju.
ENOBUFS je problem veličine, ne nedostatak međuspremnika
Pisanje koje vraća ENOBUFS na TUN sučelju obično odbija duljinu paketa. Sučelje ima MTU, paket veći od njega ne može se staviti u red, a ponovni pokušaj istog paketa pada isto svaki put. Ta je determiniranost znak. Pravi nedostatak međuspremnika čisti se kako pritisak opada, dok odbijanje veličine ne zanima koliko dugo čekate.
TCP ima mehanizam upravo za ovo, i to je korak koji ljudi preskaču: MSS clamp. Kada sintetizirate segmente u korisničkom prostoru, birate veličinu payloada, a biranje iz MTU lokalnog sučelja umjesto iz MTU tunela emitira segmente koje tunel ne može nositi. Ograničite na manji od dvaju. Clamp pokriva samo TCP, pa se preveliki UDP datagram mora fragmentirati ili odbaciti vašim kodom.
Bilježite duljinu svakog paketa koji pišete dok debuggirate. Duljina koja pada je odgovor i neće se promijeniti između pokušaja.
Tri mjerenja koja razdvajaju slučajeve
Ne treba vam debugger ni za jedan od dvaju otkaza. Tri su zapažanja dovoljna da se usmjerite na pravi odjeljak i traju kraće od ponovne izgradnje.
| What you see | Where to look |
|---|---|
| Reader parked, peer send queue empty, last advertised window zero | Window updates in your ACK path |
| Write fails on one length and succeeds on a smaller one | MSS clamp and tunnel MTU |
| Both directions look fine, app still sees no data | Protected socket and routing loop |
Ako se treći red poklapa, sama je petlja u redu i problem je tamo gdje ga tunel ne vidi: nezaštićeni transportni socket koji nosi vaš vlastiti VPN promet natrag u tunel, što se obično pojavljuje kao veza koja uspije i zatim ne isporuči ništa.
Popravite vodovod o kojem ovise oba puta
Nijedan simptom nije vrijedan lova dok sučelje nije ispravno izgrađeno. Builder pozivi koji su važni su adresa, ruta, MTU i privola, tim redom:
Builder()
.addAddress(10.8.0.2, 32)
.addRoute(0.0.0.0, 0)
.setMtu(1400)
.establish()Postavite MTU na stvarnu vrijednost tunela umjesto da ga ostavite na onome što je primjer koristio. Sve što kasnije pišete ograničeno je tim brojem, a nesklad između MTU-a koji postavljate i MSS-a na koji ograničavate najčešći je način da ponovno uvedete grešku pisanja nakon popravka. Zatim zaštitite socket koji nosi promet do samog VPN poslužitelja. Bez protect ti se paketi usmjeravaju natrag u vaš vlastiti tunel i veza se nikada ne dovrši, što izgleda kao zaustavljeno čitanje ali je routing petlja.
Privolu nije moguće zaobići. Usluga pokrenuta bez toga da prepare vrati null ne dobiva sučelje, a establish vraća null umjesto da baci iznimku, zato provjerite deskriptor prije pokretanja petlje. VpnService referenca mjesto je na kojem potvrđujete builder ugovor umjesto da kopirate primjer.
Provjerite jedan smjer po jedan
Pošaljite UDP echo kroz tunel i promatrajte duljine paketa prije nego dirnete TCP. Zatim otvorite handshake i potvrdite da segmenti ostaju ispod MSS-a koji ste izračunali. Brojite pakete, a ne veze, da bi se zastoj pojavio kao praznina u brojaču umjesto kao visoki socket. Ako se čitanja zaustave nakon rafala pisanja, prozor je osumnjičen; ako pisanja padaju odmah, duljina je.
Zatim ponovno pokrenite uređaj i ponovite jednom, jer je VpnService koji radi samo do prve promjene mreže routing problem, a ne problem kontrole toka.