Туннель көтеріледі, кілт белгішесі шығады, бірақ браузердегі әр сұраныс әлі де уақыт бітіп сәтсіз аяқталады. VpnService қолданбасында бұл үйлесім әдетте оқу циклі тірі, ал шығыс жартысы бүлінгенін білдіреді, сервер қол алысуды қабылдамағанын емес. Android интерфейсті әлдеқашан жасап, қолданба трафигін соған көшіріп, процесіңізге файл дескрипторын берген. Сол дескриптордан кейінгінің бәрі сіздің кодыңыз, және бір жетіспейтін шақыру бүкіл ағынды жұтып қоюға жетеді.

Жүйе не береді, не бермейді

establish() tun интерфейсі үшін ParcelFileDescriptor қайтарады. Жүйе оған шығыс IP пакеттерін жазып, процесіңіз оларды оқып, қайда кететінін шешіп, кәдімгі socket арқылы жібереді деп күтеді. Астында ядролық беріліс жоқ, сондықтан ешкім оқымаса, пакеттер тасталып, интерфейс санағыштары ешқашан қозғалмайды.

Болжаудың орнына санаудан бастаңыз. Әр сәтті оқуда санағышты арттырып, алғашқы пакеттердің межелісі мен протоколын жазыңыз. Трафик жасалып жатқанда санағыш нөлде тұрса, ақау сіздің беріліс кодыңыздан жоғарыда, бұл іздеуді builder конфигурациясы мен оқу режиміне тарылтады. Санағыш қозғалып, браузер әлі сәтсіз болса, ақау жіберу жолында немесе қайтару жолында.

Блоктайтын оқу циклдің қозғалатынын шешеді

setBlocking(true) дескрипторды блоктайтын режимге қояды және API 21-ден бар. Ескі шығарылымдарда шешім fcntl арқылы шикі дескрипторда O_NONBLOCK-ты тазалайтын шағын JNI көмекшісі. Режим маңызды, өйткені күтіп тұрған пакеті жоқ оқудың не істей алатынын сол шешеді.

Блоктамайтын режимде бұл оқу бірден теріс мәнмен қайтады, ал нәтижені елемейтін цикл ешқандай трафикті қозғалтпай ядроны айналдырады. Блоктайтын режимде оқу күтеді, бұл беріліс ағынына керек, бірақ содан кейін ағынды үзу мүмкін емес. Дескрипторды жабу оқуды сәтсіз етудің қолдаулы жолы, осылайша цикл шыға алады. Процесті өлтіру немесе ағынды үзу келесі іске қосылуға интерфейске енді сай келмейтін дескриптор қалдыруы мүмкін.

Әр шақыруға интерфейстің MTU өлшеміндегі буферге бір пакет оқыңыз, ал нөлді немесе теріс қайтарымды бос пакет емес, қате деп қараңыз.

protect() бірінші жіберуден бұрын, кейін емес

Инкапсуляцияланған пакет таситын әр socket алдымен қорғалуы керек. Қорғалмаған socket жаңа ғана орнатқан маршруттау ережелеріне бағынады, сондықтан оның пакеттері туннельге қайтады және сіздің оқу цикліңіз оларды қайта алады. Сонда туннель өз шығысын шексіз жібереді, ал сырттан бұл ашылып, ешқашан жауап бермейтін байланыс сияқты көрінеді.

Тәртіп: protect, содан кейін connect немесе bind, содан кейін send. connect-тен кейін protect шақыру бұрынғы маршрут шешімін қалдыруы мүмкін, сондықтан шақыру true қайтарса да socket қате жолды қолдана береді. Қайтарылған boolean-ды тексеріп, ол false болғанда қатты сәтсіздікке ұшыраңыз, жұмыс істей алмайтын жіберуге кіруден гөрі.

protect()-ке ешқашан тимеген socket-те sendto-дан келген рұқсат қатесі бұл қатенің әдеттегі белгісі.

UDP үлгісі HTTPS тасымайды

Көптеген оқулықтар көшіретін беріліс үлгісі тек UDP-мен жұмыс істейді. TCP-ге қол алысу, реттік нөмірлер, растаулар және қайта жіберу қажет, ал tun дескрипторы мен socket арасында жүктемені көшіретін цикл олардың ешқайсысын бермейді. Сондықтан UDP DNS сұранысы өтіп кетуі мүмкін, ал әр HTTPS сұранысы тоқтап қалады. Не пайдаланушы кеңістігінде TCP стегін іске қосыңыз, не қосылымдарды құрылғыда TCP-ны аяқтап, ағынды жіберетін жергілікті проксиге беріңіз.

Қандай да бір нәрсені қайта жазбас бұрын бөліністі тексеріңіз: бір UDP сұраныс жіберіп, сол хостқа бір TCP қосылым ашыңыз. UDP жолы жұмыс істеп, TCP жолы бірінші SYN-де тұрса, мәселе беріліс кодында, серверде емес. Сол бір салыстыру пайдалы ештеңесі жоқ сервер журналдарын оқуға кететін көп уақытты үнемдейді.

Маршруттар тізімі мен қолданба бойынша сүзгілер көретініңізді шектейді

addAddress, addRoute және addDns жүйенің туннельге не жіберетінін анықтайды. Маршруттар тізімінен тыс межелі дескрипторыңызға ешқашан жетпейді, сондықтан қанша беріліс логикасы болса да оны түзетпейді. Жасаған builder шақыруларыңызды жазып, қолданба шынымен жетуге тырысатын мекенжайлармен салыстырыңыз.

Қолданба бойынша таңдаудың өз шегі бар: addAllowedApplication мен addDisallowedApplication API 21 қажет етеді, ал ескі шығарылымдарда бұл әдістер мүлде жоқ. Пакет бойынша сүзетін құрылым онда мүлде жұмыс істей алмайды.

Қайта орнату ескірген дескрипторларды қалдырады

establish()-ті қайта шақыру жаңа дескриптор қайтарып, алдыңғы интерфейсті жабады. Ескі дескриптор енді жарамсыз, сондықтан онда әлі блокталып тұрған ағын қате алады, ал ескі жол үшін жасалған socket-терді ауыстыру қажет болуы мүмкін. onRevoke — пайдаланушы немесе жүйе алып қойған туннель үшін кері шақыру. Өмірлік циклді қайта орнату алдында оқырманды тоқтататын бір иеде ұстау қосылған-бірақ-өлі күйлердің көбін болдырмайды.

Әр қатені өзі тиесілі қадамға қарай оқыңыз. recvfrom-нан байланыс қалпына келтірілуі қашықтағы жақ жабылғанын білдіреді, ал кодыңыз әлі оқып жатқан. Жарамсыз мекенжай қатесі тақырып талдауына немесе offset қатесіне нұсқайды, желіге емес.

Жұмыс тәртібі: блоктайтын режим қосулы, әр оқуға бір пакет, тақырып талданған, socket қорғалған, жүктеме жіберілген, тапсыру кезінде таза жабу және межеліні қамтитын маршруттар тізімі.

Қатысты мақалалар