VPN 连接监控与 Telegram 实时通知
# 背景
之前部署了 IKEv2 VPN 供个人使用,但一直不知道有没有其他人在使用。每次想查看连接状态都要手动登录服务器执行命令,非常麻烦。
需求很简单:
- 有人连接 VPN 时收到通知
- 有人断开 VPN 时收到通知
- 通知要推送到手机,方便随时查看
本文记录从零到完成 VPN 监控通知系统的完整过程,包括踩过的坑和最终方案。
# 一、最初方案:定时检查
# 1.1 思路
最直观的想法:写个脚本定时检查 VPN 连接数,有变化就发通知。
#!/bin/bash
# 获取当前 VPN 连接数
get_connections() {
ip xfrm state list 2>/dev/null | grep "proto esp" | wc -l
}
current_count=$(get_connections)
actual_count=$((current_count / 2)) # 每个 VPN 连接有 2 个 SA
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# 1.2 问题
实际测试发现,VPN 连接时长如果很短(比如 1-2 分钟),而检查间隔是 1 分钟,很容易漏掉:
时间轴:
22:03:00 - 用户连接 VPN
22:04:00 - 脚本检查(检测到连接,发送通知)
22:04:30 - 用户断开 VPN
22:05:00 - 脚本检查(SA 已删除,检测不到变化)
1
2
3
4
5
2
3
4
5
结果就是:连接通知发出了,但断开通知永远发不出。
# 二、进阶方案:实时日志监控
# 2.1 思路
既然轮询会漏事件,那就改成实时监控 strongswan 日志:
journalctl -u strongswan-starter -f | while read -r line; do
# 检测连接
if echo "$line" | grep -q "IKE_SA.*established"; then
# 发送通知
fi
# 检测断开
if echo "$line" | grep -q "deleting IKE_SA"; then
# 发送通知
fi
done
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# 2.2 新问题:重复通知
测试时发现,同一条断开日志在 journalctl 里出现了多次:
Jun 06 22:19:54 charon[12345]: 10[IKE] deleting IKE_SA ikev2-vpn[1343] between...
Jun 06 22:19:54 charon[12345]: 10[IKE] deleting IKE_SA ikev2-vpn[1343] between...
1
2
2
结果就是断开时收到了多条重复通知。
# 2.3 解决方案:SA ID 去重
从日志中提取 SA 编号,用变量记录已处理的 SA:
processed_connected=""
processed_disconnected=""
# 连接时
if echo "$line" | grep -q "IKE_SA.*established between"; then
sa_id=$(echo "$line" | grep -oP 'IKE_SA[^[]+\[\K[0-9]+')
if [ -n "$sa_id" ] && ! echo "$processed_connected" | grep -q ":$sa_id:"; then
processed_connected="$processed_connected:$sa_id:"
send_notify "CONNECTED" "$remote_ip" "$timestamp"
fi
fi
# 断开时(同理)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# 三、通知方案演进
# 3.1 方案一:Cron + Agent
最初尝试用 OpenClaw 的 cron 任务:
- systemd 服务监控日志,检测到事件时写入通知文件
- cron 任务每分钟检查通知文件
- 如果文件存在,调用 agent 发送 Telegram 消息
问题:
- agent 启动慢(15-30 秒),经常超时
- 文件不存在时 agent 直接报错
- 每次执行都要加载大量上下文,浪费资源
# 3.2 方案二:直接调用 CLI
最终发现 OpenClaw 提供了直接的命令行发送消息功能:
openclaw message send --channel telegram --target YOUR_CHAT_ID --message "通知内容"
1
这个命令执行只需 1-2 秒,完美解决问题!
# 四、最终方案
# 4.1 监控脚本
创建 /home/moyin/vpn-monitor/vpn-monitor.sh:
#!/bin/bash
# VPN 实时日志监控 - 直接发送 Telegram 通知
# 记录已处理的 SA,避免重复通知
processed_connected=""
processed_disconnected=""
# 发送 Telegram 通知
send_notify() {
local event="$1"
local ip="$2"
local timestamp="$3"
if [ "$event" = "CONNECTED" ]; then
local msg="🔒 VPN 连接通知
✅ 新设备已连接
📍 IP: ${ip}
🕐 时间: ${timestamp}"
else
local msg="🔒 VPN 连接通知
❌ 设备已断开
📍 IP: ${ip}
🕐 时间: ${timestamp}"
fi
# 使用 OpenClaw CLI 发送消息
openclaw message send --channel telegram --target YOUR_CHAT_ID --message "$msg" 2>/dev/null
}
# 监控 strongswan 日志
journalctl -u strongswan-starter -f --no-pager 2>/dev/null | while read -r line; do
timestamp=$(date '+%Y-%m-%d %H:%M:%S')
# 新连接建立
if echo "$line" | grep -q "IKE_SA.*established between"; then
sa_id=$(echo "$line" | grep -oP 'IKE_SA[^[]+\[\K[0-9]+')
remote_ip=$(echo "$line" | grep -oP '\.\.\.([0-9.]+)' | sed 's/\.\.\.//')
if [ -n "$sa_id" ] && ! echo "$processed_connected" | grep -q ":$sa_id:"; then
processed_connected="$processed_connected:$sa_id:"
processed_disconnected=$(echo "$processed_disconnected" | sed "s/:$sa_id://g")
send_notify "CONNECTED" "$remote_ip" "$timestamp"
fi
fi
# 连接断开
if echo "$line" | grep -q "deleting IKE_SA"; then
sa_id=$(echo "$line" | grep -oP 'IKE_SA[^[]+\[\K[0-9]+')
remote_ip=$(echo "$line" | grep -oP '\.\.\.([0-9.]+)' | sed 's/\.\.\.//')
if [ -n "$sa_id" ] && ! echo "$processed_disconnected" | grep -q ":$sa_id:"; then
processed_disconnected="$processed_disconnected:$sa_id:"
send_notify "DISCONNECTED" "$remote_ip" "$timestamp"
fi
fi
done
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# 4.2 Systemd 服务
创建 /etc/systemd/system/vpn-monitor.service:
[Unit]
Description=VPN Real-time Log Monitor
After=network.target strongswan-starter.service
Requires=strongswan-starter.service
[Service]
Type=simple
ExecStart=/home/moyin/vpn-monitor/vpn-monitor.sh
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# 4.3 启用服务
chmod +x /home/moyin/vpn-monitor/vpn-monitor.sh
systemctl daemon-reload
systemctl enable vpn-monitor
systemctl start vpn-monitor
1
2
3
4
2
3
4
# 五、效果展示
连接 VPN 时收到通知:
🔒 VPN 连接通知
✅ 新设备已连接
📍 IP: 203.0.113.50 (文档示例 IP)
🕐 时间: 2026-06-06 22:25:30
1
2
3
4
5
2
3
4
5
断开 VPN 时收到通知:
🔒 VPN 连接通知
❌ 设备已断开
📍 IP: 203.0.113.50 (文档示例 IP)
🕐 时间: 2026-06-06 22:26:15
1
2
3
4
5
2
3
4
5
# 六、技术要点总结
| 问题 | 解决方案 |
|---|---|
| 轮询漏事件 | 改用实时日志监控(journalctl -f) |
| 重复通知 | SA ID 去重 |
| Agent 超时 | 直接用 CLI 发送消息 |
| 服务自动启动 | Systemd 服务管理 |
# 七、扩展思路
这个方案还可以扩展:
- 多渠道通知:除了 Telegram,还可以发送到企业微信、钉钉、邮件等
- 连接统计:记录连接时长、频率,生成使用报告
- 异常检测:比如短时间内频繁连接/断开,可能有问题
- IP 地理位置查询:通知中显示连接者的地理位置
# 八、参考链接
- 02
- Claude Code Java 开发常用插件指南06-02
- 03
- Windows开发环境配置指南05-28