Android VpnService: TUN оқуының тоқтап қалуы ENOBUFS жазу қателеріне қарсы
Ешқашан қайтпайтын оқу мен ENOBUFS-пен сәтсіз аяқталатын жазу қарама-қарсы себептерге ие. Түзетуді таңдау үшін жарияланған терезені және пакет ұзындығын өлшеңіз.
Бір VpnService циклінде екі симптом пайда болады және екеуіне де бірдей нәрсе кінәланады: ешқашан қайтпайтын оқырман және ENOBUFS-пен сәтсіз аяқталатын жазу. Бұлар бір қатенің нұсқалары емес. Тоқтап қалған оқу — бұл теңсерімен ағынды басқару туралы келіспеушілік; сәтсіз жазу — интерфейс қабылдамайтын пакет. Оларды бір мәселе ретінде қарасаңыз, ешқашан қатыспаған буферлерді реттеуге бір күн жұмсайсыз. Төмендегі бөлім — циклдің қай жартысын бірінші өлшеуді қалай шешетінім.
Бір цикл, екі тәуелсіз ақау нүктесі
Қызметіңіз TUN файл дескрипторынан IP пакетін оқиды, оны тексереді және сол дескрипторға жауап жазады. Осы жолда сынатынның бәрі осы екі шақырудың бірінде көрінеді, сондықтан екеуі де туннель бұзылды деп хабарланады. Шақырулардың келісімшарттары әртүрлі. Оқу ядро сізге пакет беруін күтеді, ал жазу ядроға қабылдамауға құқығы бар пакетті береді.
Оқулар — ойлауға жеңіл жартысы. Ештеңе қайтармайтын оқу не ешқашан келмейтін трафикті күтеді, не теңсері жіберуді тоқтатқаны үшін күтеді.
Тоқтап қалған оқу — ағынды басқарудағы келіспеушілік
TUN құрылғысын оқу пакет қолжетімді болғанша блоктайды. Бұл жобаланған мінез-құлық, сондықтан read ішінде тұрған ағын бос арнада қалыпты көрінеді. Бұл ілулі қалыпқа айналады, егер сіздің TCP стекіңіз жергілікті қолданбадан деректер қабылдауды жалғастырса, бірақ қашықтағы теңсеріңізге көбірек жіберу үшін жеткілікті терезе жарияламаса. Теңсері сіз жариялаған терезені толтырады, ACK пен терезе жаңартуын күтеді және тоқтайды. Содан кейін оқырманыңызда қайтарар ештеңе болмайды, ал қолданба ашық әрі үнсіз қосылымды көреді.
Жиі себептер — жарияланған терезені бақыламай сокетке жазатын стек және буферін босатқаннан кейін терезе жаңартуын жіберуді ұмытатын стек. Сырттан олар бірдей көрінеді: байттар шығады, ештеңе қайтпайды, қате көтерілмейді. Оқу ағынын талдау, DNS шешу немесе дискілік I/O кезінде блоктау байланыссыз себептен дәл осы пішіндегі симптомды тудырады, сондықтан ағынды басқаруды кінәламас бұрын ағынның шынымен read ішінде екенін растаңыз.
Теңсеріңіздің жіберу кезегін және ол соңғы рет көрген терезені тексеріңіз. Теңсері сіздің терезеңіз нөл болғандықтан деректерді ұстаса, түзету сіздің ACK жолыңызда, TUN оқуында емес.
ENOBUFS — өлшем мәселесі, буфер жетіспеушілігі емес
TUN интерфейсінде ENOBUFS қайтаратын жазу әдетте пакет ұзындығын қабылдамайды. Интерфейстің MTU бар, одан үлкен пакетті кезекке қою мүмкін емес, және сол пакетті қайталап көру әр жолы бірдей сәтсіз аяқталады. Бұл детерминизм — белгі. Нағыз буфер тапшылығы қысым төмендегенде кетеді, ал өлшемнен бас тарту қанша күткеніңізге мән бермейді.
TCP-де дәл осыған арналған механизм бар, және ол адамдар өткізіп жіберетін қадам: MSS шектеуі. Пайдаланушы кеңістігінде сегменттерді синтездегенде пайдалы жүктеме өлшемін өзіңіз таңдайсыз, және оны туннель MTU-ының орнына жергілікті интерфейс MTU-ынан алу туннель тасымалдай алмайтын сегменттерді шығарады. Екеуінің кішісіне шектеңіз. Шектеу тек TCP-ді қамтиды, сондықтан тым үлкен UDP датаграммасын кодыңыз бөлшектеуі немесе тастауы керек.
Жөндеу кезінде жазған әр пакеттің ұзындығын журналға жазыңыз. Сәтсіз болатын ұзындық — жауап, және ол әрекеттер арасында өзгермейді.
Жағдайларды ажырататын үш өлшем
Екі ақаудың ешқайсысы үшін дебуггер қажет емес. Үш бақылау өзіңізді дұрыс бөлімге бағыттауға жеткілікті, және олар қайта құрудан аз уақыт алады.
| 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 |
Үшінші жол сәйкес келсе, циклдің өзі дұрыс және мәселе туннель көре алмайтын жерде: өз VPN трафигіңізді туннельге қайта таситын қорғалмаған транспорттық сокет, бұл әдетте сәтті болып, содан кейін ештеңе жеткізбейтін қосылым ретінде көрінеді.
Екі жол да тәуелді құбырды жөндеңіз
Интерфейс дұрыс салынбайынша, ешбір симптомның соңына түсудің мәні жоқ. Маңызды builder шақырулары — мекенжай, маршрут, MTU және келісім, осы тәртіппен:
Builder()
.addAddress(10.8.0.2, 32)
.addRoute(0.0.0.0, 0)
.setMtu(1400)
.establish()MTU-ды үлгі пайдаланған мәнде қалдырмай, туннельдің нақты мәніне қойыңыз. Кейін жазғаныңыздың бәрі осы санмен шектелген, ал сіз қоятын MTU мен шектейтін MSS арасындағы сәйкессіздік — түзетуден кейін жазу ақауын қайта енгізудің ең көп тараған жолы. Содан кейін VPN серверінің өзіне трафик таситын сокетті қорғаңыз. protect болмаса, бұл пакеттер өз туннеліңізге қайта маршрутталады және қосылым ешқашан аяқталмайды, бұл тоқтаған оқуға ұқсайды, бірақ маршруттау циклі.
Келісімді айналып өту мүмкін емес. prepare null қайтармай басталған қызмет интерфейс алмайды, ал establish лақтырудың орнына null қайтарады, сондықтан циклді бастамас бұрын дескрипторды тексеріңіз. VpnService анықтамасы — үлгіні көшірудің орнына builder келісімшартын растайтын орын.
Бір уақытта бір бағытты тексеріңіз
TCP-ге тиместен бұрын туннель арқылы UDP жаңғырығын жіберіп, пакет ұзындықтарын бақылаңыз. Содан кейін қолтаңбаны ашып, сегменттердің есептеген MSS-тен төмен қалатынын растаңыз. Қосылымдарды емес, пакеттерді санаңыз, сонда тоқырау ілулі сокеттің орнына санауыштағы олқылық ретінде көрінеді. Егер жазбалар легінен кейін оқулар тоқтаса, күдікті — терезе; егер жазбалар бірден сәтсіз болса, ұзындық.
Содан кейін құрылғыны қайта іске қосып, бір рет қайталаңыз, өйткені тек бірінші желі өзгерісіне дейін жұмыс істейтін VpnService — ағынды басқару емес, маршруттау мәселесі.