我的笔记本在学校图书馆连 Wi-Fi 会断网。手机在同一个 Wi-Fi 下好好的。
排查了大半天,最后定位到:是我的 VPN(Clash 系,TUN 模式)启动太早了。
这台笔记本开机后我如果不开 VPN,一切正常。开了 VPN 去图书馆,必断。
一、为什么 TUN 模式的 VPN 会让图书馆连不上网
TUN 模式在干什么
普通代理模式(系统代理 / HTTP 代理)只接管明确配置了走代理的应用。TUN 模式不一样 —— 它在系统里创建一张虚拟网卡,然后把默认路由抢过来:
正常状态: 0.0.0.0/0 → 192.168.1.1 via WLAN (metric 25) ✓ 生效 0.0.0.0/0 → 10.0.0.1 via Clash-TUN (metric 5) ✗ 被压制
TUN 启动后: 0.0.0.0/0 → 10.0.0.1 via Clash-TUN (metric 5) ✓ 现在生效 0.0.0.0/0 → 192.168.1.1 via WLAN (metric 25) ✗ 还在表里,但不承载流量Windows 同时只会用一条默认路由(多条时按 metric 取最小)。TUN 的目标就是把这条唯一的名额抢走,这样才能做到”全局代理”。
但图书馆的 WLAN 有一个特殊之处
家里的 Wi-Fi 连上就是通的。学校图书馆的 WLAN 不是 —— 它通常要过 802.1X(企业级认证) 或 Portal(网页认证)。这个过程有几个硬性要求:
(物理层已连接)"] --> B["4 次握手
拿到 IP / 网关 / DNS"] B --> C{"需要认证吗?"} C -->|家用WiFi| H["可以直接上网 ✓"] C -->|图书馆 802.1X / Portal| D["必须能发出
未认证的 EAPOL / Portal 流量"] D --> E{"TUN 已接管默认路由?"} E -->|否 未启动| F["认证包正常走 WLAN ✓"] E -->|是 已启动| G["认证包被路由进 TUN
或被 kill-switch 丢弃 ✗"] G --> I["认证一直不完成
= 连上了 Wi-Fi 但没网"]
问题的本质:“物理上关联成功”和”能上网”之间,还隔着一个必须依赖默认路由的认证阶段。 而 TUN 恰好在这个阶段把默认路由拿走了。
具体的死法有几种,通常几个一起
无法改走物理网卡 OS->>AP: 发起 802.1X 认证 / Portal 探测 alt strict-route / kill-switch 开启 AP--xOS: 认证响应被丢弃 Note over OS,AP: ✗ 永远认证不通过 else 认证包被路由进 TUN OS->>V: 认证包进了隧道 V--xAP: 出不去,或回包进不来 Note over OS,AP: ✗ 同样卡死 end V->>OS: DNS 已劫持到隧道内 (1.1.1.1 等) Note over OS,V: 要解析出口域名才能建隧道,
但 DNS 在隧道里 → 先有鸡还是先有蛋 Note over OS: 重试 → 认证失败 → 认证不通过 → 系统拿不到 IP
把上面压缩成一句话:
TUN 模式假设”你已经能上网了”。而图书馆的 WLAN 假设”你还没认证完”。两个假设互相打架。
还有一个更隐蔽的:DNS 死锁。TUN 模式通常会把 DNS 也劫持到隧道内的公共 DNS(1.1.1.1 / 8.8.8.8)。而隧道要建连,得先能解析出口服务器的域名 —— 解析请求被送进一个还没建好的隧道。配置不讲究的客户端就会一直卡在这里反复重试。
WARNING成熟客户端一般会维护一份 bypass 列表,把出口服务器 IP 和内网网段排除在隧道之外,避免自噬。这份列表配得好不好,直接决定你会不会遇到”自己把自己套进去”。
二、解法:把 VPN 启动推迟到”网络就绪之后”
不要在开机时启动 VPN。等系统告诉你”网络连接已经建立”,再启动。
Windows 里这件事的官方触发器就是 Microsoft-Windows-NetworkProfile/Operational 事件日志的 EventID 10000(网络连接建立)。
任务计划程序 → 创建基本任务 → 触发器 → 自定义 → 事件触发器:
| 项 | 值 |
|---|---|
| 日志 | Microsoft-Windows-NetworkProfile/Operational |
| 事件 ID | 10000 |
| 任务 | 启动 VPN 客户端 |
我本机跑的任务实际长这样(\用户程序库\Clash Network Start):
<?xml version="1.0" encoding="UTF-16"?><Task version="1.3" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task"> <Principals> <Principal id="Author"> <UserId>S-1-5-21-xxxxxxxx-1001</UserId> <LogonType>InteractiveToken</LogonType> </Principal> </Principals> <Settings> <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries> <StopIfGoingOnBatteries>false</StopIfGoingOnBatteries> <ExecutionTimeLimit>PT0S</ExecutionTimeLimit> <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy> <UseUnifiedSchedulingEngine>true</UseUnifiedSchedulingEngine> </Settings> <Triggers> <EventTrigger> <Subscription><QueryList><Query Id="0" Path="Microsoft-Windows-NetworkProfile/Operational"><Select Path="Microsoft-Windows-NetworkProfile/Operational">*[System[Provider[@Name='Microsoft-Windows-NetworkProfile'] and EventID=10000]]</Select></Query></QueryList></Subscription> </EventTrigger> </Triggers> <Actions Context="Author"> <Exec> <Command>"C:\Program Files\YourVPN\YourVPN.exe"</Command> </Exec> </Actions></Task>一键注册脚本(PowerShell,管理员)
$xml = @'<?xml version="1.0" encoding="UTF-16"?><Task version="1.3" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task"> <RegistrationInfo><Author>me</Author></RegistrationInfo> <Principals> <Principal id="Author"> <UserId>DOMAIN\user</UserId> <LogonType>InteractiveToken</LogonType> </Principal> </Principals> <Settings> <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries> <StopIfGoingOnBatteries>false</StopIfGoingOnBatteries> <ExecutionTimeLimit>PT0S</ExecutionTimeLimit> <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy> </Settings> <Triggers> <EventTrigger> <Subscription><QueryList><Query Id="0" Path="Microsoft-Windows-NetworkProfile/Operational"><Select Path="Microsoft-Windows-NetworkProfile/Operational">*[System[Provider[@Name='Microsoft-Windows-NetworkProfile'] and EventID=10000]]</Select></Query></QueryList></Subscription> </EventTrigger> </Triggers> <Actions Context="Author"> <Exec><Command>"C:\path\to\vpn-client.exe"</Command></Exec> </Actions></Task>'@
Register-ScheduledTask -TaskName "VPN On Network Up" -Xml $xml -Force验一下:
Get-ScheduledTask -TaskName "VPN On Network Up" | Select-Object TaskName, State三、你问的那两个坑:你的理解是对的,而且很常见
坑 1:默认 72 小时后任务被杀掉
是真的。
Task Scheduler 的 ExecutionTimeLimit(“如果任务运行时间超过此时间,则停止任务”)默认值是 PT72H,也就是 72 小时。文档里的原话是”如果未指定该值,默认使用 72 小时”。
一个开机常驻的任务 —— 比如 VPN 客户端 —— 跑满三天就会被系统主动干掉,而且:
- 日志里只记一句”任务因超出时间限制而终止(任务已完成)”
- 不会弹任何提示
- 你不会意识到是计划任务干的
改法:PT0S = 不限时。
<ExecutionTimeLimit>PT0S</ExecutionTimeLimit>坑 2:拔电源会停任务
也是真的,而且是两个独立的开关。
任务计划程序创建任务时,GUI 里默认勾着「如果计算机使用电池电源,则停止任务」。这个勾选会写出两个 XML 节点:
<DisallowStartIfOnBatteries>true</DisallowStartIfOnBatteries> <!-- 电池上干脆不启动 --><StopIfGoingOnBatteries>true</StopIfGoingOnBatteries> <!-- 运行中拔电就杀掉 -->两个默认都是 true。表现是:笔记本拔掉电源线的那一瞬间,VPN 客户端进程被计划任务管理器直接结束。
改法:两个都置 false。
<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries><StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>等价的 cmdlet 写法:
New-ScheduledTaskSettingsSet ` -ExecutionTimeLimit ([TimeSpan]::Zero) ` -AllowStartIfOnBatteries ` -DontStopIfGoingOnBatteries ` -MultipleInstances IgnoreNewTIP我本机这个任务的 XML 里这两个坑都已经改过来了(
PT0S+ 两个false)。所以如果你现在遇到的是我之前那个症状 —— 图书馆连不上网但别的地方正常 —— 那就不是这两个默认值的问题,回到第一节的路由抢占去看。
顺带第三个坑:IdleSettings
有些任务里还会看到这一段:
<IdleSettings> <Duration>PT10M</Duration> <WaitTimeout>PT1H</WaitTimeout> <StopOnIdleEnd>true</StopOnIdleEnd> <RestartOnIdle>false</RestartOnIdle></IdleSettings>它的含义是「仅当计算机空闲至少 10 分钟时才启动任务」,加上「一旦不空闲就停止」。只有 <RunOnlyIfIdle>true</RunOnlyIfIdle> 时才生效 —— 默认是 false,所以一般无害。
但如果你哪天在 GUI 里手滑勾上了「仅当计算机空闲时才启动任务」,拔电源断网这种玄学问题就又能重现了。
四、EventID 10000 的两个副作用
它不只对 Wi-Fi 触发
EventID 10000 是任何网络配置建立都会触发,包括:
- 有线网
- 手机 USB 网络共享
- VPN 自己那张虚拟网卡重连
- Docker / WSL2 虚拟交换机
所以完全可能出现”VPN 自己断了 → 触发 10000 → 计划任务又去启动 VPN → 反复重启”。
想严格只认 Wi-Fi,把动作改成调一个脚本做判断:
$wifi = Get-NetAdapter | Where-Object { $_.Status -eq 'Up' -and $_.NdisPhysicalMedium -eq 9 }if ($wifi) { Start-Process 'C:\Program Files\YourVPN\YourVPN.exe' }NdisPhysicalMedium -eq 9 就是 Native 802.11。
事件日志通道必须处于启用状态
Microsoft-Windows-NetworkProfile/Operational 默认是开的。但如果被组策略关掉或者日志被清空过,触发器会静默失效 —— 任务状态显示 Ready,永远不触发,也没有任何错误提示。
验证:
wevtutil gl Microsoft-Windows-NetworkProfile/Operational | Select-String enabled五、排查这类问题的通用顺序
以后再遇到”某个网络下连不上”,按这个顺序查,能少走很多弯路:
1. 关掉 VPN 试试 └─ 好了 → 是路由/DNS/防火墙问题,不是 Wi-Fi 的问题2. route print 看 0.0.0.0/0 有几条、哪条 metric 最小 └─ TUN 的那条生效 → 就是本文说的抢占3. nslookup 域名 + ping 网关 └─ 网关通、域名不通 → DNS 劫持/死锁4. ipconfig /all 看 IPv4 是不是 169.254.x.x └─ 是 → DHCP 续租失败,认证阶段就被卡住了5. netsh wlan show interfaces 看 认证状态WARNING第 2 步是分水岭。如果你看到一个软件的虚拟网卡出现在默认路由里且 metric 最小,那基本可以确定问题出在它身上,不用再往 Wi-Fi 配置上找。
小结
- 图书馆连不上网的根因:TUN 抢占了默认路由,而 802.1X / Portal 认证必须在认证完成前使用默认路由。TUN 假设”你已能上网”,图书馆 WLAN 假设”你还没认证”,直接对撞。
- 解法:用计划任务监听
Microsoft-Windows-NetworkProfile/Operational的 EventID 10000,等网络建立后再启动 VPN。 - 你记得的两个默认坑都是真的:
ExecutionTimeLimit默认PT72H(改成PT0S);DisallowStartIfOnBatteries/StopIfGoingOnBatteries默认true(改成false)。 - 还有第三个:
IdleSettings的StopOnIdleEnd,勾上「仅空闲时运行」就会重现玄学掉线。 - EventID 10000 不区分网络类型,严谨点要加 Wi-Fi 判断,否则 VPN 重连会自触发。
Windows 计划任务这玩意儿的默认值都是为”跑一次就完事的短任务”设计的。你一旦用它跑常驻服务,就得把这几个默认一个个翻过来 —— 症状还都是”过几天突然不好使了”,不查根本想不到是它。