تضع أمر route في /etc/ppp/ip-up، ثم تعيد الاتصال بنفق المكتب، فلا يُنفَّذ الملف أبدًا. لا شيء خطأ في السكربت: ذلك الاتصال لا يمر عبر PPP إطلاقًا. عملاء Cisco IPSec وIKEv2 المدمجون لا يشغّلون pppd أبدًا، لذا لا يُقرأ مجلد الخطافات قط، بينما PPTP و L2TP عبر IPSec ما زالا يمرّان عبره. حدّد أي حالة تنطبق عليك، ثم أطلق حالة غير PPP من مهمة مراقبة launchd.

تحقّق من الواجهة قبل تعديل أي خطاف

أمران يحسمان أي آلية يمكن أن تعمل. اسرد الخدمات المهيّأة، ثم اسرد الواجهات الموجودة أثناء عمل النفق. اسم الواجهة هو الدليل؛ أما التسمية في إعدادات النظام فليست دليلًا.

scutil --nc list
ifconfig -l | tr ' ' '\n' | grep -E '^(utun|ppp)[0-9]+$'

الخدمة التي تنتج ppp0 تشغّل خطافات PPP وستنفّذ ملفك. أما خدمة Cisco IPSec أو IKEv2 فتنتج جهاز utun بدلًا من ذلك، أي واجهة نفق بلا طبقة PPP، فلا شيء داخل /etc/ppp يستطيع الوصول إليها. وعندما يظهر النوعان، تحقّق من الخدمة التي اتصلت بها. يضيّع الناس بعد الظهر على الخطاف الخاطئ لأنهم يقرؤون اسم العميل.

على مسار PPP يجب أن يكون الملف مملوكًا لـ root وقابلًا للتنفيذ، لأن pppd يتحقق من الأمرين قبل تشغيل أي شيء. و install يضبط المالك والوضع في خطوة واحدة.

sudo install -o root -g wheel -m 0755 /dev/null /etc/ppp/ip-up
sudo ls -l /etc/ppp/ip-up

الملف الذي يُنشأ عبر Finder أو محرر رسومي غالبًا ما ينتهي مملوكًا لحسابك، أو بلا بت التنفيذ، فيفشل بصمت كالخطاف المفقود نفسه. ضع ip-down بجانبه الآن. root و wheel في عمود المالك مع rwxr-xr-x في عمود الوضع هو ما تريده.

دع launchd يشغّل السكربت عند ظهور نفق

بالنسبة للعملاء الذين لا يلمسون PPP أبدًا، يجب أن يأتي المشغّل من مكان آخر، ومخزن إعدادات النظام هو المصدر الصادق: يعيد macOS كتابته عند كل انتقال للنفق. مهمة launchd تراقب ذلك المجلد تُستدعى عند ظهور النفق أو اختفائه.

<?xml version="1.0" encoding="UTF-8"?>
<plist version="1.0">
<dict>
  <key>WatchPaths</key>
  <array><string>/Library/Preferences/SystemConfiguration</string></array>
  <key>Label</key><string>net.example.tunnel-watch</string>
  <key>ProgramArguments</key>
  <array><string>/bin/sh</string><string>/usr/local/sbin/tunnel-hook</string></array>
  <key>RunAtLoad</key><true/>
  <key>ThrottleInterval</key><integer>20</integer>
</dict>
</plist>

ثبّته كخادم launch، لا كعميل لكل مستخدم، لأن إصلاح المسارات يخص الجهاز. و bootstrap يحل محل الصيغة الأقدم load -w في الإصدارات الحالية. و RunAtLoad يجعل التنفيذ الأول قابلًا للملاحظة، و ThrottleInterval يمنع دفعة من تغييرات الانضمام إلى Wi-Fi من تشغيل السكربت مرارًا.

sudo install -o root -g wheel -m 0644 tunnel-watch.plist \
  /Library/LaunchDaemons/net.example.tunnel-watch.plist
sudo launchctl bootstrap system \
  /Library/LaunchDaemons/net.example.tunnel-watch.plist

الفخ هو أن المجلد المراقب يتغير لأحداث لا علاقة لها بشبكتك الخاصة: انضمام إلى Wi-Fi، تجديد DHCP، توصيل كابل. الحارس أدناه يحوّل مشغّلًا صاخبًا إلى خطاف صالح للاستخدام، وسطر سجل يفسّر مسارًا ظهر في ساعة غريبة.

