悦书阁 悦书阁
首页
学习笔记
技术文档
AI技术
更多
  • 分类
  • 标签
  • 归档

Felix

大道至简 知易行难
首页
学习笔记
技术文档
AI技术
更多
  • 分类
  • 标签
  • 归档
  • 部署文档

    • 博客搭建
    • Jenkins教程
    • Docker安装和简单使用
    • minio安装
    • Mysql安装
    • Redis安装
    • Docker部署服务
    • IKEv2 VPN 部署教程
    • Windows开发环境配置指南
    • VPN 连接监控与 Telegram 实时通知
      • 背景
      • 一、方案演进
        • 1.1 最初方案:定时检查(IKEv2 时代)
        • 1.2 进阶方案:实时日志监控(IKEv2)
        • 1.3 当前方案:Shadowsocks 日志监控
      • 二、最终方案
        • 2.1 架构说明
        • 2.2 监控脚本
        • 2.3 Systemd 服务
        • 2.4 启用服务
      • 三、效果展示
      • 四、技术要点总结
      • 五、工作原理
      • 六、注意事项
      • 六、历史方案(已废弃)
        • IKEv2 日志监控脚本(已废弃)
      • 七、参考链接
    • Windows Gitea 搭建完整指南
    • XXL-JOB 分布式任务调度平台部署
  • 常用手册

  • 常用工具

  • 经验技巧

  • 电脑配置

  • 技术文档
  • 部署文档
moyin
2026-06-22
目录

VPN 连接监控与 Telegram 实时通知

# 背景

之前部署了 VPN 供个人使用,但一直不知道有没有其他人在使用。每次想查看连接状态都要手动登录服务器执行命令,非常麻烦。

需求很简单:

  • 有人连接 VPN 时收到通知
  • 有人断开 VPN 时收到通知
  • 通知要推送到手机,方便随时查看

本文记录从零到完成 VPN 监控通知系统的完整过程,包括踩过的坑和最终方案。

更新记录:2026-06-20 从 IKEv2 迁移到 Shadowsocks + WebSocket + TLS,监控脚本同步更新。

# 一、方案演进

# 1.1 最初方案:定时检查(IKEv2 时代)

最直观的想法:写个脚本定时检查 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

问题:VPN 连接时长如果很短(比如 1-2 分钟),而检查间隔是 1 分钟,很容易漏掉事件。

# 1.2 进阶方案:实时日志监控(IKEv2)

改为实时监控 strongswan 日志,使用 SA ID 去重避免重复通知。

问题:IKEv2 服务已于 2026-06-20 下线,迁移到 Shadowsocks。

# 1.3 当前方案:Shadowsocks 日志监控

Shadowsocks + v2ray-plugin 的日志格式与 IKEv2 不同:

Jun 21 23:07:15 server ssserver[1685735]: 2026/06/21 23:07:15 tcp:203.218.155.130:0 accepted tcp:127.0.0.1:0
1

挑战:

  1. 每个 WebSocket 连接会产生多条 accepted 日志(一个连接多个 TCP 流)
  2. 没有明确的断开事件日志
  3. 需要用 IP + 时间窗口去重

# 二、最终方案

# 2.1 架构说明

┌─────────────┐      WebSocket + TLS      ┌─────────────┐
│   Client    │ ───────────────────────▶ │   Caddy     │
│  (Shadowsocks)                          │  (HTTPS)    │
└─────────────┘                           └──────┬──────┘
                                                │
                                                ▼
                                         ┌─────────────┐
                                         │ v2ray-plugin│
                                         │  (WS Server)│
                                         └──────┬──────┘
                                                │
                                                ▼
                                         ┌─────────────┐
                                         │ Shadowsocks │
                                         │   Server    │
                                         └─────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 2.2 监控脚本

创建 /home/moyin/vpn-monitor/ss-monitor.sh:

#!/bin/bash
# Shadowsocks VPN 实时日志监控 - 连接/断开通知
# 使用超时检测实现断开通知(30分钟无活动)

