Skip to content

Latest commit

 

History

History
498 lines (400 loc) · 28.8 KB

File metadata and controls

498 lines (400 loc) · 28.8 KB

验证门槛

判断某个变更需要什么证据时,先读这个页面。

OpenSurge for Mac 会修改 host networking,所以验证结论必须严格限定范围。 单元测试、静态检查和 virtual LAN lab 证明的是不同层级的事情。

快速门槛

运行:

make test

这个命令运行 go test ./...,是当前 CI 级别检查。对于不宣称真实 host-network 行为的小型代码、parser、renderer 和单元级变更,通常足够。

如果沙箱阻止 Go cache 写入,把 cache 放到 /private/tmp 下。

Host-network 门槛

运行:

make lab-test

宣称 DHCP、DNS、mihomo 进程或配置生成、pf/NAT 规则、IPv4 forwarding、 rollback、网关生命周期清理、lab 拓扑或 runtime traffic defaults 被真实验证前,应运行这个门槛。

lab 会把 gateway 保持在 macOS 上,并用 socket_vmnet 网络中的 Lima 客户端 测试它。它验证 DHCP、DNS、ICMP/NAT、直连 HTTPS、通过 mihomo mixed-port 的显式代理 HTTPS,以及清理行为。

root-required Lab 目标应在同一个 TTY 里用 sudo -v && make <lab-target> 启动。 macOS sudo 缓存既会过期,也可能因 TTY/执行上下文不同而无法被脚本中的 sudo -n 复用;长时间连续跑多个门禁时,每个目标前都重新验证。除非运行环境明确需要无人值守, 不要把临时凭据问题扩大成宽泛的免密 sudo;仓库提供的可选规则也只限 root-owned network helper 的三个固定子命令,不能代替网关测试所需的 sudo 缓存。 冷 lab-up 可能比 sudo ticket 活得更久,因此它完成后必须再次验证再启动测试;长门禁 结束后的 lab-down 同理。VM 停止但 helper stop 报 sudo: a password is required 时, 应把它视为不完整清理并重新执行带 sudo -vlab-down

Apple Silicon 上的 Codex/agent 终端可能经 Rosetta 运行,使 uname -m 显示 x86_64 并触发安装器架构拒绝。确认 sysctl.proc_translated=1hw.optional.arm64=1 后,用 /usr/bin/arch -arm64 /bin/bash ./tests/lab/install-host-deps.sh 运行安装器;Intel Mac 不能使用这条绕行命令。

第一次 lab-up 包含固定镜像下载和 guest 依赖安装,不能和持久化 VM 的后续启动耗时 直接比较。正常清理使用 lab-down 保留磁盘,只有损坏或有意重建时使用 lab-destroy。 guest 的数据面 DNS 在一次测试后会指向 192.168.50.1;而下一次 lab-up 时被测网关 尚未运行,所以 provisioning 必须先恢复 Lima 控制面 DNS,并在依赖已齐全时跳过 apt, 否则会表现为 UDP/53 connection refused 与很慢的 boot scripts。 冷重建保持串行 provisioning,稳定复用的 VM 则并行启动;这样既不让两个 apt 任务争抢 上游带宽,又避免日常启动累加两次独立 guest boot 时间。 修改 tests/lab/lima/client.yaml 会使 Lima 按精确配置比较删除并重建对应 VM,这是有意的冷启动。VZ 冷启动可能在约两分钟内没有新输出,然后从 vsock SSH 回退到 usernet forwarder 并进入 READY;不要只因为这段静默就杀掉进程。 先看 runtime/tools/lima/bin/limactl list~/.lima/<client>/ha.stderr.log

