تقسيم النفق في Android VpnService: ما يمكنك توجيهه فعلاً
لا يوجّه VpnService إلا الحزم التي يرسلها Builder إلى واصفه. تحقّق من البادئات ومصادقة الوسيط وDNS وقوائم التطبيقات قبل لوم تقسيم النفق.
يتعطّل تقسيم النفق في تطبيق VpnService في مواضع محدّدة قليلة، ويختلف الإصلاح لكل موضع. لا يعرض العميل إلا الوجهة الفاشلة، لذا فأول مهمة هي تحديد الحدّ الذي تصطدم به: المسارات التي تلقّاها Builder، أو الوسيط الذي لا تستطيع مصادقته، أو المُحلِّل الذي اكتفيت بالإعلان عنه، أو قائمة التطبيقات التي تطبّقها المنصّة لكل مستخدم. لهذا السبب بالضبط أحتفظ بفحص من أربع خطوات. يعمل بالترتيب أدناه ويتوقّف عند أول فشل.
تأكّد مما سلّمه Builder إلى المنصّة
VpnService.Builder قائمة تصريحات. تُرجع establish() كائن ParcelFileDescriptor لواجهة افتراضية، ولا ترسل المنصّة إلى ذلك الواصف إلا حركة المرور المطابقة للبادئات المضافة عبر addAddress وaddRoute؛ ويخبر addDnsServer العملاء أي مُحلِّل يستعلمون. الوجهة الواقعة خارج كل بادئة أضفتها لا تصل إلى تطبيقك أبداً، مهما وعدت الواجهة. هذه الحقيقة الواحدة تفسّر معظم التقارير عن تقسيم نفق معطّل قبل أي تغيير في الشيفرة.
سجّل قوائم العناوين والمسارات وDNS التي جمعها Builder، ثم قارنها بالوجهات الفاشلة. بادئة /32 ناقصة لمضيف إداري أكثر شيوعاً بكثير من نفق معطّل. فإن كانت القائمة صحيحة فامضِ قدماً، وإن كانت خاطئة فما بقي من القائمة هدر للجهد.
excludeRoute موجود فقط من API 33
الاستثناء القائم على الوجهة هو الطلب الذي يتحوّل غالباً إلى سلسلة دعم. وصل builder.excludeRoute(IpPrefix) في API 33؛ وقبله كان التقريب الوحيد المدعوم هو إضافة مسارات لكل شيء عدا البادئة المستثناة، أي أن تحسب البادئات المكمّلة بنفسك. الاستدعاءان ليسا صورة مرآة: أحدهما يطرح من مجموعة مسارات الواجهة، والآخر يبني المجموعة من الصفر، والجهاز الأقدم سيرمي استدعاءً لا يعرفه أو يتجاهله بصمت. تفرّع حسب Build.VERSION.SDK_INT واختبر المسارين على عتاد حقيقي.
عند حساب المكمّلات، راقب محاذاة البادئات. استثناء /24 داخل مسار /16 يحتاج البادئات المغطّية التي تصف الباقي فعلاً، لا بادئة /8 مشوّهة واحدة. سجّل القائمة النهائية وتأكّد أن المضيف المستثنى ما زال يُحلّ عبر مسار خارج النفق.
بيانات اعتماد الوسيط لا يمكن التعبير عنها
يوجّه setHttpProxy(ProxyInfo) الواجهة إلى وسيط HTTP أو SOCKS، ويمكن لعنوان PAC أن يختار بينهما لكل وجهة. لا ProxyInfo ولا صيغة PAC تحمل اسم مستخدم وكلمة مرور، لذا يبقى الوسيط الذي يطلب مصادقة خارج واجهة المنصّة. التطبيقات التي تحتاجه تضمّ عادةً مكدّساً في فضاء المستخدم، أو tun2socks أو محركاً مشابهاً، وتجيب بنفسها عن تحدّي 407.
لهذا الاختيار تبعات. مكدّس فضاء المستخدم لا يرى إلا الحركة التي توجّهها إليه، لذا يجب أن يعيش داخل حدود الواصف نفسه، ويرث المطلب نفسه الخاص بـ protect() لمقبس قناته الصاعدة. إن أهملت protect() التقط النفق ناقله الخاص، ويظهر ذلك كمصافحة تتوقّف بعد الحزمة الأولى. اختبر مقابل وسيط يطلب بيانات اعتماد، لأن الوسيط المفتوح ينجح على مسار المنصّة ولا يثبت شيئاً.
قوائم التطبيقات وDNS تصريحات أيضاً
يملك Builder كلاً من addAllowedApplication وaddDisallowedApplication، وهما متعارضان: استدعاء الاثنين يرمي استثناءً. وفي كلتا الحالتين تُطبَّق القائمة لكل مستخدم عند إعداد الواجهة، لذا يخضع تطبيق ملف العمل لنفق ملف العمل لا لنفقك. وما لا يمنحه أي API هو التحكّم في قرار تطبيق آخر: لا يمكنك دفع حزمة إلى نفقك من الخارج، ولا يمكنك إغلاق VPN نشط أو إعادة تكوينه.
يتبع DNS النمط نفسه. يعلن addDnsServer عنوان مُحلِّل؛ ولا يتيح لتطبيقك الإجابة عن الاستعلامات. إن أردت حلّ الأسماء بنفسك، فشغّل مُحلِّلاً ووجّه عنوانه إلى النفق. التطبيقات التي تستخدم DoT أو DoH إلى اسم مضيف ثابت يمكنها تجاوز العنوان الذي أعلنت عنه، فاعتبر حلّ الأسماء جهداً أفضل ما يمكن ما لم تعترض مسار الاستعلام أيضاً.
البيان والتشغيل الدائم واختبار الخطوات الأربع
تُعلَن الأهلية في البيان: BIND_VPN_SERVICE، ومرشّح النية android.net.VpnService، وخدمة أمامية إن كان على النفق أن يبقى في الخلفية. ويطلب VPN الدائم إضافةً إلى ذلك onRevoke نظيفاً، لأن النظام يستدعيه عندما يغيّر المستخدم الإعدادات أو يستولي تطبيق آخر على التحكّم.
نفّذ الفحوص بالترتيب وتوقّف عند أول فشل. أولاً، أفرغ قوائم العناوين والمسارات وDNS التي مُرّرت إلى Builder. ثانياً، تأكّد أن establish() أرجع واصفاً وأن مقبس النقل محمي. ثالثاً، اطلب وجهة داخل بادئة مضمّنة وأخرى داخل بادئة مستثناة. رابعاً، تحقّق من المُحلِّل الذي أجاب فعلاً باستعلام اسم مضيف لا يستطيع حلّه سوى مُحلِّلك.
المصافحة المكتملة لا تثبت أي خطوة لاحقة. كل فحص يعزل طبقة واحدة، لذا فنجاح في الخطوة الثانية لا يقول شيئاً عن الخطوة الرابعة.
يسرد مرجع VpnService.Builder كامل سطح Builder. وكل ما تحتاجه بعده هو API آخر أو تطبيق آخر أو مكدّسك في فضاء المستخدم، وذلك قرار تصميم لا خلل في تقسيم النفق.