2d2baf887c
- docs/macos-sysext-realize-troubleshooting.html:补"运行时"章节(libbox 空指针、 接口时序、队列死锁、DNS 劫持)+ 运行时 checklist。 - docs/vpn-testing-research.md:VPN 测试调研(行业方法论 + GFW 专项 + Pangolin 分层测试项 + 三层测试工具设计)。 - CLAUDE.md:新增「client/ macOS 原生隧道」铁律章节(构建发版/系统扩展 realize 要求/ libbox+NE 集成/配置由服务端渲染/已知坑)。 - scripts/vpn_test.sh:客户端侧黑盒测试工具(出口 IP/DNS/可达性/延迟/吞吐/IPv6 泄漏, PASS/WARN/FAIL,可远程跑)。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JEHzjEcFzvGwgbxT6Wbt6c
242 lines
20 KiB
HTML
242 lines
20 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>macOS PacketTunnel 系统扩展 realize 失败(code=4)踩坑复盘</title>
|
|
<style>
|
|
:root{
|
|
--bg:#0f1117; --panel:#171a22; --panel2:#1d2129; --fg:#e6e8ee; --fg2:#a8afbd;
|
|
--accent:#e0884f; --accent2:#5fb0c9; --ok:#5ec27a; --bad:#e06a6a; --warn:#e0b84f;
|
|
--border:#272c36; --mono:"SF Mono",ui-monospace,Menlo,Consolas,monospace;
|
|
--sans:-apple-system,"PingFang SC","Helvetica Neue",Arial,sans-serif;
|
|
}
|
|
*{box-sizing:border-box}
|
|
body{margin:0;background:var(--bg);color:var(--fg);font-family:var(--sans);line-height:1.7;font-size:15px}
|
|
.wrap{max-width:920px;margin:0 auto;padding:48px 24px 96px}
|
|
h1{font-size:30px;line-height:1.3;margin:0 0 8px;letter-spacing:-.01em}
|
|
.sub{color:var(--fg2);font-size:15px;margin:0 0 32px}
|
|
h2{font-size:21px;margin:48px 0 14px;padding-bottom:8px;border-bottom:1px solid var(--border)}
|
|
h3{font-size:16px;margin:28px 0 8px;color:var(--accent2)}
|
|
p{margin:10px 0}
|
|
code{font-family:var(--mono);font-size:.88em;background:var(--panel2);padding:1px 6px;border-radius:5px;color:#f0d9c4}
|
|
pre{background:#0a0c11;border:1px solid var(--border);border-radius:10px;padding:14px 16px;overflow-x:auto;font-family:var(--mono);font-size:13px;line-height:1.55;color:#cdd3df}
|
|
pre .c{color:#6b7385}
|
|
pre .r{color:var(--bad)}
|
|
pre .g{color:var(--ok)}
|
|
pre .y{color:var(--warn)}
|
|
.tag{display:inline-block;font-size:12px;font-weight:600;padding:2px 9px;border-radius:999px;vertical-align:middle}
|
|
.tag.bad{background:rgba(224,106,106,.16);color:var(--bad)}
|
|
.tag.ok{background:rgba(94,194,122,.16);color:var(--ok)}
|
|
.card{background:var(--panel);border:1px solid var(--border);border-radius:12px;padding:18px 20px;margin:16px 0}
|
|
.card.root{border-left:3px solid var(--accent)}
|
|
.card h3{margin-top:0}
|
|
table{width:100%;border-collapse:collapse;margin:16px 0;font-size:14px}
|
|
th,td{text-align:left;padding:9px 12px;border-bottom:1px solid var(--border);vertical-align:top}
|
|
th{color:var(--fg2);font-weight:600;font-size:13px}
|
|
td code{font-size:.85em}
|
|
.ok-c{color:var(--ok)} .bad-c{color:var(--bad)} .warn-c{color:var(--warn)}
|
|
ul,ol{padding-left:22px;margin:10px 0}
|
|
li{margin:5px 0}
|
|
.lead{background:linear-gradient(180deg,rgba(224,136,79,.10),transparent);border:1px solid var(--border);border-radius:12px;padding:18px 20px;margin:0 0 8px}
|
|
.kbd{font-family:var(--mono);font-size:.85em;color:var(--accent)}
|
|
.small{color:var(--fg2);font-size:13px}
|
|
.step{counter-increment:step;position:relative;padding-left:6px}
|
|
hr{border:none;border-top:1px solid var(--border);margin:40px 0}
|
|
a{color:var(--accent2)}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="wrap">
|
|
|
|
<h1>macOS PacketTunnel 系统扩展 realize 失败(<code>OSSystemExtensionErrorDomain code=4</code>)踩坑复盘</h1>
|
|
<p class="sub">Pangolin 客户端 · P1 原生隧道 · 2026-06-21 · 在 macOS 15.3.2 / 26 上排查与修复</p>
|
|
|
|
<div class="lead">
|
|
<strong>一句话结论:</strong> 客户端内嵌的 <code>PacketTunnel</code> 系统扩展(<code>NEPacketTunnelProvider</code>)一直无法激活,报
|
|
<code>code=4 "Extension not found in App bundle. Unable to find any matched extension"</code>。
|
|
表面像"找不到扩展",实则是 <b>三个叠加的工程配置 bug</b> 让 <code>sysextd</code> 在 realize/暂存/分类校验三个不同阶段先后拒绝。
|
|
逐个修复后扩展成功 <code>activated enabled</code>、隧道连通。
|
|
</div>
|
|
|
|
<h2>1. 现象</h2>
|
|
<p>客户端点"连接"时,主 app 调 <code>OSSystemExtensionRequest.activationRequest</code> 激活内嵌的 packet-tunnel 系统扩展,但回调恒为失败:</p>
|
|
<pre><span class="r">sysext didFailWithError ✗ domain=OSSystemExtensionErrorDomain code=4</span>
|
|
desc=Extension not found in App bundle. Unable to find any matched extension
|
|
with identifier: com.pangolin.pangolin.PacketTunnel</pre>
|
|
<p>诡异之处:</p>
|
|
<ul>
|
|
<li>扩展明明在 <code>Contents/Library/SystemExtensions/</code> 里,<code>sysextd</code> 日志也打了 <code>attempting to realize</code>(说明找到了)。</li>
|
|
<li>签名、公证、staple、Gatekeeper 全部通过;<code>codesign --verify --deep --strict</code> 通过。</li>
|
|
<li><code>sysextd</code> 在验签通过后 <b>~10ms 内静默 <code>xpc_connection_cancel</code></b>,自身不打印任何拒绝原因。</li>
|
|
<li><b>从不弹"允许扩展"批准框</b>——在批准阶段之前就被拒。</li>
|
|
</ul>
|
|
|
|
<h2>2. 走过的弯路(全部排除)</h2>
|
|
<p>以下都查过并确认<strong>不是</strong>根因,记录下来免得后人重复:</p>
|
|
<table>
|
|
<tr><th>假设</th><th>结论</th></tr>
|
|
<tr><td>签名 / 公证 / 时间戳 / hardened runtime / get-task-allow</td><td class="ok-c">全部正确</td></tr>
|
|
<tr><td>entitlements 变体(<code>packet-tunnel-provider-systemextension</code>)</td><td class="ok-c">正确,且描述文件已授权</td></tr>
|
|
<tr><td>Info.plist 用 <code>NetworkExtension</code>/<code>NEProviderClasses</code>(而非 appex 的 <code>NSExtension</code>)</td><td class="ok-c">正确(sysext 就该用这个)</td></tr>
|
|
<tr><td>Xcode 26 beta / macOS 26.2 SDK 工具链太新</td><td class="bad-c">证伪:Tailscale 用更新的 SDK 26.5 照样能用</td></tr>
|
|
<tr><td>App 文件归属(user vs root:wheel)</td><td class="bad-c">改 root:wheel 无效</td></tr>
|
|
<tr><td>App 不在 /Applications / LaunchServices 注册 / 翻译重定位</td><td class="bad-c">都正常,无效</td></tr>
|
|
<tr><td><code>systemextensionsctl reset</code> 清缓存</td><td class="bad-c">无效</td></tr>
|
|
<tr><td>App Group 用 iOS 式 <code>group.</code> 前缀</td><td class="warn-c">确实该改成 macOS 原生格式,但单独改它仍失败</td></tr>
|
|
</table>
|
|
|
|
<h2>3. 破局方法:在同一台机器上对照一个"能用的"开源同类</h2>
|
|
<p>关键转折是<strong>不再盲猜,而是拿一个已知能用、且分发模型完全相同的开源 app 在同一台机器上对照</strong>。我们选了 <b>Tailscale 独立版</b>(<code>io.tailscale.ipn.macsys</code>):同样是 Developer ID 公证 + <code>NEPacketTunnelProvider</code> <b>系统扩展</b>分发(非 App Store appex)。</p>
|
|
<div class="card">
|
|
<p>把本机能用的 <code>Tailscale.app</code> 直接拷到出问题的测试机(cara,干净 macOS 15.3.2,Intel),它的扩展<strong>一路走完</strong> <code>validating → staging → validating_by_category → activated_waiting_for_user</code> 并弹出"允许扩展"框。</p>
|
|
<p><strong>这一步同时证明了两件事:</strong></p>
|
|
<ul>
|
|
<li>cara 的环境完全正常——能 realize 第三方 Developer ID 系统扩展。</li>
|
|
<li>所以 100% 是<strong>我们自己的包</strong>有问题(用户的直觉:"是我们代码的问题")。</li>
|
|
</ul>
|
|
</div>
|
|
<p class="small">附:也对照了 Shadowrocket,但它是 App Store 的 <b>appex</b> 模型(<code>NSExtension</code>、系统预信任、不走 sysextd 批准流),与我们不是一个机制,不能直接类比。要对照必须找<strong>同为 Developer ID system extension</strong> 的实现。</p>
|
|
|
|
<h2>4. 三个根因(按 sysextd 处理阶段排序)</h2>
|
|
|
|
<div class="card root">
|
|
<h3>根因 ① 扩展不自包含 <span class="tag bad">staging 前被拒</span></h3>
|
|
<p>这是 Flutter + 扩展工程的经典坑。工程用 CocoaPods,<b>项目级</b>配置经 <code>Release.xcconfig</code> 注入了链接所有 pod 框架的 <code>OTHER_LDFLAGS</code>。<code>PacketTunnel</code> target 没有自己的 <code>OTHER_LDFLAGS</code>,于是<strong>继承了项目级的</strong>,把主 app 的 Flutter 插件 <code>flutter_secure_storage_macos.framework</code> 也链进了扩展——而该框架并不在扩展 bundle 内:</p>
|
|
<pre>$ otool -L PacketTunnel.systemextension/Contents/MacOS/PacketTunnel
|
|
<span class="r">@rpath/flutter_secure_storage_macos.framework/.../flutter_secure_storage_macos</span> ← 悬空依赖!
|
|
/System/Library/Frameworks/...</pre>
|
|
<p>同时,gomobile 产出的 <code>Libbox.xcframework</code> 实为<strong>静态库</strong>(<code>ar archive</code>),已被静态链进扩展二进制,却又被 <b>Embed Frameworks</b> 冗余地塞了一份畸形的 <code>Libbox.framework</code> 进扩展。</p>
|
|
<p><b>system extension 必须自包含</b>(realize 时会被拷到 <code>/Library/SystemExtensions/<UUID>/</code> 独立运行,相对 rpath 回不到主 app)。</p>
|
|
<p><strong>修复:</strong></p>
|
|
<ul>
|
|
<li>给 <code>PacketTunnel</code> 的 Debug/Release 配置加 <code class="kbd">OTHER_LDFLAGS = "";</code>,切断对项目级 pod 链接标志的继承。</li>
|
|
<li>从 <b>Embed Frameworks</b> 移除 <code>Libbox.xcframework</code>(它是静态库,只需 <b>Link</b>,不需 Embed)。</li>
|
|
</ul>
|
|
<p class="small">验证:<code>otool -L</code> 扩展二进制应只剩 <code>/System/...</code> 与 <code>/usr/lib/...</code>,无任何 <code>@rpath</code>;<code>Contents/</code> 下不再有 <code>Frameworks/</code> 目录(与 Tailscale 一致)。</p>
|
|
</div>
|
|
|
|
<div class="card root">
|
|
<h3>根因 ② 扩展 bundle 名 ≠ bundle 标识符 <span class="tag bad">staging 阶段</span></h3>
|
|
<p>我们扩展的产物名是 <code>PacketTunnel.systemextension</code>,而 <code>CFBundleIdentifier</code> 是 <code>com.pangolin.pangolin.PacketTunnel</code>。Tailscale 的产物名 = 标识符(<code>io.tailscale.ipn.macsys.network-extension.systemextension</code>)。</p>
|
|
<p>在受影响的 macOS 上,<code>sysextd</code> 必须能按标识符把 bundle 拷进暂存区;名字对不上时,在 <code>attempting to realize</code> 之后直接静默 cancel,<b>进不了 staging</b>。改名后日志立刻出现 <code>[Staging] Imported: .../com.pangolin.pangolin.PacketTunnel.systemextension</code>。</p>
|
|
<p><strong>修复:</strong> 把 <code>PacketTunnel</code> target 的 <code class="kbd">PRODUCT_NAME</code> 从 <code>$(TARGET_NAME)</code> 改为 <code>com.pangolin.pangolin.PacketTunnel</code>(三个配置 Debug/Release/Profile 都改)。</p>
|
|
<p class="small">连带:<code>PRODUCT_MODULE_NAME</code> 会变成 <code>com_pangolin_pangolin_PacketTunnel</code>,而 Info.plist 的 <code>NEProviderClasses</code> 用 <code>$(PRODUCT_MODULE_NAME).PacketTunnelProvider</code> 自动跟随,无需手改。</p>
|
|
</div>
|
|
|
|
<div class="card root">
|
|
<h3>根因 ③ 扩展 Info.plist 缺 <code>NSSystemExtensionUsageDescription</code> <span class="tag bad">category 校验阶段</span></h3>
|
|
<p>过了 staging 后,日志终于给出<strong>明确原因</strong>:</p>
|
|
<pre><span class="r">com.pangolin.pangolin.PacketTunnel: extension failed category property check:
|
|
extensions belonging to the com.apple.system_extension.network_extension category
|
|
require the presence of the 'NSSystemExtensionUsageDescription' property.</span>
|
|
→ uninstalling invalid extension</pre>
|
|
<p>网络扩展类别<strong>强制要求扩展自己的 Info.plist</strong> 里有 <code>NSSystemExtensionUsageDescription</code>。我们之前只在<strong>主 app</strong> 的 Info.plist 加了——<b>不顶用</b>。Tailscale 的扩展 Info.plist 里就有这个键。</p>
|
|
<p><strong>修复:</strong> 在 <code>PacketTunnel/Info.plist</code> 顶层加:</p>
|
|
<pre><key>NSSystemExtensionUsageDescription</key>
|
|
<string>Pangolin 需要安装网络扩展以提供安全的网络连接。</string></pre>
|
|
</div>
|
|
|
|
<h3>附带一并修正的项</h3>
|
|
<ul>
|
|
<li><b>App Group 格式</b>:<code>group.com.pangolin.pangolin</code>(iOS 式)→ macOS 原生 <code>BYL4KQHMTN.com.pangolin.pangolin</code>(主 app + 扩展 entitlements + Swift <code>appGroup</code> 常量三处同步)。</li>
|
|
<li><b>NEMachServiceName</b>:必须以扩展所属 App Group 为前缀 → <code>BYL4KQHMTN.com.pangolin.pangolin.PacketTunnel</code>。</li>
|
|
<li><b>扩展网络权限</b>:沙箱扩展补 <code>com.apple.security.network.client</code> / <code>network.server</code>(packet tunnel 要对外建连)。</li>
|
|
<li><b>公证前置</b>:<code>get-task-allow=false</code>、Xcode 签名加 <code>--timestamp</code>(<code>OTHER_CODE_SIGN_FLAGS</code>)、构建期把静态 Libbox 以 Developer ID 重签。</li>
|
|
</ul>
|
|
|
|
<h2>5. 成功的 sysextd 日志长这样</h2>
|
|
<pre>attempting to realize extension with identifier com.pangolin.pangolin.PacketTunnel
|
|
[Staging] Imported: .../com.pangolin.pangolin.PacketTunnel.systemextension
|
|
advancing state from staging to <span class="g">validating</span>
|
|
advancing state from validating to <span class="g">validating_by_category</span>
|
|
<span class="g">Category delegate com.apple.system_extension.network_extension returned error (null)</span>
|
|
advancing state ... to <span class="g">activated_waiting_for_user</span>
|
|
observer 'notify user' reached a success state <span class="c"># ← 弹"允许扩展"框</span>
|
|
|
|
<span class="c"># 用户在 系统设置 → 隐私与安全性 点允许后:</span>
|
|
sysext didFinishWithResult ✓ result=0 (completed)
|
|
$ systemextensionsctl list
|
|
* * BYL4KQHMTN com.pangolin.pangolin.PacketTunnel (1.0/1) <span class="g">[activated enabled]</span></pre>
|
|
|
|
<h2>5.5 加载之后:从"扩展能起"到"真正连通"(运行时)</h2>
|
|
<p>系统扩展能 realize/激活只是第一步。让内嵌的 libbox(sing-box)真正建起隧道、能上网,又踩了四个坑(均在 <code>PacketTunnelProvider.swift</code> / 服务端配置):</p>
|
|
|
|
<div class="card root">
|
|
<h3>运行时 ① libbox <code>startOrReloadService(options:)</code> 传 nil → 空指针崩溃 <span class="tag bad">扩展进程 SIGABRT</span></h3>
|
|
<p>扩展进程启动 360ms 后自杀(SIGABRT)。把 stderr 重定向到 App Group 容器文件后抓到 Go panic:</p>
|
|
<pre><span class="r">panic: runtime error: invalid memory address or nil pointer dereference</span>
|
|
libbox.(*CommandServer).StartOrReloadService(server, {config}, <span class="r">0x0</span>) command_server.go:175</pre>
|
|
<p>此版本 libbox 会解引用第三个 <code>options</code> 参数(虽标 <code>_Nullable</code>)。<strong>修复:</strong>传非空 <code>LibboxOverrideOptions()</code> 而非 <code>nil</code>。</p>
|
|
</div>
|
|
|
|
<div class="card root">
|
|
<h3>运行时 ② 默认接口监控异步返回 → <code>no available network interface</code> <span class="tag bad">启动报错</span></h3>
|
|
<p><code>startDefaultInterfaceMonitor</code> 启动 <code>NWPathMonitor</code> 后立即返回,但首个 path 回调是异步晚到的;sing-box 紧接着的网络操作拿到的默认接口索引还是 -1。<strong>修复:</strong>用信号量**阻塞到首个 path 更新再返回**(带 5s 超时),对齐 sing-box-for-apple。</p>
|
|
</div>
|
|
|
|
<div class="card root">
|
|
<h3>运行时 ③ provider 队列三方死锁 → 隧道永远卡 connecting <span class="tag bad">最隐蔽</span></h3>
|
|
<p>openTun 打点显示卡在 <code>setTunnelNetworkSettings</code> 的信号量等待,回调永不触发:</p>
|
|
<ul>
|
|
<li>NE 在 <b>provider 队列</b>上调 <code>startTunnel</code> → 同步调 <code>startOrReloadService</code>(阻塞该队列)</li>
|
|
<li>sing-box 在 Go 线程调 openTun → <code>sem.wait()</code> 等 <code>setTunnelNetworkSettings</code> 完成</li>
|
|
<li>而该完成回调**正要在被阻塞的 provider 队列上投递** → 死锁</li>
|
|
</ul>
|
|
<p><strong>修复:</strong>把 libbox 启动整段放进后台队列(<code>DispatchQueue.global().async</code>),<code>startTunnel</code> 立即返回、provider 队列腾出来投递回调。</p>
|
|
</div>
|
|
|
|
<div class="card root">
|
|
<h3>运行时 ④ 缺 DNS 劫持 → 隧道连上但打不开网站 <span class="tag bad">最后一关</span></h3>
|
|
<p>隧道起来了、TCP 能经 REALITY 出海,但域名打不开。box.log 显示发往隧道 DNS 的查询走了直连:</p>
|
|
<pre>inbound packet connection to <span class="y">172.19.0.2:53</span>
|
|
router: match ip_cidr=[..<span class="r">172.16.0.0/12</span>..] => route(<span class="r">direct</span>) <span class="c"># DNS 被 LAN 规则吞去直连 → 解析失败</span></pre>
|
|
<p>隧道 DNS 地址 172.19.0.2 落在 LAN 直连规则 172.16/12 内,被路由成直连(发往不存在的主机)→ 解析全失败。<strong>修复(服务端 <code>clientconfig.go</code>):</strong>route.rules 首条加 <code>{"action":"hijack-dns","port":[53]}</code>(排在 LAN 规则之前),把 :53 查询交给 sing-box DNS 模块。</p>
|
|
<p class="small">注:sing-box 1.13 的 <code>protocol:"dns"</code> 匹配需先 sniff;按目的端口 53 匹配最稳、不依赖 sniff。</p>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<p><strong>另一个发版必知:每次构建必递增 <code>CFBundleVersion</code>。</strong> <code>sysextd</code> 按(标识符, 版本)去重;同版本号重装**不会替换**已激活的旧扩展,跑的还是旧代码(排查时极易被误导)。</p>
|
|
</div>
|
|
|
|
<p><strong>连通验证(从命令行):</strong></p>
|
|
<pre>$ curl https://api.ipify.org → <span class="g">103.119.13.48</span> <span class="c"># 出口=节点 IP,流量走隧道</span>
|
|
$ curl -o/dev/null -w '%{http_code}' https://github.com → <span class="g">200</span>
|
|
box.log: router: match[0] port=53 => <span class="g">hijack-dns</span> → dns: exchanged A github.com 140.82.116.3</pre>
|
|
|
|
<h2>6. 给后人的排查 checklist(Developer ID 网络系统扩展)</h2>
|
|
<ol>
|
|
<li><b>先找同模型的能用实现对照</b>(Tailscale 独立版 / Mullvad),<u>在同一台机器</u>上验证环境没问题,把范围锁到自己的包。</li>
|
|
<li>扩展二进制 <code>otool -L</code> 必须<strong>零 <code>@rpath</code> 外部依赖</strong>(自包含)。警惕 CocoaPods/Flutter 的 <code>OTHER_LDFLAGS</code> 继承。</li>
|
|
<li>扩展 <b>bundle 名 = CFBundleIdentifier</b>(设 <code>PRODUCT_NAME</code> = 标识符)。</li>
|
|
<li>扩展 Info.plist <strong>必须有 <code>NSSystemExtensionUsageDescription</code></strong>(不是主 app 的)。</li>
|
|
<li>App Group 用 macOS 原生 <code><TeamID>.<name></code>;<code>NEMachServiceName</code> 以该 group 为前缀。</li>
|
|
<li>沙箱扩展按需补 <code>network.client/server</code>、文件访问等能力。</li>
|
|
<li>静态库 xcframework 只 <b>Link</b> 不 <b>Embed</b>;动态 framework 才需 Embed + CodeSignOnCopy。</li>
|
|
<li>排查时:<code>log stream --debug --predicate 'process=="sysextd" OR process=="syspolicyd"'</code>,看卡在 <code>realize / staging / validating_by_category</code> 哪一步;category 校验失败会打出明确缺什么键。</li>
|
|
</ol>
|
|
|
|
<h2>7. 已知环境坑:macOS 26 (Tahoe) 回归</h2>
|
|
<div class="card">
|
|
<p>在 macOS 26 上,即使包完全正确,<code>sysextd</code> 仍可能报
|
|
<code>no policy, cannot allow apps outside /Applications</code>(app 明明在 /Applications)。这是 <b>Apple 在 Tahoe 的已知回归</b>(开发者论坛多人复现、非 MDM 个人机),<u>不是我们能修的</u>,也不影响 macOS 15/14 的真实用户。开发机若是 26,本机调试可考虑关 SIP 后开
|
|
<code>systemextensionsctl developer on</code>,或在 15/14 机器上验证。</p>
|
|
</div>
|
|
|
|
<h2>8. 运行时 checklist(libbox + NE 集成)</h2>
|
|
<ol>
|
|
<li>libbox <code>startOrReloadService(options:)</code> 传**非空** <code>LibboxOverrideOptions()</code>,别传 nil。</li>
|
|
<li><code>startTunnel</code> 里的 libbox 启动放**后台队列**(<code>DispatchQueue.global().async</code>),别在 provider 队列同步跑(死锁)。</li>
|
|
<li><code>startDefaultInterfaceMonitor</code> **阻塞到首个 path 更新再返回**。</li>
|
|
<li>TUN 配置必须有 **DNS 劫持**(<code>action:hijack-dns</code>,按 port 53),排在 LAN/分流规则之前。</li>
|
|
<li>**每次构建递增 <code>CFBundleVersion</code>**,否则 sysextd 不更新已激活的扩展。</li>
|
|
<li>看 libbox 自身日志:配置 <code>log.output</code> 指向容器文件(<code>writeLogs</code> 回调启动期不触发);Go fatal 走 stderr,需把扩展 stderr 重定向到文件才看得到。</li>
|
|
</ol>
|
|
|
|
<hr>
|
|
<p class="small">改动文件:扩展 <code>Info.plist</code>/<code>entitlements</code>/<code>main.swift</code>/<code>PacketTunnelProvider.swift</code> · <code>Runner/Release.entitlements</code> · <code>Runner.xcodeproj/project.pbxproj</code>(<code>OTHER_LDFLAGS</code>/<code>PRODUCT_NAME</code>/移除 Embed Libbox/<code>--timestamp</code>/版本号) · <code>scripts/local_test.sh</code> · <code>client/macos/sign_libbox.sh</code> · 服务端 <code>server/internal/httpapi/clientconfig.go</code>(DNS 劫持)。验证机:cara,干净 macOS 15.3.2 Intel,出口 IP=节点、国外站可达。</p>
|
|
|
|
</div>
|
|
</body>
|
|
</html>
|