Lab 环境问题必须与数据面失败分开记录:

  • 启动和清理会把 guest /etc/resolv.conf 恢复到 Lima 控制网关,并保证本机 hostname 可解析。如果 provisioning 报 sudo: unable to resolve host 或仍向已停止的 192.168.50.1 查询,先运行 guest helper 的 restore-control,不要把它算作 IPv6 数据面结果。
  • omg0/etc/systemd/network/05-open-mihomo-gateway-lab.network 单一接管。 networkctl status omg0 应显示该文件;重复 IPv4/IPv6 默认路由通常意味着 netplan 和手工 DHCP/RA 同时在管理接口。IPv6 READY 信号还必须排除 tentative / dadfailed 地址,并要求正的 preferred_lft
  • 自动 RA 模式不保证 /etc/resolv.conf 直接出现 IPv6 nameserver。IPv6 client probe 应在没有显式 IPv6 nameserver 时使用 omg0 IPv6 默认路由的 link-local next hop,并附加接口 scope;空的 HTTP/3 client evidence 通常先检查这一点。
  • quic-go 在最小 guest 中可能报告无法把 UDP receive buffer 增大到建议值。若随后有 CLIENT_IPV6_HTTP3_OK,这是吞吐告警而不是握手失败;这个功能门槛不证明 QUIC 性能。
  • runtime/lab/proxy.env 中不可达的旧代理和专用 /private/tmp Go module cache 残缺都是 patched Mihomo 的构建前故障。脚本会对 Go mirror 绕过旧代理, 并在日志确认是该专用 cache 的缺文件后清理并重试一次。先查 runtime/lab/logs/mihomo-build.log,不要进入数据面调试。
  • agent 沙箱中的 sysctl kern.bootsessionuuid / kern.boottimesysctl.proc_translated: operation not permitted 是执行环境权限信号。需要 host-network 结论时,在已批准的同一 PTY 重跑对应门槛。
  • Lima 停止 VZ 时可能在红色日志中打印 use of closed network connection。 如果后续同时出现 has shut downlab network stopped,这是 hostagent 关闭 listener 后的收尾噪声,不是清理失败。

不要仅凭启动耗时把默认 1 CPU / 512 MiB 判定为不足。先采集 guest 的 available memory、load、CPU idle/iowait 和 OOM 记录;如果 CPU 主要 idle、内存仍可用且没有 OOM, 应优先排查 DNS、下载和重复 provisioning,而不是增加 VM 常驻资源。

策略控制面门槛

运行:

make policy-control-test

这个门槛启动真实 mihomo 二进制和 OpenSurge CLI,但不启动 dnsmasq、pf、TUN, 也不需要 sudo。它用 imported profile fixture 验证 policiespolicy-selectconnectionsprovidersprovider-update 和聚合 snapshot 能通过 live external-controller API 工作,并会重启 mihomo 证明 profile.store-selected 可以 恢复选中的策略。它还验证本机/私网 mixed-port 目标保持 DIRECT、专用 local-routing 控制器协调三种模式、HTTP-only Global 的 UDP fail-closed,以及普通 policy 接口不泄露内部组。它适合策略组控制、file/HTTP provider 状态读取和刷新、 机器可读 CLI、mihomo API wrapper 和 profile.store-selected 相关改动;不要用它 宣称 DHCP、DNS 下发、TUN 透明代理、same-LAN、真实设备路径或真实远端代理出口已验证。

下游 IPv6 门槛

运行:

make lab-test-ipv6-userspace
make lab-test-ipv6-same-wifi
make lab-test-ipv6-same-lan

