Skip to content
SlippinDylan
Go back

在公司和家之间来回跑:用脚本让 Mac 自动管理代理

问题

我平时带着 MacBook 在公司和家之间来回跑。

公司得靠 ClashMeta X 跑在本机做代理才能访问外网。回到家之后,OpenWrt 路由器上跑着 Mihomo,在网关层做了透明代理,整个局域网的流量都被接管,连上 Wi-Fi 就直接能用,本机什么都不用开。

两套环境,两种状态,每天切换两次。

如果回家忘了关 ClashMeta X,本机代理和路由器透明代理会同时生效,两层 NAT 叠在一起,轻则延迟翻倍,重则连接直接超时。最初的解法是手动切:回家关,出门开。坚持了大概三天。


识别「我在哪」

要自动化,首先要解决的问题是:当前连的是哪个网络?

方案一:Wi-Fi SSID

读当前连接的 SSID,如果是家里的名字就判定在家。

问题在于 SSID 不唯一。咖啡馆、同事家、邻居,都可能有一样的名字。而且路由器换了 SSID 改了,脚本就失效了。

方案二:网关 IP

家里路由器的 LAN IP 通常固定,比如 192.168.10.1

192.168.1.1 是全球用量最高的路由器默认地址,几乎每个网络都可能用这个。单凭 IP 判断,误判的概率不低。

最终方案:网关 IP + MAC 地址双重验证

MAC 地址烧录在网卡硬件里,理论上全球唯一,不会随网络环境变化。只要当前网关的 IP 和 MAC 同时匹配,就可以高置信度地确认这是家里的网络。


MAC 地址怎么拿

网关 IP 好拿,读系统路由表就行。MAC 地址需要走 ARP。

ARP 是局域网里把 IP 映射到 MAC 的协议,查询结果会缓存在本地 ARP 表里。arp -n <网关IP> 就能取到对应的 MAC。

但这里有个坑:ARP 缓存可能是过期的。

刚切换网络时,ARP 表里可能还留着上一个网络的记录。直接读,拿到的是旧的 MAC,判断就错了。

正确的做法是:先 arp -d 清掉这个 IP 的缓存,再发几个 ping 强制触发新的 ARP 请求,然后再读。多一步,但拿到的是真实结果。


什么时候触发

触发时机比逻辑本身更值得想清楚。

最朴素的方案是定时轮询,每几秒检查一次。能跑,但会持续占用资源,对 Mac 是不必要的消耗。

更好的时机是:网络刚发生变化的那一刻。

macOS 切换网络时,系统会更新 DNS 配置文件 /var/run/resolv.conf。这个文件变化,意味着网络环境切换了。

macOS 的 LaunchAgent 提供了 WatchPaths 配置项,可以监听指定路径的文件变动,文件一旦被修改,系统自动拉起对应的程序。用它监听 /var/run/resolv.conf,空闲时零消耗,切换时精准触发。


怎么关应用

确认在家之后,需要把 ClashMeta X 关掉。

直接 kill 进程最省事,但粗暴。某些代理软件被强杀后,下次启动可能状态异常。

更好的方式是模拟正常退出。macOS 上可以用 AppleScript 向应用发退出指令:

tell application "ClashMetaX" to quit

效果等同于用户点菜单里的「退出」。如果这一步失败(应用无响应),备用方案是用 System Events 模拟 Command + Q。两层降级,覆盖绝大多数情况。

flowchart TD
    A[需要退出应用] --> B[AppleScript\ntell app to quit]
    B --> C{成功?}
    C -->|是| D[完成]
    C -->|否| E[System Events\n模拟 Command+Q]
    E --> F{成功?}
    F -->|是| D
    F -->|否| G[记录失败\n放弃]

启动方向也有对称的检查:先 pgrep -x 确认应用没有在跑,没有才 open -a 启动,避免同一个应用开多个实例。


收尾:DNS 刷新

切换完成后还有一步容易漏掉:刷新 DNS 缓存

系统 DNS 缓存里可能还留着旧网络解析出来的 IP。带着旧缓存进新网络,某些域名会解析到错误的地址。主动清掉:

sudo dscacheutil -flushcache
sudo killall -HUP mDNSResponder

不论切到家里还是切出去,这一步都执行。


完整流程

flowchart TD
    A[网络切换\n/var/run/resolv.conf 变化] --> B[LaunchAgent 唤起脚本]
    B --> C[刷新 ARP 缓存\n触发新的 ARP 请求]
    C --> D[读取当前网关\nIP + MAC]
    D --> E{与家庭网关匹配?\nIP + MAC 双重验证}
    E -->|匹配 - 在家| F[关闭 ClashMeta X\n关闭 Tailscale]
    E -->|不匹配 - 其他地方| G[确保 ClashMeta X\n正在运行]
    F --> H[刷新 DNS 缓存]
    G --> H
    H --> I[完成]

整个过程全自动,网络切换后几秒内完成。


最后

这个脚本解决的问题很小,但每天都会遇到。整个实现过程里细节不少:ARP 缓存的时序问题、应用退出的兼容性、网关信息获取的容错处理……都是跑起来发现问题再修的。

到目前为止没再因为忘记切换代理导致网络出问题。

脚本已开源:SlippinDylan/ahaMoment - router-monitor


Share this post on:

Previous Post
平台型业务里,钱的每一步都是一个设计决策
Next Post
macOS Monterey 下,如何制作 Debian 11 的 U 盘启动盘