# 配置
TELEGRAM_CHAT_ID="YOUR_TELEGRAM_CHAT_ID"
STATE_DIR="/tmp/ss-monitor"
LOG_FILE="/var/log/ss-monitor.log"

# 去重时间窗口(秒)- 同一IP短时间内不重复通知连接
DEDUP_WINDOW=60

# 断开超时时间(秒)- 30分钟无活动判定为断开
DISCONNECT_TIMEOUT=1800

# 创建状态目录
mkdir -p "$STATE_DIR"

# 发送 Telegram 通知
send_notify() {
    local event="$1"
    local ip="$2"
    local timestamp="$3"
    local msg=""
    
    if [ "$event" = "CONNECTED" ]; then
        msg="🔒 Shadowsocks VPN 连接通知

✅ 新设备已连接
📍 IP: ${ip}
🕐 时间: ${timestamp}
🌐 协议: WebSocket + TLS"
    elif [ "$event" = "DISCONNECTED" ]; then
        msg="🔒 Shadowsocks VPN 连接通知

❌ 设备已断开
📍 IP: ${ip}
🕐 时间: ${timestamp}"
    fi
    
    openclaw message send --channel telegram --target "$TELEGRAM_CHAT_ID" --message "$msg" 2>/dev/null
}

# 获取IP状态文件路径
get_state_file() {
    echo "$STATE_DIR/${1}.state"
}

# 更新IP最后活动时间
update_activity() {
    echo $(date +%s) > "$(get_state_file "$1")"
}

# 检查是否应该发送连接通知(去重)
should_notify_connect() {
    local ip="$1"
    local current_time=$(date +%s)
    local notify_file="$STATE_DIR/${ip}.notify"
    
    # 首次连接
    [ ! -f "$(get_state_file "$ip")" ] && return 0
    
    # 检查去重窗口
    if [ -f "$notify_file" ]; then
        local diff=$((current_time - $(cat "$notify_file")))
        [ $diff -lt $DEDUP_WINDOW ] && return 1
    fi
    return 0
}