الملكية تشمل ملف plist كما تشمل السكربت. والخادم الذي ينفّذ ملفًا في مجلد يستطيع حسابك الكتابة فيه يمنح ذلك الحساب صلاحية root.

احمِ السكربت حتى لا يغيّر التشغيل الثاني شيئًا

يقرر السكربت أولًا هل النفق موجود، ويخرج بهدوء إذا لم يكن كذلك. وكل ما يأتي بعد ذلك يجب أن يكون آمنًا للتكرار، لأن المشغّل يعمل أكثر بكثير من اتصال واحد. حدّد الواجهة في وقت التشغيل بدل تثبيت فهرس يتغير في الاتصال التالي.

#!/bin/sh
set -u
TUN=$(ifconfig -l | tr ' ' '\n' | grep -m1 '^utun[0-9]' || true)
[ -n "$TUN" ] || exit 0
netstat -rn -f inet | grep -q '^10\.42' && exit 0
route -n add -net 10.42.0.0/16 -interface "$TUN"

route add يخرج بحالة غير صفرية عندما يكون المسار موجودًا بالفعل، لذا فالسكربت الذي يعامل كل حالة غير صفرية كفشل يبلّغ عن خطاف معطوب كلما فعل الشيء الصحيح. واختبار netstat أعلاه يحوّل تلك الحالة إلى لا-عملية صريحة.

وجود الواجهة دليل أضعف مما يبدو، لأن عملاء VPN آخرين وبعض بيئات الحاويات ينشئون أيضًا أجهزة utun. اسأل الخدمة نفسها بـ scutil --nc status "Office VPN" واشترط الحالة المتصلة قبل لمس التوجيه. واحتفظ بالإجابة في سطر سجل، لأن بعد أسبوع سيكون ذلك السجل هو التفسير الوحيد لديك.

أعد البوابة دون كسر DNS

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

PHYS=en0
GW=$(ipconfig getoption "$PHYS" router)
[ -n "$GW" ] && sudo route -n change default "$GW"

المحلّلات طبقة منفصلة بأداة منفصلة، وإصلاح المسار لا يصلحها. انظر إلى ما ساري المفعول بـ scutil --dns، ثم أصلح ما ينتجه: ترتيب الخدمات ومدخلات DNS على الخدمة الفيزيائية. وتفريغ الذاكرة المؤقتة يشخّص بحثًا عالقًا بعد تبديل شبكة؛ وليس شيئًا يُجدول.

النفق الذي يصمد أمام تغيير Wi-Fi بينما يتوقف DNS هو عطل ثالث: sudo killall -HUP mDNSResponder يجعل المحلّل يعيد قراءة إعداده. استخدمه مرة واحدة، يدويًا.

أثبت أن المهمة تنطلق من تلقاء نفسها

أجبر تشغيلًا واحدًا بـ kickstart -k قبل أن تثق بمسار المراقبة، ثم اقرأ حالة الخروج التي سجّلتها المهمة. الخروج النظيف يثبت أن السكربت يعمل؛ ولا يثبت أن المشغّل يعمل. ثم افصل وأعد الاتصال والمهمة محمّلة، وراقب تشغيلًا لم تطلبه.

sudo launchctl kickstart -k system/net.example.tunnel-watch
sudo launchctl print system/net.example.tunnel-watch | grep -E 'last exit code|state ='

تحقّق من scutil --nc status قبل كل محاولة وبعدها. السكربت الذي عمل بينما كان النفق متوقفًا لا يثبت شيئًا عن مسار الاتصال.

فرع الفك يستحق المعاملة نفسها كفرع الاتصال: أزل المسار يدويًا، ودع المهمة تعمل، وأكّد أنه يعود. الخطاف الذي يضيف مسارًا ولا يزيله أبدًا يترك جهازًا يسلك سلوكًا سيئًا بعد أسبوع، دون شيء يربط الحدثين. وعندما يصمد السلوكان، يسحب sudo launchctl bootout system/net.example.tunnel-watch المهمة بنظافة. وإبقاء خادم مراقبة محمّلًا هو الطريق الذي يصبح به إصلاح لمرة واحدة مسارًا غير مفسّر بعد أشهر.

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