前两条分别覆盖独立下游 LAN 与同 LAN DHCP 全屋接管:两台客户端必须获得 fdfe:dcba:9878::/64 SLAAC 地址、Medium 优先级 OpenSurge IPv6 默认路由和 RDNSS。 其中 same-WiFi 门槛把第一台客户端配置为 IPv4 主路由绕行,要求设备状态报告 ipv6_blocked、不生成 selector,并让 IPv6 TCP 命中最前置的 packet-listener TUN + InUser REJECT;第二台 客户端仍须继续完成 IPv6 TCP、UDP 与 QUIC carrier 的 DIRECT 路径。 第三条覆盖选择性旁路由:客户端手工使用不同 ULA,并把 Mac link-local 地址同时作为默认网关和 DNS, dnsmasq 配置中不得出现 RA。随后三条门槛都要求无显式代理的 IPv6 TCP、受控 UDP request/response、1200-byte QUIC Initial-shaped UDP carrier 和真实 HTTP/3-only request/response 通过本机 fixture 出现在 patched Mihomo 的 opensurge-packet 路径, 并按两台客户端各自的 MAC/InUser 命中不同设备策略。HTTP/3 client 没有 TCP/HTTP/2 fallback;它分别要求 DIRECT 和受控 SOCKS5 UDP 出口完成 QUIC TLS 与 HTTP/3 GET,并要求 HTTP-only 出口记录 UDP REJECT、origin 与 CONNECT proxy 均无 请求。TCP origin 必须收到实际 HTTP request,UDP fixture 必须返回固定答案,HTTP/3 origin 必须记录 HTTP/3.0,不依赖公网上游作为唯一捕获证据。最后必须停止网关,验证 自动模式的 RA/default route 或旁路由手工配置,以及 gateway alias、broker、 socket/ready file 和 runtime state 被撤销。RFC 4862 允许客户端暂时保留 deprecated/等待过期的 SLAAC 地址,因此门槛不要求地址瞬间消失。

QUIC-shaped 项只证明 UDP carrier 和策略命中;HTTP/3-only fixture 证明的是三个本机 受控出口场景,不等于所有 QUIC/HTTP3 版本、0-RTT、连接迁移、公网节点或代理组合。单元测试或直接 Unix packet injection 可以证明 gVisor 与设备身份,但不能替代 macOS BPF、真实 RA 和 停止撤销的 host-network 证据。

真实订阅和公网 IPv6 出口使用独立的补充门槛:

sudo -v && \
  OMG_LAB_IPV6_REAL_PROFILE=/absolute/path/to/profile.yaml \
  make lab-test-ipv6-imported-egress

它不能取代 lab-test-ipv6-userspace 的受控 origin/UDP fixture。补充门槛把订阅复制到 mode 0600 的 Lab runtime,使用 tun_ipv6: auto 并要求 native_ipv6_available: true / ipv6_reason: native_ipv6_available;先证明一台 VM 的 MAC/InUser 域名 REJECT,再让它完成 IPv6-only HTTPS、IPv6 UDP DNS 回包和 QUIC 形态 UDP 的 DIRECT 命中,并要求 Mac 基线和 VM HTTPS 回显分别属于当前上游接口的 GUA;不同 socket 允许选择不同的 IPv6 隐私地址。之后选择一个不泄露名称、且已通过 SOCKS5 UDP ASSOCIATE 公网 IPv6 DNS 回包的真实叶子节点,让另一台 VM 完成 fake-AAAA HTTPS、真实 IPv6 字面地址 HTTPS、IPv6 UDP DNS 回包和 QUIC 形态 UDP 的 GLOBAL 命中。VM 的 ULA 是真实下游 IPv6 包,但不是运营商委派的 GUA;DIRECT 是 Mihomo/gVisor 从 Mac 发起新 socket,不是 ULA 原样转发。

这个门槛只能输出脱敏 artifact。订阅副本、生成的 Mihomo 配置、原始 Mihomo 日志、 selector/API 输出和 cache 都不得进入 artifact;保留文件必须再接受订阅 server、凭据和 较长节点名 marker 扫描。只有确认网关停止成功后才删除 runtime secret;停止失败时保留 mode 0600 的恢复材料并把门槛判为失败。

真实设备 smoke

虚拟 LAN lab 通过后,真实设备 smoke 的推荐入口是:

make real-device-start-off
make real-device-client-check
make real-device-stop

该入口由 tests/real-device/smoke.sh 提供,会生成本地 runtime/real-device 配置、构建当前 CLI、绑定下游接口、运行 root doctor、 启动 gateway,并做基础 DNS/API/listener 探测。它仍然需要用户在终端里输入一次 sudo,但把 root-required 步骤收敛到一个 sudo 会话;不要把它描述为免密 root helper。

真实设备 smoke 能证明物理客户端接入下游 LAN 后,能从 OpenSurge 获得 DHCP/DNS 并通过 Mac 网关出站。它不替代 make lab-test / make lab-test-tun 作为代码改动的可复现 lab 门槛。

