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

ما يمنحك النظام إياه، وما لا يمنحه

يعيد establish() واصف ParcelFileDescriptor لواجهة tun. يكتب النظام حزم IP الصادرة فيه ويتوقع من عمليتك قراءتها وتحديد وجهتها وإرسالها عبر مقبس عادي. لا يوجد تمرير في النواة تحت ذلك، فعندما لا يقرأ أحد تُسقط الحزم ولا تتحرك عدادات الواجهة أبدًا.

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

القراءة الحاجزة تحدد ما إذا كانت الحلقة تتحرك

يضع setBlocking(true) الواصف في الوضع الحاجز، وهو موجود منذ API 21. في الإصدارات الأقدم يكون الحل مساعد JNI صغيرًا يمسح O_NONBLOCK بواسطة fcntl على الواصف الخام. الوضع مهم لأنه يحدد ما يُسمح لقراءة بلا حزمة معلقة بفعله.

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

اقرأ حزمة واحدة في كل نداء داخل مخزن بحجم MTU الواجهة، وعامل الصفر أو العائد السالب كخطأ لا كحزمة فارغة.

protect() قبل أول إرسال، لا بعده

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

الترتيب هو protect ثم connect أو bind ثم send. استدعاء protect بعد connect قد يترك قرار التوجيه السابق في مكانه، فيظل المقبس يستخدم المسار الخطأ رغم أن النداء أعاد true. افحص القيمة المنطقية التي يعيدها وافشل بصوت عالٍ عندما تكون false بدل السير إلى إرسال لا يمكن أن يعمل.

خطأ صلاحيات من sendto على مقبس لم يلمس protect() قط هو البصمة المعتادة لهذا الخطأ.

نموذج UDP لا يحمل HTTPS

نموذج التمرير الذي تنسخه معظم الدروس يتعامل مع UDP ولا شيء غيره. يحتاج TCP إلى مصافحة وأرقام تسلسل وإقرارات وإعادة إرسال، والحلقة التي تنسخ الحمولات بين واصف tun ومقبس لا تقدم أيًا منها. لهذا يمر استعلام DNS عبر UDP بينما يتوقف كل طلب HTTPS. إما أن تشغل مكدس TCP في فضاء المستخدم أو تسلم الاتصالات إلى وكيل محلي ينهي TCP على الجهاز ويمرر التدفق.

اختبر الفصل قبل إعادة كتابة أي شيء: أرسل استعلام UDP واحدًا وافتح اتصال TCP واحدًا إلى المضيف نفسه. إن عمل مسار UDP وتعلّق مسار TCP عند أول SYN فالمشكلة في شفرة التمرير لا في الخادم. هذه المقارنة الواحدة توفر وقتًا كبيرًا يُهدر في قراءة سجلات خادم لا تحتوي شيئًا مفيدًا.

قائمة المسارات ومرشحات التطبيقات تحدد ما تراه

يحدد addAddress وaddRoute وaddDns ما يرسله النظام إلى النفق. الوجهة خارج قائمة المسارات لا تصل إلى واصفك أبدًا، فلا توجد كمية من منطق التمرير تصلح ذلك. سجل نداءات الباني التي تجريها وقارنها بالعناوين التي يحاول التطبيق الوصول إليها فعلًا.

لاختيار حسب التطبيق حد أدنى خاص به: يحتاج addAllowedApplication وaddDisallowedApplication إلى API 21، وفي الإصدارات الأقدم تغيب الطريقتان ببساطة. التصميم الذي يرشح حسب الحزمة لا يمكن أن يعمل هناك إطلاقًا.

إعادة الإنشاء تترك واصفات قديمة

استدعاء establish() مرة أخرى يعيد واصفًا جديدًا ويغلق الواجهة السابقة. الواصف القديم أصبح غير صالح، فخيط ما زال محجوزًا عليه يتلقى خطأ، وقد تحتاج المقابس المنشأة للمسار القديم إلى استبدال. onRevoke هو النداء الراجع لنفق انتزعه المستخدم أو النظام. إبقاء دورة الحياة عند مالك واحد، يوقف القارئ قبل الإنشاء من جديد، يمنع معظم حالات المتصل لكن الميت.

اقرأ كل خطأ حسب الخطوة التي ينتمي إليها. إعادة ضبط الاتصال من recvfrom تعني أن الطرف البعيد أغلق بينما شفرتك ما زالت تقرأ. خطأ عنوان سيئ يشير إلى تحليل الترويسة أو خلل إزاحة، لا إلى الشبكة.

ترتيب العمل: وضع حاجز مفعّل، حزمة واحدة لكل قراءة، ترويسة محللة، مقبس محمي، حمولة مرسلة، إغلاق نظيف عند التسليم، وقائمة مسارات تغطي الوجهة.

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