Android VpnService: paghinto ng pagbasa sa TUN kumpara sa mga error sa pagsulat na ENOBUFS
Ang pagbasang hindi na bumabalik at ang pagsulat na nabigo sa ENOBUFS ay magkasalungat ang sanhi. Sukatin ang inihayag na window at haba ng packet para piliin ang tamang ayos.
Dalawang sintomas ang lumilitaw sa parehong loop ng VpnService at pinagbibintangan ang parehong bagay: isang mambabasa na hindi na bumabalik, at isang pagsulat na nabigo sa ENOBUFS. Hindi ito mga baryante ng isang bug. Ang naka-stall na pagbasa ay hindi pagkakasundo sa flow control kasama ang peer; ang nabigong pagsulat ay isang packet na hindi tatanggapin ng interface. Kung ituturing mo silang isang problema, gugugol ka ng isang araw sa pag-tune ng mga buffer na hindi kailanman nasangkot. Ang paghahati sa ibaba ang paraan ng pagpapasya ko kung aling kalahati ng loop ang unang susukatin.
Isang loop, dalawang malayang punto ng pagkasira
Ang serbisyo mo ay nagbabasa ng packet ng IP mula sa file descriptor ng TUN, sinusuri ito, at nagsusulat ng sagot pabalik sa parehong descriptor. Lahat ng nasisira sa landas na iyon ay lumilitaw sa isa sa dalawang tawag na iyon, kaya pareho silang nirereport bilang sirang tunnel. Magkaiba ang kontrata ng dalawang tawag. Ang pagbasa ay naghihintay na ibigay sa iyo ng kernel ang packet, at ang pagsulat ay nagbibigay sa kernel ng packet na may karapatan itong tanggihan.
Ang pagbasa ang madaling kalahating pag-isipan. Ang pagbasang walang ibinabalik ay naghihintay ng trapikong hindi dumarating, o naghihintay dahil tumigil nang magpadala ang peer.
Ang naka-stall na pagbasa ay hindi pagkakasundo sa flow control
Ang pagbasa sa isang device na TUN ay humihinto hanggang may packet na available. Ito ay disenyo, at kaya ang isang thread na nakaparada sa read ay mukhang normal sa isang idle na link. Nagiging stall ito kapag ang sarili mong stack ng TCP ay patuloy na tumatanggap ng data mula sa lokal na app ngunit hindi kailanman naghahayag ng sapat na window para sa malayong peer na makapagpadala pa. Pinupuno ng peer ang inihayag mong window, naghihintay ng ACK at update ng window, at humihinto. Wala nang maibabalik ang mambabasa mo, at nakikita ng app ang isang koneksyong bukas ngunit tahimik.
Ang karaniwang sanhi ay isang stack na nagsusulat sa socket nang hindi sinusubaybayan ang inihayag na window, at isa pang nakakalimot magpadala ng update ng window pagkatapos maubos ang buffer. Mula sa labas ay magkapareho sila: lumalabas ang bytes, walang bumabalik, walang error na itinatapon. Ang pagharang sa thread ng pagbasa sa parsing, DNS resolution o disk I/O ay gumagawa ng parehong hugis ng sintomas sa kadahilanang walang kaugnayan, kaya tiyakin na ang thread ay talagang nasa loob ng read bago sisihin ang flow control.
Tingnan ang pila ng padala ng peer at ang window na huling nakita nito. Kung hinahawakan ng peer ang data dahil zero ang window mo, ang ayos ay nasa landas ng ACK mo, hindi sa pagbasa ng TUN.
Ang ENOBUFS ay problema sa laki, hindi kakulangan ng buffer
Ang pagsulat na nagbabalik ng ENOBUFS sa isang interface ng TUN ay karaniwang tinatanggihan ang haba ng packet. May MTU ang interface, hindi maisasalang sa pila ang mas malaking packet, at ang muling pagsubok sa parehong packet ay nabibigo sa parehong paraan tuwing. Ang determinismong iyon ang palatandaan. Ang tunay na kakulangan ng buffer ay nawawala kapag bumaba ang pressure, samantalang ang pagtanggi sa laki ay walang pakialam kung gaano katagal ka maghintay.
May mekanismo ang TCP para mismo dito, at ito ang hakbang na nilalaktawan ng mga tao: ang MSS clamp. Kapag nag-synthesize ka ng mga segment sa user space, ikaw ang pumipili ng laki ng payload, at ang pagkuha nito mula sa MTU ng lokal na interface sa halip na MTU ng tunnel ay naglalabas ng mga segment na hindi kayang dalhin ng tunnel. I-clamp sa mas maliit sa dalawa. Ang clamp ay sumasaklaw lamang sa TCP, kaya ang sobrang laking datagram ng UDP ay kailangang i-fragment o itapon ng code mo.
I-log ang haba ng bawat packet na isinusulat mo habang nagde-debug. Ang habang nabibigo ang sagot, at hindi ito magbabago sa pagitan ng mga pagsubok.
Tatlong sukat na naghihiwalay sa mga kaso
Hindi mo kailangan ng debugger para sa alinman sa dalawang pagkasira. Sapat na ang tatlong obserbasyon para gabayan ka sa tamang seksyon, at kumukuha ito ng mas kaunting oras kaysa rebuild.
| 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 |
Kung tumugma ang ikatlong hilera, maayos ang loop mismo at ang problema ay nasa lugar na hindi nakikita ng tunnel: isang hindi protektadong transport socket na nagdadala ng sarili mong trapiko ng VPN pabalik sa tunnel, na karaniwang lumilitaw bilang koneksyong nagtagumpay at pagkatapos ay walang naihahatid.
Ayusin ang tubo na pinagkakatiwalaan ng dalawang landas
Walang sintomas ang sulit habulin hanggang maayos na maitayo ang interface. Ang mahahalagang tawag ng builder ay ang address, ruta, MTU at pahintulot, sa ganoong pagkakasunod-sunod:
Builder()
.addAddress(10.8.0.2, 32)
.addRoute(0.0.0.0, 0)
.setMtu(1400)
.establish()Itakda ang MTU sa tunay na halaga ng tunnel sa halip na iwanan ito sa ginamit ng halimbawa. Lahat ng isusulat mo mamaya ay nililimitahan ng numerong iyon, at ang hindi pagkakatugma ng MTU na itinakda mo at ng MSS na kinakalamp mo ay ang pinakakaraniwang paraan para maibalik ang pagkasira ng pagsulat pagkatapos ng ayos. Pagkatapos ay protektahan ang socket na nagdadala ng trapiko sa mismong server ng VPN. Kung walang protect, ang mga packet na iyon ay iruruta pabalik sa sarili mong tunnel at hindi kailanman makukumpleto ang koneksyon, na mukhang naka-stall na pagbasa ngunit loop ng routing.
Hindi maiiwasan ang pahintulot. Ang serbisyong sinimulan nang hindi nagbabalik ng null ang prepare ay walang nakukuhang interface, at ang establish ay nagbabalik ng null sa halip na magtapon, kaya suriin ang descriptor bago simulan ang loop. Ang sanggunian ng VpnService ang lugar para kumpirmahin ang kontrata ng builder sa halip na kopyahin ang halimbawa.
I-verify ang isang direksyon sa isang pagkakataon
Magpadala ng UDP echo sa tunnel at panoorin ang haba ng mga packet bago hawakan ang TCP. Pagkatapos ay buksan ang handshake at tiyakin na nananatili ang mga segment sa ilalim ng MSS na kinakalkula mo. Bilangin ang mga packet sa halip na koneksyon, para lumitaw ang paghinto bilang puwang sa counter sa halip na nakabitin na socket. Kung humihinto ang pagbasa pagkatapos ng bugso ng pagsulat, ang window ang pinaghihinalaan; kung agad na nabibigo ang pagsulat, ang haba.
Pagkatapos ay i-reboot ang device at ulitin nang isang beses, dahil ang VpnService na gumagana lamang hanggang sa unang pagbabago ng network ay problema sa routing, hindi sa flow control.