Android VpnService: jumittuva TUN-luku vastaan ENOBUFS-kirjoitusvirheet
Luku, joka ei koskaan palaa, ja kirjoitus, joka epäonnistuu ENOBUFSilla, johtuvat vastakkaisista syistä. Mittaa ilmoitettu ikkuna ja paketin pituus valitaksesi korjauksen.
Kaksi oiretta ilmaantuu samaan VpnService-silmukkaan ja molempia syytetään samasta asiasta: lukija, joka ei koskaan palaa, ja kirjoitus, joka epäonnistuu ENOBUFSilla. Ne eivät ole yhden bugin muunnelmia. Jumissa oleva luku on erimielisyyttä vuonhallinnasta vertaisen kanssa; epäonnistunut kirjoitus on paketti, jota rajapinta ei hyväksy. Jos käsittelet niitä yhtenä ongelmana, vietät päivän säätäen puskureita, jotka eivät koskaan olleet mukana. Alla oleva jako on tapani päättää, kumpi silmukan puolisko instrumentoidaan ensin.
Yksi silmukka, kaksi riippumatonta vikapistettä
Palvelusi lukee IP-paketin TUN-tiedostokuvauksesta, tutkii sen ja kirjoittaa vastauksen takaisin samaan kuvaukseen. Kaikki, mikä tuolla polulla hajoaa, ilmenee toisessa näistä kahdesta kutsusta, minkä vuoksi molemmat raportoidaan rikkinäisenä tunnelina. Kutsuilla on erilaiset sopimukset. Luku odottaa, että ydin antaa sinulle paketin, ja kirjoitus antaa ytimelle paketin, jonka se saa hylätä.
Luvut ovat se helppo puolisko ajatella. Luku, joka ei palauta mitään, joko odottaa liikennettä, joka ei koskaan saavu, tai odottaa koska vertainen lopetti lähettämisen.
Jumissa oleva luku on erimielisyyttä vuonhallinnasta
TUN-laitteen lukeminen estyy, kunnes paketti on saatavilla. Se on suunniteltu käyttäytyminen, ja siksi readin sisään pysäköity säie näyttää normaalilta tyhjällä linkillä. Siitä tulee jumitus, kun oma TCP-pinosi jatkaa datan vastaanottamista paikalliselta sovellukselta mutta ei koskaan ilmoita riittävää ikkunaa, jotta etävertainen voisi lähettää lisää. Vertainen täyttää ilmoittamasi ikkunan, odottaa ACKia ja ikkunan päivitystä ja pysähtyy. Silloin lukijallasi ei ole mitään palautettavaa, ja sovellus näkee yhteyden, joka on auki ja hiljainen.
Yleiset syyt ovat pino, joka kirjoittaa socketiin seuraamatta ilmoitettua ikkunaa, ja toinen, joka unohtaa lähettää ikkunan päivityksen tyhjennettyään puskurinsa. Ulkopuolelta ne näyttävät identtisiltä: tavuja lähtee, mitään ei tule takaisin, virhettä ei nosteta. Lukusäikeen estäminen jäsentämisessä, DNS-selvityksessä tai levy-I/Ossa tuottaa saman muotoisen oireen eri syystä, joten varmista, että säie on todella readin sisällä ennen kuin syytät vuonhallintaa.
Tarkista vertaisen lähetysjono ja ikkuna, jonka se viimeksi näki. Jos vertainen pitää dataa, koska ikkunasi on nolla, korjaus on ACK-polullasi, ei TUN-luennassa.
ENOBUFS on kokoprobleema, ei puskuripula
TUN-rajapinnalla ENOBUFSin palauttava kirjoitus hylkää yleensä paketin pituuden. Rajapinnalla on MTU, sitä suurempaa pakettia ei voi jonottaa, ja saman paketin uudelleenyritys epäonnistuu joka kerta samalla tavalla. Se deterministisyys on vihje. Todellinen puskuripula väistyy paineen laskiessa, kun taas kokohylkäys ei välitä, kuinka kauan odotat.
TCP:llä on mekanismi juuri tähän, ja se on vaihe, jonka ihmiset ohittavat: MSS-rajoitus. Kun muodostat segmenttejä käyttäjätilassa, valitset hyötykuorman koon, ja sen ottaminen paikallisen rajapinnan MTU:sta tunnelin MTU:n sijaan tuottaa segmenttejä, joita tunneli ei pysty kuljettamaan. Rajoita kahdesta pienempään. Rajoitus kattaa vain TCP:n, joten liian suuri UDP-datagrammi on koodisi pilkottava tai hylättävä.
Kirjaa jokaisen kirjoittamasi paketin pituus virheenjäljityksen aikana. Pituus, joka epäonnistuu, on vastaus, eikä se muutu yritysten välillä.
Kolme mittausta, jotka erottavat tapaukset
Et tarvitse debuggeria kumpaankaan vikaan. Kolme havaintoa riittää ohjaamaan sinut oikeaan osioon, ja ne vievät vähemmän aikaa kuin uudelleenrakennus.
| 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 |
Jos kolmas rivi täsmää, silmukka itsessään on kunnossa ja ongelma on siellä, missä tunneli ei näe sitä: suojaamaton kuljetussocket, joka kuljettaa oman VPN-liikenteesi takaisin tunneliin, mikä näkyy yleensä yhteytenä, joka onnistuu ja sitten ei toimita mitään.
Korjaa putkisto, josta molemmat polut riippuvat
Mikään oire ei ansaitse jahtaamista ennen kuin rajapinta on rakennettu oikein. Merkitsevät builder-kutsut ovat osoite, reitti, MTU ja suostumus, tässä järjestyksessä:
Builder()
.addAddress(10.8.0.2, 32)
.addRoute(0.0.0.0, 0)
.setMtu(1400)
.establish()Aseta MTU tunnelin todelliseen arvoon sen sijaan, että jättäisit sen siihen, mitä esimerkki käytti. Kaikki myöhemmin kirjoittamasi rajoittuu tuohon numeroon, ja asettamasi MTU:n ja rajoittamasi MSS:n välinen ristiriita on yleisin tapa tuoda kirjoitusvirhe takaisin korjauksen jälkeen. Suojaa sitten socket, joka kuljettaa liikennettä itse VPN-palvelimelle. Ilman protectia ne paketit reititetään takaisin omaan tunneliisi eikä yhteys koskaan valmistu, mikä näyttää jumittuneelta luvulta mutta on reitityssilmukka.
Suostumusta ei voi ohittaa. Palvelu, joka käynnistetään ilman että prepare palauttaa null, ei saa rajapintaa, ja establish palauttaa null heittämisen sijaan, joten tarkista kuvaus ennen silmukan käynnistämistä. VpnService-viite on paikka vahvistaa builder-sopimus sen sijaan, että kopioisit esimerkin.
Vahvista yksi suunta kerrallaan
Lähetä UDP-kaiku tunnelin läpi ja seuraa pakettien pituuksia ennen kuin kosket TCP:hen. Avaa sitten kättely ja vahvista, että segmentit pysyvät laskemasi MSS:n alla. Laske paketteja älä yhteyksiä, jotta jumitus näkyy laskurin aukkona eikä jumittuneena socketina. Jos luvut pysähtyvät kirjoitusten ryöpyn jälkeen, ikkuna on epäilty; jos kirjoitukset epäonnistuvat heti, se on pituus.
Käynnistä sitten laite uudelleen ja toista kerran, koska VpnService, joka toimii vain ensimmäiseen verkkomuutokseen asti, on reititysongelma eikä vuonhallintaongelma.