当前进度快照来自 docs/agent-wiki/sources/validation/real-device-smoke.md。 截至 2026-07-06 CST,本轮已经验证 explicit/off runner、TUN runner 和最小 proxy egress runner 可以在物理下游 LAN 启动,真实 Pixel 手机可以获得 192.168.50.100-200 范围租约且 router/DNS 为 192.168.50.1。手机侧无代理 直连 HTTPS/NAT、显式 192.168.50.1:17890 HTTP proxy HTTPS、TUN 模式下无 显式代理 HTTPS,以及本机受控 upstream proxy 命中 open-surge-egress 均已完成 一次 smoke;Mac 侧能对应看到租约、DNS 查询、fake-ip 查询、mihomo.log 中的 客户端目标连接,以及受控代理日志中的 CONNECT example.com:443stop 清理 范围包括释放下游接口上的 192.168.50.1 测试 LAN IP,避免后续 virtual LAN lab 把 192.168.48.0/22 的重叠回程路由选到真实设备接口。

explicit 模式的关键验收信号是:

  • make real-device-status 显示 Gateway: runningDHCP: runningmihomo: runningpf anchor: loadedIP forwarding: enabled
  • leases 中出现真实物理客户端,而不只是备用 AP 或小路由器;
  • dig @192.168.50.1 example.com A 能从 Mac 或下游客户端获得响应;
  • curl --proxy http://192.168.50.1:17890 https://example.com/ 能通过 mihomo mixed-port 访问 HTTPS。

只有运行 make real-device-start-tun 并在客户端无显式代理时观察到成功出站、 fake-ip DNS 响应和 mihomo.log 中的真实客户端目标连接,才可以宣称真实设备 TUN smoke 被验证。

本轮已通过的真实手机检查是:

  • 无代理直连 HTTPS/NAT:手机 Wi-Fi HTTP proxy 关闭,HTTPS 页面成功加载, Mac 侧能看到该手机租约和 DNS 查询。
  • 显式 HTTP 代理 HTTPS:手机 Wi-Fi HTTP proxy 设为 192.168.50.1:17890, HTTPS 页面成功加载;如果日志级别允许,mihomo.log 能看到来自手机 IP 的 HTTPS 连接。
  • TUN 透明模式:make real-device-start-tun 启动后,手机保持无显式代理, HTTPS 页面成功加载,并且 mihomo.log 中出现来自手机 IP 的目标连接。
  • 最小 proxy egress:make real-device-start-tun-proxy 启动后,手机保持无 显式代理,https://example.com/ 成功加载;dnsmasq.log 中出现该手机的 example.com fake-ip 查询,mihomo.log 中出现 example.com:443 match Domain(example.com) using open-surge-egress[real-device-egress],受控本机 HTTP CONNECT proxy 日志中 出现 CONNECT example.com:443

默认生成的 mihomo 配置仍然是 MATCH,DIRECT。已验证的 proxy egress smoke 只证明 最小受控 upstream_proxy 切片能把指定域名交给 open-surge-egress 和本机受控 代理;它不证明订阅规则、远端代理节点、策略组切换或出口 IP 变化。只有换成具有 独立出口的实际代理,并同时看到 mihomo.log 使用 open-surge-egress、受控代理 日志有对应请求、出口 IP 发生预期变化,才可以宣称远端代理出口路径被验证。

same-LAN TUN smoke

同 LAN 默认网关 smoke 的入口是:

make same-lan-start-tun
make same-lan-start-tun-proxy
make same-lan-start-tun-imported-egress
make same-lan-adb-check
make same-lan-adb-check-imported-egress
make same-lan-stop

这个 gate 覆盖 Mac 和一台 Android 测试设备处于同一家庭或办公室 LAN 的场景。 第一版只允许 gateway.mode: "same_lan"dhcp.enabled: falsetransparent.mode: "tun"。OpenSurge 不在主 LAN 上发 DHCP;测试手机需要手动或由 外部 DHCP 配置把默认网关和 DNS 指向 Mac 的 LAN IP。

