Press "Enter" to skip to content

解决群晖VPN 对端动态 IP 变更后无法自动重连的痛点

1. 问题背景

在日常的异地组网或家庭实验室(Home Lab)互联中,我们经常使用群晖(Synology NAS)的内置 VPN 客户端拨号到另一个家里的路由器(如 MikroTik RouterOS 或其他支持 L2TP 的设备)。

由于家庭宽带的公网 IP 通常是动态的(Dynamic IP),我们一般会使用 DDNS(动态域名解析)来绑定对端路由器。但在实际使用中遇到了一个痛点:

  • 现象:当对端路由器的公网 IP 发生变化时,群晖的 L2TP 连接会断开。
  • 原因:群晖底层的 VPN 守护进程(xl2tpd/pppd)在重连时死死缓存了初次解析的旧 IP,并不会重新发起 DNS 查询,导致即使 DDNS 已经更新,群晖也无法自动恢复连接,处于“僵死”状态。

为了实现无人值守的稳定性,我们需要一个自动检测、清理 DNS 缓存并强制重连的自动化方案。


2. 核心解决方案

既然群晖内置机制不会自动刷新 DNS,我们利用群晖自带的 任务计划(Task Scheduler),定期运行一个 Shell 脚本。
脚本的逻辑非常直接:

  1. 定时(如每 5 分钟)检测对端 VPN 网关的连通性。
  2. 如果连通性正常,直接退出。
  3. 如果检测到断开,强制终止当前 VPN 客户端进程,重启系统 DNS 缓存服务(nscd) 释放旧 IP,然后读取配置文件重新拨号,并将全过程记录到指定的日志文件中。

3. 详细配置步骤

步骤一:获取群晖 VPN 配置文件 ID

群晖的 VPN 拨号是基于配置文件的,我们需要通过 SSH 获取该配置文件的唯一 ID。

  1. 开启群晖的 SSH 功能(控制面板 -> 终端机和 SNMP)。
  2. 使用终端工具登录群晖,并切换至 root 权限:
   sudo -i
  1. 查看 L2TP 配置文件目录:
ls /usr/syno/etc/synovpnclient/l2tp/
  1. 你会看到类似 l2tp_o1634567890.conf 的文件。记录下其中的 l2tp_o1634567890(注意:每个人生成的数字不同),这就是后续脚本中需要的 VPN_ID

步骤二:准备自动化脚本

在群晖的任意共享文件夹中(例如 public),创建一个日志文件,以便后续观察(如 /volume1/public/l2tp_reconnect.log)。

核心 Shell 脚本内容如下:

#!/bin/bash
# 群晖 L2TP 客户端自动重连(加固版 v2)
# 适用 DSM 7.x,任务计划中须以 root 运行

# ================= 基础配置 =================
VPN_ID="l2tp_o1634567890"                    # SSH: ls /usr/syno/etc/synovpnclient/l2tp/ 获取
VPN_GATEWAY="10.255.30.1"                    # 隧道对端地址(ip addr 里 peer 后面那个)
VPN_HOST="your.ddns.net"                     # 对端 DDNS 域名,仅用于日志记录解析结果
LOG_FILE="/volume1/public/l2tp_reconnect.log"

PROBE_RETRY=3                                # 连续失败几次才判定断线
PROBE_INTERVAL=10                            # 每次探测间隔(秒)
CONNECT_TIMEOUT=60                           # synovpnc connect 最长等待(秒)
WAIT_UP=60                                   # 拨号后等待隧道建立的最长时间(秒)
# ===========================================

STATE_FILE="/usr/syno/etc/synovpnclient/vpnc_connecting"
LOCK_FILE="/tmp/l2tp_reconnect.lock"

# ---- 防重入:上一轮没跑完就直接退出,避免打断正在进行的握手 ----
exec 9>"$LOCK_FILE"
if ! flock -n 9; then
    exit 0
fi

log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >> "$LOG_FILE"; }

# 隧道存活判断:去往对端的路由走 ppp 接口(隧道存在)+ 对端可达(隧道可用)
# 不写死接口名——群晖分配的 ppp 单元号是动态的(可能是 ppp300 这类编号)
tunnel_up() {
    ip route get "$VPN_GATEWAY" 2>/dev/null | grep -q " dev ppp" || return 1
    ping -c 2 -W 2 "$VPN_GATEWAY" > /dev/null 2>&1
}

