Android VpnService: застой чытання TUN супраць памылак запісу ENOBUFS
Чытанне, якое ніколі не вяртаецца, і запіс, які падае з ENOBUFS, маюць супрацьлеглыя прычыны. Вымерайце абвешчанае акно і даўжыню пакета, каб выбраць выпраўленне.
Два сімптомы ўзнікаюць у адным цыкле VpnService і спісваюцца на адно і тое ж: чытач, які ніколі не вяртаецца, і запіс, які падае з ENOBUFS. Гэта не варыяцыі адной памылкі. Застой чытання - гэта нязгода адносна кіравання патокам з суразмоўцам; няўдалы запіс - гэта пакет, які інтэрфейс не прыме. Калі разглядаць іх як адну праблему, вы выдаткаваце дзень на наладку буфераў, якія ніколі не ўдзельнічалі. Ніжэйшае размеркаванне - гэта тое, як я вырашаю, якую палову цыкла інструментаваць першай.
Адзін цыкл, дзве незалежныя кропкі адмовы
Ваша служба чытае IP-пакет з дэскрыптара файла TUN, правярае яго і запісвае адказ назад у той жа дэскрыптар. Усё, што ламаецца на гэтым шляху, праяўляецца ў адным з гэтых двух выклікаў, таму пра абодва паведамляюць як пра зламаны тунель. У выклікаў розныя кантракты. Чытанне чакае, пакуль ядро дасць вам пакет, а запіс перадае ядру пакет, які яно мае права адхіліць.
Чытанні - гэта лёгкая палова для разважанняў. Чытанне, якое нічога не вяртае, альбо чакае трафік, які ніколі не прыйдзе, альбо чакае, бо суразмоўца спыніў адпраўку.
Застой чытання - гэта нязгода па кіраванні патокам
Чытанне прылады TUN блакуецца, пакуль пакет не стане даступным. Гэта прадугледжаныя паводзіны, і менавіта таму паток, які стаіць унутры read, выглядае нармальна на прастойным канале. Гэта становіцца завісаннем, калі ваш уласны стек TCP працягвае прымаць даныя ад лакальнай праграмы, але ніколі не абвяшчае дастаткова акна для аддаленага суразмоўцы, каб адправіць больш. Суразмоўца запаўняе абвешчанае вамі акно, чакае ACK і абнаўлення акна і спыняецца. Тады вашаму чытачу няма чаго вярнуць, і праграма бачыць злучэнне, якое адкрытае і маўчыць.
Агульныя прычыны - гэта стек, які піша ў сокет, не адсочваючы абвешчанае акно, і той, які забывае адправіць абнаўленне акна пасля спусташэння буфера. Звонку яны выглядаюць аднолькава: байты выходзяць, нічога не вяртаецца, памылка не ўзнікае. Блакаванне патоку чытання на разборы, DNS-разрозненні або дыскавым уводзе-вывадзе дае тую ж форму сімптому па несвязанай прычыне, таму пераканайцеся, што паток сапраўды ўнутры 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-трафік назад у тунель, што звычайна выглядае як злучэнне, якое ўдаецца, а затым нічога не дастаўляе.
Выправіце разводку, ад якой залежаць абодва шляхі
Ні адзін сімптом не варта ганяць, пакуль інтэрфейс не пабудаваны правільна. Выклікі будаўніка, якія маюць значэнне, - гэта адрас, маршрут, 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 - гэта месца, дзе пацвердзіць кантракт будаўніка, а не капіраваць прыклад.
Правярайце па адным напрамку за раз
Адпраўце UDP-рэха праз тунель і назірайце за даўжынямі пакетаў, перш чым кранаць TCP. Затым адкрыйце рукапоціск і пацвердзіце, што сегменты застаюцца ніжэй за разлічаны MSS. Лічыце пакеты, а не злучэнні, каб застой выглядаў як прабел у лічыльніку, а не як завіслы сокет. Калі чытанні спыняюцца пасля ўсплёску запісаў, падазроны - акно; калі запісы падаюць адразу, - даўжыня.
Затым перазагрузіце прыладу і паўтарыце адзін раз, бо VpnService, які працуе толькі да першай змены сеткі, - гэта праблема маршрутызацыі, а не кіравання патокам.