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

تحقق من فتحة VPN قبل أن تلمس protect

يمنح أندرويد كل مستخدم واجهة VPN نشطة واحدة فقط. هذه قاعدة منصة، وليست إذنًا يمكن لتطبيقك طلبه، لذلك لا يمكن لعميلين يريدان كلاهما أن يكونا النفق أن يتعايشا.

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

الخطوة 1: اقرأ ما يعيده prepare

يجيب VpnService.prepare عن سؤال واحد: هل يجوز لهذا التطبيق أن يصبح VPN؟ يعيد null عندما تكون الموافقة موجودة بالفعل، ويعيد Intent عندما يبقى على المستخدم الموافقة عليه، وعليك إطلاقه باستخدام startActivityForResult. تعامل مع إعادة null كإذن بالمتابعة، لا كدليل على أن establish سينجح.

Intent intent = VpnService.prepare(context);
if (intent != null) {
    startActivityForResult(intent, REQUEST_VPN);
}

إذا عاد مسار Intent ورفض المستخدم، فسيستمر prepare في إعادة Intent في المحاولة التالية. سجّل رمز النتيجة بدلًا من إعادة المحاولة في حلقة، لأن الموافقة المرفوضة قرار مستخدم، لا خطأ عابر.

الخطوة 2: تأكد أن الواجهة تخص تطبيقك

بعد establish، يكون ParcelFileDescriptor الذي تستلمه دليل الملكية، ويستحق التحقق منه قبل أي عمل على المقابس. الواصف الذي يكون null أو مغلقًا بالفعل يعني أن الواجهة لم تُنشأ قط، حتى لو لم يظهر أي استثناء. قارن اسم الواجهة من الباني بالاسم الذي يبلغ عنه النظام لـ VPN النشط، باستخدام dumpsys على جهاز اختبار. فإن اختلفا، فالنفق الذي بنيته ليس النفق الذي يحمل الحركة.

adb shell dumpsys connectivity

يشرح هذا التناقض نفسه أيقونة متصلة مع حركة ميتة. يعرض النظام نفق التطبيق الآخر، بينما يحتفظ كودك بواصف لواجهة لا يملكها.

الخطوة 3: استدعِ protect قبل connect

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

استدعِ protect قبل connect أو bind، وليس بعده أبدًا. يجب أن تكون الحماية قائمة قبل توصيل المقبس أو ربطه، لأن المقبس المتصل اختار مساره بالفعل وprotect اللاحق لا ينقله بشكل موثوق. إذا أعاد protect قيمة false، توقف وأعد فحص الملكية: فهو يفشل عندما لا تخص الواجهة تطبيقك، وهي بالضبط الحالة التي يتركها VPN آخر. إعادة القيمة false إشارة للعودة إلى الخطوة 1، لا لإعادة محاولة المقبس نفسه.

DatagramSocket socket = new DatagramSocket();
if (!vpnService.protect(socket)) {
    return;
}
socket.connect(serverAddress, serverPort);

الخطوة 4: اربط عملية عندما يكون المقبس غير ملائم

بعض وسائط النقل ليست مقبسًا واحدًا: قد تفتح مكتبة عدة مقابس، أو قد تريد أن تستخدم كل اتصالات عملية مساعدة المخرج نفسه. تربط ConnectivityManager.bindProcessToNetwork العملية بشبكة مختارة، وقد فعل setProcessDefaultNetwork الأقدم الأمر نفسه قبل إهماله. اطلب الاتصال الأساسي بـ NET_CAPABILITY_NOT_VPN حتى يختار الربط الشبكة تحت نفقك لا النفق نفسه.

NetworkRequest request = new NetworkRequest.Builder()
    .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
    .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
    .build();

ربط عملية ليس VPN ثانيًا. إنه يختار مخرجًا لتلك العملية، وهو مفيد لنقلك وغير مفيد لتشغيل نفقين في وقت واحد.

أصلح جهاز المستخدم، لا مسار الكود

لا يوجد API يتيح لتطبيقين مشاركة فتحة VPN، لذا ينتمي الإصلاح إلى حالة الجهاز. اعرض على المستخدم مسارين محددين: اقطع أو امسح الدائم التشغيل للتطبيق الآخر حتى يتمكن prepare لديك من الاستيلاء، أو افتح إعدادات لكل تطبيق في ذلك التطبيق واستبعد تطبيقك من نفقه. يحافظ الاستبعاد على تشغيل المرشح لكل شيء آخر ويتيح لتطبيقك استخدام الاتصال العادي، وهو غالبًا النتيجة التي يريدها المستخدم فعلًا.

اكتب المسارين في وثائق الدعم، لأن المستخدمين نادرًا ما يعرفون أي تطبيق يملك VPN. اطلب منهم إرسال اسم VPN النشط من الإعدادات ونتائج prepare وestablish وprotect من سجلاتك، ويمكنك معرفة أي خطوة فشلت خلال دقيقة. ثم تحقق بحركة حقيقية: بحث عن اسم، واتصال TCP، وطلب واحد عبر النفق، لا مجرد أيقونة متصلة. أثبت الواصف الصالح والمؤشر الأخضر الملكية في أحسن الأحوال، وليس أبدًا أن النقل نجا من قواعد التوجيه.

إذا نجحت الفحوص الأربعة كلها وظلت الحركة ميتة، فلم تعد الملكية مشكلتك. انتقل إلى التوجيه وإمكانية الوصول إلى الخادم، واختبر البيانات نفسها من جهاز لا يحتوي على تطبيق VPN.

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