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

Jedna petlja, dvije 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 ono smije odbiti.

Č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 slati.

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š vlastiti TCP stek 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 šta vratiti, 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 poslati ažuriranje prozora nakon što isprazni svoj bafer. Izvana 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 prije nego okrivite kontrolu toka.

Provjerite peerov send queue i prozor koji je posljednji vidio. 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 sintetizirate segmente u korisničkom prostoru, vi birate veličinu payloada, a biranje iz MTU lokalnog interfejsa umjesto iz MTU tunela emitira segmente koje tunel ne može nositi. Clampajte 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 promijeniti između pokušaja.

Tri mjerenja koja razdvajaju slučajeve

Ne treba vam debugger ni za jedan od dva otkaza. Tri zapažanja su dovoljna da se usmjerite na pravi odjeljak 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 gdje ga tunel ne vidi: nezaštićeni transportni socket koji nosi vaš vlastiti VPN saobraćaj natrag u tunel, što se obično pojavljuje kao veza koja uspije i zatim ne isporuči ništa.

Popravite vodovod od kojeg zavise oba puta

Nijedan simptom nije vrijedan 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 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 clampate 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š vlastiti 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 umjesto da baci grešku, zato provjerite deskriptor prije nego pokrenete petlju. VpnService referenca je mjesto da potvrdite builder ugovor umjesto da kopirate primjer.

Verifikujte jedan smjer po jedan

Pošaljite UDP echo kroz tunel i gledajte dužine paketa prije nego 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 umjesto 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 promjene mreže je routing problem, a ne problem kontrole toka.

Povezani članci