VPN du Mac connecté mais ip-up jamais déclenché : mettre en place une surveillance launchd
Les tunnels Cisco IPSec et IKEv2 ne démarrent jamais pppd, donc /etc/ppp/ip-up reste muet. Surveillez le répertoire de configuration et faites prouver le tunnel au script.
Vous placez une commande route dans /etc/ppp/ip-up, vous reconnectez le tunnel du bureau, et le fichier ne s’exécute jamais. Le script n’a rien de fautif : cette connexion ne passe pas du tout par PPP. Les clients intégrés Cisco IPSec et IKEv2 ne démarrent jamais pppd, donc le répertoire des hooks n’est jamais lu, tandis que PPTP et L2TP sur IPSec y passent encore. Déterminez dans quel cas vous êtes, puis déclenchez le cas non-PPP depuis une tâche de surveillance launchd.
Vérifiez l’interface avant de modifier le moindre hook
Deux commandes tranchent quel mécanisme peut fonctionner. Listez les services configurés, puis listez les interfaces qui existent pendant que le tunnel est actif. Le nom de l’interface est la preuve ; l’étiquette dans Réglages Système ne l’est pas.
scutil --nc list
ifconfig -l | tr ' ' '\n' | grep -E '^(utun|ppp)[0-9]+$'
Un service qui produit ppp0 exécute les hooks PPP et exécutera votre fichier. Un service Cisco IPSec ou IKEv2 produit à la place un périphérique utun, une interface de tunnel sans couche PPP, donc rien dans /etc/ppp ne peut l’atteindre. Quand les deux types apparaissent, vérifiez à quel service vous vous êtes connecté. Les gens perdent un après-midi sur le mauvais hook en lisant le nom du client.
Sur le chemin PPP, le fichier doit appartenir à root et être exécutable, car pppd vérifie les deux avant de lancer quoi que ce soit. install fixe propriétaire et mode en une seule étape.
sudo install -o root -g wheel -m 0755 /dev/null /etc/ppp/ip-up
sudo ls -l /etc/ppp/ip-up
Un fichier créé via le Finder ou un éditeur graphique finit souvent propriété de votre compte, ou sans le bit d’exécution, puis échoue aussi silencieusement que le hook manquant. Placez ip-down à côté dès maintenant. root et wheel dans la colonne propriétaire plus rwxr-xr-x dans la colonne mode, voilà ce que vous voulez.
Laissez launchd déclencher le script quand un tunnel apparaît
Pour les clients qui ne touchent jamais à PPP, le déclencheur doit venir d’ailleurs, et le magasin de configuration système est la source honnête : macOS le réécrit à chaque transition de tunnel. Une tâche launchd qui surveille ce répertoire est appelée quand un tunnel apparaît ou disparaît.
<?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>
Installez-le comme démon launch, pas comme agent par utilisateur, car la réparation des routes appartient à la machine. bootstrap remplace l’ancienne forme load -w sur les versions actuelles. RunAtLoad rend la première exécution observable, et ThrottleInterval empêche une rafale de changements de connexion Wi-Fi de lancer le script à répétition.
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
Le piège est que le répertoire surveillé change pour des événements sans rapport avec votre VPN : une connexion Wi-Fi, un renouvellement DHCP, un câble branché. Le garde-fou ci-dessous transforme un déclencheur bruyant en hook utilisable, et une ligne de journal explique une route à une heure étrange.
La propriété couvre le plist autant que le script. Un démon qui exécute un fichier dans un répertoire où votre propre compte peut écrire donne root à ce compte.
Protégez le script pour qu’une seconde exécution ne change rien
Le script décide d’abord si le tunnel existe et sort silencieusement quand ce n’est pas le cas. Tout ce qui suit doit pouvoir être répété sans risque, car le déclencheur se déclenche bien plus souvent qu’une seule connexion. Résolvez l’interface à l’exécution au lieu de coder en dur un index qui change à la connexion suivante.
#!/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 sort avec un statut non nul quand la route existe déjà, donc un script qui traite chaque statut non nul comme un échec signale un hook cassé chaque fois qu’il fait ce qu’il faut. Le test avec netstat ci-dessus transforme ce cas en no-op explicite.
La présence de l’interface est une preuve plus faible qu’elle n’y paraît, car d’autres clients VPN et certains environnements de conteneurs créent aussi des périphériques utun. Interrogez le service lui-même avec scutil --nc status "Office VPN" et exigez l’état connecté avant de toucher au routage. Gardez la réponse dans une ligne de journal, car une semaine plus tard cet enregistrement sera la seule explication dont vous disposerez.
Récupérez la passerelle sans casser le DNS
Quand le tunnel prend la route par défaut, la navigation ordinaire sort par le bureau et le chemin internet local cesse de fonctionner. Capturez d’abord le routeur de l’interface physique, puis renvoyez la route par défaut vers lui. DHCP bat l’analyse d’une table qui contient déjà la route par défaut du tunnel.
PHYS=en0
GW=$(ipconfig getoption "$PHYS" router)
[ -n "$GW" ] && sudo route -n change default "$GW"
Les résolveurs sont une couche distincte avec un outil distinct, et réparer la route ne les répare pas. Regardez ce qui est en vigueur avec scutil --dns, puis corrigez ce qui le produit : l’ordre des services et les entrées DNS du service physique. Vider le cache diagnostique une résolution bloquée après un changement de réseau ; ce n’est pas à planifier.
Un tunnel qui survit à un changement de Wi-Fi pendant que le DNS cale est une troisième panne : sudo killall -HUP mDNSResponder force le résolveur à relire sa configuration. Utilisez-le une fois, à la main.
Prouvez que la tâche se déclenche d’elle-même
Forcez une exécution avec kickstart -k avant de faire confiance au chemin de surveillance, puis lisez le statut de sortie enregistré par la tâche. Une sortie propre prouve que le script fonctionne ; elle ne prouve pas que le déclencheur fonctionne. Puis déconnectez et reconnectez avec la tâche chargée, et guettez une exécution que vous n’avez pas demandée.
sudo launchctl kickstart -k system/net.example.tunnel-watch
sudo launchctl print system/net.example.tunnel-watch | grep -E 'last exit code|state ='
Vérifiez scutil --nc status avant et après chaque tentative. Un script exécuté alors que le tunnel était coupé ne prouve rien sur le chemin de connexion.
La branche de démontage mérite le même traitement que la branche de connexion : retirez la route à la main, laissez la tâche s’exécuter et confirmez qu’elle revient. Un hook qui ajoute une route et ne la retire jamais laisse une machine qui se comporte mal une semaine plus tard, sans rien qui relie les deux événements. Quand les deux comportements tiennent, sudo launchctl bootout system/net.example.tunnel-watch retire la tâche proprement. Un démon de surveillance laissé chargé est la façon dont une réparation ponctuelle devient une route inexpliquée des mois plus tard.