تنقر على صف VPN في الإعدادات، فترى ملف تعريف ثبّته شخص آخر، وتبدأ في البحث عن الاستدعاء الذي يضيف واحدًا. لا يوجد استدعاء كهذا. يستطيع التطبيق فتح نفق خاص به عبر VpnService، لكن الإدخالات في تلك القائمة تخصّ المنصّة وأي عميل أنشأها. معرفة موضع الحدّ بدقة توفّر عليك أسبوعًا من مطاردة الأصناف المخفية.

ابدأ بما تعرضه شاشة الإعدادات

قائمة VPN سجلّ لملفات التعريف التي ثبّتها النظام أو عميل من مورّد: PPTP وL2TP/IPsec وIKEv2، وأي صفوف دفعها تطبيق OEM أو نظام MDM. يحمل كل صف عنوان خادم وبيانات اعتماد وتنفيذ بروتوكول يعيش خارج عمليتك. عندما ينقر المستخدم على «اتصال» هناك، فإن عميل المنصّة هو من يرفع النفق، لا شفرتك. ولهذا يبقى الصف بعد إعادة التشغيل، ولهذا لا يزيله إلغاء تثبيت تطبيقك أبدًا.

من جهة المستخدم تبدو القائمة كسطح API، مكانًا ينبغي أن تكون فيه إضافة VPN على بُعد نيّة واحدة. وهي ليست كذلك. نقاط الدخول العامة الوحيدة التي تلامسها هي نيّات التنقّل، وكل ما يكتب فيها يخصّ المنصّة أو مكوّنًا ذا صلاحيات مميّزة.

النتيجة العملية: إذا كان نص دعم يخبر المستخدمين بإضافة ملف التعريف من الإعدادات، فتطبيقك ليس جزءًا من ذلك المسار إطلاقًا.

ما يمنحك VpnService فعليًا

يصف VpnService وVpnService.Builder نفقًا واحدًا يملكه تطبيقك. تضيف عناوين ومسارات وخوادم DNS ووحدة MTU، وتحدّد نطاق حركة المرور عبر addAllowedApplication() أو addDisallowedApplication()، ثم يعيد establish() قيمة ParcelFileDescriptor لواجهة TUN. من تلك اللحظة يقرأ التطبيق حزم IP ويكتب الردود، ما يعني أن البروتوكول ملكك لتنفّذه أو تضمّنه: OpenVPN أو WireGuard أو نقل مخصّص.

لا شيء في ذلك التسلسل يتحدّث PPTP أو L2TP/IPsec أو IKEv2. لا يوجد في Builder مفتاح يقول استخدم عميل L2TP للنظام، ولا يوجد استدعاء راجع يتيح لك قيادة ملف تعريف تملكه المنصّة بالفعل. هذا هو الحدّ الحقيقي، وهو حدّ تصميمي لا ثغرة في الأذونات.

لذا يمكن للتطبيق أن يكون عميل VPN على أندرويد بكل تأكيد. لكنه لا يستطيع أن يكون مديرًا للعملاء الذين تأتي مع الهاتف.

مربّع الموافقة ليس زينة

لأن نفقك يستطيع التقاط حركة التطبيقات الأخرى، تجعل المنصّة المستخدم يوافق عليه صراحة. يعيد prepare() نيّة Intent عليك تشغيلها، والموافقة مرتبطة بحزمتك، وبينما النفق قائم يعرض شريط الحالة مؤشرًا دائمًا. أي VPN ثانٍ يُرفع يعطّل الأول، لأن أندرويد يشغّل شبكة VPN نشطة واحدة في كل مرة.

قاعدة النفق الواحد تُوقع الناس في الاختبار. ترفع نفقك بينما عميل مؤسسي متصل، ثم تستنتج أن شفرتك معطوبة، بينما انتقلت المنصّة ببساطة بعيدًا عن العميل الآخر. يمكن سحب الموافقات لاحقًا، ويمكن أن يعيد prepare() القيمة null على أجهزة ينقصها نشاط الموافقة، لذا يبقى على establish() التعامل مع الفشل.

الواجهات المخفية ونيّات الإعدادات

تشير الإجابات الأقدم إلى أصناف com.android.internal وواجهات AIDL التي يستخدمها العميل المدمج. وهي ليست في SDK، وتتغيّر بين الإصدارات دون إشعار، وتعدّ متاجر التطبيقات ذلك الوصول خطرًا. كما أن استخراج صلاحيات الجذر لا يجعلها مستقرة، لأن الجذر والوصول إلى واجهات API المخفية مشكلتان غير مترابطتين.

المعين المدعوم للتنقّل هو Settings.ACTION_VPN_SETTINGS، وقيمته android.net.vpn.SETTINGS. حلّله قبل تشغيله، لأن بعض إصدارات OEM لا تتضمن ذلك النشاط، وارجع إلى خطوات مكتوبة عندما يفشل التحليل. ينقل المستخدم إلى الشاشة ولا يضبط شيئًا.

مسارات مالك الجهاز وMDM هي الاستثناء الذي يقصده الناس عادة. يستطيع DevicePolicyManager تعيين حزمة VPN دائمة التشغيل، ويمكن للتكوينات المُدارة أن تحمّل مسبقًا إعدادات لعميل يدعمها، لكن هذا لا يزال ليس SDK العام وهو يؤلّف ملف تعريف PPTP.

امشِ في القرار من جهة المستخدم

إذا كان منتجك يحتاج إلى حمل حركة المرور، فخطّط لـ VpnService مع مكدّس بروتوكول حقيقي، وخصّص وقتًا لمسار الموافقة وقاعدة النفق الواحد. وإذا كان الهدف الاتصال بملف تعريف مؤسسي قائم، فعملك إرشادي: رابط عميق إلى شاشة الإعدادات، وتوثيق الحقول، واترك عميل المنصّة يقوم بالنفق. وإذا كان التطبيق يحتاج فقط إلى الوصول إلى نقطة نهاية خاصة، فهو لا يحتاج إلى نفق خاص به إطلاقًا.

اكتب ذلك القرار قبل أن تكتب الشفرة، لأن المسارات الثلاثة لا تشترك تقريبًا في شيء بعد السبرنت الأول.

فحص أخير قبل أن تبدأ: هل يقول المتطلب إن تطبيقنا يتصل، أم إن الجهاز يتصل؟ الأول هو VpnService، والثاني هو ملف تعريف لا يمكنك إنشاؤه. ضبط تلك الجملة أرخص من أي إعادة هيكلة.

مقالات ذات صلة