Mac-VPN verbunden, aber ip-up lief nie: einen launchd-Watch einrichten
Cisco IPSec und IKEv2 starten niemals pppd, deshalb bleibt /etc/ppp/ip-up stumm. Beobachte das Konfigurationsverzeichnis und lass das Skript den Tunnel beweisen.
Du trägst einen route-Befehl in /etc/ppp/ip-up ein, verbindest den Büro-Tunnel neu, und die Datei wird nie ausgeführt. Mit dem Skript ist nichts falsch: Diese Verbindung läuft überhaupt nicht über PPP. Die integrierten Cisco-IPSec- und IKEv2-Clients starten nie pppd, deshalb wird das Hook-Verzeichnis nie gelesen, während PPTP und L2TP über IPSec weiterhin hindurchlaufen. Finde heraus, welcher Fall bei dir vorliegt, und löse dann den Nicht-PPP-Fall über einen launchd-Watch-Job aus.
Prüfe die Schnittstelle, bevor du einen Hook bearbeitest
Zwei Befehle klären, welcher Mechanismus funktionieren kann. Liste die konfigurierten Dienste auf, dann die Schnittstellen, die existieren, während der Tunnel steht. Der Schnittstellenname ist der Beweis; die Bezeichnung in den Systemeinstellungen ist es nicht.
scutil --nc list
ifconfig -l | tr ' ' '\n' | grep -E '^(utun|ppp)[0-9]+$'
Ein Dienst, der ppp0 erzeugt, führt die PPP-Hooks aus und wird deine Datei ausführen. Ein Cisco-IPSec- oder IKEv2-Dienst erzeugt stattdessen ein utun-Gerät, eine Tunnelschnittstelle ohne PPP-Schicht, sodass nichts in /etc/ppp sie erreichen kann. Wenn beide Arten auftauchen, prüfe, mit welchem Dienst du verbunden bist. Viele verlieren einen Nachmittag am falschen Hook, weil sie den Client-Namen lesen.
Auf dem PPP-Pfad muss die Datei root gehören und ausführbar sein, weil pppd beides prüft, bevor irgendetwas läuft. install setzt Eigentümer und Modus in einem Schritt.
sudo install -o root -g wheel -m 0755 /dev/null /etc/ppp/ip-up
sudo ls -l /etc/ppp/ip-up
Eine Datei, die über den Finder oder einen grafischen Editor entsteht, gehört oft deinem Konto oder hat kein Ausführungsbit und scheitert dann genauso still wie der fehlende Hook. Lege jetzt ip-down daneben. root und wheel in der Eigentümerspalte plus rwxr-xr-x in der Modusspalte sind das, was du willst.
Lass launchd das Skript auslösen, wenn ein Tunnel erscheint
Bei Clients, die PPP nie berühren, muss der Auslöser von woanders kommen, und der Systemkonfigurationsspeicher ist die ehrliche Quelle: macOS schreibt ihn bei jedem Tunnelwechsel neu. Ein launchd-Job, der dieses Verzeichnis beobachtet, wird gerufen, wenn ein Tunnel erscheint oder verschwindet.
<?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>
Installiere ihn als Launch-Daemon, nicht als Agent pro Benutzer, denn Routenreparatur gehört zur Maschine. bootstrap ersetzt in aktuellen Versionen die ältere Form load -w. RunAtLoad macht die erste Ausführung beobachtbar, und ThrottleInterval verhindert, dass eine Serie von Wi-Fi-Beitritten das Skript wiederholt startet.
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
Die Falle ist, dass sich das beobachtete Verzeichnis auch bei Ereignissen ohne Bezug zu deinem VPN ändert: ein Wi-Fi-Beitritt, eine DHCP-Erneuerung, ein eingestecktes Kabel. Die Wache unten macht aus einem lauten Auslöser einen brauchbaren Hook, und eine Logzeile erklärt eine Route zur ungewöhnlichen Stunde.
Die Eigentümerschaft umfasst die plist ebenso wie das Skript. Ein Daemon, der eine Datei in einem Verzeichnis ausführt, das dein eigenes Konto schreiben kann, übergibt diesem Konto root.
Sichere das Skript ab, damit ein zweiter Lauf nichts ändert
Das Skript entscheidet zuerst, ob der Tunnel existiert, und beendet sich still, wenn nicht. Alles danach muss gefahrlos wiederholbar sein, denn der Auslöser feuert viel öfter als eine einzelne Verbindung. Löse die Schnittstelle zur Laufzeit auf, statt einen Index fest zu verdrahten, der sich beim nächsten Verbinden ändert.
#!/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 endet mit einem Nonzero-Status, wenn die Route schon vorhanden ist, sodass ein Skript, das jeden Nonzero-Status als Fehler behandelt, jedes Mal einen kaputten Hook meldet, wenn es das Richtige tut. Der netstat-Test oben macht aus diesem Fall ein ausdrückliches No-op.
Die Anwesenheit der Schnittstelle ist schwächere Evidenz, als sie aussieht, denn andere VPN-Clients und manche Container-Runtimes erzeugen ebenfalls utun-Geräte. Frage den Dienst selbst mit scutil --nc status "Office VPN" und verlange den verbundenen Zustand, bevor du das Routing anfasst. Halte die Antwort in einer Logzeile fest, denn eine Woche später ist dieser Eintrag die einzige Erklärung, die du hast.
Hol das Gateway zurück, ohne DNS zu brechen
Wenn der Tunnel die Standardroute übernimmt, verlässt das normale Surfen das Büro, und der lokale Internetpfad funktioniert nicht mehr. Erfasse zuerst den Router der physischen Schnittstelle, dann richte die Standardroute wieder darauf. DHCP ist besser als das Parsen einer Tabelle, die bereits den eigenen Default des Tunnels enthält.
PHYS=en0
GW=$(ipconfig getoption "$PHYS" router)
[ -n "$GW" ] && sudo route -n change default "$GW"
Resolver sind eine eigene Schicht mit einem eigenen Werkzeug, und die Route zu reparieren repariert sie nicht. Schau mit scutil --dns, was gerade gilt, und behebe dann, was es erzeugt: die Dienstreihenfolge und die DNS-Einträge am physischen Dienst. Den Cache zu leeren diagnostiziert eine Suche, die nach einem Netzwerkwechsel hängt; es ist nichts, was man einplanen sollte.
Ein Tunnel, der einen Wi-Fi-Wechsel übersteht, während DNS stockt, ist ein dritter Fehler: sudo killall -HUP mDNSResponder bringt den Resolver dazu, seine Konfiguration neu zu lesen. Verwende es einmal, von Hand.
Beweise, dass der Job von selbst feuert
Erzwinge einen Lauf mit kickstart -k, bevor du dem Watch-Pfad traust, und lies dann den Exit-Status, den der Job aufgezeichnet hat. Ein sauberer Exit beweist, dass das Skript funktioniert; er beweist nicht, dass der Auslöser funktioniert. Trenne dann die Verbindung und verbinde neu, während der Job geladen ist, und achte auf einen Lauf, den du nicht angefordert hast.
sudo launchctl kickstart -k system/net.example.tunnel-watch
sudo launchctl print system/net.example.tunnel-watch | grep -E 'last exit code|state ='
Prüfe scutil --nc status vor und nach jedem Versuch. Ein Skript, das lief, während der Tunnel unten war, beweist nichts über den Verbindungspfad.
Der Abbauzweig verdient dieselbe Behandlung wie der Verbindungszweig: Entferne die Route von Hand, lass den Job laufen und bestätige, dass sie zurückkommt. Ein Hook, der eine Route hinzufügt und nie entfernt, hinterlässt eine Maschine, die sich eine Woche später falsch verhält, ohne dass etwas die beiden Ereignisse verbindet. Wenn beide Verhaltensweisen halten, zieht sudo launchctl bootout system/net.example.tunnel-watch den Job sauber zurück. Ein geladener Watch-Daemon ist der Weg, auf dem eine einmalige Reparatur Monate später zu einer unerklärten Route wird.