Pui o comandă route în /etc/ppp/ip-up, reconectezi tunelul biroului, iar fișierul nu rulează niciodată. Scriptul nu are nicio problemă: acea conexiune nu trece deloc prin PPP. Clienții încorporați Cisco IPSec și IKEv2 nu pornesc niciodată pppd, așa că directorul de hook-uri nu este citit niciodată, în timp ce PPTP și L2TP peste IPSec trec încă prin el. Stabilește care este cazul tău, apoi declanșează cazul non-PPP dintr-o sarcină de supraveghere launchd.

Verifică interfața înainte de a edita orice hook

Două comenzi decid ce mecanism poate funcționa. Listează serviciile configurate, apoi listează interfețele care există cât timp tunelul este activ. Numele interfeței este dovada; eticheta din Setările Sistemului nu este.

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

Un serviciu care produce ppp0 rulează hook-urile PPP și va executa fișierul tău. Un serviciu Cisco IPSec sau IKEv2 produce în schimb un dispozitiv utun, o interfață de tunel fără strat PPP, deci nimic din interiorul /etc/ppp nu poate ajunge la el. Când apar ambele tipuri, verifică la ce serviciu te-ai conectat. Oamenii pierd o după-amiază pe hook-ul greșit citind numele clientului.

Pe calea PPP fișierul trebuie să aparțină lui root și să fie executabil, deoarece pppd le verifică pe amândouă înainte de a rula ceva. install setează proprietarul și modul într-un singur pas.

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

Un fișier creat prin Finder sau printr-un editor grafic ajunge des în proprietatea contului tău, sau fără bitul de execuție, și apoi eșuează la fel de silențios ca hook-ul lipsă. Pune acum ip-down lângă el. root și wheel în coloana proprietarului plus rwxr-xr-x în coloana modului este ceea ce vrei.

Lasă launchd să declanșeze scriptul când apare un tunel

Pentru clienții care nu ating niciodată PPP, declanșatorul trebuie să vină de altundeva, iar depozitul de configurare al sistemului este sursa onestă: macOS îl rescrie la fiecare tranziție a tunelului. O sarcină launchd care supraveghează acel director este apelată când un tunel apare sau dispare.

<?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>

Instalează-l ca launch daemon, nu ca agent per utilizator, pentru că repararea rutelor aparține mașinii. bootstrap înlocuiește forma mai veche load -w în versiunile actuale. RunAtLoad face prima execuție observabilă, iar ThrottleInterval oprește un val de modificări de conectare Wi-Fi să lanseze scriptul în mod repetat.

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

Capcana este că directorul supravegheat se schimbă și pentru evenimente fără legătură cu VPN-ul tău: o conectare Wi-Fi, o reînnoire DHCP, un cablu introdus. Gardianul de mai jos transformă un declanșator zgomotos într-un hook utilizabil, iar o linie de jurnal explică o rută la o oră ciudată.

Proprietatea acoperă plist-ul la fel ca scriptul. Un daemon care execută un fișier într-un director în care contul tău poate scrie îi dă acelui cont root.

Protejează scriptul ca o a doua rulare să nu schimbe nimic

Scriptul decide mai întâi dacă tunelul există și iese în tăcere când nu există. Tot ce urmează trebuie să fie sigur de repetat, pentru că declanșatorul pornește mult mai des decât o singură conexiune. Rezolvă interfața la rulare în loc să codifici rigid un index care se schimbă la conexiunea următoare.

#!/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 iese cu status diferit de zero când ruta există deja, așa că un script care tratează fiecare status diferit de zero ca eșec raportează un hook rupt de fiecare dată când face lucrul corect. Testul cu netstat de mai sus transformă acel caz într-un no-op explicit.

Prezența interfeței este o dovadă mai slabă decât pare, pentru că și alți clienți VPN și unele medii de containere creează dispozitive utun. Întreabă serviciul însuși cu scutil --nc status "Office VPN" și cere starea conectată înainte de a atinge rutarea. Ține răspunsul într-o linie de jurnal, pentru că o săptămână mai târziu acea înregistrare va fi singura explicație pe care o ai.

Adu poarta înapoi fără a rupe DNS-ul

Când tunelul preia ruta implicită, navigarea obișnuită iese prin birou și calea locală către internet nu mai funcționează. Capturează mai întâi routerul interfeței fizice, apoi îndreaptă implicitul înapoi spre el. DHCP bate analizarea unui tabel care conține deja implicitul tunelului însuși.

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

Resolverele sunt un strat separat cu un instrument separat, iar repararea rutei nu le repară. Privește cu scutil --dns ce este în vigoare, apoi repară ce produce asta: ordinea serviciilor și intrările DNS de pe serviciul fizic. Golirea cache-ului diagnostichează o căutare blocată după o schimbare de rețea; nu este ceva de programat.

Un tunel care supraviețuiește unei schimbări de Wi-Fi în timp ce DNS-ul se blochează este a treia defecțiune: sudo killall -HUP mDNSResponder face resolverul să recitească configurația. Folosește-l o dată, manual.

Demonstrează că sarcina se declanșează singură

Forțează o rulare cu kickstart -k înainte de a avea încredere în calea de supraveghere, apoi citește statusul de ieșire înregistrat de sarcină. O ieșire curată demonstrează că scriptul funcționează; nu demonstrează că declanșatorul funcționează. Apoi deconectează și reconectează cu sarcina încărcată și urmărește o rulare pe care nu ai cerut-o.

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

Verifică scutil --nc status înainte și după fiecare încercare. Un script care a rulat cât timp tunelul era jos nu demonstrează nimic despre calea de conectare.

Ramura de demontare merită același tratament ca ramura de conectare: scoate ruta manual, lasă sarcina să ruleze și confirmă că revine. Un hook care adaugă o rută și nu o scoate niciodată lasă o mașină care se comportă prost o săptămână mai târziu, fără nimic care să lege cele două evenimente. Când ambele comportamente rezistă, sudo launchctl bootout system/net.example.tunnel-watch retrage sarcina curat. Un daemon de supraveghere lăsat încărcat este modul în care o reparație unică devine o rută neexplicată luni mai târziu.

Articole similare