Mac-VPN verbonden maar ip-up nooit uitgevoerd: zet een launchd-wacht op
Cisco IPSec- en IKEv2-tunnels starten nooit pppd, dus /etc/ppp/ip-up blijft stil. Houd de configuratiemap in de gaten en laat het script de tunnel bewijzen.
Je zet een route-opdracht in /etc/ppp/ip-up, verbindt de kantoortunnel opnieuw, en het bestand wordt nooit uitgevoerd. Aan het script mankeert niets: die verbinding loopt helemaal niet via PPP. De ingebouwde Cisco IPSec- en IKEv2-clients starten nooit pppd, dus de hook-map wordt nooit gelezen, terwijl PPTP en L2TP over IPSec er nog wel doorheen gaan. Bepaal welk geval je hebt en activeer dan het niet-PPP-geval vanuit een launchd-wachtbaan.
Controleer de interface voordat je een hook aanpast
Twee opdrachten beslissen welk mechanisme kan werken. Noem de geconfigureerde services op en noem daarna de interfaces die bestaan terwijl de tunnel omhoog staat. De interfacenaam is het bewijs; het label in Systeeminstellingen niet.
scutil --nc list
ifconfig -l | tr ' ' '\n' | grep -E '^(utun|ppp)[0-9]+$'
Een service die ppp0 oplevert, voert de PPP-hooks uit en zal je bestand uitvoeren. Een Cisco IPSec- of IKEv2-service levert in plaats daarvan een utun-apparaat op, een tunnelinterface zonder PPP-laag, dus niets in /etc/ppp kan die bereiken. Als beide soorten opduiken, controleer met welke service je verbonden was. Mensen verliezen een middag aan de verkeerde hook door de naam van de client te lezen.
Op het PPP-pad moet het bestand eigendom zijn van root en uitvoerbaar zijn, omdat pppd beide controleert voordat er iets draait. install stelt eigenaar en modus in één stap in.
sudo install -o root -g wheel -m 0755 /dev/null /etc/ppp/ip-up
sudo ls -l /etc/ppp/ip-up
Een bestand dat via Finder of een grafische editor is gemaakt, is vaak eigendom van je account of mist het uitvoerbit, en faalt dan net zo stil als de ontbrekende hook. Zet nu ip-down ernaast. root en wheel in de eigenaarskolom plus rwxr-xr-x in de moduskolom is wat je wilt.
Laat launchd het script afvuren wanneer een tunnel verschijnt
Voor clients die PPP nooit aanraken, moet de trigger ergens anders vandaan komen, en het systeemconfiguratiearchief is de eerlijke bron: macOS herschrijft het bij elke tunnelovergang. Een launchd-taak die die map bewaakt, wordt aangeroepen wanneer een tunnel verschijnt of verdwijnt.
<?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>
Installeer het als launch-daemon, niet als agent per gebruiker, want routeherstel hoort bij de machine. bootstrap vervangt de oudere vorm load -w op huidige releases. RunAtLoad maakt de eerste uitvoering waarneembaar, en ThrottleInterval voorkomt dat een reeks Wi-Fi-verbindingswijzigingen het script steeds opnieuw start.
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
De valkuil is dat de bewaakte map ook verandert bij gebeurtenissen die niets met je VPN te maken hebben: een Wi-Fi-verbinding, een DHCP-vernieuwing, een ingeplugde kabel. De wachter hieronder maakt van een luidruchtige trigger een bruikbare hook, en een logregel verklaart een route op een vreemd uur.
Eigendom dekt de plist net zo goed als het script. Een daemon die een bestand uitvoert in een map waarin je eigen account kan schrijven, geeft dat account root.
Beveilig het script zodat een tweede run niets verandert
Het script beslist eerst of de tunnel bestaat, en stopt stil wanneer dat niet zo is. Alles daarna moet veilig te herhalen zijn, want de trigger gaat veel vaker af dan één verbinding. Los de interface tijdens de run op in plaats van een index hard te coderen die bij de volgende verbinding verandert.
#!/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 eindigt met een niet-nulstatus wanneer de route al bestaat, dus een script dat elke niet-nulstatus als fout behandelt, meldt elke keer een kapotte hook wanneer het het juiste doet. De netstat-test hierboven maakt dat geval een expliciete no-op.
De aanwezigheid van de interface is zwakker bewijs dan het lijkt, omdat andere VPN-clients en sommige containeromgevingen ook utun-apparaten maken. Vraag de service zelf met scutil --nc status "Office VPN" en eis de verbonden toestand voordat je aan routing komt. Bewaar het antwoord in een logregel, want een week later is die registratie de enige verklaring die je hebt.
Haal de gateway terug zonder DNS te breken
Wanneer de tunnel de standaardroute overneemt, gaat gewoon browsen via kantoor naar buiten en stopt het lokale internetpad met werken. Leg eerst de router van de fysieke interface vast en wijs de standaardroute daarna weer daarheen. DHCP verslaat het parsen van een tabel die al de eigen standaard van de tunnel bevat.
PHYS=en0
GW=$(ipconfig getoption "$PHYS" router)
[ -n "$GW" ] && sudo route -n change default "$GW"
Resolvers zijn een aparte laag met een apart gereedschap, en het repareren van de route repareert ze niet. Kijk met scutil --dns wat geldt en herstel dan wat het veroorzaakt: de servicevolgorde en de DNS-vermeldingen op de fysieke service. De cache leegmaken diagnoseert een opzoeking die na een netwerkwissel vastloopt; het is niets om te plannen.
Een tunnel die een Wi-Fi-wissel overleeft terwijl DNS stokt, is een derde fout: sudo killall -HUP mDNSResponder laat de resolver zijn configuratie opnieuw lezen. Gebruik het één keer, met de hand.
Bewijs dat de taak vanzelf afgaat
Forceer één run met kickstart -k voordat je het wachtpad vertrouwt, en lees daarna de exitstatus die de taak heeft vastgelegd. Een schone exit bewijst dat het script werkt; het bewijst niet dat de trigger werkt. Verbreek daarna de verbinding en verbind opnieuw met de taak geladen, en let op een run die je niet hebt gevraagd.
sudo launchctl kickstart -k system/net.example.tunnel-watch
sudo launchctl print system/net.example.tunnel-watch | grep -E 'last exit code|state ='
Controleer scutil --nc status voor en na elke poging. Een script dat draaide terwijl de tunnel neerlag, bewijst niets over het verbindingspad.
De afbreektak verdient dezelfde behandeling als de verbindingstak: verwijder de route met de hand, laat de taak lopen en bevestig dat die terugkomt. Een hook die een route toevoegt en nooit verwijdert, laat een machine achter die een week later verkeerd doet, zonder dat iets de twee gebeurtenissen verbindt. Als beide gedragingen standhouden, neemt sudo launchctl bootout system/net.example.tunnel-watch de taak netjes uit dienst. Een geladen achtergebleven wachtdaemon is de manier waarop een eenmalige reparatie maanden later een onverklaarde route wordt.