كيف تتحقق من اتصال VPN على Android في الشيفرة
لا يوفر Android أي دالة للاستعلام عن اتصال VPN. استعلم عن قدرات الشبكة، وراقب تغيّر النقل، وافصل موافقة التطبيق وحالة الجهاز وإثبات الخادم عن بعضها.
لا يمكنك أن تسأل Android ما إذا كان VPN متصلًا. لا يعرض VpnService أي دالة للاستعلام عن الاتصال، وأيقونة المفتاح في شريط الحالة يرسمها النظام، لا أي شيء يستطيع تطبيقك قراءته. ما يمكنك فعله هو أن تطرح على حزمة الاتصال سؤالًا أضيق: أي شبكة يستخدمها مرور هذا التطبيق، وهل تحمل تلك الشبكة نقل VPN. يمر هذا الدليل على الفحوص بالترتيب الذي يتجنب النتائج الإيجابية الخاطئة، من أمر shell على الجهاز وصولًا إلى callback تتركه يعمل في التطبيق.
افحص الجهاز من shell قبل لمس شيفرة التطبيق
احصل على خط أساس أولًا. يطبع adb shell dumpsys connectivity كل شبكة يعرفها النظام، وكل كتلة تسرد وسائل النقل الخاصة بها، فتظهر شبكة VPN مع VPN في تلك القائمة. اقرأ كتلة الشبكة الافتراضية قبل أي شيء آخر، ثم ابحث عن شبكات أخرى تحمل النقل نفسه.
مخرجان يجيبان عن سؤالين مختلفين. وجود شبكة VPN ليس مماثلًا لكون تطبيقك يستخدمها، وعلى جهاز به ملف عمل أو مستخدم ثانٍ قد يكون الأمران صحيحين معًا بينما تتحدث عمليتك إلى الإنترنت مباشرة. سطر الشبكة النشطة هو الذي يحدد ما تفعله مقابسك.
اطلب الشبكة التي يستخدمها التطبيق
في الشيفرة يكون اختبار القدرات أربعة أسطر. اطلب ACCESS_NETWORK_STATE في المانيفست، ثم قارن قدرات الشبكة النشطة بنقل 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 على أنه «المرور يخرج عبر نفق ما» واحتفظ بسجلاتك منفصلة. القيمة المنطقية التي تعني ثلاثة أشياء في وقت واحد هي كيف تنتهي شاشة الإعدادات إلى الإبلاغ عن أن VPN مغلق بينما يتصفح المستخدم عبره.
حين تحتاج بدلًا من ذلك إلى معرفة ما إذا كانت أي شبكة VPN موجودة، كرر المرور على cm.getAllNetworks() واختبر كل مجموعة قدرات. يختلف الجوابان على جهاز يشغّل VPN لكل تطبيق يغطي تطبيقات أخرى لا تطبيقك، وهذا الاختلاف هو مصدر معظم تقارير الأخطاء التي تقول إن التطبيق لا يرى VPN من الواضح أنه مفعّل.
راقب التغييرات بدل الاستقصاء الدوري
الاستقصاء الدوري بمؤقت هو الشكل الخاطئ لهذا السؤال، لأن تغييرات الحالة تصل كأحداث. سجّل طلبًا يطابق نقل VPN ودع الإطار يخبرك عندما تظهر شبكة مطابقة أو تختفي أو تتغير:
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 مرتبطًا بنشاط زال بالفعل. التطبيق بلا مكوّن مرئي مضيف سيئ لمراقب دائم، وcallbacks التي تنساها هي التي تنهار بعد تغيير في الإعدادات.
افصل ثلاثة أسئلة
معظم الالتباس في هذا المجال يأتي من معاملة ثلاثة فحوص مختلفة كفحص واحد. تستخدم واجهات API مختلفة، ويمكن أن يكون كل واحد صحيحًا بينما الآخران خاطئان.
| Question | How you answer it | What a yes means |
|---|---|---|
| Did the user grant VPN consent to my app? | VpnService.prepare() returns null | Consent exists; the tunnel may still be down |
| Is the device sending default traffic through a VPN? | TRANSPORT_VPN on the active network | Something is tunneling, not necessarily your app |
| Did this request arrive through the VPN? | Source address seen by the server | Proof for that connection, nothing wider |
الصف الثالث هو ما يحاول الناس الإجابة عنه على العميل، ولا يمكن الإجابة عنه هناك. امنح عملاء VPN مجموعة عناوين خاصة بهم واقرأ عنوان المصدر على الخادم؛ فهذا هو الدليل الوحيد الذي يصمد حين يبدّل المستخدم الأنفاق في منتصف الجلسة.
ترتيب تحقق يستبعد النتائج الإيجابية الخاطئة
لا تزال شيفرة نموذجية أقدم متداولة تستدعي getNetworkInfo(ConnectivityManager.TYPE_VPN). هذا الاستدعاء مهجور، وفي عدة إصدارات أعاد null حتى مع اتصال VPN، ولهذا تبدو كثير من المقاطع المنسوخة معطوبة على جهاز يعمل. تتجنب فحوص القدرات هذا الفخ وتتصرف بالطريقة نفسها منذ Android 5.0. إذا كنت تصون قاعدة شيفرة ما زالت تستدعيه، فاستبدل الاستدعاء بدل تغليفه ببديل احتياطي، لأن البديل الاحتياطي هو ما أنتج الجواب الخاطئ أصلًا.
اختصار واحد يستحق تحذيرًا. مقارنة العنوان العام الذي تبلغ عنه خدمة خارجية بعنوان خروج VPN تثبت شيئًا عن المسار، لكنها تضيف طلبًا لطرف ثالث إلى كل فحص، وتعتمد على بقاء تلك الخدمة عاملة، ويمكن تخزينها مؤقتًا أو حجبها. أبقها تشخيصًا يدويًا، لا فحص حالة داخل التطبيق أبدًا.
نفّذ الفحوص بهذا الترتيب وتوقف عند أول فحص لا يتفق مع تقرير المستخدم: سرد النقل في shell، وقدرات الشبكة النشطة، وأحداث callback أثناء تبديل النفق، ثم عنوان المصدر على الخادم. إذا تطابقت الأربعة جميعًا وما زال التطبيق يسيء التصرف، فالعطل في آلة حالتك لا في رؤية Android للشبكة.