两件事常被报成同一个故障:笔记本换了 Wi-Fi 网络,或者在 Wi-Fi 仍在运行时插了网线。客户端仍显示已连接,图标看着正常,隧道却什么也传不过去。底下能移动的只有三块状态——链路、本地地址、默认路由——而移动的是哪一块,决定了隧道会自己恢复还是必须重连。我按这个顺序检查,因为改账号设置从来没修好过其中任何一块。

链路、地址与路由各自独立移动

同一网络内接入点之间的漫游通常是 L2 事件。射频重新关联,接口保留自己的地址和网关,隧道依赖的东西没有变,所以穿行于大办公室有时会悄无声息地过去。加入另一个 SSID 不是这样:旧接口可能消失,DHCP 会发下新地址、新网关和新解析器,隧道所用的那条外层流也随之不复存在。

Ethernet 加上了第三种形态。Wi-Fi 从不掉线,所以根本不会触发链路事件,但有线接口会带着自己的地址出现,并且常常按 metric 赢下默认路由。隧道继续走在一条已不再优先的路径上,或者客户端把自己的 socket 钉在最初接入的无线网络上。从浏览器看两者一模一样:网线一到位,页面就卡住。

所以第一个问题不是 Wi-Fi 有没有变。而是你绑定的那个地址,或者通往端点的路由,在隧道运行期间有没有变。

外层 socket 被钉在一个刚刚消失的地址上

每条隧道都是一条外层流:一个本地地址和端口,与服务器的地址和端口对话。在 Linux 上那是一个绑定旧源地址的 UDP socket;当地址所在的接口被移除后,发送要么以 EADDRNOTAVAIL 失败,要么从新接口发出、带上服务器从未见过的源地址。

远端会保留自己的钉扎。没有 MOBIKE 的 IKEv2 SA 靠地址对来标识,而一条 OpenVPN会话绑定在它学到的对端地址上,NAT 映射则属于创造它的私有地址、端口和公网地址。换到另一个 NAT 后面,每个回包都会送到一个已不存在的映射。你机器上没有任何配置出错;五元组指向的是一个你已经离开的网络。

先读状态,再做推断:

  • ss -unp显示每条隧道 socket 实际持有的本地地址和端口。
  • ip route get 203.0.113.10打印内核此刻为端点会选的源地址和设备。
  • wg show wg0 endpoints、swanctl --list-sas或客户端日志会显示远端认为正在对话的对端地址。

如果 socket 地址与路由查询结果不一致,你就找到了断点,任何凭据变更都碰不到它。

各协议面对新源地址会怎么做

各客户端消化这次变化的能力不同,而我用来与客户端配置对照的正是这张表,而不是从重连行为去猜。

TunnelRecovery behaviourFailure signature
WireGuardUpdates the peer endpoint when an authenticated packet arrives from a new address; no handshake needed if traffic continues.Handshake current, no return traffic; wg show still lists the old endpoint.
IKEv2/IPsecMOBIKE (RFC 4555) moves the SA to the new address; without it the responder discards packets from an unknown source.DPD or keepalive timeouts, then a full rekey or a dead SA.
OpenVPN over UDP--float accepts a packet from a new source mid-session; otherwise the session stays tied to the original peer address.Idle until ping-restart fires, then a visible reconnect.
OpenVPN over TCPThe tunnel is a byte stream, so a changed source address breaks it outright.Immediate reset and a new connection attempt.

WireGuard 的漫游规则只在一个方向成立:客户端必须先从新地址发出。仍绑定在旧接口上的 socket 永远不会这么做,所以隧道看起来还活着,它的握手计时器却在变旧。协议描述会精确说明端点更新的条件,改动配置之前值得读一遍。

MOBIKE 是协商来的,不是假定的。服务器不提供它,客户端就必须重建,这是你从 SA 列表或日志里确认的事实,而不是从转圈的指示器里推断出来的。

决定路径的是路由优先级,不是 Wi-Fi 图标

两个接口都在时,内核按 metric 和前缀长度来选择。ip route show default列出候选,ip route get 203.0.113.10则给出真正的裁决,包括源地址。有线默认路由 metric 100、无线 600,意味着网线一落下端点就移位,而此前已打开 socket 的每个应用仍保留旧源地址。

Windows 用自动接口 metric 做同样的事,并受链路速率影响。Get-NetRoute -DestinationPrefix 0.0.0.0/0对候选排序,Find-NetRoute -RemoteIPAddress 203.0.113.10返回协议栈会选的接口和源地址。把外层 socket 绑在某个接口上的客户端,在 metric 翻转之后仍继续用它;那是绑定决定,不是路由故障。

Android 把这种分裂显式化:ConnectivityManager 报告可用网络和单独的默认值,把 socket 绑定到某个 Network 对象的 VPN 应用会一直持有这条绑定,直到重新绑定。在 macOS 上,scutil --nwi打印主接口以及系统会采用的顺序。

DNS、MTU 与 IPv6 随连接一起移动

新连接带来新解析器。用 systemd-resolved 时 DNS 是按链路的,resolvectl status会显示哪个链路在回答某个名字,而分发自己解析器的访客网络,可能让隧道推下去的 DNS 一直闲置,直到该链路重新成为首选。强制门户会让这更糟:第一个 HTTPS 请求由门户而非端点回答,客户端于是记下一个死掉的服务器。

路径 MTU 也会变。Ethernet 和 Wi-Fi 通常承载 1500 字节,PPPoE 是 1492,移动链路常常更少。继承了更小 MTU 的隧道会卡在大包上,而小请求仍能通过,看起来就像一条只坏了一半的连接。ping -M do -s 1472 203.0.113.10会发送一个带 DF 标志的 1500 字节包;没有回应就说明路径比你设想的更小。

IPv6 是另一半。分发新前缀的网络会改变双栈客户端使用的源地址,而只走 v4 的隧道会把 v6 流量留在外面,直到策略跟上。ip -6 route show default和ip -6 addr show会回答新链路到底有没有 v6 路径。

与其描述,不如复现这次切换

挑一个无害的请求,在切换的两侧各跑同样的三条命令:ip -br addr、ip route show default、ip route get 203.0.113.10。然后观察隧道是否还在发包——tcpdump -ni any udp port 51820对 WireGuard 就是这一条,其它情况换用对应的端口。包从旧源地址出去,说明是绑定问题。包从新源地址出去却没有回应,说明问题在远端或中间的 NAT。

记下第一个故障同时发生了哪个事件:链路断开、地址变更,或默认路由变更。它们指向不同的责任方——需要重新绑定的客户端、需要重新获取的地址,或者属于配置文件而不是常驻绕行规则的路由。

在说修好之前,先确认直连流量的拦截在整个空档里仍然有效,并且名字能通过新连接解析。如果切换后客户端还抱着旧端点,先修漫游或 float 设置。如果它漫游正确却仍拿不到回应,就去看服务器上的对端身份和路径上的 NAT,并把两个网络上下文都留在记录里,好让下一个人对照。

相关文章