为什么 23 小时 VPN 提醒会为错误的会话触发
23 小时提醒应当跟随单个会话,而不是账号。把计时器绑定到那次开始的连接,并把休眠与重连当作两种独立情况处理。
连接二十分钟后,提醒出现了。它下面的计时器从昨天早上开始走,或者从账号上次认证时开始走,而提醒里没有任何文字说明是哪一个。这就是症状:一条绑在别的东西上、而不是绑在眼前这条连接上的提醒。
解决办法不是更好的提醒。而是先确定需求指的是哪一只钟,再把计时器绑到那只钟里存在的对象上。
什么被触发了,它又在数什么
三个可能的来源会产生同一条消息。日历规则在固定的本地时间运行,对隧道一无所知。客户端里的计数在任意连接存续期间不断累加,并能跨越重连。网关策略在服务端让会话过期,并先推送一条警告。
只有后两个与某个会话有关,而它们彼此也不一致。客户端计数衡量的是本地进程观察到的东西;网关衡量的是它认证并计费的东西。休眠、网络切换或观察者重启可以让两者相差数小时,这正是警告与强制到期逐渐分离的原因。
一个需求背后的三只钟
先把你真正想要的那句话写下来。在网关结束这条连接之前发出警告,与在一天连接时间之后提醒,并不是同一个需求。它也不等于报告“这个会话已经持续 23 小时”。每个版本命名的是不同的事件,也要由不同的组件来回答。
然后检查数据是否存在。本地开始时间戳能在不依赖服务端的情况下给出会话年龄。网关策略能给出剩余寿命,但前提是网关愿意暴露它。用量报告给的是事后的时长,它无法驱动提前通知。
把计时器绑到会话上,而不是账号上
先选对象。一个账号可能挂着多台设备和多个同时进行的会话,所以按用户名绑定的提醒说不清是哪条连接到了那个年龄。把它绑到配置文件加上那一次激活上,并在激活结束时取消待发的提醒。
| Evidence you can read | What it establishes | Where it misleads |
|---|---|---|
| Connection state changes to active | A session started now | Does not carry the gateway's own session identifier |
| Duration reported at disconnect | Total service time for that session | Arrives too late for any advance notice |
| Interim usage update | Time accounted up to that update | Staleness equals the update interval |
| Local timestamp taken at activation | Age of your observation | Resets whenever the observer restarts |
那张表是给你自己的通知器用的契约,不是任何客户端回调 API 的说明。已安装产品对连接状态记录了什么,才是应当绑定的来源;如果它只暴露认证事件,就大声说出来,并接受更粗的行为,而不是去推断一个会话。
用很短的阈值去验证
永远不要用一整天的等待去调试一整天的计时器。加一个可配置的阈值,把它设成三十秒,然后按顺序跑四种转换:连接、等待、断开、重连。每种转换应当只产生一次状态变化,而重连应当安排新的期限,而不是继承旧的。
# current connection state and profile identity
nmcli -f GENERAL.STATE,GENERAL.CONNECTION connection show work
# the notifier unit you wrote: does it restart the deadline or resume it?
journalctl --user -u vpn-timer.service --since "-5 min" -o cat
看时间戳,而不是看措辞。带着上一个会话开始时间到达的提醒绑错了对象;同一个期限到来两次的提醒,说明已发送标志从来没有被持久化。
休眠、重连与错过的那次投递
休眠会让两种朴素设计同时失效。单调倒数在机器睡眠时停住,挂钟期限继续移动,而睡眠期间触发的计时器会延迟送达或干脆不送达。先决定你的需求要哪种行为,然后让机器跨过阈值睡一觉再确认。
投递也值得同样的谨慎。如果提醒无法显示,就先排队,并在最终显示之前重新确认目标会话仍然存在;否则用户会被提醒一条已经结束的连接。强制断开和凭据续期留在这条路径之外,因为一条悄悄改变连接命运的提醒,是披着通知外衣的策略决定。