VpnService protect() أم استثناء التطبيق: أيّهما يصلح التوجيه
احمِ المقبس الذي يحمل النفق، أو استثنِ التطبيق كله: الشروط الثلاثة التي تحدد النطاق الذي يحتاجه تطبيق VPN على أندرويد.
لديك طريقتان لإبقاء حزم النفق نفسه خارج النفق: حماية مقبس النقل، أو إخراج التطبيق كله من الـ VPN. إنهما يعالجان مشكلتين مختلفتين، واختيار الأوسع لأنه أسهل في الربط هو السبب الذي تفقد به التطبيقات الوصول إلى الشبكة المحلية و DNS المنقسم دون أن تلاحظ. ابدأ بما يجب أن يبقى خارج النفق، ثم اختر أصغر نطاق يحقق ذلك.
ما الذي يزيله protect() من النفق
VpnService.protect() هو استدعاء لكل مقبس. تسلّم إليه المقبس أو الواصف الذي يحمل النفق، فيعلّم أندرويد ذلك المقبس كي تستخدم حركته الشبكة الأساسية بدل واجهة الـ VPN. وكل ما يرسله التطبيق بعد ذلك يتبع مسار الـ VPN العادي.
هذا النطاق الضيق هو المقصود. المقبس المحمي لا يغيّر طريقة عميل HTTP في تحليل الأسماء، ولا الواجهة التي تستخدمها نداءات التحليلات، ولا ما يحدث لاتصال ثانٍ يُفتح لاحقًا.
ثلاثة شروط تحدد النطاق
الشرط الأول هو ما إذا كان نقل النفق وحده يجب أن يخرج. إذا كان على بقية حركة التطبيق أن تمر عبر الـ VPN، فحماية المقبس هي النطاق الصحيح. الشرط الثاني هو ما إذا كنت تتحكم في إنشاء المقبس. المكتبة التي تفتح مقبسها في العمق لا تترك لك مكانًا لاستدعاء protect() إلا إذا كشفت خطافًا أو قبلت مقبسًا جاهزًا. الشرط الثالث هو ما إذا كان على التطبيق الوصول إلى الشبكة المحلية أو إلى بوابة captive portal مباشرة، وهذا شرط مختلف عن إبقاء النفق حيًا.
استثناء التطبيق بـ addDisallowedApplication() هو الأداة الفظة. فهو يزيل كل اتصال يفتحه التطبيق من الـ VPN، بما في ذلك الاتصالات التي أردت فحصها، وعلى بعض الأجهزة يغيّر أيضًا سلوك الاكتشاف المحلي.
الخيار الثالث هو ربط النقل بشبكة مختارة باستخدام bindProcessToNetwork() أو مقبض شبكة على مستوى المقبس. هذا مفيد عندما يملك الجهاز Wi-Fi وشبكة خلوية في الوقت نفسه وتريد أن يسير النفق على وصلة رفع محددة.
استدعِه أثناء إنشاء النقل
استدعِprotect() في الكتلة نفسها التي تنشئ النقل، قبل connect() أو send(). إذا أعاد الاستدعاء false، فأغلق المقبس وأفشل الإعداد؛ فالاستمرار يتركك بنقل قد تعود حزمه إلى واجهتك أنت.
الشكل صغير بما يكفي ليبقى في مكان واحد:
DatagramSocket transport = new DatagramSocket();
if (!protect(transport)) {
transport.close();
throw new IOException("transport not protected");
}
transport.connect(serverAddress);ثلاثة تفاصيل تلدغ هنا. أولًا،القيمة المنطقية هي الإشارة الوحيدة التي تحصل عليها، لذا سجّل الفشل بسياق يكفي للتمييز بين موافقة مسحوبة واستدعاء مرفوض. ثانيًا،protect() يغطي المقبس لا رقم الواصف: إذا أغلقت المقبس وأعدت لاحقًا استخدام العدد الصحيح نفسه لمقبس آخر، فالمقبس الجديد غير محمي. ثالثًا، عندما تفتح مكتبة أصلية النقل، احمِ الواصف قبل أن تتصل الشيفرة الأصلية، وإلا فقد يكون المسار قد اختير بالفعل.مرجع VpnService هو المكان الذي تتأكد فيه من الأحمال الزائدة التي يوفرها مستوى API الهدف فعليًا.
اختبر المسار المحجوب، لا المسار السعيد فقط
تحقق مع تشغيل النفق، لأنه الحالة الوحيدة التي يمكن أن يختلف فيها المساران. أرسل طلبًا عبر النقل المحمي وآخر من مسار شيفرة لم يستدعِprotect() قط، ثم قارن ما يراه الطرف البعيد: يجب أن يصل الاتصال المحمي والشبكة الأساسية مصدرًا له، بينما يُظهر غير المحمي عنوان النفق نفسه ويحرّك عدادات بايتات النفق. على الجهاز، يعرض ss أو netstat التقسيم نفسه قبل أن يخرج أي شيء من الراديو.
اختبر السحب بشكل منفصل، لأن تغيير الموافقة يهدم الواجهة دون أن يمس شيفرة المقبس لديك. اسحب موافقة الـ VPN أو أوقف الخدمة، وأعد بناء النقل، وتأكد من أن protect() ما زال يعيد true وأن النقل يعيد الاتصال بمجرد منح الموافقة مرة أخرى.
إذا كان الطلب المحمي يتبع النفق على أي حال، فاستدعاء الحماية يحدث على الأرجح بعد أن يكون المقبس قد وُجّه. انقله إلى وقت أبكر واختبر مرة أخرى.
أبقِ القرار مكتوبًا بجوار إعداد النقل: أي المقابس محمية، وأي حركة تطبيق مستثناة، وما يفعله التطبيق عند فشل الحماية. هذا السطر هو ما يمنع إعادة هيكلة لاحقة من توسيع الاستثناء إلى التطبيق كله لأن اختبارًا واحدًا كان يفشل. وهو يمنح الشخص التالي طريقة لفحص النية دون هندسة عكسية لملف البيان.