Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 37 additions & 13 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,14 @@ jobs:
$fv = (Get-Item .\WarpGameBoost.exe).VersionInfo
"file version $($fv.FileVersion), product $($fv.ProductName) $($fv.ProductVersion)"
if (-not $fv.FileVersion.StartsWith($env:VERSION_NUM)) { throw "file version $($fv.FileVersion)" }
- name: Self-test -v6only with system DNS (the runner has no IPv6, so IPv4 entries)
- name: Self-test -exit ipv6 with system DNS (the runner has no IPv6, so IPv4 entries)
shell: pwsh
run: |
.\WarpGameBoost.exe -selftest -v6only -dns system -diag=false -count 80
if ($LASTEXITCODE -ne 0) { throw "selftest -v6only failed" }
.\WarpGameBoost.exe -selftest -exit ipv6 -dns system -diag=false -count 80
if ($LASTEXITCODE -ne 0) { throw "selftest -exit ipv6 failed" }
Start-Sleep 2
if (Get-NetAdapter -Name WarpGameBoost -ErrorAction SilentlyContinue) { throw "adapter left behind" }
- name: Self-test with defaults (diagnosis, both exits, malware-blocking DNS)
- name: Self-test with defaults (diagnosis, IPv4-only exit, malware-blocking DNS)
shell: pwsh
run: |
.\WarpGameBoost.exe -selftest -count 80
Expand All @@ -77,17 +77,23 @@ jobs:
function Fail($m) { Get-Content out.txt -Encoding utf8; Stop-Process -Id $p.Id -Force; throw $m }
foreach ($i in 1..40) { Start-Sleep 2; if ((Get-Content out.txt -Raw -Encoding utf8 -ErrorAction SilentlyContinue) -match '加速已开启|失败') { break } }
Get-Content out.txt -Encoding utf8
$t4 = curl.exe -4 -s -m 10 https://www.cloudflare.com/cdn-cgi/trace
$t6 = curl.exe -6 -s -m 10 https://www.cloudflare.com/cdn-cgi/trace
"IPv4 exit: $($t4 | Select-String 'ip=|warp=')"
"IPv6 exit: $($t6 | Select-String 'ip=|warp=')"
$t4 = curl.exe -s -m 10 https://www.cloudflare.com/cdn-cgi/trace
"default exit: $($t4 | Select-String 'ip=|warp=')"
if (-not ($t4 -match 'warp=on')) { Fail "IPv4 exit is not WARP" }
if (-not ($t6 -match 'warp=on')) { Fail "IPv6 exit is not WARP" }
if (-not ($t4 -match 'ip=\d+\.')) { Fail "exit is not IPv4" }
$sw = [Diagnostics.Stopwatch]::StartNew()
$t6 = curl.exe -s -m 10 -w '%{errormsg}' "https://[2606:4700:4700::1111]/cdn-cgi/trace"
"IPv6 literal: $t6 ($($sw.ElapsedMilliseconds) ms)"
if ($t6 -match 'warp=') { Fail "IPv6 not blocked" }
if ($sw.ElapsedMilliseconds -gt 6000) { Fail "IPv6 is dropped, not refused" }
$addrs = [Net.Dns]::GetHostAddresses('www.cloudflare.com') -join ' '
"getaddrinfo: $addrs"
if ($addrs -match ':') { Fail "Windows still hands out IPv6 addresses" }
# fec0:0:0:ffff::1-3 are the placeholders Windows shows on IPv6
# interfaces without DNS servers.
$dns = (Get-DnsClientServerAddress -InterfaceAlias WarpGameBoost).ServerAddresses | Where-Object { $_ -notlike 'fec0:0:0:ffff::*' }
"adapter DNS servers: [$($dns -join ', ')]"
if (($dns -join ',') -ne '1.1.1.2,1.0.0.2,2606:4700:4700::1112,2606:4700:4700::1002') { Fail "unexpected adapter DNS: $dns" }
if (($dns -join ',') -ne '1.1.1.2,1.0.0.2') { Fail "unexpected adapter DNS: $dns" }
# The system resolver (no -Server) must be the malware-blocking one:
# Cloudflare's test name resolves to 0.0.0.0 there and normally elsewhere.
Clear-DnsClientCache
Expand All @@ -96,9 +102,8 @@ jobs:
"malware.testcategory.com -> $blocked; www.blizzard.com -> $normal"
if ($blocked -ne '0.0.0.0') { Fail "malware name not blocked" }
if (-not $normal) { Fail "name resolution broken" }
$addrs = [Net.Dns]::GetHostAddresses('www.cloudflare.com') -join ' '
"getaddrinfo: $addrs"
if ($addrs -notmatch ':') { Fail "Windows does not hand out IPv6 addresses" }
$iwr = (Invoke-WebRequest https://github.com -UseBasicParsing -TimeoutSec 15).StatusCode
"Invoke-WebRequest github.com: $iwr"
Stop-Process -Id $p.Id -Force
Start-Sleep 3
if (Get-NetAdapter -Name WarpGameBoost -ErrorAction SilentlyContinue) { throw "adapter left behind after kill" }
Expand All @@ -109,6 +114,25 @@ jobs:
$t = (Invoke-WebRequest https://www.cloudflare.com/cdn-cgi/trace -UseBasicParsing -TimeoutSec 10).Content
if ($t -notmatch 'warp=off') { throw "network not restored: $t" }
"network restored: warp=off"
exit 0 # curl's expected failure above would otherwise fail the step
- name: Disconnects a running official WARP client first
shell: pwsh
run: |
Invoke-WebRequest https://1111-releases.cloudflareclient.com/windows/Cloudflare_WARP_Release-x64.msi -OutFile warp.msi
Start-Process msiexec -ArgumentList '/i','warp.msi','/qn','/norestart' -Wait
$cli = "$env:ProgramFiles\Cloudflare\Cloudflare WARP\warp-cli.exe"
foreach ($i in 1..30) { Start-Sleep 2; $st = & $cli --accept-tos status 2>&1 | Out-String; if ($st -notmatch 'Unable to connect|daemon') { break } }
& $cli --accept-tos registration new
& $cli --accept-tos connect
foreach ($i in 1..30) { Start-Sleep 2; $st = & $cli --accept-tos status | Out-String; if ($st -match 'Connected' -and $st -notmatch 'Disconnected') { break } }
"official client before: $($st.Trim())"
if ($st -notmatch 'Connected' -or $st -match 'Disconnected') { throw "could not connect the official client for the test" }
.\WarpGameBoost.exe -selftest -diag=false -count 80 | Tee-Object -Variable out
if ($LASTEXITCODE -ne 0) { throw "selftest with the official client connected failed" }
if (-not ($out -match '已断开 VPN:Cloudflare WARP')) { throw "official client was not reported as disconnected" }
$st = & $cli --accept-tos status | Out-String
"official client after: $($st.Trim())"
if ($st -notmatch 'Disconnected') { throw "official client still connected" }
- name: Package
shell: pwsh
run: |
Expand Down
35 changes: 19 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,32 @@

**Windows 一键 Cloudflare WARP 游戏加速:自动挑出最稳的入口,游戏的 UDP 流量也走加速,关掉窗口就恢复原网络。**

## 默认模式:阻止恶意软件 DNS + IPv4/IPv6 双栈
## 默认模式:阻止恶意软件 DNS + 仅 IPv4 出口

从 v0.7.0 起,双击运行就是这个模式,不用加任何参数:
从 v0.8.0 起,双击运行就是这个模式,不用加任何参数:

| | 默认 |
| :--- | :--- |
| 打开时 | **先自动断开其他 VPN**(官方 WARP 客户端、Windows 自带 VPN、WireGuard),避免隧道套隧道 |
| 入口(连 WARP) | **IPv4 和 IPv6 入口一起优选**,选综合最稳的那个;本机没有 IPv6 时只用 IPv4 入口 |
| 出口(访问网站、游戏) | **IPv4 和 IPv6 都走 WARP**:只有 IPv4 的游戏(如守望先锋)照常能玩,支持 IPv6 的网站走 IPv6 |
| DNS | **Cloudflare 阻止恶意软件**(`1.1.1.2`、`1.0.0.2`、`2606:4700:4700::1112`、`2606:4700:4700::1002`),经隧道查询 |
| 出口(访问网站、游戏) | **仅 IPv4**,经 WARP;IPv6 禁用,程序只会拿到 IPv4 地址 |
| DNS | **Cloudflare 阻止恶意软件**(`1.1.1.2`、`1.0.0.2`),经隧道查询 |
| 加密设备到 Cloudflare 的连接 | 是 |
| 隐藏网站看到的原始公网 IP | 通常可以 |
| 提供匿名性 / 绕过所有封锁 | 不保证 |

**阻止恶意软件**:Cloudflare 已知的恶意软件、钓鱼网站的域名会被解析成 `0.0.0.0`,直接打不开;其他网站不受影响,不过滤成人内容和广告。想确认生效,开着程序访问 Cloudflare 的测试地址 `malware.testcategory.com`,应该打不开。

**自动断开的 VPN 不会在程序退出后自动重连**,需要时自己再连上。其他 VPN(OpenVPN、Tailscale 出口节点等)无法自动断开,程序会提示你手动断开。

### 其他模式

| 参数 | 效果 |
| :--- | :--- |
| `-dns system` | **仅流量模式**:虚拟网卡不设 DNS,保持系统设置。路由器 DNS(如 `192.168.1.1`)的查询留在局域网;公网 DNS(如 `8.8.8.8`)的查询经隧道发出 |
| `-exit dual` | 出口 IPv4 和 IPv6 都走 WARP(DNS 同时配 `2606:4700:4700::1112`、`::1002`) |
| `-exit ipv6` | 出口只走 IPv6,IPv4 全部禁用并立即拒绝。**只有 IPv4 的游戏(如守望先锋)会连不上** |
| `-dns system` | **仅流量模式**:虚拟网卡不设 DNS,保持系统设置 |
| `-ipv4=false` | 只用 IPv6 入口(本机没有 IPv6 时无效,仍用 IPv4) |
| `-v6only` | 出口只走 IPv6,IPv4 全部禁用并立即拒绝。**只有 IPv4 的游戏(如守望先锋)会连不上**,不适合打游戏 |

想固定用某个模式:右键 exe →“创建快捷方式”→ 右键快捷方式 →“属性”→ 在“目标”最后加一个空格和参数,以后双击这个快捷方式。

Expand All @@ -47,19 +51,14 @@

### 网络诊断(默认开启)

每次启动先花约 20 秒测三段,告诉你**卡在哪一段、该怎么办**,再开始加速:
每次启动先花约 20 秒测两段,告诉你**卡在哪一段、该怎么办**,再开始加速:

| 测哪一段 | 怎么测 | 不稳说明什么 |
| :--- | :--- | :--- |
| 电脑 → 路由器 | ping 默认网关 | 家里的 Wi-Fi/网线/路由器有问题。**WARP 帮不上**,程序会给出具体做法(插网线、5GHz、关定位、关网卡节能、开 SQM) |
| → 运营商 | 先 traceroute 找到第一个公网路由器,再 ping 它 | 运营商接入有问题,WARP 也要经过这一段,**帮不上** |
| → 暴雪(美东) | 对 `overwatch.blizzard.com`(AWS 弗吉尼亚)做 TLS 握手计时 | 前两段正常而这段不稳 = 运营商到暴雪的线路问题,**这种情况 WARP 可能有帮助** |

连上 WARP 后还会**经 WARP 再测一次暴雪**,和直连并排对比,直接告诉你开加速更稳还是直连更好。

说明:
- 测的是暴雪美东的网站服务器,不是对局服务器(对局服务器地址不公开),所以延迟数字只代表到暴雪美东的线路,和游戏里显示的不完全一样。
- 测暴雪用 TLS 握手而不是普通 TCP 连接:WARP 会在自己的边缘直接完成 TCP 握手,那样测出来只有几毫秒,是假的。
- 连的是**手机热点**(iPhone `172.20.10.1`、安卓 `192.168.43.1`)时会直接提示:手机网络本身延迟高、抖动大,打游戏尽量用家里宽带。
- 有些路由器不回应 ping,会显示“没有回应”,这不算故障。
- 不想每次都等,加 `-diag=false` 跳过。

Expand All @@ -80,7 +79,7 @@
- **当前入口失效**(连续 15 秒不通,或 3 分钟没有握手):自动重新优选并切换。
- **网络变了**(Wi-Fi 重连、插上网线、电脑睡眠后唤醒):隧道自动改走新的网卡。
- **重复打开**:提示“已经在运行”,不会开第二个。
- **官方 WARP 客户端或其他 VPN 还连着**:提示先断开再运行。两个同时开会变成隧道套隧道,延迟翻倍。
- **官方 WARP 客户端或其他 VPN 还连着**:自动断开官方 WARP 客户端、Windows 自带 VPN、WireGuard 隧道;断不了的提示手动断开。两个同时开会变成隧道套隧道,延迟翻倍。
- **程序被强制结束或崩溃**:虚拟网卡和路由由系统随之清除,网络自动恢复。

### 参数
Expand All @@ -90,7 +89,7 @@
| `-diag=false` | 跳过启动时的网络诊断 |
| `-dns system` | DNS 保持系统设置(仅流量模式);默认 `malware` = Cloudflare 阻止恶意软件 |
| `-ipv4=false` | 只用 IPv6 入口(默认 IPv4、IPv6 入口一起优选) |
| `-v6only` | 出口只走 IPv6,禁用 IPv4(守望先锋会连不上) |
| `-exit dual` / `-exit ipv6` | 出口改为 IPv4+IPv6 / 仅 IPv6(默认 `ipv4`,仅 IPv4) |
| `-count 200` | 每次优选测试的入口数量 |
| `-v` | 显示 WireGuard 调试日志(排查连不上时用) |
| `-version` | 显示版本号后退出 |
Expand Down Expand Up @@ -118,7 +117,7 @@
2. **隧道**:用 [wireguard-go](https://git.zx2c4.com/wireguard-go) 和 WireGuard 官方的 [Wintun](https://www.wintun.net/) 虚拟网卡建立隧道,MTU 1280。
3. **路由**:加 `0.0.0.0/1`、`128.0.0.0/1`、`::/1`、`8000::/1` 四条路由,盖过默认路由但不修改它;隧道自己的 UDP 包绑定在实体网卡上发出。关闭虚拟网卡,这些路由随之消失。
4. **DNS**:虚拟网卡的接口优先级设为最高(metric 0)并配上 Cloudflare 阻止恶意软件的 DNS,Windows 优先问它,查询经隧道发出。程序退出、虚拟网卡删除后,DNS 自动回到系统原来的设置。
5. **出口仅 IPv6**(`-v6only`):IPv4 仍然全部进虚拟网卡(不会绕过隧道直连),但在交给 WireGuard 之前就被拦下:TCP 回一个 RST、其他回 ICMP 不可达,程序立刻知道“连不上”并改用 IPv6,而不是干等超时。系统里原本的 IPv4 DNS 服务器例外放行。虚拟网卡另加一条 `::/0` 路由,让没有 IPv6 的网络上 Windows 也把 IPv6 当作可用。
5. **仅一种出口**(默认仅 IPv4;`-exit ipv6` 仅 IPv6):被禁用的那一种仍然全部进虚拟网卡(不会绕过隧道直连),但在交给 WireGuard 之前就被拦下:TCP 回一个 RST、其他回 ICMP 不可达,程序立刻知道“连不上”并改用另一种,而不是干等超时。系统里原本的 DNS 服务器例外放行。仅 IPv4 时虚拟网卡不配 IPv6 地址,Windows 只给程序 IPv4 地址;仅 IPv6 时虚拟网卡另加一条 `::/0` 路由和 IPv6 DNS,让没有 IPv6 的网络上 Windows 也把 IPv6 当作可用。

## 文件

Expand Down Expand Up @@ -154,6 +153,10 @@ go build -trimpath -ldflags "-s -w" -o WarpGameBoost.exe .

版本号显示在窗口标题和第一行,也可以右键 exe →“属性”→“详细信息”查看。

- **v0.8.0**:
- 打开时先自动断开官方 WARP 客户端、Windows 自带 VPN、WireGuard 隧道。
- 出口默认改为仅 IPv4(IPv6 禁用并立即拒绝);`-exit dual`/`-exit ipv6` 取代 `-v6only`。
- 网络诊断不再测暴雪服务器、不再做直连对比,只测路由器和运营商;识别手机热点。
- **v0.7.1**:检测到官方 WARP 客户端或其他 VPN 正在连接时提示先断开(同时开会隧道套隧道,延迟翻倍)。
- **v0.7.0**:默认改为“阻止恶意软件 DNS(Cloudflare `1.1.1.2`)+ IPv4/IPv6 入口一起优选 + IPv4/IPv6 双出口”,守望先锋不用再加参数。`-v6only` 改为可选(默认关);原来的仅流量模式用 `-dns system`。
- **v0.6.0**:新增网络诊断(默认开启,`-diag=false` 跳过):启动时测“电脑→路由器”“→运营商”“→暴雪美东”三段的延迟、抖动、丢包,指出问题在哪一段并给出做法;`-v6only=false` 时连上后再经 WARP 测一次暴雪,与直连对比,告诉你开不开加速更好。
Expand Down
4 changes: 3 additions & 1 deletion RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
检测到官方 WARP 客户端或其他 VPN 正在连接时,提示先断开再运行。两个同时开会变成隧道套隧道,延迟翻倍(实际遇到:游戏里 140ms)。
- **打开时先自动断开其他 VPN**:官方 WARP 客户端、Windows 自带 VPN、WireGuard 隧道。避免隧道套隧道、延迟翻倍。断开的 VPN 不会在退出后自动重连;断不了的(OpenVPN、Tailscale 等)会提示手动断开。
- **出口默认仅 IPv4**:IPv6 禁用并立即拒绝,程序只会拿到 IPv4 地址。DNS 仍是 Cloudflare 阻止恶意软件(`1.1.1.2`、`1.0.0.2`)。需要 IPv6 出口用 `-exit dual` 或 `-exit ipv6`(取代原来的 `-v6only`)。
- **网络诊断不再测暴雪服务器**,只测电脑→路由器、→运营商两段;连手机热点时直接提示手机网络延迟本来就高。
53 changes: 16 additions & 37 deletions diag.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,10 @@ func (st stats) String() string {
st.avg.Milliseconds(), st.jitter.Milliseconds(), st.max.Milliseconds(), st.loss*100)
}

// verdict turns the three hops into advice. gw is the router, isp the first
// public hop, target a Blizzard server; any may be nil when it could not be
// found or measured.
func verdict(gw, isp, target *series, wifi bool) []string {
// verdict turns the two measured legs into advice. gw is the router, isp the
// first public hop; either may be nil when it could not be found. hotspot is
// set when the router is a phone sharing its mobile connection.
func verdict(gw, isp *series, wifi, hotspot bool) []string {
// A hop that never answered is unmeasured, not broken: plenty of
// routers ignore pings altogether.
measured := func(s *series) bool { return s != nil && len(s.rtts) > 0 }
Expand All @@ -97,47 +97,26 @@ func verdict(gw, isp, target *series, wifi bool) []string {
lines = append(lines, " · 换一根网线或换个路由器口试试;重启路由器")
}
return append(lines, " · 家里有人在下载、看视频、传文件时最明显:路由器有 SQM/QoS 就打开")
case bad(target, false) && bad(isp, true):
case hotspot:
return []string{
"问题在运营商接入:路由器正常,但出了家门到运营商的第一段就不稳。",
" · WARP 加速的流量也要先经过这一段,帮不上;可以打电话让运营商查线路,或换运营商",
}
case bad(target, false):
return []string{
"问题在运营商到暴雪的线路:家里和运营商接入都正常,往后才不稳。",
" · 这种情况 WARP 加速可能有帮助:开和不开各打几局,用游戏里的网络图(Ctrl+Shift+N)对比",
"你在用手机热点:手机网络本身就比宽带多几十毫秒,延迟也时高时低,WARP 改变不了。",
" · 打游戏尽量用家里的宽带(网线最好,其次 Wi-Fi)",
}
case !measured(target):
return []string{"家里网络和运营商接入正常;暴雪服务器没测到,无法判断后面的线路。"}
default:
case bad(isp, true):
return []string{
"三段都正常,网络本身没有明显问题。",
" · 游戏里仍然卡,可能是游戏服务器或本机(后台下载、同步、杀毒扫描)",
" · 不一定需要加速;下面会经 WARP 再测一次,对比后再决定",
"问题在运营商接入:路由器正常,但出了家门到运营商的第一段就不稳。",
" · WARP 加速的流量也要先经过这一段,帮不上;可以打电话让运营商查线路,或换运营商",
}
}
}

// compareVerdict weighs the direct route against WARP the way a game would:
// loss and jitter first, then latency.
func compareVerdict(direct, warp stats) string {
score := func(st stats) time.Duration {
return st.avg + 2*st.jitter + time.Duration(st.loss*1000)*time.Millisecond
}
d, w := score(direct), score(warp)
switch {
case w+5*time.Millisecond < d:
return "WARP 更稳,建议开着加速打游戏。"
case d+5*time.Millisecond < w:
return "直连更好:WARP 绕远了。建议关掉加速直接玩。"
case !measured(gw) && !measured(isp):
return []string{"路由器和运营商都不回应 ping,没法判断。"}
default:
return "两者差不多,开不开都行;以游戏里的网络图(Ctrl+Shift+N)为准。"
return []string{"家里网络和运营商接入都正常。游戏里仍然卡,就开着加速和不开各打几局,用网络图(Ctrl+Shift+N)对比。"}
}
}

func report(hops []*series, gw, isp, target *series, wifi bool) string {
func report(gw, isp *series, wifi, hotspot bool) string {
var b strings.Builder
for _, s := range hops {
for _, s := range []*series{gw, isp} {
if s == nil {
continue
}
Expand All @@ -148,7 +127,7 @@ func report(hops []*series, gw, isp, target *series, wifi bool) string {
fmt.Fprintf(&b, " %-26s %s\n", s.name, s.stats())
}
b.WriteString("\n")
for _, l := range verdict(gw, isp, target, wifi) {
for _, l := range verdict(gw, isp, wifi, hotspot) {
b.WriteString(l + "\n")
}
return b.String()
Expand Down
Loading
Loading