如果进入“同一个 Wi-Fi 关闭主路由 DHCP、由 OpenSurge 接管 DHCP/DNS”的测试切片, 先阅读 tests/same-lan/WIFI-DHCP-RECOVERY.zh-CN.md。恢复不是附属步骤,而是验收 的一部分:需要证明路由器 DHCP 已恢复、Mac Wi-Fi 回到 DHCP、至少一台客户端能重新 自动获取地址并上网。

这个高风险切片使用独立的 gateway.mode: "same_wifi_dhcp",而不是放宽 same_landhcp.enabled: false 不变量。入口是:

OMG_SAME_WIFI_DHCP_ROUTER_DHCP_DISABLED=confirmed \
OMG_SAME_WIFI_DHCP_PROTECTED_IPS=<static-ip-list> \
make same-wifi-dhcp-start-imported-egress
make same-wifi-dhcp-adb-check-imported-egress
make same-wifi-dhcp-stop

runner 不会改变路由器设置;confirmed 只是要求操作者在手动关闭路由器 DHCP 后明确 确认。它拒绝把 Mac gateway 或 OMG_SAME_WIFI_DHCP_PROTECTED_IPS 中的静态地址放进 租约池。ADB gate 要求 Android 的地址在池内且出现在 omg leases 与 DHCPACK 日志中, 并继续要求 DNS、无显式代理 TUN、provider/update、TunEgress 的 DIRECT 与 egress-proxy 切换,以及受控 CONNECT 证据。stop 检查 OpenSurge runtime/PF/listener/ helper 清理和 IPv4 forwarding 恢复;随后仍必须由操作者恢复路由器 DHCP 和客户端自动 获取。完整 runbook 在 tests/same-lan/WIFI-DHCP-RUNNER.zh-CN.md

Web GUI 允许操作者跳过客户端验收,或在网关停止后保留 Mac 静态 IPv4 并结束状态机; 这些是交互流程的明确 waiver,不是 Lab / same-WiFi gate 的通过证据。凡是宣称 DHCP、DNS、 TUN 客户端路径或停止后自动获取恢复已验收,仍必须收集本节要求的真实证据,不能用 client_validation_skipped / complete_static 代替。

make same-lan-adb-check 是客户端证据入口。它应通过 ADB 采集 Android 默认路由、 DNS 查询、无显式代理探针结果,并回看 Mac 侧 dnsmasq.logmihomo.log。只有 同时看到 Android 默认路由包含 via <mac-lan-ip>、DNS 查询走 Mac、客户端触发目标 连接、mihomo.log 中出现 TUN 路径目标连接,才可以宣称 same-LAN TUN smoke 通过。

基础 same-lan-start-tun gate 不证明主路由 DHCP 全局下发、所有设备兼容、IPv6、 DoH/Private Relay、UDP/QUIC、imported profile 或策略组切换。Android 镜像如果缺少 curlwgetncnslookupdig,应把结果记录为 ADB 客户端探针能力不足, 而不是把人工浏览器页面成功当成完整自动化证据。

same-LAN 的真实代理出口可以先用最小 upstream_proxy 切片验证,不必先导入完整 订阅。2026-07-09 已用 api.ipify.org、Pixel 测试手机和 LAN HTTP 代理完成这一 层:Android 默认路由经 Mac、Android 显式代理为空、dnsmasq.log 看到 Android 源 IP 查询 api.ipify.orgmihomo.log 显示 Domain(api.ipify.org) using open-surge-egress[same-lan-http-egress],Android 浏览器显示的出口 IP 与 Mac 直接使用该 LAN 代理访问 api.ipify.org 的结果一致。

策略组切换需要单独验证。当前自动生成的 open-surge-egress 只有一个代理成员,只能 证明命中代理;要证明切换,应让 group 至少包含 LAN 代理和 DIRECT 两个候选,通过 omg policy-select --config <path> --group <name> --policy <member> 切换选中项后 重复出口 IP 探针。

