Android VpnService: stûkjende TUN-lêzing tsjin ENOBUFS-skriuwflaters
In lêzing dy't nea weromkomt en in skriuwing dy't mislearret mei ENOBUFS hawwe tsjinoerstelde oarsaken. Mjit it oankundige finster en de pakketlingte om de oplossing te kiezen.
Twa symptomen ferskine yn deselde VpnService-lus en wurde oan itselde ding witen: in lêzer dy't nea weromkomt, en in skriuwing dy't mislearret mei ENOBUFS. It binne gjin fariaasjes fan ien bug. In stûke lêzing is in meningsferskil oer streamkontrôle mei de peer; in mislearre skriuwing is in pakket dat de ynterface net akseptearret. As jo se as ien probleem behannelje, bringe jo in dei troch mei it ôfstimmen fan buffers dy't nea belutsen wiene. De splitsing hjirûnder is hoe't ik beslute hokker helte fan 'e lus ik earst ynstruminttearje.
Ien lus, twa ûnôfhinklike flaterpunten
Jo tsjinst lêst in IP-pakket út 'e TUN-bestânbeskriuwer, besjocht it en skriuwt in antwurd werom nei deselde beskriuwer. Alles dat op dat paad brekt, docht him foar by ien fan dy twa oproppen, en dêrom wurde beide rapportearre as de tunnel dy't stikken is. De oproppen hawwe ferskillende kontrakten. In lêzing wachtet oant de kernel jo in pakket jout, en in skriuwing jout de kernel in pakket dat it ôfwize mei.
Lêzingen binne de maklike helte om oer te redenearjen. In lêzing dy't neat weromjaan kin, wachtet of op ferkear dat nea oankomt of wachtet om't de peer opholden is mei stjoeren.
In stûke lêzing is in meningsferskil oer streamkontrôle
It lêzen fan in TUN-apparaat blokkearret oant in pakket beskikber is. Dat is ûntwurpen gedrach, en dêrom sjocht in tried dy't yn read parkearret der normaal út op in loisêre keppeling. It wurdt in hingjen as jo eigen TCP-stapel trochgiet mei it akseptearjen fan gegevens fan 'e lokale app, mar nea genôch finster oankundiget foar de fiere peer om mear te stjoeren. De peer folt it finster dat jo oankundige hawwe, wachtet op in ACK en in finsterfernijing, en stoppet. Dan hat jo lêzer neat werom te jaan, en de app sjocht in ferbining dy't iepen en stil is.
De gewoane oarsaken binne in stapel dy't yn 'e socket skriuwt sûnder it oankundige finster te folgjen, en ien dy't ferjit in finsterfernijing te stjoeren nei't it syn buffer leechmakke hat. Fan bûten sjogge se identyk út: bytes gean út, neat komt werom, gjin flater wurdt oproppen. It blokkearjen fan 'e lêstried op parsen, DNS-oplossing of skiif-I/O produsearret deselde foarm fan symptoom om in net besibbe reden, dus befêstigje dat de tried echt yn read is foardat jo streamkontrôle de skuld jouwe.
Kontrolearje de stjoerwachtrij fan 'e peer en it finster dat it lêst seach. As de peer gegevens hâldt om't jo finster nul is, sit de oplossing yn jo ACK-paad, net yn 'e TUN-lêzing.
ENOBUFS is in grûteprobleem, gjin buffertekoart
In skriuwing dy't ENOBUFS weromjout op in TUN-ynterface, wegert meast de pakketlingte. De ynterface hat in MTU, in pakket grutter as dat kin net yn 'e wachtrij, en it opnij probearjen fan itselde pakket mislearret elke kear itselde. Dat determinisme is de oanwizing. In echt buffertekoart wurdt klear as de druk sakket, wylst in grûte-ôfwizing net omtinken hat foar hoe lang jo wachtsje.
TCP hat in meganisme krekt hjirfoar, en it is de stap dy't minsken oerslaan: de MSS-clamp. As jo segminten yn brûkersromte syntetisearje, kieze jo de grûte fan 'e paylud, en it kiezen fan 'e MTU fan 'e lokale ynterface ynstee fan 'e MTU fan 'e tunnel stjoert segminten út dy't de tunnel net drage kin. Behein ta de lytsere fan 'e beide. De clamp omfettet allinnich TCP, dus in te grut UDP-datagram moat troch jo koade fragmintearre of weilitten wurde.
Log de lingte fan elk pakket dat jo skriuwe wylst jo debugge. De lingte dy't mislearret is it antwurd, en it sil net feroarje tusken pogingen.
Trije mjittingen dy't de gefallen skiede
Jo hawwe foar ien fan beide flaters gjin debugger nedich. Trije observaasjes binne genôch om josels nei de goede seksje te lieden, en se kostje minder tiid as in werbou.
| 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 |
As de tredde rigel oerienkomt, is de lus sels goed en sit it probleem wêr't de tunnel it net sjen kin: in ûnbeskerme transport-socket dy't jo eigen VPN-ferkear werom de tunnel yn bringt, wat meast ferskynt as in ferbining dy't slagget en dan neat leveret.
Reparearje de lieding dêr't beide paden fan ôfhingje
Gjin symptoom is it jagen wurdich oant de ynterface goed boud is. De builder-oproppen dy't fan belang binne, binne it adres, de rûte, de MTU en de tastimming, yn dy folchoarder:
Builder()
.addAddress(10.8.0.2, 32)
.addRoute(0.0.0.0, 0)
.setMtu(1400)
.establish()Set de MTU op 'e echte wearde fan 'e tunnel ynstee fan it te litten op wat it foarbyld brûkte. Alles wat jo letter skriuwe, wurdt begrinzge troch dat getal, en in mismatch tusken de MTU dy't jo sette en de MSS dêr't jo ta beheine is de meast foarkommende manier om de skriuwflater nei in oplossing werom te bringen. Beskermje dan de socket dy't ferkear nei de VPN-tsjinner sels draacht. Sûnder protect wurde dy pakketten werom de eigen tunnel yn rûte en de ferbining komt nea klear, wat liket op in stûke lêzing mar in routing-lus is.
Tastimming kin net omgien wurde. In tsjinst dy't start wurdt sûnder dat prepare null weromjout, krijt gjin ynterface, en establish jout null werom ynstee fan te smiten, dus kontrolearje de beskriuwer foardat jo jo lus starte. De VpnService-referinsje is it plak om it builder-kontrakt te befêstigjen ynstee fan in foarbyld te kopiearjen.
Ferifiearje ien rjochting tagelyk
Stjoer in UDP-echo troch de tunnel en besjoch pakketlingten foardat jo TCP oanreitsje. Iepenje dan in handshake en befêstigje dat de segminten ûnder de MSS bliuwe dy't jo berekkene hawwe. Tel pakketten ynstee fan ferbiningen, sadat in stûking ferskynt as in gat yn 'e teller ynstee fan in hingjende socket. As lêzingen stopje nei in rafale skriuwingen, is it finster de fertochte; as skriuwingen fuortendaliks mislearje, is it de lingte.
Start dan it apparaat opnij en werhelje ien kear, om't in VpnService dy't allinnich oant de earste netwurkferoaring wurket in routingprobleem is ynstee fan in streamkontrôleprobleem.