دامنه‌ای را به فهرست مجاز افزوده‌اید، پرس‌وجو به برنامه‌تان می‌رسد، تطبیق‌دهنده مجاز برمی‌گرداند و صفحه هنوز هرگز بارگذاری نمی‌شود. در این نقطه بیشتر مردم شروع می‌کنند به بازنویسی تطبیق‌دهنده و افزودن مقایسه‌های نویسه‌ای. بر اساس تجربه من، تطبیق‌دهنده معمولاً درست است و خرابی یک لایه پایین‌تر قرار دارد: در پاسخ DNS که ساخته‌اید، در مسیری که نصب کرده‌اید، یا در سوکتی که برای ارسال پرس‌وجو به کار برده‌اید. فهرست بخش آسان است؛ مسیر بسته جایی است که فهرست‌های مجاز می‌شکنند.

تطبیق‌دهنده به‌ندرت مقصر است

نخستین چیزی که باید جدا کرد، تفاوت مسدود با خراب است. نام مسدود باید سریع و آشکار شکست بخورد، چون مرورگر پاسخی می‌گیرد که می‌گوید نام وجود ندارد و متوقف می‌شود. نام خراب معلق می‌ماند، روی IPv6 دوباره تلاش می‌کند، به انتقال‌دهنده دیگری می‌افتد و شما را با یک نشانگر چرخان تنها می‌گذارد. اگر نشانگر چرخان می‌بینید، فهرست مجاز شما احتمالاً چیزی پاسخ می‌دهد که کلاینت نمی‌تواند کاری با آن بکند.

رکورد A خالی اشتباه کلاسیک است. بازگرداندن NOERROR با صفر پاسخ معتبر به نظر می‌رسد، پس محلل stub آن را می‌پذیرد، مرورگر دوباره تلاش می‌کند، AAAA را می‌آزماید، محلل دیگری را می‌آزماید و بسیار دیرتر رها می‌کند. کد پاسخ 3، یعنی NXDOMAIN، همراه با بازتکرار بخش پرسش و کپی شناسه تراکنش، گفت‌وگو را فوراً پایان می‌دهد. آن را برای هر نامی که آگاهانه رد می‌کنید به کار ببرید.

IPv6 هم سزاوار همان تصمیم است، نه یک مقدار پیش‌فرض. اگر برای A پاسخ NXDOMAIN بدهید و AAAA را رد کنید، کلاینت می‌تواند از طریق IPv6 به نام مسدود برسد و فیلتر نشت می‌کند. برخی پیاده‌سازی‌ها به پرس‌وجوهای AAAA مسدود با نشانی loopback پاسخ می‌دهند تا اتصال فوراً شکست بخورد و به گزینه جایگزین نیفتد. یک سیاست انتخاب کنید، آن را به هر دو نوع رکورد اعمال کنید و هر دو را ثبت کنید.

مسیری که همه را می‌گیرد، مالک همه است

طراحی فقط DNS هرگز addRoute("0.0.0.0", 0) را اضافه نمی‌کند، پس تنها پرس‌وجوها وارد رابط TUN می‌شوند و بقیه چیزها همچنان از شبکه زیرین استفاده می‌کنند. این شکل ایمن است و همچنین توضیح می‌دهد چرا برخی فهرست‌های مجاز روی Wi-Fi کار می‌کنند و در شبکه همراه شکست می‌خورند.

لحظه‌ای که مسیر پیش‌فرض وجود دارد، هر جریان مال شماست. بسته TCP که به رابط می‌رسد و نه ارسال و نه پاسخ داده می‌شود، تا زمانی که زمان کلاینت تمام شود در صف می‌ماند، و هیچ خطایی برای ثبت وجود ندارد چون هیچ‌چیز خطا نکرد. دیدم کسی یک بعدازظهر را روی فهرست مجاز خرابی گذراند که در واقع فقط یک ارسال‌کننده گمشده برای درگاه 443 بود. پروتکل و مقصد هر بسته‌ای را که تصمیم به حذفش می‌گیرید ثبت کنید.

سوکت محلل باید از تونل بیرون برود

ارسال، جایی است که تله بعدی می‌نشیند. محلل‌های اپراتور اغلب روی نشانی خصوصی زندگی می‌کنند که تونل به آن نمی‌رسد، پس پرس‌وجویی که ارسال می‌کنید به جایی نمی‌رود و برنامه پایان زمان خواندن را گزارش می‌دهد. پرس‌وجوهای تأییدشده را به محللی بفرستید که می‌دانید در دسترس است و ابتدا آن سوکت را به VpnService.protect() بدهید. سوکت محافظت‌نشده پرس‌وجو را به تونل خودتان برمی‌گرداند، جایی که دوباره به برنامه می‌رسد و تا فعال شدن پایان زمان دور می‌زند.

شکست دقیقاً مثل قطع شبکه به نظر می‌رسد و به همین دلیل در گزارش‌های خطا این‌قدر دوام می‌آورد. شمارنده‌هایی برای پرس‌وجوهای ارسال‌شده و پاسخ‌های دریافت‌شده نگه دارید: اگر عدد اول بالا می‌رود و دومی صفر می‌ماند، شما به سوکت نگاه می‌کنید نه به فهرست.