same-LAN imported provider 策略切换的入口是 make same-lan-start-tun-imported-egressmake same-lan-adb-check-imported-egress。前者渲染 imported profile fixture,并启动本地 HTTP provider/受控 HTTP CONNECT proxy;后者要求 Android 无显式代理路径先命中 TunEgress[DIRECT],再通过 omg policy-select --group TunEgress --policy egress-proxy 切换后命中 TunEgress[egress-proxy],同时受控 proxy 日志出现 CONNECT <host>:443。这个 smoke 证明 same-LAN TUN 下的 imported provider-backed 策略切换可以命中受控本地代理;它不证明完整订阅兼容性或真实远端出口 IP。

2026-07-10 已用一台人工操作的 Android(未使用 ADB)完成这个 gate:手机手动把 网关和 DNS 指向 Mac、保持无显式代理;同一运行期内,example.com:443 先记录为 TunEgress[DIRECT] 且受控 proxy 日志为空,切换后记录为 TunEgress[egress-proxy] 且出现 CONNECT example.com:443。两次浏览器访问均成功, 随后 make same-lan-stop 恢复了 PF、IPv4 forwarding、监听端口和 runtime state。

生成的 imported profile 路径相对 runtime/same-lan/config-tun.yaml 解析,必须写为 ./mihomo-profile.imported-tun-egress.yaml。同时,helper 必须同时监听本地 HTTP provider 与受控 CONNECT proxy 两个端口才可开始客户端探针。

透明代理门槛

运行:

make lab-test-tun

宣称透明代理路径被验证前,应运行这个门槛。它启用 transparent.mode: "tun",保持客户端没有显式代理配置,并要求 HTTPS 请求 出现在 mihomo TUN 路径中。

运行前确认:

  • sudo -v 和 lab target 在同一个终端/TTY 里连续执行。sudo ticket 不是跨 agent exec 会话可靠共享的状态。
  • 192.168.50.1 只配置在当前 lab bridge 上;virtual LAN 使用 /22,真实设备 smoke 默认仍使用 /24。如果 en7 等接口残留 192.168.50.1/24,macOS 可能把 重叠范围内的 lab client 回程路由到 错误接口,表现为 TUN DNS timeout。先运行 make real-device-stop 或删除重复 地址。

当前重要验收信号:

  • 客户端不依赖显式代理配置;
  • 客户端 helper 运行 transparent 测试路径;
  • mihomo.log 中出现透明 HTTPS 目标,例如 --> example.com:443
  • 成功时输出 transparent TUN log observed for example.com:443
  • gateway 被停止,runtime/lab/state.json 被移除;
  • artifacts 写入 artifacts/lab

make lab-test-tun 的标准配置保持 local_system_proxy.enabled: false,因此它能证明 TUN 主路径没有回归,但不能证明系统代理协同已应用,也不能证明它解决某个真实 Network Extension 冲突。该兼容层需要额外的真实 Mac 验收:记录原 HTTP/HTTPS/PAC/自动发现状态, 让冲突扩展保持启用,证明 TUN-only 失败、协同开关成功恢复目标应用访问,再停止网关并 确认原状态恢复。只完成 mock networksetup 单元测试时必须明确写为未运行真实兼容验收。

修改 mihomo profile 导入或 OpenSurge gateway overlay 行为时,优先使用:

make lab-test-tun-imported-profile

这个门禁使用 tests/lab/mihomo-profile.imported-tun.yaml 启动 imported profile 配置,并保持规则为 MATCH,DIRECT。它证明 imported profile overlay 可以进入 TUN lab 路径;它不证明外部代理出口或远端节点可用。

验证 imported provider 和策略组切换会影响透明 TUN 出口时,使用:

make lab-test-tun-imported-egress

