目錄 Link to heading
序 Link to heading
本文旨在講解如何用Wireguard設定一個瀏覽特定域名(如chatgpt.com)時才生效的VPN。
為甚麼? Link to heading
我公司正開發一個使用OpenAI API的網頁程式。作為主要開發者,我一手包辦開發及測試環境設定、前端後端架構及開發。
因為OpenAI封禁了香港地區,我要用VPN去做開發。我選擇了Mullvad VPN,原因如下:
- Mullvad月費劃一為5歐元(約42港元),不像其他VPN公司般要買一年或以上才會便宜
- Mullvad支持Wireguard(一個非常快的VPN協議),所以不用安裝他們的VPN程式,直接用Wireguard官方程式都可(不過其實Mullvad的VPN程式也是開源的)
- Mullvad以用戶私隱著稱,註冊甚至不需提供電郵,此外亦不會儲存任何用戶資料(警察都找不到)
因為這OpenAI網頁程式屬試驗性質,上頭隨時都可能叫停開發,所以Mullvad的劃一月費便成為最大優勢。我也是這樣說服上司讓我用Mullvad的。
其實支持Wireguard又著重用戶私隱的VPN公司還有AirVPN和ProtonVPN,不過它們的單月月費比Mullvad貴;主流的Surfshark和NordVPN都支持Wireguard(Surfshark教學/NordVPN非官方教學)
公司出錢的話其實選哪個VPN都沒所謂,只是我想支持Mullvad,所以才選它🤣
問題 Link to heading
正常預設所有流量都會走VPN,這樣不但較慢,而且經常遇到因使用VPN才會有的Captcha測試,很麻煩。
Wireguard有一個AllowedIPs
設定,可加入不同IP地址。Wireguard只會在連接這些指定IP時才生效,其他IP則不走Wireguard。
我想達成的是瀏覽chatgpt.com或openai.com時才經VPN上網,AllowedIPs
功能有不足:其一,我不知道OpenAI所有子網域的IP;其二,無法排除OpenAI以後出現新IP或更改舊IP的可能性,我希望能一勞永逸。
如果有根據域名去決定用不用VPN的方法就好了🤔
解決方案:Wireproxy + Proxy Auto-Configuration(PAC)檔 Link to heading
經過一輪搜尋,我找到了解決方案:Wireproxy + Proxy Auto-Configuration(PAC)檔。
Wireproxy是一個開源的Wireguard客戶端,同時亦是一個SOCK5/HTTP代理伺服器。它會和Mullvad建立Wireguard連接,然後我再指定客戶端用它做網路代理,這樣就能經它用VPN。
PAC檔(代理自動配置)則是一個Javascript檔。節錄MDN的介紹:
代理自动配置(PAC)文件是一个 JavaScript 脚本,其核心是一个 JavaScript 函数,用来决定网页浏览请求(HTTP、HTTPS,和 FTP)应当直连目标地址,还是被转发给一个网页代理服务器并通过代理连接。
將這兩個結合,就能做到瀏覽特定域名時才用VPN。
設定步驟 Link to heading
購買Mullvad VPN並下載Wireguard設定檔 Link to heading
前往Mullvad網站產生一個帳號,然後購買VPN。
購買後,前往Wireguard設定頁:
- 按
產生金鑰
- 在下方選擇你指定的國家/城市/伺服器
- 按
產生組態並下載
下方的下載檔案
以下載Wireguard設定檔
下載Wireproxy並執行 Link to heading
於此處下載Wireproxy並解壓縮。
把上一步下載的Wireguard設定檔放進Wireproxy同一文件夾,然後用文字編輯器打開,並於設定檔最後添加以下:
[Socks5]
# 端口25344,可自訂
BindAddress = 127.0.0.1:25344
[http]
# 端口25345,可自訂
BindAddress = 127.0.0.1:25345
更改後Wireguard設定檔應像這樣:
[Interface]
# Device: <不給你看>
PrivateKey = <不給你看>
Address = <不給你看>
DNS = <不給你看>
[Peer]
PublicKey = <不給你看>
AllowedIPs = 0.0.0.0/0,::0/0
Endpoint = <不給你看>
PersistentKeepalive = 25
[Socks5]
BindAddress = 127.0.0.1:25344
[http]
BindAddress = 127.0.0.1:25345
於Wireproxy所在文件夾開啟指令行(Windows教學),用以下指令啟動Wireproxy:
# Windows
wireproxy.exe -c <Wireguard設定檔名稱>
# Linux/MacOS
wireproxy -c <Wireguard設定檔名稱>
最後,在系統設定中設定代理伺服器為http://127.0.0.1:25345
(Windows教學(手動設定)/MacOS教學),再試試能不能上https://chatgpt.com即可。
其實我們只需要SOCKS5代理,不需HTTP代理。
不過我在測試Windows系統設定代理伺服器時,不知道為甚麼用SOCKS5時無法連接網絡,只能用HTTP。
PAC檔則沒有這個問題,所以測試HTTP代理成功後就可以將Wireguard設定檔中[http]
一項刪除,然後重啟Wireproxy。
注意:HTTP及SOCKS5都是無加密,千萬不要在公共網絡上使用Wireproxy!
創造PAC檔 Link to heading
上一步讓我們成功透過Wireproxy瀏覽ChatGPT網站。問題是這是全域(Global)代理,全部流量都會經過它,這不是我想要的效果。
這時就到PAC檔出場了。創造一個pac.js
檔案,並複製以下內容:
function FindProxyForURL(url, host) {
// 於此處自訂需要走Wireproxy的域名
var domain_list = ["chatgpt.com", "openai.com", "claude.ai", "anthropic.com"];
// 我不知道是不是所有OS都支援最新的Javascript格式,所以這裡用了最原始的Javascript寫法
for (var i = 0; i < domain_list.length; i++) {
if (host == domain_list[i] || dnsDomainIs(host, "." + domain_list[i])) {
return "SOCKS5 127.0.0.1:25344";
}
}
return "DIRECT";
}
以下是簡單解釋(PAC檔詳細文檔請看此):
FindProxyForURL
FindProxyForURL(url, host)
是一個Javascript函數,根據你指定條件決定流量走代理還是直連。我們想要的效果是特定網頁(OpenAI及Anthropic)走代理,其他全部直連。函數內的
url
和host
分別代表「整個網址」和「域名」,例子:- 網址是
https://platform.openai.com
:url
為https://platform.openai.com
,host
為platform.openai.com
- 網址是
https://docs.anthropic.com/en/docs/welcome
:url
為https://docs.anthropic.com/en/docs/welcome
,host
為docs.anthropic.com
- 網址是
dnsDomainIs
dnsDomainIs(host, domain)
函數則用來判定host
是否屬於domain
,如是則返回true
,反之返回false
。我覺得它只是一個簡單的子字串(Substring)檢查,例子:dnsDomainIs("https://platform.openai.com", ".openai.com")
:返回true
dnsDomainIs("https://platform.openai.com", ".anthropic.com")
:返回false
dnsDomainIs("https://openai.com", ".openai.com")
:返回false
(注意!)dnsDomainIs("https://clopenai.com", "openai.com")
:返回true
(注意!)
第三/四點就是我說它純粹是子字串檢查的原因。為了解決第三點的同時又避免第四點的情況發生,我在判斷式中另外加了
host == <域名>
項。返回值(
return
)最後
return
一定要返回DIRECT
(代表直連,不走代理)或格式為<代理協議名> <代理地址>:<端口>
的字串。代理協議名可以是PROXY
/SOCKS
/HTTP
/HTTPS
/SOCKS4
/SOCKS5
,我們只用SOCKS5
。你可以將不同代理用分號組合,例如
"SOCKS5 127.0.0.1:25344; HTTP 127.0.0.1:25345; DIRECT"
。系統會優先用最左的一項,如失敗才用下一個,如此類推。
請根據自己需要更改PAC檔。PAC檔詳細文檔內有除dnsDomainIs
以外的函數可供使用,例如有日期相關函數,可以用它們設定特定日子才走代理,本文不作解釋。
使PAC檔可經網絡存取 Link to heading
PAC檔必須可以經網絡存取(應該說我找不到讓系統直接讀取檔案系統內的PAC檔的方法)。
我想到有兩個做法:Python的HTTP server(最簡單)和Github(一勞永逸)。當然方法不只這兩個,例如你可以自己搭建一個Apache/NGINX伺服器。本文只討論Python和Github方法。
Python方法
首先要安裝Python,並確保Python在環境變數路徑(Path)上。
然後在PAC檔所在文件夾內開啟指令行,並執行以下:
# 127.0.0.1是地址,8000是端口,可自訂 python -m http.server -b 127.0.0.1 8000
用瀏覽器前往
http://127.0.0.1:8000/pac.js
,如看到PAC檔內容即成功。注意此方法在重開機/停止Python後便會失效,你要自己重新啟動它。
Github方法
首先安裝Git,並於Github註冊一個帳號。
然後在Github創造一個Repo,名字隨意(以下假設Repo名為
pac
)。創造一個空白文件夾,將PAC檔移至此,並於文件夾內開啟指令行,執行以下:
git init git add pac.js git commit -m "first commit" git branch -M main # 請手動更改下一項 git remote add origin https://github.com/<你Github用戶名>/pac.git git push -u origin main
最後用瀏覽器前往
https://raw.githubusercontent.com/<你Github用戶名>/pac/main/pac.js
,如看到PAC檔內容即成功。
電腦設定使用PAC檔 Link to heading
最後要在系統設定中設定使用PAC檔。如果你跟著我上方做法,那麼你的PAC檔網址應是:
http://127.0.0.1:8000/pac.js
(Python方法)https://raw.githubusercontent.com/<你Github用戶名>/pac/main/pac.js
(Github方法)
這個設定每個OS的字眼都不同,Windows稱為「使用設定指令碼」,MacOS就稱「自動代理伺服器」,請按教學填上PAC檔網址即可。(Windows教學(設定指令碼)/MacOS教學)
最後驗證能登上https://chatgpt.com,而去https://whatismyip.com顯示的是你本地IP的話即大功告成。
雜項 Link to heading
Chromium基底的瀏覽器(如Chrome和Microsoft Edge)會用系統設定的代理。Firefox則不一定,如你用的是Firefox,可能要去Firefox設定頁手動設定代理(Firefox稱PAC網址為「Proxy自動設定網址」)。
VSCode作為Electron應用程序,同樣會用系統設定的代理;但VSCode內運行的程式卻不一定會走系統代理,例如OpenAI的Python庫所用的httpx
則不走系統代理。
令OpenAI使用Wireproxy的方法如下:
from openai import OpenAI
import httpx
client = OpenAI(
api_key="<不給你看>",
http_client=httpx.Client(proxy="socks5://127.0.0.1:25344"),
)
手機端做法 Link to heading
以上是電腦端的做法。手機想做到同樣效果比較麻煩,因為iOS和Android只有在連接Wifi的時候才能設定代理,使用流量時不能用代理,變相廢了一半武功。
不過我找到了代替品:Shadowrocket(iOS)和v2rayNG(Android)。
這兩個手機程式都是「翻牆工具」,但我們不是用它來翻牆,也不用搭建翻牆伺服器:我們只需要這些程式中的「按域名決定用VPN」功能。
注意:v2rayNG是免費,但Shadowrocket要付費(一次性2.99美元)。
Android Link to heading
於此處下載v2rayNG。
下載後,準備好從Mullvad下載的Wireguard設定檔,然後開啟v2rayNG:
添加配置文件(選
手動輸入[Wireguard]
)輸入以下,然後儲存:
- 服務器地址:Wireguard設定檔的
Endpoint
(不包括端口) - 服務器端口:Wireguard設定檔的
Endpoint
的端口 - SecretKey:Wireguard設定檔的
PrivateKey
- PublicKey:Wireguard設定檔的
PublicKey
- 本地地址:Wireguard設定檔的
Address
- 服務器地址:Wireguard設定檔的
進入v2rayNG設定:
域名策略:
AsIs
預定義規則:
全局直連
自定義規則:代理的網址或IP,輸入以下
domain:openai.com,domain:chatgpt.com,domain:claude.ai,domain:anthropic.com
最後啟動v2rayNG(VPN),同樣去https://chatgpt.com和https://whatismyip.com驗證即可。
iOS Link to heading
於此處下載Shadowrocket。
下載後,準備好從Mullvad下載的Wireguard設定檔,然後開啟Shadowrocket:
(以下步驟建議接駁家中Wifi做)
右上角新增伺服器,型別選擇Wireguard
輸入以下,然後儲存:
- 地址:Wireguard設定檔的
Endpoint
(不包括端口) - 埠:Wireguard設定檔的
Endpoint
的端口 - 私鑰:Wireguard設定檔的
PrivateKey
- 公鑰:Wireguard設定檔的
PublicKey
- 子網IP:Wireguard設定檔的
Address
- DNS:Wireguard設定檔的
DNS
- 地址:Wireguard設定檔的
往「配置」頁,按
default.conf
,再按編輯純文字
。這時會彈出一個界面,上方有一個網址(你可以選擇在手機界面繼續,但我建議你用電腦前往這網址,比較方便修改)
修改
default.conf
:將
[Rule]
項中除了# LAN
、# Final
以外的項全部刪除將
# Final
項下方改成FINAL,DIRECT
於
# LAN
上方加上以下內容:# AI DOMAIN-SUFFIX,openai.com,PROXY DOMAIN-SUFFIX,chatgpt.com,PROXY DOMAIN-SUFFIX,claude.ai,PROXY DOMAIN-SUFFIX,anthropic.com,PROXY
將最下方的
[URL Rewrite]
項刪除按右上角
save
儲存
最後返回首頁並啟動Shadowrocket(VPN),同樣去https://chatgpt.com和https://whatismyip.com驗證即可。
其他可行方案 Link to heading
如果你家中的路由器是用OpenWrt或pfSense/OPNsense等OS的話,可以設定Policy-based routing。設定後,整個家庭網絡上的全部電腦都能做到特定網域走VPN。
以下是我找到的教學,但我沒實際測試過,不能保證其真確性,只作參考:
OpenWrt:Don’t VPN Everything! - Split Tunnel Your Traffic - Policy Based Routing
OPNsense:WireGuard Selective Routing to External VPN Endpoint