Android VpnService: توقف خواندن TUN در برابر خطاهای نوشتن ENOBUFS
خواندنی که هرگز بازنمیگردد و نوشتنی که با ENOBUFS شکست میخورد علتهای متضاد دارند. پنجره اعلامشده و طول بسته را اندازه بگیرید تا اصلاح را انتخاب کنید.
دو نشانه در همان حلقه VpnService ظاهر میشوند و به یک چیز نسبت داده میشوند: خوانندهای که هرگز بازنمیگردد، و نوشتنی که با ENOBUFS شکست میخورد. اینها گونههای یک باگ نیستند. خواندن متوقفشده اختلاف بر سر کنترل جریان با همتاست؛ نوشتن ناموفق بستهای است که واسط آن را نمیپذیرد. اگر آنها را یک مشکل ببینید، یک روز را صرف تنظیم بافرهایی میکنید که هرگز دخیل نبودهاند. تقسیم زیر روش من برای تصمیمگیری درباره اینکه ابتدا کدام نیمه حلقه را ابزارگذاری کنم است.
یک حلقه، دو نقطه شکست مستقل
سرویس شما یک بسته IP را از توصیفگر فایل TUN میخواند، بررسیاش میکند و پاسخی به همان توصیفگر برمیگرداند. هر چیزی که در این مسیر بشکند در یکی از این دو فراخوان ظاهر میشود، و به همین دلیل هر دو به عنوان تونل خراب گزارش میشوند. این فراخوانها قراردادهای متفاوتی دارند. خواندن منتظر میماند تا هسته بستهای به شما بدهد، و نوشتن بستهای به هسته میدهد که حق دارد ردش کند.
خواندنها نیمه آسان برای استدلال هستند. خواندنی که چیزی برنمیگرداند یا منتظر ترافیکی است که هرگز نمیرسد یا منتظر است چون همتا ارسال را متوقف کرده است.
خواندن متوقفشده اختلافی در کنترل جریان است
خواندن از دستگاه TUN تا زمانی که بستهای موجود نباشد مسدود میشود. این رفتار طراحیشده است و به همین دلیل رشتهای که داخل read پارک شده روی پیوند بیکار عادی به نظر میرسد. زمانی به گیر کردن تبدیل میشود که پشته TCP خودتان همچنان دادهها را از برنامه محلی میپذیرد اما هرگز پنجره کافی برای همتای دور اعلام نمیکند تا بیشتر بفرستد. همتا پنجرهای را که اعلام کردید پر میکند، منتظر ACK و بهروزرسانی پنجره میماند و میایستد. سپس خواننده شما چیزی برای برگرداندن ندارد و برنامه اتصالی را میبیند که باز و ساکت است.
علتهای رایج پشتهای است که بدون ردیابی پنجره اعلامشده در سوکت مینویسد، و پشتهای که پس از تخلیه بافرش ارسال بهروزرسانی پنجره را فراموش میکند. از بیرون یکسان به نظر میرسند: بایتها بیرون میروند، چیزی برنمیگردد، خطایی بالا نمیآید. مسدود کردن رشته خواندن روی تجزیه، تحلیل DNS یا ورودی/خروجی دیسک همان شکل نشانه را به دلیلی نامرتبط تولید میکند، پس پیش از متهم کردن کنترل جریان تأیید کنید که رشته واقعاً داخل 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 آن بستهها به تونل خودتان مسیریابی میشوند و اتصال هرگز کامل نمیشود، که شبیه خواندن متوقفشده است اما یک حلقه مسیریابی است.
رضایت را نمیتوان دور زد. سرویسی که بدون بازگرداندن null از prepare شروع شود واسطی نمیگیرد، و establish بهجای پرتاب خطا null برمیگرداند، پس پیش از شروع حلقه توصیفگر را بررسی کنید. مرجع VpnService جایی است که قرارداد builder را تأیید کنید نه اینکه نمونهای را کپی کنید.
هر بار یک جهت را بررسی کنید
یک بازتاب UDP از تونل بفرستید و پیش از دست زدن به TCP طول بستهها را ببینید. سپس یک دستدهی باز کنید و تأیید کنید که قطعهها زیر MSS محاسبهشده شما میمانند. بستهها را بشمارید نه اتصالها، تا توقف به صورت شکافی در شمارنده ظاهر شود نه سوکت معلق. اگر خواندنها پس از انبوهی از نوشتن متوقف شوند، پنجره مشکوک است؛ اگر نوشتنها فوراً شکست بخورند، طول است.
سپس دستگاه را راهاندازی مجدد کنید و یک بار تکرار کنید، زیرا VpnServiceای که فقط تا اولین تغییر شبکه کار میکند مشکل مسیریابی است نه کنترل جریان.