WireGuard组网之动态对端IP"自愈"方案
一、背景
A地为我的主要生活地,B地为父母常住地,有时需要连接B地网络设备,每次都需要VPN切换比较麻烦。因为两地都有软路由,而且都有公网IP,我就选择了用WireGuard组网的方式,划分不同网段,这样就可以无缝访问A,B两地网络环境。虽然组网的时候遇到了很多坑,但是好在有AI加持,都一一解决了(后续准备在开一篇组网教程)。实际使用效果也是非常满意的,美中不足的是:因为WireGuard服务端是动态IP,在IP变化后每次都需要手动在客户端重连一下,比较麻烦。
二、原理
由于 WireGuard 的底层机制在接口启动后,会“死认”第一次解析到的 Endpoint IP,一旦对端运营商强制重新拨号导致公网 IP 发生变更,本地的 WireGuard 就会彻底断连,且无法自动重新解析域名。
所以我们只需要编写一个自动化脚本,检测服务端IP是否发生变化,如果发生变化执行重启WireGuard服务就好了。
三、方案调研
在动手写脚本前,我们首先要了解为什么普通的对比脚本在实际运行中会掉链子:
坑一:依赖 wg show 造成的“无限重启死循环”
传统的逻辑通常是:用 nslookup 拿到新 IP,再用 wg show 抓取当前本机的 Endpoint IP,两者不一致就重启接口。 然而,当对端 IP 改变后,本地执行 ifdown / ifup 重启接口的瞬间,如果两端没有立刻产生新的数据握手(Handshake),底层驱动里的 Endpoint 数据根本不会更新,依然残留着上一次连接成功时的老 IP。 这会导致下一分钟定时任务进来时,判定依然不一致,从而陷入每两分钟疯狂重启接口的死循环。
坑二:网络瞬断导致进程无休止堆积挂起
当对端因断网或拨号触发网络瞬断时,OpenWrt 内部的精简版 nslookup 在向外请求 DNS 时可能会陷入长期的无响应状态。 因为定时任务是每 2 分钟执行一次,如果上一次的脚本卡在 DNS 解析没有退出,下一次的任务又被拉起,久而久之后台就会堆积几十个僵尸进程,导致软路由内存吃紧、定时任务彻底瘫痪。
💡 我们的破局思路:本地缓存 + 进程守护 + 纯净解析
为了彻底解决上述痛点,本方案引入了以下硬核机制:
脱离本机网卡依赖,改用本地文件缓存(Paper Trail):不看底层不靠谱的
wg show状态,只要成功重启过一次接口,立刻在软路由的/tmp内存目录中写下一个小文件记录新 IP。下一次只对比“最新外网解析”与“本地小纸条”。区分“网页映射名”与“底层网卡名”:在 OpenWrt 中,LuCI 前端显示的接口名(如小写映射
wiregard)和底层 Linux 网卡代号(如大小写敏感的WireGuard)往往不一致。脚本显式解耦两个变量,确保kill进程、查看状态与重启接口各自精准执行。安全单例锁与纯净正则:在脚本头部加入 PID 守护,运行前强杀可能卡死的旧进程;解析时抛弃容易产生兼容性问题的
timeout机制,改用最纯粹的原生命令配合 IPv4 正则过滤,确保 100% 稳定。
四、脚本编写
1. 首先使用SSH工具登陆上OpenWrt软路由
ssh root@软路由IP 2. 在 OpenWrt 中创建脚本文件
vi /root/check_wg.sh3. 写入以下完整代码(根据你的实际环境修改前几行核心自定义配置)
#!/bin/sh
# 强制引入标准环境变量
export PATH="/usr/sbin:/usr/bin:/sbin:/bin"
# ----------- 🛠️ 核心自定义配置区域 -----------
DOMAIN="your-ddns-domain.com" # 你的 WireGuard 对端 DDNS 域名
WG_INTERFACE="WireGuard" # ifup/ifdown 命令中实际操作的 OpenWrt 网络接口名
LOG_FILE="/var/log/wireguard_check.log" # 日志存储路径
MAX_LOG_SIZE=50 # 日志体积上限 (单位: KB)
CACHE_IP_FILE="/tmp/wg_current_ip.txt" # 本地 IP 缓存文件
PID_FILE="/var/run/check_wg.pid" # 进程单例锁
# --------------------------------------------
# 1. 【存储守护】
if [ -f "$LOG_FILE" ]; then
FILE_SIZE=$(du -k "$LOG_FILE" | awk '{print $1}')
if [ "$FILE_SIZE" -ge "$MAX_LOG_SIZE" ]; then
echo "$(date '+%Y-%m-%d %H:%M:%S') - [INFO] 日志达到上限,自动清空重置。" > $LOG_FILE
fi
fi
# 2. 【防进程堆积】
if [ -f "$PID_FILE" ]; then
PID=$(cat "$PID_FILE")
if kill -0 "$PID" 2>/dev/null; then
kill -9 "$PID" 2>/dev/null
fi
fi
echo "$$" > "$PID_FILE"
# 3. 【纯净解析】使用你测试完全通过的最原始的过滤方式
# 通过 grep -E 强行只过滤纯 IPv4 格式,排除 127.0.0.1
CURRENT_IP=$(nslookup $DOMAIN 2>/dev/null | grep -E -o '([0-9]{1,3}\.){3}[0-9]{1,3}' | grep -v '127.0.0.1' | head -n 1)
# 如果解析出来的值为空,说明此时真断网了,安全退出
if [ -z "$CURRENT_IP" ]; then
echo "$(date '+%Y-%m-%d %H:%M:%S') - [ERROR] 域名解析结果为空,网络可能断开,退出等待。" >> $LOG_FILE
rm -f "$PID_FILE"
exit 1
fi
# 4. 【本地缓存读取】
if [ -f "$CACHE_IP_FILE" ]; then
LAST_IP=$(cat "$CACHE_IP_FILE")
else
LAST_IP=""
fi
# 5. 【核心逻辑比对】对比当前解析 IP 和本地文件缓存
if [ "$CURRENT_IP" != "$LAST_IP" ]; then
echo "$(date '+%Y-%m-%d %H:%M:%S') - [NOTICE] 检测到对端 IP 改变! 旧缓存: [${LAST_IP:-无历史}] -> 新解析: [$CURRENT_IP]。正在重启接口..." >> $LOG_FILE
# 使用你指定的 WG_INTERFACE 重启 WireGuard 网络接口
ifdown $WG_INTERFACE && ifup $WG_INTERFACE
# 立刻写入缓存,死循环彻底终结
echo "$CURRENT_IP" > "$CACHE_IP_FILE"
echo "$(date '+%Y-%m-%d %H:%M:%S') - [SUCCESS] 接口 $WG_INTERFACE 重启完成,本地缓存已同步。" >> $LOG_FILE
else
# IP 一致,网络稳定
echo "$(date '+%Y-%m-%d %H:%M:%S') - [INFO] 检查完毕,IP 一致 ($CURRENT_IP),无需重启。" >> $LOG_FILE
fi
rm -f "$PID_FILE"
exit 04. 脚本文件赋权
chmod +x /root/check_wg.sh5. 首次手动初始化测试
/bin/sh /root/check_wg.sh此时通过 cat /var/log/wireguard_check.log 查看日志,应能看到由于无历史记录触发的首次缓存同步成功信息,如果没有,请检查对应脚本文件是否存在错误等。
五、添加后台定时任务 (Cron)
进入 OpenWrt 后台 LuCI 界面 -> 系统 -> 定时任务(或在终端执行 crontab -e),追加以下配置,设置为每 2 分钟自动执行一次:
*/2 * * * * /bin/sh /root/check_wg.sh保存后,执行 /etc/init.d/cron restart 重启定时任务服务即可。
六、运行效果与维护说明
在实际生产环境中,当对端 IP 未发生改变时,脚本每两分钟运行一次会产生如下日志
2026-06-25 08:00:00 - [INFO] 检查完毕,IP 一致 (180.172.xx.xx),无需重启。
2026-06-25 08:02:00 - [INFO] 检查完毕,IP 一致 (180.172.xx.xx),无需重启。当对端发生公网拨号变动时,脚本会实现毫秒级捕获、单接口精准重启并刷新缓存,完美阻断死循环。同时,由于加入了 MAX_LOG_SIZE=50 的日志体积守护,日志文件只要达到 50KB 就会在内存中自动重置,零存储开销,极度保护软路由的物理闪存寿命
七、注意事项
脚本的自定义配置需要根据你的实际情况填写,需要注意的是WireGuard的接口名称,你可以使用wg show命令获取。
root@OpenWrt:~# wg show
interface: WireGuard
public key: Oedv9p3W5UwHis+3Nf4sdjkksldjdjdkl2CTc=
private key: (hidden)
listening port: 10001如果不放心你填写的是否正确,可以手动执行WireGuard重启命令,看WireGuard接口是重启成功.
ifdown WireGuard && ifup WireGuard如果没有报错就是正确的。