Claude Code 的公网 IP 防护:代理、LuLu 与网关规则自动维护

以外网代理和 LuLu 网关阻止规则分层限制 Claude Code 获取真实公网出口 IP,并用后台任务维护切网后的规则,记录实现、故障恢复与防护边界。


我给 Claude Code 设计了一套分层的网络防护:外网请求默认交给代理,访问当前物理网关的连接由 LuLu 阻止,再用后台任务处理切换网络后的规则更新。目标是减少程序获取真实公网出口 IP 的途径,同时保留正常的代理联网能力。

这里记录的是我的设计和已核对的本地实现,不代表已经发现 Claude Code 在探测或上报真实 IP。目前没有这样的抓包或日志证据。

先明确要保护的是什么

我关心的“真实 IP”,是这台 Mac 不经过代理访问互联网时,对外呈现的公网出口 IP。它与本机的局域网地址、路由器的管理地址、代理服务器的出口地址是不同的概念。在家庭宽带或运营商 NAT 环境中,这个公网出口也未必由 Mac 独占。

本机局域网 IP 可以通过系统网络接口直接读取,不需要请求路由器,LuLu 的网关阻止规则也管不到这种本地读取。公网出口 IP 则需要区分下面两条获取路径。

两条路径,两层限制

第一条是询问外部服务。例如请求 https://api.ipify.org,服务返回的是这一次请求到达服务器时的公网来源 IP。请求经过代理,返回的就是代理出口;请求直连,返回的就是当前网络的公网出口。这里决定结果的是实际连接路径,而不是程序是否配置过代理。

因此,我的第一层设计是让外网请求走代理。但“默认代理”还不能等同于“禁止直连”:系统代理或环境变量可能不被程序遵循,分流规则可能包含 DIRECT,IPv6、UDP 或代理故障回退也需要单独确认。如果要保证外部查 IP 请求无法获得真实出口,就必须让相关流量只能经代理发送,代理不可用时连接失败。本次只记录这个设计条件,没有完成所有流量类型和故障情形的验证。

第二条是直接询问局域网路由器。部分路由器提供 UPnP 的 GetExternalIPAddress,或 NAT-PMP 的外部地址查询。后者向网关 UDP 5351 端口发送查询,可以得到网关报告的外部 IPv4 地址。这条路径无需访问外部查 IP 网站,因此外网走代理也不自动消除它。

查询结果仍取决于网络结构:如果路由器 WAN 口直接持有公网地址,结果可能就是实际公网出口;如果上游还有光猫 NAT 或运营商 CGNAT,返回的可能只是上游内网地址。我的网关是否开启了这些服务,本次没有进行探测验证。

LuLu 具体阻止什么

第二层规则限定三个条件:程序是 Claude Code,目标地址是当前选中的物理 IPv4 网关,目标端口是 *,动作为 Block。本文用 192.168.5.1 举例;这个地址需要随所在网络变化,不能永久写死。

访问网关与经过网关转发是两件事。连接 192.168.5.1 上的服务时,目标就是网关;访问 ipify 等外部服务时,即便数据包经过路由器转发,连接目标仍然是外部服务器。所以这条规则用于限制 Claude 直接查询网关,不能代替外网代理策略,也不是把 Claude 的全部上网流量一并阻断。

换网络后,规则如何自动跟随

这部分由本地工具 LuLu Gateway Guard 完成。launchd 在登录时和此后每 15 秒启动任务,原生 Swift 宿主承接界面操作权限,Python 负责检测与决策,AppleScript 操作 LuLu 的规则窗口。

1. 识别物理网关。通过 scutil 读取 macOS 网络服务,选取 en0、en1 等接口的 IPv4 Router;排除隧道接口、回环、未指定、多播和 198.18.0.0/15 地址。优先选择主网络服务,其次参考系统服务顺序。

2. 检查现有状态。state.json 保存已管理规则的地址和 UUID。任务按 Claude 的代码签名身份读取 LuLu 规则;如果网关未变化、已有规则仍有效,本轮结束。首次初始化只接管已有的当前网关阻止规则,没有可接管规则时会报错。

3. 先建立新规则。网关变化后,解析 /opt/homebrew/bin/claude 的实际程序路径,通过 LuLu 界面新增针对新网关、全部端口的阻止规则。

4. 确认保存,再清理旧规则。重新读取 LuLu 规则文件,确认新规则已持久化,记录新旧 UUID 的过渡状态,再删除旧的受管理规则并核对删除结果。若清理中断,后续任务可以继续处理。

任务读取 LuLu 的私有规则文件进行核验,但不直接修改数据库。它也不会启动 Claude。这样的选择把写入交给 LuLu 自己处理,代价是依赖 LuLu 的界面、语言、辅助功能权限和登录会话;切网时可能会把规则窗口带到前台。

失败时保留什么,哪些情况不会强行覆盖

没有可用物理 IPv4 网关时,任务保留旧规则。锁屏、LuLu 未运行、权限不足或界面操作失败会使更新延迟;更新和清理失败通常设置 5 分钟重试间隔,避免每 15 秒反复打开窗口。LuLu 被停用或切换到非默认配置时,任务拒绝修改。

如果用户手动删除、停用或修改了管理中的规则,任务会报错而不是强行恢复;如果新网关已经有符合条件的阻止规则,也会停止本次切换,留待人工核对。这样可以减少自动维护误删用户规则的机会。先确认新规则再删除旧规则,能够保留已有保护,但不能消除切网到新规则生效之间的时间窗口。

我如何判断这套设计是否生效

截至 2026 年 10 月 10 日,本地只读核对确认:后台任务已加载,检测到的网关、state.json 和 LuLu 中对应 Claude 的阻止规则一致,目标端口为全部端口。已有日志记录过界面增删测试成功,也出现过锁屏和删除确认失败。这证明了规则维护的实现与部分运行记录,不等于验证了持续、完整的网络隔离。

要进一步验证,应分别观察目标进程及其子进程的代理命中记录、IPv4/IPv6 实际出口、UDP 处理、代理故障行为,以及切网后新旧规则的替换过程。浏览器查 IP 的结果只能证明浏览器那一次请求的路径,不能替 Claude Code 或它启动的 curl、脚本和其他子进程作证。

当前自动化只维护一个选中的物理 IPv4 网关和 Claude 对应程序的规则,不覆盖所有子进程、所有局域网设备或 IPv6 网关。这里采用的是减少两类查询途径的分层防护;若要抵御具有广泛本地执行权限的程序主动绕过限制,还需要更完整的进程与网络隔离。

参考资料

UPnP WANIPConnection:ExternalIPAddress 与 GetExternalIPAddress

本地实现:~/.local/share/lulu-gateway-guard/ 下的 guard.py、Host.swift、launcher.applescript 与 rules.applescript;后台配置为 com.wenren.lulu-gateway-guard.plist。