Communitygithub.com

lan-direct

讓應用程式和瀏覽器透過真實 ISP 直連國內服務,而 AI 代理人保持連線在 VPN 上。基於 per-socket 綁定、per-host 路由,無須管理員權限,不影響全域設定。

lan-direct 是什麼?

lan-direct 是一個專為 Claude Code 和 Codex 設計的 AI 代理人技能(也包含瀏覽器 MCP),解決同時需要 VPN 隱私和國內服務直連的棘手問題。其核心機制是 per-socket 綁定,讓「應用程式的流量」走 ISP 出去,而「代理人本身的連線」則繼續留在 VPN 內。它完全不修改路由表、系統 proxy 設定或 VPN 用戶端配置,只透過子行程環境變數或工具層級的來源綁定旗標來達成。這代表它沒有全域副作用,不需管理員權限,且不會意外影響到自己的連線。支援多種技術堆疊:Node.js(Next.js 等)透過 preload 腳本直接綁定 socket,跳過 proxy 程序;其他後端(Python、Go、.NET、PHP CLI)則使用 HTTP_PROXY 環境變數搭配內建的 LAN proxy 伺服器。內建的瀏覽器 MCP(lan-browser.mjs)讓 Playwright 瀏覽器可以在同一個工作階段中,讓特定國內網站走 ISP,其他網站走 VPN。所有關鍵路徑都經過兩次 live probe 驗證,確保 bypass 真的有效,而不是猜測。如果 bypass 驗證失敗,腳本會拒絕繼續,避免產生誤導的 false pass。

相容平台✓Claude Code✓Codex CLI~Cursor
npx skills add farshadmomo/lan-direct

在你喜歡的 AI 中提問

開啟一個已預先載入此 Agent Skill 的新對話。

說明文件

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 CLIlibcurl 從行程環境中讀取 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>                                        # 復原,務必執行

這是全域的。 它會將每個行程對於該目的地的連線都拉離隧道——包括你自己,如果你曾經連過同一個主機。需要提高權限,在刪除之前一直存在,而且很容易忘記。使用前務必詢問,並在同一個會話中將其刪除。

相關技能