نمی‌توانید از اندروید بپرسید که آیا VPN متصل است. VpnService هیچ getter اتصال را آشکار نمی‌کند و آیکون کلید در نوار وضعیت را سیستم می‌کشد، نه چیزی که برنامه شما بتواند بخواند. کاری که می‌توانید بکنید این است که از پشته اتصال پرسشی باریک‌تر بپرسید: ترافیک این برنامه از کدام شبکه استفاده می‌کند و آیا آن شبکه transport مربوط به VPN را حمل می‌کند. این راهنما بررسی‌ها را به ترتیبی می‌گذراند که از مثبت‌های کاذب پرهیز می‌کند، از فرمان shell روی دستگاه تا callback که در برنامه روشن می‌گذارید.

پیش از دست زدن به کد برنامه، دستگاه را از یک shell بررسی کنید

ابتدا یک خط پایه بگیرید. adb shell dumpsys connectivity هر شبکه‌ای را که سیستم می‌شناسد چاپ می‌کند و هر بلوک transportهایش را فهرست می‌کند، پس یک شبکه VPN با VPN در آن فهرست ظاهر می‌شود. پیش از هر چیز بلوک شبکه پیش‌فرض را بخوانید، سپس شبکه‌های دیگری را که همان transport را حمل می‌کنند جستجو کنید.

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

شبکه‌ای را که برنامه استفاده می‌کند بپرسید

در کد، آزمون توانمندی چهار خط است. ACCESS_NETWORK_STATE را در manifest درخواست کنید و سپس توانمندی‌های شبکه فعال را با transport VPN مقایسه کنید:

ConnectivityManager cm = (ConnectivityManager)
    context.getSystemService(Context.CONNECTIVITY_SERVICE);
Network active = cm.getActiveNetwork();
NetworkCapabilities caps = cm.getNetworkCapabilities(active);
boolean vpnInUse = caps != null
    && caps.hasTransport(NetworkCapabilities.TRANSPORT_VPN);

پاسخ به‌عمد باریک است: آیا شبکه پیش‌فرض برنامه هم‌اکنون VPN است. درباره اینکه کدام VPN است، آیا مال شماست یا آیا تونل به انتهای دورش می‌رسد چیزی نمی‌گوید. true را «ترافیک از طریق تونلی بیرون می‌رود» بدانید و حساب و کتاب خودتان را جدا نگه دارید. یک boolean که بی‌صدا سه چیز را همزمان می‌رساند همان دلیلی است که یک صفحه تنظیمات سرانجام VPN را خاموش گزارش می‌کند در حالی که کاربر از آن عبور می‌کند.

وقتی در عوض باید بدانید آیا هیچ شبکه VPN وجود دارد، روی cm.getAllNetworks() پیمایش کنید و هر مجموعه توانمندی را بیازمایید. این دو پاسخ روی دستگاهی که VPN برای هر برنامه اجرا می‌کند و برنامه‌های دیگر را پوشش می‌دهد ولی برنامه شما را نه، از هم جدا می‌شوند، و همین جدایی منبع بیشتر گزارش‌های خطایی است که می‌گویند برنامه VPNی را که آشکارا روشن است نمی‌بیند.

به‌جای نظرسنجی دوره‌ای، تغییرات را دنبال کنید

نظرسنجی با تایمر شکل نادرست این پرسش است، چون تغییرات وضعیت به‌صورت رویداد می‌رسند. درخواستی ثبت کنید که با transport VPN مطابقت دارد و بگذارید framework به شما بگوید چه زمانی شبکه‌ای مطابق ظاهر، ناپدید یا تغییر می‌کند:

NetworkRequest request = new NetworkRequest.Builder()
    .addTransportType(NetworkCapabilities.TRANSPORT_VPN)
    .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
    .build();
cm.registerNetworkCallback(request, callback);

شبکه VPN هرگز NET_CAPABILITY_NOT_VPN را حمل نمی‌کند، بنابراین حذف صریح نیت را مستند می‌کند. بدون آن هم درخواست مطابقت دارد، اما نفر بعدی که کد را می‌خواند باید خودش این را دریابد.

ثبت مربوط به دامنه‌ای است که به مشاهده نیاز دارد، معمولاً یک صفحه یا یک سرویس پیش‌زمینه. در onStop یا onDestroy ثبت را لغو کنید و callback را به activity‌ای که دیگر نیست وصل نگذارید. برنامه‌ای بدون مؤلفه دیدنی میزبان بدی برای ناظر دائمی است، و callbackهایی که فراموش می‌کنید همان‌هایی هستند که پس از تغییر پیکربندی فرو می‌ریزند.

سه پرسش را جدا نگه دارید

بیشتر سردرگمی در این حوزه از یکسان گرفتن سه بررسی متفاوت می‌آید. آنها از APIهای متفاوت استفاده می‌کنند و هر یک می‌تواند درست باشد در حالی که دیگران نادرست‌اند.

QuestionHow you answer itWhat a yes means
Did the user grant VPN consent to my app?VpnService.prepare() returns nullConsent exists; the tunnel may still be down
Is the device sending default traffic through a VPN?TRANSPORT_VPN on the active networkSomething is tunneling, not necessarily your app
Did this request arrive through the VPN?Source address seen by the serverProof for that connection, nothing wider

ردیف سوم همان چیزی است که مردم می‌کوشند در کلاینت پاسخ دهند و آنجا پاسخ‌دادنی نیست. به کلاینت‌های VPN مجموعه نشانی خودشان را بدهید و نشانی مبدأ را در سرور بخوانید؛ این تنها شاهدانی است که وقتی کاربر میان نشست تونل را عوض می‌کند دوام می‌آورد.

ترتیب بررسی که مثبت‌های کاذب را کنار می‌گذارد

کد نمونه قدیمی‌تری که هنوز در گردش است getNetworkInfo(ConnectivityManager.TYPE_VPN) را صدا می‌زند. آن فراخوان منسوخ است و در چند انتشار حتی وقتی VPN متصل بود null برمی‌گرداند، از این رو بسیاری از قطعه‌های کپی‌شده روی دستگاهی که کار می‌کند خراب به نظر می‌رسند. بررسی‌های توانمندی از این تله پرهیز می‌کنند و از اندروید 5.0 به بعد یکسان رفتار می‌کنند. اگر پایگاه کدی را نگهداری می‌کنید که هنوز آن را صدا می‌زند، فراخوان را جایگزین کنید نه اینکه آن را در یک fallback بپیچید، چون همان fallback بود که در آغاز پاسخ نادرست را ساخت.

یک میانبر هشدار می‌خواهد. مقایسه نشانی عمومی که یک سرویس بیرونی گزارش می‌کند با نشانی خروج VPN چیزی درباره مسیر اثبات می‌کند، اما به هر بررسی یک درخواست شخص ثالث اضافه می‌کند، به بالا ماندن آن سرویس وابسته است و می‌تواند کش یا مسدود شود. آن را تشخیص دستی نگه دارید، هرگز به‌عنوان بررسی وضعیت درون برنامه نه.

بررسی‌ها را به این ترتیب اجرا کنید و در اولین موردی که با گزارش کاربر نمی‌خواند بایستید: فهرست transport در shell، توانمندی‌های شبکه فعال، رویدادهای callback هنگامی که تونل را روشن و خاموش می‌کنید، سپس نشانی مبدأ در سمت سرور. اگر هر چهار مورد همخوان باشند و برنامه هنوز بدرفتاری کند، اشکال در ماشین وضعیت خودتان است نه در نگاه اندروید به شبکه.

مقالات مرتبط