lan-direct 是做什麼的?
讓應用程式的流量透過 ISP 出去,同時**你(代理人)**繼續留在 VPN 上。
在 Claude Code 與 Codex 上都可運作——其機制是 per-socket 綁定,與執行框架無關。
不變原則
絕不修改路由表、WinINET/系統 proxy 設定或 VPN 用戶端的配置。只允許:
- 設定子行程(child process)範圍的環境變數,或
- 傳遞每個工具(per-tool)的來源綁定旗標。
兩者都是 per-socket 且 opt-in,因此不會影響你自己的連線。這是本技能的核心精神——如果你動用了 route add,那就破壞了它(請參閱文末的逃生艙小節)。
不要建議使用者中斷 VPN。這正是本技能要避免的問題。
工作流程
1. 偵測與驗證
powershell -NoProfile -ExecutionPolicy Bypass -File scripts\lan-detect.ps1
在 Linux 或 macOS 上請改用對應版本——相同的 JSON 格式,相同的退出碼合約:
bash scripts/lan-detect.sh
輸出 JSON:lanIp、gateway、lanDns、subnet、tunnelActive、vpnAdapter、directEgress、lanEgress、fakeIpDns、bypassWorks。只有在 bypassWorks 為 true 時才以 0 退出——它透過兩次即時探測來證明 bypass 有效,而不是推測。
tunnelActive: false-> VPN 沒開。告知使用者並停止;沒有東西需要 bypass。bypassWorks: false但隧道已啟用 -> 前往下方的故障排除。不要繼續執行。流量會默默留在 VPN 上,而你會回報假通過。
2. 選擇機制
Node 後端(含 Next.js)?跳過 proxy - 直接跳到下方各堆疊配置的 Node 配方。一個環境變數,不需 proxy 程序,而且是唯一能到達原生 fetch 的方式。
其他情況:啟動 LAN 綁定 proxy。伺服器程序沒有 --interface 旗標,因此這是讓它獲得 ISP 出口的方式。
node scripts/lan-proxy.mjs --bind <lanIp> --port 8899 # 背景執行
node scripts/lan-proxy.mjs --bind <lanIp> --port 8899 --dns <lanDns> # 若 fakeIpDns: true
它只監聽 127.0.0.1,並記錄它 tunnel 的每個主機,讓你可以看到應用程式實際呼叫了哪些。
3. 指向 proxy 重新啟動後端
HTTP_PROXY=http://127.0.0.1:8899
HTTPS_PROXY=http://127.0.0.1:8899
NO_PROXY=localhost,127.0.0.1,<subnet>
NO_PROXY 很重要:少了它,後端對其自身本地服務的呼叫會無緣無故地被丟進 proxy。(基於 TCP 的本地資料庫——MySQL、Postgres、Mongo——並非 HTTP,無論如何都不受影響。)
將這些設定在後端的啟動命令上,而不是全域。請參閱下方的各堆疊配置——對於某些堆疊,僅靠 HTTP_PROXY 是不夠的。
4. 執行並確認
呼叫那個對外呼叫一直失敗的端點。確認兩者:
- 呼叫現在成功了,而且
- proxy 日誌顯示了它撥接的主機。
沒有對應日誌行的 pass 表示請求從未經過 proxy。
5. 拆除
停止 proxy,啟動後端時不加這些環境變數。沒有其他需要復原的——沒有變更任何全域狀態。
在 Git Bash 中使用 pkill -f lan-proxy 不會終止 Windows 上的 node.exe。請使用:
Get-CimInstance Win32_Process -Filter "Name='node.exe'" |
Where-Object { $_.CommandLine -like '*lan-proxy*' } |
ForEach-Object { Stop-Process -Id $_.ProcessId -Force }
殘留的實例是無害的,但它會導致下次啟動因連接埠衝突而失敗。
各堆疊配置
| 堆疊 | 如何餵給它 proxy |
|---|---|
Python requests / httpx | 環境變數,直接使用 |
Go、.NET HttpClient | 環境變數,直接使用 |
| PHP CLI | libcurl 從行程環境中讀取 http_proxy / https_proxy |
| Node(含 Next.js) | 不是 proxy——請改用 lan-bind.mjs preload,見下方 |
| Apache/XAMPP 下的 PHP | 見下方——該服務不會繼承你的 shell 環境 |
| curl、wget、npm、ssh、git | 跳過 proxy,直接綁定(見下方的直接綁定) |
Node - 使用 preload,不要用 proxy
原生 fetch 運行在 Node 內建的 undici 上,它在 Node <= 23 中忽略 HTTP_PROXY,且不接受 https.Agent。在 Node 22.20 上已驗證:純環境變數、NODE_USE_ENV_PROXY=1 和 --use-env-proxy 都無效——proxy 日誌保持空白,流量默默透過 VPN 出去。這會影響到 Next.js、openai SDK 以及任何使用原生 fetch 的套件。
完全跳過 proxy 並使用 preload,它會綁定行程中的每個對外 socket:
LAN_BIND=<lanIp> node --import "file:///<技能目錄>/scripts/lan-bind.mjs" script.js
LAN_BIND=<lanIp> NODE_OPTIONS="--import file:///<技能目錄>/scripts/lan-bind.mjs" npm run dev
<技能目錄> 是此技能自身的基礎目錄,必須使用正斜線——在 Claude Code 下通常是 C:/Users/<使用者>/.claude/skills/lan-direct,或在 Codex 下是 C:/Users/<使用者>/.agents/skills/lan-direct。--import 在 Windows 上需要絕對路徑的 file:// URL;使用含反斜線的純 Windows 路徑會失敗。
NODE_OPTIONS 會傳播給 fork 出來的工作者,因此涵蓋了 Next.js 和 Vite 開發伺服器。
這能同時抵達原生 fetch、openai SDK、axios、node-fetch、got 以及純 http/https——不需要依賴項,不需 proxy 程序,也不需 HTTP_PROXY。它會跳過 loopback 目的地,因此本地資料庫或開發伺服器可以繼續運作。
只有在 preload 不可用時:Node >= 24 會搭配環境變數遵循 NODE_USE_ENV_PROXY=1,而 axios 在任何版本上都會遵循它們。
Apache / XAMPP 下的 PHP
在 httpd.conf 中使用 SetEnv/PassEnv 只會填充 $_SERVER;libcurl 讀取的是行程環境,因此它不會接收到。請使用以下任一方式:
- 從一個已經匯出
http_proxy/https_proxy的 shell 啟動 Apache(例如xampp\apache_start.bat),或 - 在
curl_init()之前使用putenv('https_proxy=http://127.0.0.1:8899');,或 - 直接使用
curl_setopt($ch, CURLOPT_PROXY, '127.0.0.1:8899');。
file_get_contents() 不遵循上述任何方式——它需要一個帶有 'proxy' => 'tcp://127.0.0.1:8899' 的 stream context。
直接綁定
當工具可以自行綁定 socket 時,不需 proxy:
| 工具 | 旗標 |
|---|---|
| curl | --interface <lanIp> |
| wget | --bind-address=<lanIp> |
| npm | --local-address=<lanIp> |
| ssh | -b <lanIp> |
| Chromium / Playwright | 不存在綁定旗標——請使用連接埠 8900 的瀏覽器 MCP,見下方 |
瀏覽器 / Playwright MCP
Chromium 沒有來源綁定旗標,因此瀏覽器只能透過 proxy 到達 ISP。請使用 lan-browser.mjs:它是一個 Playwright MCP 伺服器,會啟動並擁有自己的 proxy,因此瀏覽器不可能在沒有 proxy 的情況下啟動。
它隨附於此技能——scripts/install.ps1 會註冊它:
powershell -NoProfile -ExecutionPolicy Bypass -File <技能目錄>\scripts\install.ps1
哪些主機使用 ISP 來自三個合併的來源,因此這幾乎從不需要重新安裝:技能目錄中的 lan-hosts.txt(隨附預設值,在 git pull 時會被取代)、~/.lan-direct-hosts(使用者自己的,從不被覆寫),以及啟動時的 --lan-hosts a,b。每行一個規則,允許 # 註解,支援尾碼匹配。
當使用者希望另一個主機透過 ISP 路由時,請在 ~/.lan-direct-hosts 中新增一行,並告訴他們重新啟動瀏覽器——不要編輯 lan-hosts.txt,因為下一次更新會覆蓋它。
如果使用者要求手動註冊它,請注意 -- 必須能通過 PowerShell 的參數綁定器——將引數 splat 成陣列,或從 bash 執行:
# 從複製的目錄執行
claude mcp add playwright-split --scope user -- \
node <技能目錄>/scripts/lan-browser.mjs --lan-hosts ".ir"
# 從 npm 執行 - 移動複製目錄時仍然有效,因此當使用者已安裝套件時優先使用
claude mcp add playwright-split --scope user -- npx -y lan-direct browser
當套件已安裝時,其他 CLI 子命令:lan-direct detect 會為目前平台選取正確的偵測腳本,而 lan-direct route <host> 會回答「這個主機是走 ISP 還是走隧道?」而不需啟動瀏覽器。當使用者回報某網站無法載入時,請使用這個指令——一條錯誤的規則和一個掛掉的網站從外部看來一模一樣。
伺服器會偵測 LAN IP,如果 lan-detect 說 bypass 有問題則拒絕啟動,如果已有 proxy 在監聽則重複使用它,並在 MCP 退出時關閉它所啟動的項目。自己的日誌輸出到 stderr;stdout 保留給 JSON-RPC。
它使用連接埠 8900,刻意與上述後端測試使用的 8899 不同。重複使用是依連接埠而定,因此共用號碼會讓一個綁定所有連線的後端 proxy 悄悄接管瀏覽器,並將所有流量透過 ISP 路由出去。
在同一個瀏覽器中可以進行混合瀏覽。 --lan-hosts 讓 proxy 依主機做決定:符合的主機透過 ISP 出口,其他所有內容保持未綁定,走隧道。因此「檢查 hoosha 和 youtube」不需要切換工具——hoosha 走 ISP 出去,youtube 走 VPN,同一個工作階段。proxy 日誌會顯示每行連線的決定:
CONNECT lan hoosha.com:443
CONNECT vpn youtube.com:443
保持清單準確:一個國內主機如果沒列在清單中,會走隧道並回傳空回應而失敗;一個國外主機如果被錯誤地加入清單,則會走 ISP 並可能被過濾。當一個網站的失敗方式看起來像連線中斷時,在假設網站掛掉之前,請先檢查日誌選擇了哪條路由。
為什麼用明確清單而不是自動偵測:連線到一個被封鎖的主機,透過隧道成功在 TCP 層級成功(實測:connect@14ms),並且只在約 860ms 後才掛掉,而此時 proxy 已經回應了 200 Connection Established。回退意味著需要緩衝和重播 TLS 握手,並在每個國內請求上付出這個延遲代價。
Playwright 會將 loopback 流量路由到 proxy。 Chrome 通常會繞過 localhost,但設定 proxy 時 Playwright 會覆寫此行為,因此 http://127.0.0.1:3000 會到達 lan-proxy.mjs 而不是直接連線。已在 proxy 日誌中驗證。Proxy 的 loopback 保護正是讓本地開發伺服器保持可達的原因——沒有它,每個這類請求都會因 EADDRNOTAVAIL 而失敗。不要添加 --proxy-bypass=<-loopback>;它在這裡不會改變任何事情,只會混淆情況。
故障排除
directEgress 與 lanEgress 在隧道啟用時相等。 VPN 正在執行 kill-switch 或 strict-route(一個阻止綁定 socket 的 WFP 過濾器)。請在 VPN 用戶端中修正,而不是繞過它:停用 strict route,或為你需要的主機新增一條 direct/bypass 路由規則。nekoray/sing-box:關閉 strict_route,或新增一條 direct 出站規則。
在 Linux 上,即使沒有 kill-switch 也可能發生這種情況:來源位址本身並不會像在 Windows 上一樣覆寫路由表,因此原則路由會將流量保持在隧道內。修復方法是一條將來自 LAN IP 的流量發送到實體閘道的規則——ip rule add from <lanIp> table <n> 加上該表格中的一條預設路由。這需要 root 權限,是一個系統層級的變更,因此請說明清楚,讓使用者決定。此技能中的所有其他操作都保持無權限且限定於行程範圍。
fakeIpDns: true。 作業系統解析器正回傳 198.18.x.x 佔位符,這些佔位符只在隧道內有意義。傳遞 --dns <lanDns> 給 proxy,讓它透過 LAN 自己的 DNS 進行解析。
出口探測沒有回傳任何資料。 無法連線到探測主機。請檢查連線,重新執行。
逃生艙
如果後端真的無法被 proxy(一個編譯好的二進位檔,不支援 proxy),唯一剩下的選項是一條特定主機的靜態路由:
route add <目標Ip> mask 255.255.255.255 <閘道> metric 1 # 需要管理員權限
route delete <目標Ip> # 復原,務必執行
這是全域的。 它會將每個行程對於該目的地的連線都拉離隧道——包括你自己,如果你曾經連過同一個主機。需要提高權限,在刪除之前一直存在,而且很容易忘記。使用前務必詢問,並在同一個會話中將其刪除。