لا يملك VpnService في Android أي مسار استبعاد، ولا يستطيع أي قدر من قراءة واجهة البرمجة أن ينتج واحدًا۔ Builder.addRoute() لا يضيف سوى وجهات إلى النفق، لذا فإن إبقاء مقطع شبكة محلية يعني أن ذلك المقطع لا يدخل قائمة المسارات إطلاقًا۔ ما تضيفه بدلًا من ذلك هو مكمّل المقطع، معبَّرًا عنه ببادئات عادية۔ العمل حسابي، وهو يفشل في عدد قليل من المواضع المتوقعة۔

اتبع الفحوص أدناه بالترتيب۔ كل فحص يفترض نجاح السابق، لذا توقف عند أول فشل بدلًا من ترقيع الأعراض في الأسفل۔

ابدأ مما يدّعيه النفق

تمرير 0.0.0.0/0 إلى addRoute() يخبر Android بأن كل وجهة IPv4 تنتمي إلى النفق۔ لهذا تبدو عملاء كثيرون غير قادرين على النفق المنقسم: يطالبون بمساحة العناوين كاملة ثم يحاولون الطرح منها۔ لا شيء في الباني يطرح۔ المسار الأكثر تحديدًا للشبكة التي أردت إبقاءها محلية يفعل عكس ما يتوقعه الناس، لأن البادئة الأطول تطابقًا تفوز وتلك الشبكة تهبط داخل النفق۔

قائمة المسارات هي السياسة كلها۔ لا يوجد حق نقض لكل وجهة خلفها، ومرجع Builder لا يسرد أي دالة توفره۔

ولّد المكمّل من حساب البادئات

مكمّل بادئة مستبعدة هو مجموعة أشقائها على الطريق إلى جذر شجرة العناوين۔ لكل طول من 1 حتى الطول المستبعد، خذ الكتلة ذات الطول نفسه التي تحتوي بادئتك، واقلب البت الذي يلي البادئة مباشرة، وأخرج النتيجة۔ يبدو مجردًا حتى تشغّله۔

استبعاد 10.0.0.0/8 ينتج ثمانية مسارات: 128.0.0.0/1، 64.0.0.0/2، 32.0.0.0/3، 16.0.0.0/4، 0.0.0.0/5، 12.0.0.0/6، 8.0.0.0/7، 11.0.0.0/8۔ لا يحتوي أي منها على 10.0.0.0/8، ومعًا تغطي كل ما تبقّى۔

ثمانية أسطر تولّد تلك القائمة لأي بادئة:

import ipaddress
def complement(addr, length):
    base = int(ipaddress.IPv4Address(addr))
    out = []
    for k in range(1, length + 1):
        block = base & (0xFFFFFFFF << (32 - k))
        sibling = block ^ (1 << (32 - k))
        out.append(f"{ipaddress.IPv4Address(sibling)}/{k}")
    return out
print(complement("10.0.0.0", 8))

عدد المسارات يساوي طول البادئة، وهذا هو الرقم الذي يجب مراقبته۔ استبعاد /21 واحد يكلّف 21 مسارًا ويبقى مقروءًا۔ عشر عمليات استبعاد قد تكلّف بضع مئات، وهو ما يعني عادة أن تصميم الشبكة يحتاج إلى تغيير لا إلى المولّد۔

افحص عائلة العناوين ونطاق النفق

مسارات IPv4 لا تفعل شيئًا لـIPv6۔ إذا ادّعى النفق عنوان IPv6، فبادئة LAN من IPv6 تحتاج إلى مكمّلها الخاص، مولَّدًا بالحساب نفسه عبر 128 بت۔ من الأسباب الشائعة لنجاح الاستبعاد على الورق بينما يبقى الجهاز المحلي غير قابل للوصول هذا: وُصل إلى المضيف عبر IPv6، ولم يكن مستبعدًا سوى IPv4۔ افحص العائلتين قبل أن تستنتج أن الحساب خاطئ۔

النطاق هو النصف الآخر من الفحص۔ يتتبع Android التوجيه لكل شبكة ولكل تطبيق، لذا قد تكون البادئة نفسها منقولة عبر النفق لتطبيق ومباشرة لتطبيق آخر بحسب كيفية إعلان VPN۔

ضع قواعد DNS وكل تطبيق حيث تنتمي

استبعادات المسارات لا تغيّر المحلّل۔ قد يظل خادم DNS التابع للنفق يجيب عن اسم مضيف محلي بينما يُوجَّه عنوانه محليًا، ويظهر عدم التطابق كاسم يُحلّ إلى شيء لا تستطيع الشبكة المحلية الوصول إليه۔ اضبط سياسة المحلّل عمدًا، بنطاق بحث أو قاعدة DNS منقسمة۔ لا تفترض أن المسارات قررت ذلك عنك۔

addDisallowedApplication() يبقي تطبيقًا واحدًا خارج النفق؛ ولا يبقي وجهة خارجًا للتطبيقات التي تبقى داخله۔ protect() يتيح لمقبس تفتحه تجاوز النفق، وبذلك يتجنب النفق التقاط نقله الخاص۔ لا أي منهما يحل محل المكمّل۔

تحقق ببحث، لا بقائمتك

بعد إقامة النفق، اسأل الجهاز إلى أين يذهب عنوان فعلًا بدلًا من إعادة قراءة القائمة التي بنيتها۔ في بناء يشحن iproute2، يسمّي adb shell ip route get 10.1.2.3 الواجهة التي اختارها النواة۔ نفّذ الأمر نفسه لعنوان عام وأكّد أنه يسمّي النفق۔

فحصان آخران يغلقان الحلقة۔ حلّل اسمًا محليًا أولًا، ثم نفّذ adb shell ip route get على العنوان المحلول واطلب واجهة ليست النفق، لأن adb shell ping -c 1 host.lan الناجح يثبت الوصول لا المسار۔ كرر اختبار عائلة العناوين عبر IPv6 إذا ادّعى النفق واحدة۔ اختبر عند مستوى API الذي تشحنه، لأن طريقة عرض شبكات VPN لجدول التوجيه تغيّرت عبر الإصدارات۔ قائمة مسارات تبدو صحيحة على الورق وبحث يختار الواجهة الخاطئة عيبان مختلفان، والثاني وحده يصل إلى المستخدمين۔

إذا استمرت قائمة الاستبعاد في النمو، توقف عن توليد المسارات وأعد ترقيم الشبكة البعيدة أو ضيِّق النفق بدلًا من ذلك۔ المولّد حلٌّ مؤقت لواجهة البرمجة الغائبة، لا أداة تصميم۔

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