记录一次 WireGuard 故障排查:从 Windows 断网到 UDP 端口异常
记录使用 WireGuard 过程中遇到的客户端连通性故障,分析全隧道路由黑洞、服务端运行态与配置同步、UDP 默认端口异常丢包的排查过程及 iptables 端口重定向解决方法。
在上一篇笔记中,我记录了在海外 VPS 上搭建 WireGuard 私网并配置多客户端的过程。笔记本作为客户端之一(分配内网 IP 10.66.66.x 作为示例),配置了全局隧道。
配置跑通后用了一天,今天本地 Windows 笔记本唤醒后突然无法联网。排查发现本次故障并非单一原因,而是服务端运行态 Peer 配置异常与网络路径对 UDP 源端口 51820 的返回流量异常叠加,记录一下排查过程和处理方法。
故障现象:Windows 本机断网
电脑唤醒后,右下角网络图标显示“无 Internet,安全”。
初步检查了一下基础网络:
- 手机连同一个 Wi-Fi 可以正常上网,切到手机热点后电脑依然连不上;
- 执行
ipconfig,无线网卡正常分配到了内网 IP192.168.3.x,网关为192.168.3.1; - 执行
ping 192.168.3.1,终端提示:
来自 192.168.3.x 的回复: 常见故障。 / PING: transmit failed. General failure.
尝试在 PowerShell 中停掉 WireGuard 隧道服务:
Stop-Service 'WireGuardTunnel$client2' -Force
服务停止后,本地网络立刻恢复正常,网关能 ping 通,网页也能打开。这说明断网是由 WireGuard 隧道引起的。
全隧道路由机制
在客户端配置中,为了测试全隧道转发路径,客户端设置为 AllowedIPs = 0.0.0.0/0:
[Peer]
AllowedIPs = 0.0.0.0/0
WireGuard 基于无状态的 UDP 运行。虚拟接口启动后就会下发路由并向服务端发送握手包。如果隧道已经激活,但握手始终无法完成,原本被路由到 WireGuard 接口的业务流量将无法通过隧道正常转发,最终表现为公网连接失败。
在本次 Windows 环境中,隧道异常时观察到对网关的 ICMP 请求也出现 General failure。该现象与本机当时的路由/过滤状态有关,不应视为所有全隧道故障都会出现的固定表现。
在客户端界面查看状态,发送流量只有一两百字节(握手请求每次 148 字节),接收流量固定为 0 B,最新握手时间为空。这里需要注意:排查时不要仅以 transfer 是否增加判断隧道是否建立,握手尝试本身也会产生少量 UDP 流量;应以 latest handshake 是否刷新、双向流量是否持续增长以及实际业务请求是否成功为准。接下来需要排查握手失败的原因。
服务端配置与运行态同步
通过 SSH 登录 VPS,检查服务端配置:
ssh -p <ssh-port> root@<server-ip>
查看 /etc/wireguard/wg0.conf,笔记本对应的 Peer 配置如下:
# client2
[Peer]
PublicKey = <client2-public-key>
AllowedIPs = 10.66.66.x/32
在本地 Windows 端,通过私钥计算公钥确认凭据匹配:
$priv = ((Get-Content "$env:USERPROFILE\Downloads\client2.conf" | Select-String '^PrivateKey\s*=').Line -split '=',2)[1].Trim()
$priv | & "C:\Program Files\WireGuard\wg.exe" pubkey
本地计算出的公钥与服务端配置文件一致。但在服务端执行 wg show 查看当前内核运行状态时,发现对应的公钥和配置文件不一致:
peer: <another-public-key>
allowed ips: 10.66.66.x/32
这里涉及 WireGuard 的配置生效机制:
/etc/wireguard/wg0.conf → 持久配置(磁盘)
wg show → 当前运行状态(内核内存)
wg set → 只改运行态(不自动写回磁盘)
wg syncconf → 将磁盘配置同步到运行态
wg0.conf 是“磁盘配置”,wg show 显示的是“内核运行态”,两者不一定自动保持一致。之前排查测试时临时用 wg set 修改过内存中的 Peer,但没有同步更新配置文件;且服务自开机以来没有重载过,导致内核运行态保留了旧公钥。客户端发送握手包时,服务端内核找不到匹配的 Peer,直接丢弃了请求。
在服务端执行配置同步,将磁盘配置重新加载到内核:
bash -c 'wg syncconf wg0 <(wg-quick strip wg0)'
再次执行 wg show,内核中的 Peer 公钥恢复与配置文件一致。
双端抓包与 UDP 端口测试
服务端公钥恢复后,重新启动客户端隧道,接收流量依然为 0 B,握手依然无法完成。
两端公私钥已经确认配对,为了确认数据包的传输情况,在两端进行抓包。
1. 服务端 tcpdump
在 VPS 上监听默认端口 51820:
tcpdump -i <wan-interface> -n -tttt udp port 51820
Windows 端启动隧道后,服务端抓包输出:
In <client-ip>.20002 > <server-ip>.51820: UDP, length 148
Out <server-ip>.51820 > <client-ip>.20002: UDP, length 92
In <client-ip>.20002 > <server-ip>.51820: UDP, length 148
Out <server-ip>.51820 > <client-ip>.20002: UDP, length 92
抓包显示:客户端的 148 字节握手请求已到达服务端,服务端内核正常处理并在几毫秒内回发了 92 字节的握手响应。
2. 客户端 pktmon
使用 Windows 底层抓包工具 pktmon 监听本地无线网卡:
pktmon filter remove
pktmon filter add -p 51820
pktmon start --etw -m real-time
查看捕获结果,本地网卡只有发往 <server-ip>:51820 的 148 字节请求,没有捕获到任何来自服务端的 92 字节回包。这说明服务端已发出的响应包没有出现在客户端网卡侧的抓包结果中,丢包点位于服务端发包之后、客户端网卡接收之前的路径上。
3. UDP 端口对照测试
为了确认是所有 UDP 包受阻还是特定端口问题,在 VPS 上搭建一个简单的 UDP 测试程序,与本地客户端进行对照:
- 服务端从端口 51999 向客户端发送 UDP 包,客户端能正常收到回包;
- 服务端从端口 51820 向客户端发送 UDP 包,客户端收不到任何数据。
测试表明,当前网络路径对源端口为 51820 的入方向 UDP 数据包存在丢弃,无法仅凭现有实验进一步确定过滤发生在本机、接入网络、NAT、运营商还是其他中间设备。由于 WireGuard 默认监听 51820 端口,服务端发出的握手响应源端口正是 51820,导致响应包在回程被丢弃,客户端无法收到确认。
解决方法:iptables 端口重定向
服务端上还有其他设备(手机、平板)连接正常,直接更改服务端的 ListenPort 会导致其他设备也需要重新配置。
较平滑的做法是保持服务端原有的 ListenPort = 51820 不变,利用 iptables 在服务端添加一条端口重定向规则,开辟一个备用端口(例如 48321)转发给 51820:
iptables -t nat -A PREROUTING -p udp --dport 48321 -j REDIRECT --to-ports 51820
将该规则写入 /etc/wireguard/wg0.conf 的 PostUp 和 PostDown 中,确保重启后依然生效:
[Interface]
Address = 10.66.66.1/24
ListenPort = 51820
PrivateKey = <server-private-key>
PostUp = iptables -t nat -A PREROUTING -p udp --dport 48321 -j REDIRECT --to-ports 51820; iptables -t nat -A POSTROUTING -s 10.66.66.0/24 -o <wan-interface> -j MASQUERADE; iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
PostDown = iptables -t nat -D PREROUTING -p udp --dport 48321 -j REDIRECT --to-ports 51820; iptables -t nat -D POSTROUTING -s 10.66.66.0/24 -o <wan-interface> -j MASQUERADE; iptables -D FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
在本地 Windows 客户端的配置文件中,将 Endpoint 端口改为 48321:
[Peer]
PublicKey = <server-public-key>
Endpoint = <server-ip>:48321
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25
重新启动隧道后查看状态:
interface: client2
latest handshake: 4 seconds ago
transfer: 1.25 MiB received, 4.12 MiB sent
双向流量正常增长,握手恢复,公网访问正常。
本地环境收敛与多设备使用
排查过程中,本地还开启过代理工具与系统代理,容易引起混淆。网络调通后对本地环境进行了整理:
- 退出本地代理工具,将 Windows 系统代理关闭(
ProxyEnable = 0),重置 WinHTTP 代理,保持单一网络栈; - 服务模式与 GUI 模式的区别:WireGuard for Windows 区分 Manager Service 与每条隧道对应的 Tunnel Service,两者可以共同使用,也可以独立运行。因此,通过
wireguard /installtunnelservice单独安装的服务式隧道,不应仅凭 GUI 左侧列表是否显示来判断其运行状态,可结合Get-Service和wg show检查。 - 多设备配置注意:后续添加手机或其他设备时,每台设备需要使用独立的公私钥和唯一的内网 IP(如
10.66.66.4/32),不能直接共用电脑的配置文件。所有客户端均可统一使用<server-ip>:48321作为 Endpoint 连接。服务端添加 Peer 后,执行bash -c 'wg syncconf wg0 <(wg-quick strip wg0)'即可生效,无需重启整个服务。
经验总结
本次故障的关键经验有三点:
- 区分磁盘配置与内核运行态:WireGuard 排查时需区分
/etc/wireguard/wg0.conf(磁盘配置)与wg show(内核运行态),避免因临时修改导致两者脱节; - 双端抓包快速判定丢包方向:看到客户端 TX > 0 / RX = 0 时,应尽快通过服务端抓包和客户端抓包判断丢包方向,而不是持续修改密钥和路由;
- 严格的单变量对照实验:涉及端口异常时应做严格的单变量对照实验(如同一 NAT 下不同源端口的对比),而不是直接推断为运营商、防火墙或某一具体设备的问题。
文中 IP、端口、公钥及接口名均已脱敏或使用示例值。
本文由 择梦舟 执笔撰作,收录于 Zemengzhou Space。
Discussion
Comments
Share questions, corrections, or extra notes about this post.