Два симптома се появяват в един и същ цикъл на VpnService и се обвинява едно и също нещо: четец, който никога не се връща, и запис, който се проваля с ENOBUFS. Те не са разновидности на една грешка. Застоят на четенето е несъгласие относно управлението на потока със съответника; неуспешният запис е пакет, който интерфейсът няма да приеме. Ако ги третирате като един проблем, ще прекарате ден в настройка на буфери, които никога не са участвали. Разделението по-долу е как решавам коя половина от цикъла да инструментирам първо.

Един цикъл, две независими точки на отказ

Вашата услуга чете IP пакет от файловия дескриптор на TUN, преглежда го и записва отговор обратно към същия дескриптор. Всичко, което се счупи по този път, се проявява при едно от тези две извиквания, затова и двете се докладват като счупен тунел. Извикванията имат различни договори. Четенето чака ядрото да ви даде пакет, а записът дава на ядрото пакет, който то има право да откаже.

Четенията са по-лесната половина за разсъждение. Четене, което не връща нищо, или чака трафик, който никога не пристига, или чака, защото съответникът е спрял да изпраща.

Застоят на четенето е несъгласие за управление на потока

Четенето на TUN устройство блокира, докато има наличен пакет. Това е проектно поведение и затова нишка, паркирана в read, изглежда нормално на празна връзка. То става забиване, когато вашият собствен TCP стек продължава да приема данни от локалното приложение, но никога не обявява достатъчен прозорец, за да изпрати отдалеченият съответник още. Съответникът запълва обявения от вас прозорец, чака ACK и обновяване на прозореца и спира. Тогава вашият четец няма какво да върне и приложението вижда връзка, която е отворена и тиха.

Честите причини са стек, който пише в сокета, без да следи обявения прозорец, и такъв, който забравя да изпрати обновяване на прозореца, след като източи буфера си. Отвън изглеждат еднакво: байтове излизат, нищо не се връща, не се повдига грешка. Блокирането на нишката на четеца върху парсване, DNS резолюция или дисков вход-изход дава същата форма на симптом по несвързана причина, затова потвърдете, че нишката наистина е в read, преди да обвинявате управлението на потока.

Проверете опашката за изпращане на съответника и прозореца, който е видял последно. Ако съответникът държи данни, защото вашият прозорец е нула, поправката е в пътя ви за ACK, а не в четенето от TUN.

ENOBUFS е проблем с размера, а не недостиг на буфер

Запис, който връща ENOBUFS на TUN интерфейс, обикновено отхвърля дължината на пакета. Интерфейсът има MTU, пакет, по-голям от него, не може да бъде поставен в опашка и повторението на същия пакет се проваля по същия начин всеки път. Тази детерминираност е признакът. Истинският недостиг на буфер се изчиства, когато натискът спадне, докато отхвърлянето по размер не го интересува колко дълго чакате.

TCP има механизъм точно за това и това е стъпката, която хората пропускат: ограничението MSS. Когато синтезирате сегменти в потребителското пространство, вие избирате размера на полезния товар и избирането му от MTU на локалния интерфейс вместо от MTU на тунела издава сегменти, които тунелът не може да пренесе. Ограничете до по-малкото от двете. Ограничението покрива само TCP, така че прекалено голяма UDP датаграма трябва да бъде фрагментирана или изхвърлена от вашия код.

Записвайте дължината на всеки пакет, който пишете, докато отстранявате проблеми. Дължината, която се проваля, е отговорът и тя няма да се промени между опитите.

Три измервания, които разделят случаите

Не ви е нужен дебъгер за нито един от двата отказа. Три наблюдения са достатъчни, за да се насочите към правилния раздел, и отнемат по-малко време от преизграждане.

What you seeWhere to look
Reader parked, peer send queue empty, last advertised window zeroWindow updates in your ACK path
Write fails on one length and succeeds on a smaller oneMSS clamp and tunnel MTU
Both directions look fine, app still sees no dataProtected 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, който работи само до първата мрежова промяна, е проблем с маршрутизацията, а не с управлението на потока.

Подобни статии