نه، یک اپلیکیشن VpnService نمیتواند VPN گوشی را روشن کند
اپلیکیشن مالک یک تونل و چرخه عمر آن است، نه کلید VPN گوشی. رضایت را میتوان پس گرفت، هر بار فقط یک VPN اجرا میشود و بستهها برای تجزیه به شما سپرده شدهاند.
فرض اشتباه این است که اپلیکیشن VpnService یک کلید سیستمی را میچرخاند و از آن پس گوشی محافظت میشود. آنچه در واقع رخ میدهد محدودتر است: اپلیکیشن رضایت میخواهد، یک واسط میسازد، بستهها را از طریق توصیفگر فایل میخواند و مینویسد، و هنگام توقف واسط را پس میدهد. در هر لحظه فقط یک چنین تونلی میتواند زنده باشد و سیستم میتواند بیآنکه بپرسد آن را بگیرد. بیشتر رفتار گیجکنندهای که مردم آن را به گردن پروتکل میاندازند از همین مدل مالکیت میآید.
هر گذار چرخه عمر را جزئی از طراحی بدانید، نه یک حالت حاشیهای.
افسانه: اپلیکیشن VPN گوشی را روشن میکند
جریان با VpnService.prepare() آغاز میشود که یک Intent برمیگرداند که کاربر باید بپذیرد، و با establish() ادامه مییابد که پس از آن هم میتواند به دلایلی که اپلیکیشن کنترل نمیکند شکست بخورد. برپایی تونل دوم واسط اول را غیرفعال میکند و اعلان سیستم را جایگزین میکند. در هیچ نقطهای از این زنجیره اپلیکیشن مالک کلیدی برای کل دستگاه نیست؛ مالک توصیفگری است که به آن داده شده و بستههایی که روی آن فرود میآیند. حتی اعلان پایدار هم متعلق به سیستم است نه طراحی اپلیکیشن.
چون واسط اعطا میشود و نه تصاحب، یک کلاینت دیگر میتواند در هر زمان جای آن را بگیرد. اپلیکیشن این را از طریق یک فراخوان پسرو میفهمد، نه از طریق مقدار بازگشتی.
توقف اجباری اپلیکیشن به همان دلیل در سایهی اعلانها دیده میشود: سیستم گزارش میدهد چه چیزی اعطا کرده، نه آنچه اپلیکیشن تصمیم گرفته نشان دهد.
افسانه: رضایت یک بار داده میشود
وقتی سیستم تونل را به کلاینت دیگری میسپارد، یک تراکنش لغو روی binder میفرستد که کلاس پایه از onBind() برمیگرداند. اپلیکیشنی که آن binder را با نمونه خودش جایگزین میکند باید خودش تراکنش لغو را ارسال کند، وگرنه VpnService.onRevoke() هرگز فعال نمیشود و سرویس به نوشتن در توصیفگری ادامه میدهد که دیگر وجود ندارد. تأیید را کاربر هم میتواند پس بگیرد و سیستم میتواند سرویس را در هنگام راهاندازی مجدد یا اتصال دوباره همیشهفعال متوقف کند.
رسیدگی به آن کار مشخصی است نه یک خط گزارش: خواندن توصیفگر را متوقف کنید، آن را ببندید، پرچم متصل را پاک کنید و به کاربر بگویید در چه حالتی است. سپس تصمیم بگیرید که اپلیکیشن باید دوباره برپا کند، دوباره بپرسد یا صبر کند، چون در پیکربندی همیشهفعال ممکن است سیستم خودش سرویس را بازگرداند.
اپلیکیشنی که لغو را استثنا میداند نه یک حالت، گزارش متصل بودن میدهد در حالی که هر بستهای که مینویسد دور ریخته میشود، و این بدترین حالت خرابی ممکن برای یک ابزار حریم خصوصی است.
افسانه: واسط ترافیک را به جای شما پالایش میکند
آنچه میرسد IP خام است. گرفتن یا پالایش یعنی تجزیه سرصفحههای IP، TCP، UDP و IPv6، ردیابی جریانها و نوشتن پاسخها به توصیفگر، که اپلیکیشن ضبط را به یک پشته شبکه کوچک تبدیل میکند نه یک امکانات سیستمی. protect() وجود دارد تا سوکت انتقال خود اپلیکیشن تونلش را دور بزند نه اینکه خودش را ضبط کند، و یک مقدار بولی برمیگرداند که در هر سوکتی که پس از برپایی تونل ساخته میشود ارزش بررسی دارد.
فهرستهای برنامههای مجاز و غیرمجاز در سازنده تعیین میکنند ترافیک چه کسی وارد واسط میشود. آنها دسترسی به دادهای فراتر از بستههای IP که واسط حمل میکند نمیدهند و هیچ API تونل کلاینت دیگر را آشکار نمیکند یا پروفایلهای VPN سیستم را فهرست میکند. قابلیتهای شبکه میتوانند بگویند یک شبکه انتقال VPN است، اما نه اینکه تونل از آنِ کیست.
به همین دلیل پرسشهای وضعیت درباره سایر برنامهها پاسخهای متناقض میگیرند: اطلاعات بهسادگی در اختیار فرایند شما قرار نمیگیرد.
چرخه عمر در واقع چه میخواهد
حالتهایی را که کنترل میکنید پیگیری کنید: آماده، برپاشده، لغوشده، متوقف. اتصال دوباره باید برچیدن و سپس برپایی باشد، با یک مالک واحد برای توصیفگر و سوکت انتقال، چون دو مؤلفه که برای برپایی مسابقه میدهند بر سر تنها جای موجود میجنگند. شروع پسزمینه در اندروید مدرن محدود است، پس پخش بوت تضمینی نیست؛ جایی که سطح API اجازه میدهد از سرویس پیشزمینه با اعلانش استفاده کنید.
گذر را ثبت کنید نه فقط حالت نهایی را، و ماشین حالت را آنقدر کوچک نگه دارید که هر مسیر آزمونپذیر باشد. حالتی که در آزمون به آن نمیرسید حالتی است که در میدان بدون گزارش با آن روبهرو میشوید.
چرخه عمر را روی یک دستگاه بیازمایید
شبیهسازها میتوانند اتصال را جعل کنند اما مسیر رضایت و لغو را بهطور متقاعدکننده نه، پس اینها را روی دستگاه واقعی با یک کلاینت VPN دوم نصبشده اجرا کنید تا جای را بدزدد.
adb shell dumpsys connectivity | grep -i -A3 vpn
adb shell dumpsys activity services | grep -i -A2 vpnservice
adb logcat -s VpnService:V AndroidRuntime:E
adb shell am force-stop com.example.vpnapp
تونل را آغاز کنید، اعلان و واسط را تأیید کنید، سپس کلاینت دیگر را وصل کنید و ببینید آیا فراخوان پسرو لغو اجرا میشود و حالت پاک میشود. اپلیکیشن را بهاجبار متوقف کنید و بررسی کنید آیا چیزی هنوز توصیفگر زندهای گزارش میکند.
زنجیره را پس از راهاندازی مجدد همیشهفعال تکرار کنید، چون آن مسیر با اتصال آغازشده توسط کاربر تفاوت دارد. آنچه میجویید یک نماد سبز نیست بلکه شاهدی است که اپلیکیشن استفاده از توصیفگری را که دیگر مالکش نبود متوقف کرده است.