اندازه و جمع‌های کنترلی بدون پیام شکست می‌خورند

حذف‌های خاموش از اندازه هم می‌آیند. پاسخ DNS با مجموعه پاسخ بزرگ، یا دست‌دادن TCP با گزینه‌های سخاوتمندانه، می‌تواند از MTU مسیری که هرگز صریحاً تنظیم نکرده‌اید فراتر برود؛ قطعه در سمت بالا ناپدید می‌شود و کلاینت یا با اندازه کوچک‌تر دوباره تلاش می‌کند یا دست از تلاش برمی‌دارد. تنظیم MTU رابط روی 1280 انتخابی محافظه‌کارانه است که از بیشتر مسیرهای همراه جان سالم به در می‌برد، و صریح بودن بهتر از به ارث بردن یک مقدار پیش‌فرض است که هیچ‌کس اندازه‌گیری نکرده.

اگر خودتان بسته می‌سازید، جمع کنترلی قاتل خاموش دیگر است. جمع کنترلی UDP که روی شبه‌سرآیندی محاسبه شود که طول UDP یا فیلد پروتکل را جا انداخته باشد، بسته‌ای تولید می‌کند که در سمت شما درست به نظر می‌رسد و گیرنده آن را بی‌ردپا دور می‌ریزد. شبه‌سرآیند خود را فیلد به فیلد با مشخصات مقایسه کنید و هنگام اشکال‌زدایی جمع کنترلی محاسبه‌شده برای یک جریان معلوم را چاپ کنید.

VpnService چه کاری برای شما نمی‌کند

دانستن دو محدودیت پیش از طراحی حول آن‌ها ارزش دارد. هیچ برنامه معمولی نمی‌تواند بیرون از Wi-Fi یک پروکسی سراسری یا فایل PAC نصب کند و آن بخش تغییر نکرده است. مسیر هر برنامه تغییر کرده است: پیش از API 29، VpnService.Builder به‌کلی نمی‌توانست پروکسی حمل کند و از API 29 به بعد setHttpProxy(ProxyInfo.buildPacProxy(uri)) یک اسکریپت PAC برای ارزیابی برای شبکه VPN به پلتفرم می‌دهد. وقتی API‌های قدیمی‌تر را هدف می‌گیرید یا وقتی مدل PAC نمی‌تواند قاعده را بیان کند، قواعد را در کد خودتان ارزیابی کنید. UDP هم رایگان نیست: مرورگرها فقط TCP را از طریق پروکسی SOCKS می‌فرستند و بیشتر کلاینت‌های SOCKS5 هرگز UDP ASSOCIATE را پیاده نمی‌کنند، پس طراحی مبتنی بر پروکسی UDP را حذف یا نشت می‌دهد در حالی که TCP سالم به نظر می‌رسد.

این محدودیت دوم دلیل آن است که یک پروتکل مبتنی بر UDP می‌تواند در شبکه معمولی کار کند و در لحظه اتصال VPN شما شکست بخورد. اگر انتظار می‌رود تونل UDP حمل کند، ارسال‌کننده باید مسیر ارسال را برای آن پیاده کند، نه فقط مسیر TCP با نوع سوکت دیگر.

به این ترتیب بررسی کنید

فهرست را به ترتیب پیش ببرید و در نخستین مرحله‌ای که شکست می‌خورد بایستید، چون هر بررسی بعدی تا وقتی بررسی قبلی برقرار نشود فقط نوفه است.

  1. مسیرهای نصب‌شده را فهرست کنید و تأیید کنید آیا بسته‌های غیر DNS اصلاً وارد رابط TUN می‌شوند.
  2. نام پرس‌وجو، تصمیم و کد پاسخ را برای یک نام مسدود و یک نام تأییدشده ثبت کنید.
  3. تأیید کنید سوکت محلل محافظت‌شده است و محلل انتخاب‌شده از داخل تونل پاسخ می‌دهد.
  4. MTU رابط و جمع کنترلی یک پاسخی را که دستی ساخته‌اید تأیید کنید.
  5. همان صفحه را روی Wi-Fi و شبکه همراه تکرار کنید، چون محلل‌های اپراتور و MTU مسیر متفاوت‌اند.

اگر هر پنج مورد برقرار بود و صفحه هنوز معلق ماند، مظنون بعدی خود ارسال‌کننده است: یک درخواست ضبط‌شده را با یک پاسخ ضبط‌شده مقایسه کنید و دنبال شمار بایت گمشده یا سوکت نیمه‌بسته بگردید. مرجع VpnService.Builder قراردادهای مسیر، DNS و MTU را پوشش می‌دهد و protect() یک فراخوانی جداگانه VpnService است نه گزینه‌ای در builder؛ رابط متصل فقط یعنی قرارداد اول برآورده شده است.

مقالات مرتبط