Android VpnService: заглавено читање на TUN наспроти ENOBUFS грешки при пишување
Читање што никогаш не се враќа и пишување што не успева со ENOBUFS имаат спротивни причини. Измерете го објавениот прозорец и должината на пакетот за да ја изберете поправката.
Два симптоми се појавуваат во истата VpnService јамка и двете се обвинуваат истото: читач што никогаш не се враќа, и пишување што не успева со ENOBUFS. Тоа не се варијанти на една грешка. Заглавеното читање е несогласување за контролата на текот со партнерот; неуспешното пишување е пакет што интерфејсот нема да го прифати. Ако ги третирате како еден проблем, ќе потрошите ден за дотерување бафери што никогаш не биле вклучени. Поделбата подолу е како одлучувам која половина од јамката прво да ја инструментирам.
Една јамка, две независни точки на отказ
Вашата услуга чита IP пакет од датотечниот дескриптор TUN, го проверува и пишува одговор назад во истиот дескриптор. Сè што се крши на тој пат се појавува при еден од тие два повика, затоа и двете се пријавуваат како расипан тунел. Повиците имаат различни договори. Читањето чека кернелот да ви го предаде пакетот, а пишувањето му предава на кернелот пакет што тој има право да го одбие.
Читањата се полесната половина за размислување. Читање што не враќа ништо или чека сообраќај што никогаш не пристигнува или чека затоа што партнерот престанал да испраќа.
Заглавеното читање е несогласување за контрола на текот
Читањето на уред TUN блокира додека не е достапен пакет. Тоа е проектирано однесување, и затоа нишката паркирана во read изгледа нормално на неактивна врска. Станува заглавување кога вашиот сопствен TCP стек продолжува да прима податоци од локалната апликација, но никогаш не објавува доволен прозорец за оддалечениот партнер да испрати повеќе. Партнерот го полни прозорецот што сте го објавиле, чека ACK и ажурирање на прозорецот, и запира. Тогаш вашиот читач нема што да врати, а апликацијата гледа отворена и тивка врска.
Честите причини се стек што пишува во сокетот без да го следи објавениот прозорец, и друг што заборава да испрати ажурирање на прозорецот откако ќе го испразни својот бафер. Однадвор изгледаат идентично: бајти излегуваат, ништо не се враќа, не се подига грешка. Блокирањето на нишката за читање при парсирање, DNS резолуција или диск I/O произведува ист облик на симптом од неповрзана причина, затоа потврдете дека нишката навистина е внатре во read пред да ја обвините контролата на текот.
Проверете ја низата за испраќање на партнерот и прозорецот што последен пат го видел. Ако партнерот држи податоци затоа што вашиот прозорец е нула, поправката е во вашиот ACK пат, не во читањето на TUN.
ENOBUFS е проблем со големината, не недостиг на бафер
Пишување што враќа ENOBUFS на интерфејс TUN обично ја одбива должината на пакетот. Интерфејсот има 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 договорот наместо да копирате пример.
Проверете еден правец истовремено
Испратете UDP ехо низ тунелот и набљудувајте ги должините на пакетите пред да го допрете TCP. Потоа отворете ракување и потврдете дека сегментите остануваат под MSS што сте го пресметале. Бројте пакети, не врски, за да се појави застојот како празнина во бројачот наместо како заглавен сокет. Ако читањата запрат по налет на пишувања, прозорецот е осомничен; ако пишувањата не успеат веднаш, тоа е должината.
Потоа рестартирајте го уредот и повторете еднаш, зашто VpnService што работи само до првата мрежна промена е рутирачки проблем, а не проблем со контрола на текот.