这个门禁使用 tests/lab/mihomo-profile.imported-tun-egress.yaml,在 lab host 上 启动本地 HTTP provider 和受控 HTTP CONNECT proxy。脚本先让无显式代理客户端通过 TUN 访问测试 HTTPS host,并要求 mihomo.log 显示 using TunEgress[DIRECT] 且受控 proxy 未被使用;随后执行 omg policy-select --group TunEgress --policy egress-proxy 并重复透明请求,要求 mihomo.log 显示 using TunEgress[egress-proxy],同时受控 proxy 日志出现 CONNECT <host>:443。它证明 imported provider-backed 策略选择能 改变透明 TUN egress path;它不证明真实订阅节点、真实远端出口 IP、same-LAN 或真实 设备兼容性。

Lab 中的受控 CONNECT proxy 必须把上游 DNS 查询和 TCP socket 都绑定到真实 upstream interface。否则 proxy 自己的连接会再次进入正在测试的 TUN,或者把 mihomo fake-IP 错误地发到物理接口,产生递归或 TLS timeout,而不是有效的出口切换证据。

Mac 本机模式隔离门槛

运行:

make lab-test-tun-local-routing

当改动 open-surge/mac-* selector、本机 TUN/显式代理身份、规则 / 全局 / 直连语义, 或本机与下游隔离时使用。门槛使用 imported TUN egress fixture 和受控 CONNECT proxy:

  1. Rule 模式下,本机继续进入 TunEgress 网关规则;
  2. Global 模式下,本机 TCP 使用受控代理,而下游客户端仍按 TunEgress[DIRECT]
  3. Direct 模式下,本机保持 DIRECT,而下游客户端仍可按 TunEgress[egress-proxy] 使用受控代理;
  4. HTTP-only Global 出口令本机 UDP 状态明确为 reject
  5. 普通 policies 输出不暴露 open-surge/mac-* 内部组。

该门槛同时要求 mihomo.log 中本机 TUN source 为 198.18.0.1,下游仍保留自己的 LAN IPv4。make testmake web-testmake policy-control-test 都不能替代这条 真实 host-network/TUN 隔离证据。

每设备策略门槛

运行:

make lab-test-tun-device-policy

当改动 MAC 绑定 DHCP reservation、设备路由模式、每设备 selector 或设备规则覆盖的 数据路径时,使用此门槛。它让 bridge 与 DHCP 客户端实际使用 /22,并验证两个 Lima VM 跨第三段获得 192.168.50.101/192.168.51.102 固定 IPv4;先证明 dedicated 设备的 default selector 位于全局 MATCH 之前,再证明 inherit_global 设备没有 default selector 且走全局 MATCH;随后通过 reload 把后者改成 独立模式,验证两台设备可独立选择不同 TUN egress,并验证设备专属 IP REJECT。它还断言 applied policy snapshot/state digest、omg devicespolicy_identity_ready/lease_match 对真实租约成立、desired 文件修改后的 drift, 调用真实 omg reload 后网关回到 running 且 desired/applied digest 收敛,并再次检查 selector 隔离;同时要求设备默认 selector 指向 HTTP-only outbound 时 UDP/443 命中 REJECT fallback 而非 fall through 到全局 MATCH,DIRECT。它证明设备身份、跟随与 独立模式、默认出口、安全 reload、UDP fail-closed 和覆盖规则的真实 LAN/TUN 数据路径。 同一 fixture 还保留一条旧 LAN 带 MAC 登记,要求完整 desired 仍在,而 compiled/applied 设备列表、dnsmasq、Mihomo IPv4 规则/selector 和 IPv6 MAC 身份均排除它。 门槛还会让两台客户端各建立一条持久连接;切换第一台设备 selector 后,通过真实 Control API 刷新该设备连接。第一台设备的旧连接必须消失,第二台设备连接必须保留,第一台设备 随后建立的新连接必须使用当前 selector。

大型 rule-provider、模板与 domain/IP/protocol/port 组合只改变配置编译时, make test 提供相应覆盖;不需要为每条操作者定义的规则运行 Lab。系统 TUN 的设备 身份边界是 MAC 绑定 IPv4 DHCP reservation 加 IPv4 SRC-IP-CIDR;下游 IPv6 则由 packet path 把观察到的 source MAC 映射为 patched Mihomo 的 IN-USER(device:<id>)。 后者是路由归属,不是防 MAC spoofing 认证;upstream_router 设备只用它命中最高优先级 IPv6 REJECT