# ---- 探测阶段:连续多次失败才认定断线,过滤瞬时丢包 ----
for i in $(seq 1 "$PROBE_RETRY"); do
    if tunnel_up; then
        exit 0                               # 正常则静默退出;想留心跳日志在此行前加 log
    fi
    [ "$i" -lt "$PROBE_RETRY" ] && sleep "$PROBE_INTERVAL"
done

log "[WARN] 连续 ${PROBE_RETRY} 次探测失败,开始重连流程"

# ---- 清理阶段:进程 + 残留状态文件一起清,防止 UI 卡在“正在连接中” ----
/usr/syno/bin/synovpnc kill_client > /dev/null 2>&1
sleep 3
pkill -f xl2tpd > /dev/null 2>&1
pkill -f "pppd.*${VPN_ID}" > /dev/null 2>&1   # 只杀本客户端的 pppd,避免误伤 VPN Server
sleep 2
rm -f "$STATE_FILE"

# ---- 刷新 DNS 缓存,让 pppd 重新解析 DDNS ----
synosystemctl restart nscd > /dev/null 2>&1 || log "[WARN] nscd 重启失败(可忽略)"
sleep 3
RESOLVED=$(getent hosts "$VPN_HOST" | awk '{print $1}' | head -1)
log "[INFO] ${VPN_HOST} 当前解析为: ${RESOLVED:-解析失败}"

# ---- 重新拨号,超时强制结束,绝不允许无限期挂住 ----
timeout "$CONNECT_TIMEOUT" /usr/syno/bin/synovpnc connect --id="$VPN_ID" >> "$LOG_FILE" 2>&1
RC=$?
[ "$RC" -eq 124 ] && log "[ERROR] connect 命令超时被强制终止"

# ---- 验证结果 ----
DEADLINE=$((SECONDS + WAIT_UP))
while [ "$SECONDS" -lt "$DEADLINE" ]; do
    if tunnel_up; then
        log "[OK] 隧道已恢复(对端公网 IP: ${RESOLVED:-unknown})"
        exit 0
    fi
    sleep 5
done

# ---- 失败兜底:必须清掉状态文件,否则 DSM 界面会永久卡在“正在连接中” ----
log "[ERROR] ${WAIT_UP}s 内未建立隧道,清理残留状态,等待下一轮重试"
/usr/syno/bin/synovpnc kill_client > /dev/null 2>&1
sleep 2
rm -f "$STATE_FILE"
exit 1

步骤三:在群晖控制面板中设置计划任务

  1. 登录群晖 DSM 后台,打开 控制面板 -> 任务计划
  2. 点击 新增 -> 计划的任务 -> 用户定义的脚本
  3. 常规设置
  • 任务名称:Auto-Reconnect-L2TP
  • 用户账号:必须选择 root(重启网络服务需要最高权限)。
  1. 计划设置
  • 运行频率:设置为每天运行,每隔 5 分钟 运行一次(可根据对实时性的要求微调)。
  1. 任务设置
  • 在“运行命令”文本框中,将上面的脚本粘贴进去。
  • 点击 确定 保存。

4. 调试与效果验证

脚本部署完成后,我们可以通过以下方式进行验证与日常维护:

  1. 手动触发测试:在任务计划列表中选中该任务,点击“运行”。随后去你的共享文件夹下查看 l2tp_reconnect.log,如果网络正常,会看到 [OK] VPN 隧道连接正常。 的字样。
  2. 断网重连测试:可以故意拔掉对端路由器的 WAN 口网线或等待对端 IP 发生变化。脚本在下次执行时,日志会记录 [WARNING] VPN 网络异常断开...,随后刷新 DNS 并重新拨号。在 10 秒左右即可在群晖“网络”界面看到 VPN 重新变成了已连接状态。

通过这种“网络状态探针 + 强刷 DNS + 重新拨号”的组合拳,完美解决了群晖 L2TP 客户端在动态 IP 环境下的断流死锁问题,大大提升了异地组网的可用性。


标签:#群晖 #Synology #L2TP #VPN #动态IP #DDNS #自动化脚本 #网络运维

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注