تونلشکنی در Android VpnService: واقعاً چه چیزی را میتوانید مسیریابی کنید
VpnService فقط بستههایی را که builder به توصیفگر خود میفرستد عبور میدهد. پیش از متهم کردن تونلشکنی، پیشوندها، احراز هویت پروکسی، DNS و فهرست برنامهها را بررسی کنید.
تونلشکنی در یک برنامه VpnService در چند نقطه مشخص میشکند و ترمیم هر کدام متفاوت است. کلاینت فقط یک مقصد ناموفق را نشان میدهد، پس نخستین کار این است که بفهمید به کدام مرز میخورید: مسیرهایی که builder دریافت کرده، پروکسیای که نمیتوانید احراز هویت کنید، resolverی که فقط اعلام کردهاید، یا فهرست برنامههایی که پلتفرم برای هر کاربر اعمال میکند. دقیقاً به همین دلیل یک بررسی چهارمرحلهای نگه میدارم. به ترتیب زیر اجرا میشود و در نخستین شکست متوقف میشود.
تأیید کنید builder چه چیزی به پلتفرم داده است
VpnService.Builder یک فهرست اعلان است. establish() برای یک رابط مجازی یک ParcelFileDescriptor برمیگرداند و پلتفرم تنها ترافیکی را که با پیشوندهای addAddress و addRoute مطابقت دارد به آن توصیفگر میفرستد؛ addDnsServer به کلاینتها میگوید کدام resolver را بپرسند. مقصدی بیرون از همه پیشوندهایی که افزودهاید هرگز به برنامه شما نمیرسد، هر چه رابط وعده بدهد. همین یک واقعیت بیشتر گزارشهای تونلشکنی خراب را پیش از هر تغییر کد توضیح میدهد.
فهرستهای نشانی، مسیر و DNS را که builder شما ساخته در گزارش ثبت کنید و آنها را با مقصدهای ناموفق بسنجید. یک /32 گمشده برای میزبان مدیریت بسیار رایجتر از تونل خراب است. اگر فهرست درست است ادامه دهید؛ اگر نادرست است، بقیه چکلیست هدر دادن تلاش است.
excludeRoute فقط از API 33 وجود دارد
استثنا بر پایه مقصد همان درخواستی است که بیشتر اوقات به رشته پشتیبانی تبدیل میشود. builder.excludeRoute(IpPrefix) در API 33 آمد؛ پیش از آن تنها تقریب پشتیبانیشده افزودن مسیر برای همه چیز جز پیشوند استثناشده است، یعنی باید پیشوندهای مکمل را خودتان محاسبه کنید. این دو فراخوان تصویر آینهای نیستند: یکی از مجموعه مسیرهای رابط کم میکند، دیگری مجموعه را از صفر میسازد، و دستگاه قدیمیتر فراخوانی را که نمیشناسد پرتاب میکند یا بیصدا نادیده میگیرد. بر پایه Build.VERSION.SDK_INT شاخه بزنید و هر دو مسیر را روی سختافزار واقعی بیازمایید.
هنگام محاسبه مکملها به همترازی پیشوندها توجه کنید. استثنای /24 درون مسیر /16 به پیشوندهای پوشانندهای نیاز دارد که واقعاً باقیمانده را توصیف کنند، نه یک /8 بدشکل. فهرست نهایی را ثبت کنید و مطمئن شوید میزبان استثناشده هنوز از مسیری بیرون تونل حل میشود.
اعتبارنامه پروکسی قابل بیان نیست
setHttpProxy(ProxyInfo) رابط را به یک پروکسی HTTP یا SOCKS میبرد و نشانی PAC میتواند برای هر مقصد میان آن دو انتخاب کند. نه ProxyInfo و نه قالب PAC نام کاربری و گذرواژه را حمل نمیکنند، پس پروکسی نیازمند احراز هویت بیرون از API پلتفرم میماند. برنامههایی که به آن نیاز دارند معمولاً یک پشته فضای کاربر، tun2socks یا موتور مشابه جاسازی میکنند و خودشان به چالش 407 پاسخ میدهند.
این انتخاب پیامد دارد. پشته فضای کاربر تنها ترافیکی را میبیند که به سویش مسیریابی میکنید، پس باید درون همان مرز توصیفگر زندگی کند و همان الزام protect() را برای سوکت بالاسری خود به ارث میبرد. protect() را جا بیندازید و تونل ترابری خودش را میگیرد، که بهصورت دستدهیای که پس از نخستین بسته میماند دیده میشود. در برابر پروکسیای بیازمایید که اعتبارنامه میخواهد، چون پروکسی باز از مسیر پلتفرم میگذرد و چیزی را ثابت نمیکند.
فهرستهای هر برنامه و DNS هم اعلان هستند
Builder هم addAllowedApplication دارد و هم addDisallowedApplication، و این دو یکدیگر را طرد میکنند: فراخوانی هر دو استثنا پرتاب میکند. در هر حال فهرست هنگام برپایی رابط برای هر کاربر اعمال میشود، پس برنامه پروفایل کاری توسط تونل پروفایل کاری اداره میشود نه تونل شما. آنچه هیچ API نمیدهد کنترل بر تصمیم برنامه دیگر است: نمیتوانید بستهای را از بیرون به تونل خود برانید و نمیتوانید VPNی را که از پیش فعال است ببندید یا بازپیکربندی کنید.
DNS از همان الگو پیروی میکند. addDnsServer یک نشانی resolver اعلام میکند؛ به برنامه شما اجازه پاسخ دادن به پرسوجوها را نمیدهد. اگر میخواهید نامها را خودتان حل کنید، یک resolver اجرا کنید و نشانی آن را به تونل ببرید. برنامههایی که DoT یا DoH را به نام میزبان ثابتی به کار میبرند میتوانند نشانی اعلامشده شما را دور بزنند، پس حل نام را تلاش بهترینکوشش بدانید مگر آنکه مسیر پرسوجو را هم رهگیری کنید.
مانیفست، always-on و آزمون چهارمرحلهای
واجد شرایط بودن در مانیفست اعلام میشود: BIND_VPN_SERVICE، فیلتر intent با نام android.net.VpnService و یک سرویس پیشزمینه اگر تونل باید در پسزمینه بماند. Always-on VPN افزون بر آن یک onRevoke پاک میخواهد، چون سیستم آن را هنگامی که کاربر تنظیمات را تغییر میدهد یا برنامه دیگری کنترل را میگیرد فراخوانی میکند.
بررسیها را به ترتیب اجرا کنید و در نخستین شکست بایستید. نخست، فهرستهای نشانی، مسیر و DNS دادهشده به builder را بیرون بریزید. دوم، تأیید کنید establish() یک توصیفگر برگردانده و سوکت ترابری محافظت شده است. سوم، یک مقصد درون پیشوند شامل و یکی درون پیشوند مستثنا را درخواست کنید. چهارم، با پرسیدن نام میزبانای که تنها resolver شما میتواند حل کند بررسی کنید کدام resolver واقعاً پاسخ داده است.
دستدهی کامل هیچیک از مراحل بعدی را ثابت نمیکند. هر بررسی یک لایه را جدا میکند، پس موفقیت در مرحله دوم چیزی درباره مرحله چهارم نمیگوید.
مرجع VpnService.Builder تمام سطح builder را فهرست میکند. هر چیزی فراتر از آن یک API دیگر، یک برنامه دیگر یا پشته فضای کاربر خودتان است و این یک تصمیم طراحی است نه ایراد تونلشکنی.