/etc/ppp/ip-up 에 route 명령을 넣고 사무실 터널을 다시 연결해도 파일은 결코 실행되지 않습니다. 스크립트에는 문제가 없습니다. 그 연결은 PPP를 전혀 거치지 않기 때문입니다. 내장 Cisco IPSec 및 IKEv2 클라이언트는 pppd 을 실행하지 않으므로 hook 디렉터리는 읽히지 않습니다. 반면 PPTP와 IPSec 위의 L2TP 는 여전히 그곳을 지납니다. 어느 경우인지 판별한 다음 PPP가 아닌 쪽을 launchd 감시 작업에서 트리거하세요.

hook을 편집하기 전에 인터페이스를 확인하라

두 명령이 어떤 방식이 동작할 수 있는지 결정합니다. 구성된 서비스를 나열한 다음 터널이 올라와 있는 동안 존재하는 인터페이스를 나열하세요. 증거는 인터페이스 이름이며 시스템 설정의 레이블이 아닙니다.

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

ppp0 을 만드는 서비스는 PPP hook을 실행하고 당신의 파일을 실행합니다. Cisco IPSec 또는 IKEv2 서비스는 대신 utun 장치를 만듭니다. PPP 계층이 없는 터널 인터페이스이므로 /etc/ppp 안의 무엇도 거기에 닿을 수 없습니다. 두 종류가 모두 보이면 어느 서비스에 연결했는지 확인하세요. 사람들은 클라이언트 이름을 읽고 잘못된 hook에서 오후를 낭비합니다.

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나 그래픽 편집기로 만든 파일은 종종 당신의 계정 소유가 되거나 실행 비트가 없어, 사라진 hook만큼 조용히 실패합니다. 지금 그 옆에 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

함정은 감시 대상 디렉터리가 VPN과 무관한 사건으로도 바뀐다는 점입니다. Wi-Fi 연결, DHCP 갱신, 케이블 연결 등입니다. 아래 가드는 시끄러운 트리거를 쓸 만한 hook으로 바꾸고, 로그 한 줄이 이상한 시각의 경로를 설명합니다.

소유권은 스크립트뿐 아니라 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 은 경로가 이미 있으면 영이 아닌 상태로 종료하므로, 영이 아닌 모든 상태를 실패로 보는 스크립트는 옳은 일을 할 때마다 고장 난 hook을 보고합니다. 위의 netstat 테스트는 그 경우를 명시적 no-op으로 바꿉니다.

인터페이스의 존재는 보이는 것보다 약한 증거입니다. 다른 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 을 확인하세요. 터널이 내려간 동안 실행된 스크립트는 연결 경로에 대해 아무것도 증명하지 않습니다.

해체 분기는 연결 분기와 같은 대우를 받아야 합니다. 경로를 손으로 제거하고, 작업을 실행하게 두고, 다시 돌아오는지 확인하세요. 경로를 추가하고 결코 제거하지 않는 hook은 일주일 뒤 잘못 동작하는 기계를 남기며, 두 사건을 연결하는 것은 아무것도 없습니다. 두 동작이 모두 유지되면 sudo launchctl bootout system/net.example.tunnel-watch 이 작업을 깔끔하게 퇴역시킵니다. 로드된 채로 남겨 둔 감시 데몬은 일회성 수리가 몇 달 뒤 설명되지 않는 경로가 되는 길입니다.

관련 글