对 192.0.2.10 执行 ping 只能说明有一个地址回应了 ICMP 回显,仅此而已。在没有隧道的网络里它同样可能成功,因为该地址也许就在本地子网上;而隧道正常时它也可能失败,因为服务器在提供 HTTPS 的同时丢弃 ICMP。脚本真正需要的检查是查询平台状态,再加上针对应用所用目的地的路由测试。请把这两者当作两个独立字段,绝不要合并成一个布尔值。

回显应答究竟证明了什么

ICMP 是一种可达性协议,它并不认识接口或隧道。收到应答时,内核只报告有主机作出了回应,并不报告请求走了哪条路由、应答又走了哪条路由。过滤会让信号更加嘈杂,因为拦截回显请求的防火墙对其后方的端口一无所知。而分流隧道配置可以特意排除被测试的地址,同时让其他所有目的地照常工作。

你真正想知道的是:某个具体服务能否沿应用实际经过的路径使用。选定应用所调用的主机和端口,然后在应用层要求一个应答。

可达性和隧道状态是两件不同的事,其中一个为真时另一个可能为假。把二者分开,你才知道故障该由谁负责。把它们塞进同一个检查里,只会产生一张没人能派单的工单。

向 Android 询问传输方式,而不是 ping

Android 通过 ConnectivityManager 直接回答状态问题。活动网络的能力中带有 VPN 传输标志,当流量经由 VPN 接口路由时该标志会被置位。这个调用是本地操作、开销很小,而且与是否有远程主机应答无关。

这个 Java 版本在没有活动网络时返回 false,而不是抛出异常。因为它读取的是活动网络,所以反映的是用户在设置中选择的那份配置。

ConnectivityManager cm = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
NetworkCapabilities caps = cm.getNetworkCapabilities(cm.getActiveNetwork());
boolean tunnel = caps != null && caps.hasTransport(NetworkCapabilities.TRANSPORT_VPN);

需要变更通知时,请注册 NetworkCallback,而不是用定时器轮询。不要把它与 VpnService.prepare 混淆,后者只报告用户是否授予了 VPN 权限,而不报告数据包是否在流动。授权被撤销会通过 VpnService.onRevoke 送达,但在设置中忘记一份配置却完全不会产生广播。

iOS:接口名称只是经验判断,不是约定

许多 iOS 代码片段会检查代理设置,找到 __SCOPED__ 字典,再寻找 tap、tun、utun2、ppp 或 ipsec 之类的接口键。这种做法只是一种命名惯例,并非有文档保证的契约,因此一次重命名接口的系统更新就可能让它失效。它还倾向于对存活时间超过隧道的陈旧条目报告 true。

当配置由你的应用自己持有时,NEVPNManager 会给出真实状态。它描述的是你的应用所启动的隧道;在使用按应用划分的 VPN 时,其他应用可能完全位于该隧道之外。

读取状态之前请重新加载偏好设置,因为隧道起落时该值会变化:

let manager = NEVPNManager.shared()
manager.loadFromPreferences { error in
    let up = manager.connection.status == .connected
    print(up)
}

平台的状态标志仍然不能证明流量能到达对端。正是这个缺口由路由测试来填补,这也是为什么单一布尔值不足以支撑排障。

Windows 配置文件提供真实的状态字段

Windows 把 VPN 配置文件与网卡分开跟踪,Get-VpnConnection 会为它认识的每份配置公开 ConnectionStatus。通过 Windows 客户端或预配包创建的配置会出现在这里。这个 cmdlet 并不是通用的隧道探测器。

读取指定名称配置的字段,当该配置属于所有用户时再加上 -AllUserConnection:

Get-VpnConnection -Name "Fabrikam" | Select-Object Name, ConnectionStatus, ServerAddress

第三方客户端即使隧道已建立,也常常对这个 cmdlet 不可见,因此空结果意味着未知,而不是已断开。网卡列表同样容易误导:厂商网卡可以在空闲状态下继续存在,有些网卡只在会话期间存在。对于 Windows 自己管理的配置,rasdial 会报告一个可用于分支判断的退出码。所有这些描述的都是本机认知,而不是对端的可达性。

状态加路由:两段式检查

一份已连接的配置并不能说明数据包去了哪里。分流隧道可以在保持隧道的同时排除你在意的目的地。始终连接配置会在服务器已经死掉时仍显示连接,而 kill switch 会在隧道断开时故意阻断流量。

请测试两层:先确认 VPN 传输或配置处于活动状态,再确认目的地能沿该路径解析并应答。在 Linux 上,可以问内核它会使用哪个接口:

ip route get 192.0.2.10
resolvectl query intranet.example.test

当客户端不在你的控制之下时,请把两个结果都记录下来,让模式指向出问题的那一层。已连接但路由测试失败提示分流隧道、DNS 或策略问题。已断开但路由测试通过,说明流量走的是另一条路径。

可以按顺序执行的测试

先记录平台状态,再改动任何网络状态。解析主机名并记下地址族,因为 AAAA 记录可能走与 A 记录不同的路径。打开服务端口,要求拿到应用层应答,而不是仅仅完成握手。把每一项观测放进各自的日志字段。

在装有 dig 和 curl 的 Linux 机器上,三条命令就能覆盖解析、路由和服务本身。请在隧道开启时运行一遍,断开后再运行一遍:

dig +short intranet.example.test
ip route get 198.51.100.20
curl -sS -o /dev/null -w "%{http_code}" --max-time 8 https://intranet.example.test/health

四个独立字段胜过单一布尔值,因为状态、DNS、路由和策略会各自独立地失败,并落到不同的团队手里。终端里的 ping 依然是很好的手工核对手段。但对自动化而言它是糟糕的连通性定义,那里应当由平台 API 和路由测试来回答真正的问题。

相关文章