# 检查超时断开(后台循环)
check_timeout() {
    local current_time=$(date +%s)
    
    for state_file in "$STATE_DIR"/*.state; do
        [ -f "$state_file" ] || continue
        
        local ip=$(basename "$state_file" .state)
        local last_activity=$(cat "$state_file")
        local connected_marker="$STATE_DIR/${ip}.connected"
        
        # 跳过未标记为已连接的IP
        [ -f "$connected_marker" ] || continue
        
        local diff=$((current_time - last_activity))
        
        # 超过30分钟无活动,判定为断开
        if [ $diff -gt $DISCONNECT_TIMEOUT ]; then
            send_notify "DISCONNECTED" "$ip" "$(date '+%Y-%m-%d %H:%M:%S')"
            rm -f "$state_file" "$STATE_DIR/${ip}.notify" "$connected_marker"
        fi
    done
}

# 启动后台超时检查(每分钟检查一次)
while true; do sleep 60; check_timeout; done &

# 清理函数
trap 'kill $(jobs -p) 2>/dev/null; exit 0' SIGTERM SIGINT

# 监控 shadowsocks 日志
journalctl -u shadowsocks -f --no-pager 2>/dev/null | while read -r line; do
    timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    
    if echo "$line" | grep -q "accepted"; then
        remote_ip=$(echo "$line" | grep -oP 'tcp:\K[0-9.]+(?=:0 accepted)')
        
        if [ -n "$remote_ip" ]; then
            update_activity "$remote_ip"
            
            local connected_marker="$STATE_DIR/${remote_ip}.connected"
            if [ ! -f "$connected_marker" ] && should_notify_connect "$remote_ip"; then
                send_notify "CONNECTED" "$remote_ip" "$timestamp"
                date +%s > "$STATE_DIR/${remote_ip}.notify"
                touch "$connected_marker"
            fi
        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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119

# 2.3 Systemd 服务

创建 /etc/systemd/system/ss-monitor.service:

[Unit]
Description=Shadowsocks VPN Real-time Log Monitor
After=network.target shadowsocks.service
Requires=shadowsocks.service

[Service]
Type=simple
ExecStart=/home/moyin/vpn-monitor/ss-monitor.sh
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 2.4 启用服务

chmod +x /home/moyin/vpn-monitor/ss-monitor.sh
systemctl daemon-reload
systemctl enable ss-monitor
systemctl start ss-monitor
systemctl status ss-monitor
1
2
3
4
5

# 三、效果展示

连接 VPN 时收到通知:

🔒 Shadowsocks VPN 连接通知

✅ 新设备已连接
📍 IP: 203.218.155.130 (示例 IP)
🕐 时间: 2026-06-22 15:30:45
🌐 协议: WebSocket + TLS
1
2
3
4
5
6

断开 VPN 时收到通知(30分钟无活动后):

🔒 Shadowsocks VPN 连接通知

❌ 设备已断开
📍 IP: 203.218.155.130 (示例 IP)
🕐 时间: 2026-06-22 16:05:30
1
2
3
4
5

# 四、技术要点总结

对比项 IKEv2 方案 Shadowsocks 方案
日志来源 journalctl -u strongswan-starter journalctl -u shadowsocks
连接事件 IKE_SA.*established tcp:IP:0 accepted
断开事件 deleting IKE_SA 无明确事件,使用超时检测
去重方式 SA ID 去重 IP + 时间窗口去重
断开判定 日志事件 30分钟无活动超时
服务依赖 strongswan-starter.service shadowsocks.service

# 五、工作原理

┌─────────────────────────────────────────────────────────────┐
│                     主进程(日志监控)                        │
│  journalctl -f → 检测 accepted → 更新IP活动时间 → 发送连接通知  │
└─────────────────────────────────────────────────────────────┘
                            │
                            ▼
┌─────────────────────────────────────────────────────────────┐
│                   后台进程(超时检查)                         │
│  每分钟扫描状态文件 → 发现30分钟无活动的IP → 发送断开通知        │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10

状态文件存储:

  • {IP}.state - IP 最后活动时间
  • {IP}.notify - 上次发送通知时间(用于去重)
  • {IP}.connected - 已发送连接通知标记

# 六、注意事项

  1. 去重时间窗口:设置为 60 秒,避免同一 IP 短时间内重复通知
  2. 断开超时:30 分钟无活动判定为断开,发送断开通知
  3. 状态文件:存储在 /tmp/ss-monitor/ 目录,重启后清空
  4. 后台检查:每分钟扫描一次,检测超时断开的 IP
  5. 服务依赖:监控服务依赖 shadowsocks.service,确保 Shadowsocks 先启动

# 六、历史方案(已废弃)

点击展开 IKEv2 时代的方案(仅供参考)

# IKEv2 日志监控脚本(已废弃)

#!/bin/bash
# 已废弃 - IKEv2 VPN 实时日志监控

# 监控 strongswan 日志
journalctl -u strongswan-starter -f --no-pager 2>/dev/null | while read -r line; do
    # 检测连接
    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/\.\.\.//')
        # ... 发送通知
    fi
    
    # 检测断开
    if echo "$line" | grep -q "deleting IKE_SA"; then
        # ... 发送通知
    fi
done
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 七、参考链接

  • Shadowsocks-rust GitHub (opens new window)
  • v2ray-plugin GitHub (opens new window)
  • OpenClaw 文档 (opens new window)

最后更新:2026-06-22(迁移到 Shadowsocks) 历史版本:2026-06-06(IKEv2 版本)

#VPN#Shadowsocks#监控#Telegram#OpenClaw
上次更新: 2026/7/13 15:04:12
Windows开发环境配置指南
Windows Gitea 搭建完整指南

← Windows开发环境配置指南 Windows Gitea 搭建完整指南→

最近更新
01
Star-Office-UI 部署指南 - 像素风格的 AI 办公室看板
07-21
02
OpenSpec 快速入门指南
07-20
03
Mermaid 测试页面
07-20
更多文章>
Theme by Vdoing | Copyright © 2022-2026 Felix
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式