Dva simptoma se pojavljuju u istoj VpnService petlji i oba se pripisuju istoj stvari: čitač koji se nikada ne vraća i pisanje koje pada s ENOBUFS. To nisu varijante jedne greške. Zaustavljeno čitanje je neslaganje o kontroli toka sa peerom; neuspelo pisanje je paket koji interfejs neće prihvatiti. Ako ih tretirate kao jedan problem, potrošićete dan na podešavanje bafera koji nikada nisu bili uključeni. Podela ispod je kako odlučujem koju polovinu petlje prvo instrumentirati.

Jedna petlja, dve nezavisne tačke otkaza

Vaš servis čita IP paket iz TUN file deskriptora, pregleda 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 kernel preda paket, a pisanje predaje kernelu paket koji on sme da odbije.

Čitanja su lakša polovina za razmišljanje. Čitanje koje ne vraća ništa ili čeka saobraćaj koji nikada ne dolazi ili čeka jer je peer prestao da šalje.

Zaustavljeno čitanje je neslaganje o kontroli toka

Čitanje TUN uređaja blokira dok paket ne bude dostupan. To je projektovano ponašanje i zato nit parkirana u read izgleda normalno na praznom linku. To postaje zastoj kada vaš sopstveni TCP stek nastavi da prima 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 šta da vrati, a aplikacija vidi vezu koja je otvorena i tiha.

Česti uzroci su stek koji piše u socket bez praćenja oglašenog prozora i onaj koji zaboravi da pošalje ažuriranje prozora nakon što isprazni svoj bafer. Spolja izgledaju identično: bajtovi izlaze, ništa se ne vraća, nikakva greška se ne podiže. Blokiranje čitačke niti na parsiranju, DNS rezoluciji ili diskovnom I/O proizvodi isti oblik simptoma iz nepovezanog razloga, zato potvrdite da je nit zaista unutar read pre nego što okrivite kontrolu toka.

Proverite peerov red za slanje i prozor koji je poslednji video. Ako peer drži podatke jer je vaš prozor nula, popravka je u vašoj ACK putanji, ne u TUN čitanju.

ENOBUFS je problem veličine, ne nedostatak bafera

Pisanje koje vraća ENOBUFS na TUN interfejsu obično odbija dužinu paketa. Interfejs 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 determinističnost je znak. Pravi nedostatak bafera se čisti 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 sintetizujete segmente u korisničkom prostoru, birate veličinu payloada, a biranje iz MTU lokalnog interfejsa umesto iz MTU tunela emituje segmente koje tunel ne može da nosi. Clampujte na manji od dva. Clamp pokriva samo TCP, pa se preveliki UDP datagram mora fragmentirati ili odbaciti vašim kodom.

Logujte dužinu svakog paketa koji pišete dok debagujete. Dužina koja pada je odgovor i neće se promeniti između pokušaja.

Tri merenja koja razdvajaju slučajeve

Ne treba vam debugger ni za jedan od dva otkaza. Tri zapažanja su dovoljna da se usmerite na pravi odeljak i traju kraće od rebuilda.

What you seeWhere to look
Reader parked, peer send queue empty, last advertised window zeroWindow updates in your ACK path
Write fails on one length and succeeds on a smaller oneMSS clamp and tunnel MTU
Both directions look fine, app still sees no dataProtected socket and routing loop

Ako se treći red poklapa, sama petlja je u redu i problem je tamo gde ga tunel ne vidi: nezaštićeni transportni socket koji nosi vaš sopstveni VPN saobraćaj natrag u tunel, što se obično pojavljuje kao veza koja uspe i zatim ne isporuči ništa.

Popravite vodovod od kog zavise oba puta

Nijedan simptom nije vredan jurnjave dok interfejs nije ispravno izgrađen. Builder pozivi koji su važni su adresa, ruta, MTU i saglasnost, tim redom:

Builder()
  .addAddress(10.8.0.2, 32)
  .addRoute(0.0.0.0, 0)
  .setMtu(1400)
  .establish()

Postavite MTU na stvarnu vrednost tunela umesto da ga ostavite na onome što je primer koristio. Sve što kasnije pišete ograničeno je tim brojem, a nesklad između MTU-a koji postavljate i MSS-a na koji clampujete najčešći je način da ponovo uvedete grešku pisanja nakon popravke. Zatim zaštitite socket koji nosi saobraćaj do samog VPN servera. Bez protect ti paketi se rutiraju natrag u vaš sopstveni tunel i veza se nikada ne završi, što izgleda kao zaustavljeno čitanje ali je routing loop.

Saglasnost se ne može zaobići. Servis pokrenut bez toga da prepare vrati null ne dobija interfejs, a establish vraća null umesto da baci grešku, zato proverite deskriptor pre nego što pokrenete petlju. VpnService referenca je mesto da potvrdite builder ugovor umesto da kopirate primer.

Verifikujte jedan smer po jedan

Pošaljite UDP echo kroz tunel i gledajte dužine paketa pre nego što dirnete TCP. Zatim otvorite handshake i potvrdite da segmenti ostaju ispod MSS-a koji ste izračunali. Brojte pakete, a ne veze, da bi se zastoj pojavio kao praznina u brojaču umesto kao visoki socket. Ako se čitanja zaustave nakon rafala pisanja, prozor je osumnjičeni; ako pisanja padaju odmah, dužina je.

Zatim restartujte uređaj i ponovite jednom, jer VpnService koji radi samo do prve promene mreže je routing problem, a ne problem kontrole toka.

Povezani članci