أصلح حركة Docker وWSL التي تتجاهل VPN المضيف
تحتفظ الحاويات وWSL بمساراتها ومُحلِّل الأسماء ومسار التمرير الخاص بها، لذا يتركها نفق المضيف العامل مع DNS قديم أو دون منفذ خروج. أصلح كل طبقة.
قضيت معظم صباح هذا الربيع في مطاردة حاوية لم تستطع تحليل اسم داخلي واحد بينما كان المتصفح المجاور لها يفتح الويكي دون شكوى. كان النفق قائمًا، وأثبته المتصفح، وظلت الحاوية في فضاء أسماء شبكة مختلف بجدول مسارات مختلف ومُحلِّل مختلف. ما يلي هو الترتيب الذي أتحقق به الآن: جداول التوجيه أولًا، ثم التحليل، ثم التمرير، ثم منصة المضيف أخيرًا. توقف عند أول خطوة تفشل بالفعل، لأن إضافة قواعد أسفل المكدس تخفي الحد بدلًا من نقله.
قارن جداول المسارات الثلاثة قبل تغيير أي قاعدة
الحاوية على جسر Docker الافتراضي لديها مسار افتراضي واحد يشير إلى بوابة docker0، ولا فكرة لديها بوجود tun0. لدى المضيف tun0، والمسارات التي دفعها VPN، وربما قاعدة سياسة ترسل حركة الفحص عبر واجهة مختلفة عما تتوقع. يضيف WSL جدولًا ثالثًا خلف محوّله الافتراضي الخاص. اطبع الثلاثة قبل تعديل أي منها.
| Context | Command | What a wrong answer looks like |
|---|---|---|
| Host | ip route get 10.8.0.20 | Leaves a physical interface instead of tun0 |
| Container | ip route | Only 172.17.0.0/16 via 172.17.0.1 |
| Container DNS | cat /etc/resolv.conf | A public resolver, or an upstream the tunnel never pushed |
| WSL | ip addr; ip route | Only the WSL subnet, no Windows VPN adapter |
إذا كان المسار الوحيد للحاوية هو الجسر ولم يظهر tun0 أبدًا في فضاء أسمائها، فلن يساعد أي شيء أدناه: لا تغيير في خادم الأسماء ينشئ مسارًا، ولا مسار ينشئ هوية نفق يقبلها النظير. الترتيب مهم لأن فشل DNS وفشل المسار يبدوان متطابقين من داخل الصدفة.
وجّه المُحلِّل إلى النفق بدلًا من مُحلِّل عام
على المضيف، resolvectl status يسرد خوادم DNS لكل وصلة، و readlink -f /etc/resolv.conf يخبرك ما إذا كان الملف هو كعب systemd، أو ملف NetworkManager، أو شيئًا أعاد عميل أحد الموردين كتابته. داخل حاوية على شبكة يحددها المستخدم، /etc/resolv.conf يسمّي 127.0.0.11، مُحلِّل Docker المدمج، الذي يمرّر إلى أي مصدر أعلى تمت تهيئة الخادم به. أما الحاوية على الجسر الافتراضي فقد حصلت عند إنشائها على نسخة من ملف المضيف، وهي تحتفظ بها.
تلك النسخة هي سبب أن النفق الذي يظهر بعد بدء الحاوية يترك الحاوية تستعلم المصدر الأعلى القديم. إما أن تمرّر مُحلِّل VPN صراحةً باستخدام docker run --dns 10.8.0.53، أو تضبط "dns": ["10.8.0.53"] في /etc/docker/daemon.json ثم تعيد تشغيل الخادم، أو تعيد إنشاء الحاوية بعد أن يصبح النفق قائمًا. إعادة الإنشاء عادةً أرخص من تصحيح مُحلِّل كان صحيحًا قبل ساعة.
استبدال المُحلِّل بمُحلِّل عام هو الإصلاح الذي يبقي العَرَض. إنه يحل الإنترنت ويفشل في كل منطقة خاصة، ويمكنه أن يتجاوز بهدوء التصفية التي وُجد النفق لفرضها. إذا حاولت الكتابة داخل /etc/resolv.conf لحاوية قيد التشغيل، فتوقّع Device or resource busy: Docker يربط ذلك الملف ربطًا مباشرًا، لذا يحتاج التعديل إلى فك تركيب أولًا. ضبط --dns عند الإنشاء هو التسلسل الذي ينجو من إعادة التشغيل.
مرّر وترجم عندما تستعير الحاويات النفق
مع الجسر الافتراضي، يكون المضيف موجّهًا بين docker0 وtun0، وهو لا يؤدي أيًا من المهمتين افتراضيًا. فعّل التمرير، وترجم عنوان المصدر حتى يرى النظير عنوان نفق للمضيف بدلًا من عنوان لا مسار عودة إليه (استبدل tun0 بما يسميه عميلك واجهته):
sysctl -w net.ipv4.ip_forward=1
iptables -t nat -A POSTROUTING -o tun0 -j MASQUERADE
iptables -A FORWARD -i docker0 -o tun0 -j ACCEPT
iptables -A FORWARD -i tun0 -o docker0 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPTقواعد FORWARD تهم على المضيفين الذين تكون سياسة التمرير لديهم DROP. يدرج Docker سلاسله الخاصة، لذا اختبر بحزمة بدلًا من قراءة iptables -S: من الحاوية، ip route get 10.8.0.20 ينبغي أن يسمّي بوابة الجسر، و nc -vz 10.8.0.20 443 ينبغي أن يتصل أو ينتهي مهلة عند النفق بدلًا من المُحلِّل.
بدون MASQUERADE يحتاج النظير إلى مسار عودة إلى 172.17.0.0/16، وهو ما لن تدفعه معظم شبكات VPN للوصول عن بعد. هنا أيضًا ينكسر المخطط مع عميل قائم على السياسات: إذا كان لا يلتقط إلا حركة وحدات UID أو cgroups محددة، فلن ترى قواعد التمرير لديك الحزمة داخل النفق أبدًا، والإصلاح ينتمي إلى سياسة التوجيه في العميل لا إلى iptables. القواعد المضافة يدويًا تزول عند إعادة التشغيل، لذا ضعها في وحدة systemd أو جدول netfilter-persistent وأعد الاختبار بعد إعادة التشغيل، لأن نفقًا يظهر متأخرًا قد يمسحها.
ضع النفق داخل الحاوية عندما يكون العزل هو الهدف
إذا كان على حاوية واحدة أن تخرج عبر عقدة محددة، شغّل العميل في تلك الحاوية. إنها تحتاج --cap-add NET_ADMIN و --device /dev/net/tun، وعندئذ تملك tun0 ومساراتها وجدار حمايتها. وإذا مرّت حاويات أخرى عبرها، فإنها تحتاج أيضًا إلى تمرير وقاعدة MASQUERADE داخل الحاوية، ويحتاج أولئك النظيرون إلى مسار صريح أو وكيل يشير إليها؛ فجسر Docker الافتراضي لا يضيف ذلك المسار نيابة عنك.
شبكة المضيف، docker run --network host، هي النسخة الرخيصة: تنضم الحاوية إلى فضاء أسماء المضيف وترى مسارات النفق مباشرة. وهي أيضًا تُسقِط عزل الشبكة، وتدعو إلى تعارض المنافذ، وهي خيار Linux عمليًا. والمنفذ المنشور ليس بديلًا. -p 8080:80 يجعل خدمة قابلة للوصول من المضيف؛ ولا يجعل العناوين الداخلية 10.x قابلة للوصول من داخل الحاوية، والخلط بين الأمرين هو كيف يتحول مشكل توجيه إلى منفذ قاعدة بيانات مكشوف.
ميِّز فشل الأسماء عن شبكة فرعية متداخلة
جسر Docker الافتراضي هو 172.17.0.0/16، وكثير من تجمّعات VPN المؤسسية تقع داخل 172.16.0.0/12. وبمجرد أن يملك النفق ذلك النطاق، يمكن توجيه الحركة المقصودة لحاوية إلى النفق بدلًا من ذلك. تفشل الأسماء، ويبدو الخطأ كخطأ DNS، والمشكلة هي العناوين. افحص الوجهة لا المُحلِّل: ip route get 172.17.0.5 على المضيف، ثم قارن بادئة الجسر بتجمّع VPN وبـ docker network inspect bridge --format '{{range .IPAM.Config}}{{.Subnet}}{{end}}'.
الإصلاح هو نقل أحد النطاقين، إما تجمّع عناوين افتراضي مختلف لـ Docker في daemon.json أو تجمّع مختلف على المُجمِّع. إضافة خادم أسماء آخر لا تفعل شيئًا هنا، وكذلك لا يفعل ssh -b: ربط العميل بواجهة يغيّر عنوان المصدر، لا مسار وجهة يشير إلى النفق. تقسيم سريع للعمل: الاسم الذي يُحل على المضيف لكن ليس في الحاوية مشكلة مُحلِّل، بينما اتصال IP مباشر يتعلق في الحاوية لكنه يعمل على المضيف مشكلة مسار أو سلسلة تمرير.
WSL وDocker Desktop لا يتبعان قواعد Linux
في وضع NAT الافتراضي، يقف WSL خلف محوّله الافتراضي ولا يتلقى المسارات الداخلية أو لواحق DNS الخاصة بـ VPN في Windows، ولهذا يعمل المتصفح و ping داخل التوزيعة لا يعمل. الشبكة المتطابقة، المتاحة في إصدارات Windows 11 الحديثة، تشارك واجهات المضيف بدلًا من ذلك، لكنها يجب أن تُفعَّل في .wslconfig ويجب إعادة تشغيل التوزيعة بواسطة wsl --shutdown. تأكد من الوضع النشط باستخدام wslinfo --networking-mode قبل تطبيق أي إصلاح، لأن إجابة NAT خاطئة لوضع mirrored والعكس صحيح.
يضيف Docker Desktop طبقة أخرى. حاويات Linux على Windows تقف خلف تنفيذ NAT يُسمى تاريخيًا vpnkit، ومسارها يختلف عن مضيف Linux أصلي. حاويات Windows تتصل عبر مبدّل افتراضي من Hyper-V، ويمكن لعميل مؤسسي أن يحجب الحركة من ذلك المبدّل، لذا قد تصل حاويات Linux على الجهاز نفسه إلى الشبكة بينما لا تملك حاويات Windows شيئًا. المبدّل الخارجي المرتبط بالمحوّل الفيزيائي هو الحل المعتاد، وهو يغيّر هوية شبكة الحاوية بطرق قد لا تسمح بها السياسة.
قبل أن تكتب قاعدة أخرى، امشِ الطبقات الأربع بالترتيب وتوقف حيث تكون إحداها خاطئة:
- داخل الحاوية: هل يصل
ip routeإلى البادئة الخاصة، وهل يسمّي/etc/resolv.confمصدرًا أعلى يحلها؟ - على المضيف: هل التمرير مفعّل، وهل NAT مرتبط بواجهة النفق، وهل تقبل سلسلة التمرير الانتقال من الجسر إلى النفق؟
- على النظير: هل يعرف مسار عودة، أم أنك تعتمد على NAT لإخفاء الشبكة الفرعية للحاوية؟
- على المنصة: هل WSL في وضع NAT أم mirrored، وهل الحاوية Linux أم Windows؟
أعد تشغيل كل اختبار في حاوية أو صدفة جديدة. العملية طويلة العمر التي لديها إجابة مخزّنة أو اتصال قائم ستظل تفشل بعد أن تصبح التهيئة صحيحة، وذلك الفشل يبدو تمامًا كإصلاح لم يعمل.