2026-07-11 已在 P1-1..P1-5 修复(commit 7b14586)后运行此门槛并通过:两个 VM 拿到 .101/.102 固定租约且 omg devices identity 就绪,UDP 192.168.50.101 -> 1.1.1.1:443 命中设备 REJECT fallback,两设备 selector 独立 切换,设备级域名 REJECT 生效,stop 后 state.json 清除。artifacts 在 artifacts/lab/20260711-194621。ARP/ICMP reservation 冲突探测只在 same_wifi_dhcp 模式激活,此 lab(same_lan/tun)未在运行时覆盖该路径,由单元测试覆盖。

真实 same-WiFi 双设备门槛是:

make same-wifi-dhcp-start-device-policy
make same-wifi-dhcp-adb-check-device-policy
make same-wifi-dhcp-stop
make same-wifi-dhcp-verify-device-policy-recovery

它要求两台真实客户端的 SSID Wi-Fi MAC、固定 reservation 和 ADB serial,启动前主动 证明不存在其他 DHCP OFFER;数据面验证两台设备 identity、独立 default selector、 设备规则 selector、准确源 IP 和 UDP REJECT。恢复门槛在操作者重新打开路由器 DHCP 后 要求主动 OFFER、Mac DHCP server_identifier、恢复的默认路由和两台客户端 HTTPS。 截至本实现落地时该 gate 尚未在本轮真机运行;因此 same-WiFi per-device 只能标记为 Experimental / cooperative IPv4,不能借用 virtual lab 的通过记录宣称已验收。

same-WiFi 上游断链恢复门槛

Mac 与下游客户端共用同一个 Wi-Fi 接口时,普通连通性 smoke 不足以证明上游断链恢复。 需要在完整恢复手册可用的前提下,单独记录以下证据:

  1. 断链前,连通性探针分别证明一个 DIRECT 国内目标和一个真实/受控代理目标可用,并记录 Mihomo 的 rule、chain 与日志;
  2. 人工让 Wi-Fi 断开并重新关联,确认接口、静态 IPv4、router 和 DNS 已恢复;
  3. 若 Mihomo 路径持续失败,保存 macOS Wi-Fi 时间线和当前 mihomo.log,执行 sudo omg restart-mihomo --config <path>
  4. 证明 dnsmasq PID、PF anchor、IPv4 forwarding、Mac 静态网络和 DHCP 接管恢复阶段在 动作前后没有变化,只有 Mihomo PID 改变;
  5. 重复 DIRECT 与代理出口探针,要求均恢复,并确认旧日志已归档;
  6. 最后完成 same-WiFi stop 与路由器 DHCP、Mac DHCP、客户端自动获取恢复门槛。

make test 只证明独立恢复动作、单次自动触发和生命周期互斥的状态事务与接口边界。 普通 make lab-test-tun 使用虚拟 LAN,不能代替物理 Wi-Fi 断开/重关联证据;在上述真实 门槛未运行前,只能说明自动恢复机制已经实现,不能宣称物理链路恢复或根因已经彻底验收。

合盖运行门槛

单元测试必须覆盖默认关闭且不持久化、Helper lease 丢失、外部 SleepDisabled 拒绝接管、 ownership marker 引用计数和上次异常运行的 marker reconciliation。pkg 静态检查必须确认 升级与卸载只在 marker 存在时恢复睡眠。它们不证明特定 Mac 型号和 macOS 版本在电池/电源 下的真实合盖行为;正式宣称前还需在安装包环境分别验证开关启用时合盖保持运行、关闭时 恢复合盖睡眠、Control Service kill、Helper kill 与系统重启后的恢复,并记录 pmset -g 中的 SleepDisabled 证据。测试机必须保持通风,不能把合盖运行中的机器放入包内。

结论纪律

最终报告必须明确说出实际运行了哪些命令。如果只运行了 make test,不要暗示 root-required lab 行为或 transparent routing 已经被验证。