切換選單
切換偏好設定選單
切換個人選單
尚未登入
若您做出任何編輯,會公開您的 IP 位址。

使用搬瓦工api操作nftables實現更新白名單ip

出自md5.pw
於 2026年1月25日 (日) 22:00 由 EliToviyah留言 | 貢獻 所做的修訂 (修复排版)

請注意不要洩漏API_Key 任何時候都不要洩露 防止伺服器和API被盜用 應當在安全的設備上使用API 防止API_Key被盜取

根據可實時知曉ip變化的辦法不同 您需要的工具也不相同..最多需要多一台本地的Linux伺服器(不是防火牆所在的那個伺服器哦)

使用前準備

切換到root或有高權限的用戶

輸入whoami(藍色輸出)或者查看 @之前的名字(綠色框)就是使用的用戶

如果是root就是root就可以了

如果不是

你輸入 nft list ruleset 或者 sudo nft list ruleset 輸入你的密碼 和上圖一樣 說明沒權限….

嘗試切換到root帳戶

輸入 su - 然後輸入 root 用戶的密碼。輸入的密碼不會顯示 輸入好後按回車就行 變成root就說明成功了




防火牆部分

輸入 sudo nft list ruleset

你的防火牆中應該有一個空白的鏈 專用戶白名單 然後在含有粉色框框(主要是含有hook input就行)代碼的鏈中 jump這個鏈 另外先不要改成policy drop;

如果沒有鏈的話 如何創建鏈

sudo nft add chain inet my_firewall allowed_ip

請根據自己的表的類型inet 表的名字 my_firewall 自行修改哦

allowed_ip可以改成自己想要的名字 比如sudo nft add chain inet my_firewall whiteip

要先有鏈 才能jump 鏈的名字

如何添加一條jump allowed_ip 規則呢

sudo nft add rule inet my_firewall my_input jump allowed_ip

還是一樣要修改成自己的表的類型 表的名字 剛剛創建的鏈的名字

要把這個jump allowed_ip 添加到 含有粉色代碼的chain裡面也就是(my_input)

伺服器部分

創建一個腳本 用 API 調用這個腳本就好了 免去了 API中過多的指令

如果你是從 伺服器安全-防火牆 nftables 那文章一步一步 沒有修改防火牆的表 鏈 的話 直接複製代碼框的全部內容 粘貼到ssh裡面 按回車 文件就創建好了

如果你修改了防火牆的表和鏈的名字 或不是根據 那篇文章來的

請修改下面代碼中的內容 根據自身情況修改 只需要修改=後面 「」 雙引號裡面的內容


  • TABLE_NAME="my_firewall"
    • 這個是表的名字 藍色框框
  • CHAIN_NAME="allowed_ip"
    • 這個是鏈的名字 綠色框框 這個鏈的名字一定不是你 包含 hook input 粉色這一行的那個鏈..因為會被清空..然後就會可能導致防火牆規則混亂…一定要是另外一個專門的鏈
  • FAMILY="inet"
    • 這個是表類型 紅色框框

示例

如示例中的圖就要改成這樣的

CHAIN_NAME="allowed_mobile" TABLE_NAME="filter"

FAMILY="inet" 不變

如果是table ip filter就把FAMILY="inet" 改成 FAMILY="ip"

這裡還有一個位置需要輸入 ls /root/update_fw.sh

檢查這個目錄是否有一個叫update_fw.sh如果有的話你也需要修改 避免被覆蓋

上面 藍色框框的 說明 有存在的同名文件 那你就需要修改 成 > /root/別的文件名.sh那麼下文中的所有/root/update_fw.sh都要改成你自己的文件名

下面紅色框框說明沒有找到這個文件就 不用改名了 直接使用就行



在下面的代碼框裡的對應位置改 可以先複製到記事本上 改完再全選重新複製 粘貼到ssh裡面 回車


代碼框的腳本如下:

cat << 'EOF' > /root/update_fw.sh
#!/bin/bash

# --- 用户配置区 ---
TABLE_NAME="my_firewall"
CHAIN_NAME="allowed_ip"
FAMILY="inet"

# --- 获取参数并剔除所有空格 ---
IP1=$(echo "$1" | tr -d '[:space:]')
IP2=$(echo "$2" | tr -d '[:space:]')


VALID_IPS=()
NFT_TYPES=()
SUCCESS_LIST=""
FAILED_LIST=""

# --- IP 校验函数 ---
check_ip() {
    local input=$1
    [[ -z "$input" ]] && return 1
    
    local raw_ip="${input%/*}"
    local mask="${input#*/}"
    
    # 自动识别默认掩码
    if [[ "$input" != */* ]]; then
        if [[ "$raw_ip" =~ : ]]; then mask=128; else mask=32; fi
    fi

    # 1. IPv4 校验
    # 原来的 2[0-4][0-5] 改为了 2[0-4][0-9]
    if [[ "$raw_ip" =~ ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$ ]]; then
        [[ "$mask" -lt 1 || "$mask" -gt 32 ]] && return 1
        echo "ip|$raw_ip/$mask"
        return 0
    fi

    # 2. IPv6 校验 (简化版正则,兼容性更好)
    if [[ "$raw_ip" =~ ^(([0-9a-fA-F]{1,4}:){1,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|:(:[0-9a-fA-F]{1,4}){1,7}|::|([0-9a-fA-F]{1,4}:){1,7}:[0-9a-fA-F]{1,4})$ ]]; then
        [[ "$mask" -lt 1 || "$mask" -gt 128 ]] && return 1
        echo "ip6|$raw_ip/$mask"
        return 0
    fi

    return 1
}

process_arg() {
    local arg=$1
    [ -z "$arg" ] && return
    local res
    res=$(check_ip "$arg")
    if [ $? -eq 0 ]; then
        local full_ip="${res#*|}"
        VALID_IPS+=("$full_ip")
        NFT_TYPES+=("${res%|*}")
        SUCCESS_LIST+="$full_ip "
    else
        FAILED_LIST+="$arg "
    fi
}

process_arg "$IP1"
process_arg "$IP2"

# --- 极简反馈逻辑 ---
if [ ${#VALID_IPS[@]} -eq 0 ]; then
    echo "Error: No valid IP provided ($FAILED_LIST)"
    exit 1
fi

# 执行 Nftables 操作
nft flush chain $FAMILY $TABLE_NAME $CHAIN_NAME || { echo "Error: NFT chain not found"; exit 1; }

for i in "${!VALID_IPS[@]}"; do
    nft add rule $FAMILY $TABLE_NAME $CHAIN_NAME ${NFT_TYPES[$i]} saddr ${VALID_IPS[$i]} accept
done

# 成功返回
echo "OK: Allowed $SUCCESS_LIST"
EOF

解釋一下代碼框裡面指令的意思

  • cat (Concatenate):原本是用來「查看」或「拼接」內容的,但在這種組合中,它負責接收你輸入的一大段文字。
  • << (輸入重定向):這叫「在此處開始讀取」。它告訴系統:「別去翻別的文件了,接下來的內容就是我要給你的,直到我遇到結束標記為止。」
  • 'EOF' (End Of File)
    • 開始標誌:它是你自定義的一個「暗號」。
    • 為什麼要加單引號 ' ': 加了單引號,系統就會「原樣搬運」中間的內容,不會去解析裡面的 $变量。這對於寫入包含變量的腳本至關重要,否則這些變量在寫入文件前就會被當前系統搞亂。
  • > (覆蓋寫入):它的意思是「清空目標文件並把內容倒進去」。
  • /root/update_fw.sh:這是目的地。系統會在指定路徑創建或覆蓋這個腳本文件。
  • 中間的內容 就是要寫入文件的內容 是我們的核心代碼
  • 最後的 EOF:這是「結束暗號」。系統看到它,就知道「打包」結束了,正式保存文件。

我們可以看到最後藍色框的時候 有點混亂 沒事的 直接按回車就行了

然後我們可以輸入 cat /root/update_fw.sh 查看一下文件內容 確認有寫入內容就好了

創建好後


你可以先輸入 ls -l /root/update_fw.sh

這個意思是 以長格式 查看/root/update_fw.sh 文件的信息

  • 第一位 - 代表是文件的意思 d是目錄
  • 第二位到第四位 rw- 就是 擁有這個文件的人的權限 第一個代表的是讀取 如果有這個權限顯示r如果沒有就是-第二個是寫入(編輯)的權限有這個權限就顯示w沒有就是-第三位是執行(運行)這個文件的權限有就顯示x沒有就顯示- 這裡顯示-證明在chmod之前是沒有執行權限的
  • r-- 這個文件所屬組 的權限 同上 第一位是讀取r說明有 第二位是-說明沒有寫入權限 第三位是-說明沒有執行權限
  • r-- 這個是其他人 也是一樣的 有讀取權限 沒有寫入和執行權限
  • 1 代表這個文件在磁碟上只有這一個『
  • root root 第一個root 代表擁有這個文件的人 第二個 root 代表這個文件屬於root組
  • 2675 文件大小Byte
  • Jan 22 01:07 修改時間
  • /root/update_fw.sh 文件信息的文件

為了安全取消掉其他的權限我們輸入

chmod 700 /root/update_fw.sh

這個指令的意思是 設置這個文件的權限是700 700的意思就是只有這個文件的擁有人(root ls -l看到的)才可以讀取寫入和執行 其他的兩個0代表 所屬組沒有任何權限和 其他所有人沒有任何權限。

這樣,只有 root 用戶能看到這個腳本的內容,也只有 root 能運行它。


調用API部分

獲取 搬瓦工伺服器的API_Key和veid

請參考 搬瓦工api使用


使用搬瓦工API觸發伺服器上的腳本變更防火牆nftables的chain 從而實現動態更新白名單ip

ipv6的網段 V6_MASK

這個..如果你想同時更新ipv6的白名單 有一個問題

就是ipv6 每個設備都有一個ipv6地址(各不相同) ipv4的時候你用電腦手機連接到同一個路由器的話一般都是走的同一個ipv4地址 而ipv6不同…所以如果你只是更新了你這個設備的ipv6地址的話…別的設備仍然不在白名單中…這就需要網段

請登錄路由器

找到這個前綴長度…把V6_MASK改成60就行了這樣就是說 連接路由器的 都可以進白名單了

所有的代碼中都有一個可以修改的變量.. V6_MASK="/64" 單個設備請改為 /128 一些地區可能是48 但48有點危險範圍太大 你的鄰居可能也在這之中 那為什麼是64呢 因為我的是64……哦 我記錯了 我是60….

可實時更新ip變化的辦法

Linux系

OpenWrt類

檢查是否安裝了curl

輸入 curl 如果顯示 -ash: curl not found

安裝curl和證書 為了訪問https網站

opkg update && opkg install curl ca-bundle

curl 8.12.1有這樣的版本號 有IPv6 有ssl 確保有他們就可以啦

檢查 獲取ip 的url是否有效

在使用前先在ssh裡面輸入

curl -s --max-time 5 https://ifconfig.me 進行測試

看看能不能獲取ip地址 紅色方塊裡面的就是ip地址 如果不能….

在後續的代碼塊中修改這兩個=後面的內容

CHECK_URL_V4="https://ifconfig.me"

改成

CHECK_URL_V4=」https://ident.me”

CHECK_URL_V6="https://ifconfig.me"

改成

CHECK_URL_V6=」 http://v6.ipip.net”

開始

在 OpenWrt 中,每當接口(WAN)狀態發生變化(比如重連、獲取到新 IP、掉線)時,系統會自動觸發對應的腳本。 OpenWrt 的網絡熱插拔腳本存放在: /etc/hotplug.d/iface/

進入目錄:cd /etc/hotplug.d/iface/

創建腳本:

修改下面代碼 你可以先複製到記事本或者別的文本編輯工具裡面 最前面的

export VEID="你的VEID" export API_KEY="你的API_KEY"

改成類似這樣的格式

export VEID="10202" export API_KEY="private_xxxxx"

請根據你實際的veid和API_Key修改

這兩個如果你知道幹嘛的也可以改 CACHE_FILE="/etc/config/vps_last_ip" 保存上傳的ip防止重複調用API LOG_FILE="/tmp/vps_sync_error.log" 把執行過程和結果保存到這個文件里

最後要檢查的 輸入 ls /etc/hotplug.d/iface/99-sync-vps-firewall 檢查一下 有沒有同名的其他腳本

下圖就是沒有 就不需要修改

下圖就是有 那麼你就要換一個名字

/etc/hotplug.d/iface/99-這裡改成你想要的名字

比如 /etc/hotplug.d/iface/99-sync-vps-firewall1

修改後…複製粘貼到ssh裡面 回車就創建好腳本了

代碼塊如下:

cat << 'EOF' > /etc/hotplug.d/iface/99-sync-vps-firewall
#!/bin/sh

# 只要是接口启动(ifup)就触发
[ "$ACTION" = "ifup" ] || exit 0

# --- 用户配置区 ---
export VEID="你的VEID"
export API_KEY="你的API_KEY"

# 用户可以分别设置获取地址(可以相同,也可以不同)
CHECK_URL_V4="https://ifconfig.me"
CHECK_URL_V6="https://ifconfig.me"

V6_MASK="/64"  # IPv6 掩码
CACHE_FILE="/etc/config/vps_last_ip"
LOG_FILE="/tmp/vps_sync.log"
MAX_LINES=100

# --- 文件初始化与清理函数 ---
init_files() {
    sleep 10
    # 1. 处理日志文件
    if [ ! -f "$LOG_FILE" ]; then
        touch "$LOG_FILE"
        chmod 644 "$LOG_FILE"
        echo "$(date): [系统] 初始日志文件已创建" >> "$LOG_FILE"
    else
        # 日志滚动清理逻辑
        local current_lines=$(wc -l < "$LOG_FILE")
        if [ "$current_lines" -gt "$MAX_LINES" ]; then
            echo "$(tail -n 50 "$LOG_FILE")" > "$LOG_FILE"
            echo "$(date): [系统] 日志滚动清理完成" >> "$LOG_FILE"
        fi
    fi

    # 2. 处理缓存文件及其目录
    local cache_dir=$(dirname "$CACHE_FILE")
    if [ ! -d "$cache_dir" ]; then
        mkdir -p "$cache_dir"
        echo "$(date): [系统] 创建缓存目录: $cache_dir" >> "$LOG_FILE"
    fi

    if [ ! -f "$CACHE_FILE" ]; then
        touch "$CACHE_FILE"
        # 初始化空变量,防止脚本第一次读取时报错
        echo "LAST_IP4=\"\"" > "$CACHE_FILE"
        echo "LAST_IP6=\"\"" >> "$CACHE_FILE"
        echo "$(date): [系统] 初始缓存文件已创建" >> "$LOG_FILE"
    fi
}

# --- IPv4 暴力获取函数 ---
get_v4() {
    local count=0
    while [ $count -lt 10 ]; do
        local res=$(curl -s --max-time 5 "$CHECK_URL_V4" 2>/dev/null)
        if echo "$res" | grep -Eq "^([0-9]{1,3}\.){3}[0-9]{1,3}$"; then
            echo "$res" && return 0
        fi
        count=$((count + 1))
        sleep 2
    done
    return 1
}

# --- IPv6 暴力获取函数 ---
get_v6() {
    local count=0
    while [ $count -lt 10 ]; do
        local res=$(curl -s --max-time 5 "$CHECK_URL_V6" 2>/dev/null)
        if echo "$res" | grep -q ":"; then
	          echo "${res}${V6_MASK}"
            return 0
        fi
        count=$((count + 1))
        sleep 1
    done
    
    ip6=$(ip -6 addr show scope global | grep inet6 | awk '{print $2}' | cut -d/ -f1 | grep -E '^(2|3)' | head -n 1)
    if [ -n "$ip6" ]; then
        echo "${ip6}${V6_MASK}"
        return 0
    fi
    return 1
}

# 1. 初始化
init_files

# 2. 执行双栈并行/独立获取
CURRENT_IP4=$(get_v4)
CURRENT_IP6=$(get_v6)

# 3. 基础校验:至少得有一个 IP 吧
if [ -z "$CURRENT_IP4" ] && [ -z "$CURRENT_IP6" ]; then
    echo "$(date): [错误] 尝试10次后仍无法获取到任何有效IP" >> "$LOG_FILE"
    exit 1
fi

# 4. 缓存对比 (防重复刷 API)
[ -f "$CACHE_FILE" ] && . "$CACHE_FILE"
if [ "$CURRENT_IP4" = "$LAST_IP4" ] && [ "$CURRENT_IP6" = "$LAST_IP6" ]; then
    exit 0
fi

# 5. 调用 API
CMD="bash /root/update_fw.sh $CURRENT_IP4 $CURRENT_IP6"
RESPONSE_FULL=$(curl -s -w "%{http_code}" -X POST \
    --data "veid=${VEID}&api_key=${API_KEY}" \
    --data-urlencode "command=${CMD}" \
    "https://api.64clouds.com/v1/basicShell/exec")

# 6. 解析结果
HTTP_CODE="${RESPONSE_FULL:${#RESPONSE_FULL}-3}"
RESPONSE_JSON="${RESPONSE_FULL:0:${#RESPONSE_FULL}-3}"
RESPONSE_CLEAN=$(echo "$RESPONSE_JSON" | tr -d '\n\r ')
ERROR_CODE=$(echo "$RESPONSE_CLEAN" | grep -o '"error":[0-9]*' | cut -d: -f2)
MESSAGE=$(echo "$RESPONSE_CLEAN" | grep -o '"message":"[^"]*"' | sed 's/"message":"//;s/"$//')

# 7. 写入中文日志
if [ "$HTTP_CODE" = "200" ] && [ "$ERROR_CODE" = "0" ]; then
    echo "LAST_IP4=\"$CURRENT_IP4\"" > "$CACHE_FILE"
    echo "LAST_IP6=\"$CURRENT_IP6\"" >> "$CACHE_FILE"
    echo "$(date): [成功] IP变更同步成功。IPv4: $CURRENT_IP4, IPv6: $CURRENT_IP6. VPS反馈: $MESSAGE" >> "$LOG_FILE"
    echo "同步成功: $MESSAGE"
else
    [ -z "$MESSAGE" ] && MESSAGE="请求异常: $RESPONSE_JSON"
    echo "$(date): [失败] 同步失败。详细原因: $MESSAGE" >> "$LOG_FILE"
    echo "同步失败: $MESSAGE"
fi
EOF

就像這樣粘貼後回車就行


輸入chmod 755 /etc/hotplug.d/iface/99-sync-vps-firewall賦予執行權限

輸入 ls -l /etc/hotplug.d/iface/99-sync-vps-firewall 查看賦予權限是否成功

這樣你可以重啟 試試看… 可以輸入

cat /tmp/vps_sync_error.log

查看是否成功

前面的藍色框框說成功指的是API成功了 裡面的ip是本地上傳給伺服器更新的ip 後面紅色框框 OK才是防火牆成功的意思 後面的ip就是更新的ip 如果有一個ip無效或者…就不會顯示了

常見錯誤

身份驗證失敗 意思就是說 veid 或者 api_key 的內容錯誤

沒有找到鏈

你可以看到 藍色框的是防火牆指令..紅色框是表

可能是 表(inet my_firewall) 或者 鏈(allowed_ip) 的名字和伺服器防火牆的對不上 你可以去伺服器nft list ruleset看一下

華碩路由器 官方固件

因為官方固件沒有提供 重新連接網絡 後執行腳本的功能 所以我們需要一台本地的Linux伺服器了(不是防火牆所在的那個伺服器哦)…

查看Linux伺服器的ip (本地的區域網內的)

ssh連接的那個ip就是了…

如果你忘記了 輸入 ip addr 找個eth0或者 ens18 這樣 一般都是默認網絡接口 裡面的ip地址 藍色框框

藉助監聽遠程日誌觸發API更新防火牆白名單IP

用瀏覽器登陸你的路由器後台 輸入用戶名和密碼 一般的地址是 192.168.1.1 如果不是可以打開 http://www.asusrouter.com/ 試試

找到系統記錄 單擊

遠程記錄伺服器 輸入 你的 本地區域網linux伺服器的ip


遠程記錄伺服器埠 默認514 如果你的linux伺服器占用514的話 就改一下

如何查看是否占用

ssh連接那台伺服器 切換root帳戶

  • 輸入命令:su -(注意:su 後面有個空格和減號,這很重要,代表同時切換環境變量)
  • 輸入 Root 密碼(輸入時看不見)。
  • 此時你的提示符會變成 #,代表你又是 Root 了。

輸入 sudo ss -tunlp | grep :514

如果你沒有安裝sudo就會提示

那就不需要輸入sudo 了 直接

ss -tunlp | grep :514


這樣有輸出 就是被占用了 占用的話換一個就行了


這樣沒有輸出 就是沒占用

然後點擊 應用本頁面設置

另外一台Linux伺服器部分 不是防火牆所在的那個伺服器哦

ssh連接後

切換到root帳戶
  • 輸入命令:su -(注意:su 後面有個空格和減號,這很重要,代表同時切換環境變量)
  • 輸入 Root 密碼(輸入時看不見)。
  • 此時你的提示符會變成 #,代表你又是 Root 了。

安裝socat

socat的功能很強大 是一個多功能的網絡工具 我們這裡用於接收路由器發過來的日誌…

Debian / Ubuntu / PVE / Armbian 系:

apt update && apt install socat -y

Red Hat / CentOS / Rocky Linux 系:

yum install socat -y

新一些Red Hat/CentOS/Rocky Linux希的系統可以使用dnf install socat -y

安裝後 輸入 socat -V 確認安裝成功

檢查 獲取ip 的url是否有效

在使用前先在ssh裡面輸入

curl -s --max-time 5 https://ifconfig.me 進行測試

看看能不能獲取ip地址 紅色方塊裡面的就是ip地址 如果不能….

在代碼塊中修改這兩個=後面的內容

CHECK_URL_V4="https://ifconfig.me"

改成

CHECK_URL_V4=」https://ident.me”

CHECK_URL_V6="https://ifconfig.me"

改成

CHECK_URL_V6=」 http://v6.ipip.net”

創建腳本

修改下面代碼塊 的代碼 你可以複製到記事本或者其他文本編輯器裡面 修改

最前面的

export VEID="你的VEID" export API_KEY="你的API_KEY"

改成類似這樣的格式

export VEID="10202" export API_KEY="private_xxxxx"

請根據你實際的veid和API_Key修改

這兩個如果你知道幹嘛的也可以改 CACHE_FILE="/etc/config/vps_last_ip" 保存上傳的ip防止重複調用API LOG_FILE="/tmp/vps_sync_error.log" 把執行過程和結果保存到這個文件里

根據剛才測試的結果來決定要不要替換

CHECK_URL_V4="https://ifconfig.me" CHECK_URL_V6="https://ifconfig.me"

V6_MASK="/64" 也可以修改 詳情看上方的 ipv6的網段 V6_MASK

最後要檢查的 輸入 ls /root/checkip.sh 檢查一下 有沒有同名的其他腳本

下圖就是沒有 就不需要修改

下圖這樣就是有 就需要修改/root/checkip.sh 比如修改成 /root/checkip1.sh

修改後

全部選擇 複製粘貼 到ssh裡面 按回車


代碼塊:

cat << 'EOF' > /root/checkip.sh
#!/bin/bash

# --- 用户配置区 ---
export VEID="你的VEID"
export API_KEY="你的API_KEY"

# 获取 IP 的地址
CHECK_URL_V4="https://ifconfig.me"
CHECK_URL_V6="https://ifconfig.me"

V6_MASK="/64"  # IPv6 掩码
CACHE_FILE="/etc/config/vps_last_ip"
LOG_FILE="/var/log/vps_sync.log"
MAX_LINES=100  # 日志保留行数

# --- 1. 初始化文件与目录 ---
init_files() {
    # 处理日志文件
    if [ ! -f "$LOG_FILE" ]; then
        touch "$LOG_FILE"
        chmod 644 "$LOG_FILE"
    else
        # 日志滚动清理
        local current_lines=$(wc -l < "$LOG_FILE")
        if [ "$current_lines" -gt "$MAX_LINES" ]; then
            echo "$(tail -n 100 "$LOG_FILE")" > "$LOG_FILE"
            echo "$(date): [系统] 日志滚动清理完成 (保留末尾100行)" >> "$LOG_FILE"
        fi
    fi

    # 处理缓存目录及文件
    local cache_dir=$(dirname "$CACHE_FILE")
    [ ! -d "$cache_dir" ] && mkdir -p "$cache_dir"
    
    if [ ! -f "$CACHE_FILE" ]; then
        echo "LAST_IP4=\"\"" > "$CACHE_FILE"
        echo "LAST_IP6=\"\"" >> "$CACHE_FILE"
    fi
}

# --- 2. IPv4 暴力获取函数 ---
get_v4() {
    local count=0
    while [ $count -lt 10 ]; do
        local res=$(curl -s4 --max-time 5 "$CHECK_URL_V4" 2>/dev/null)
        if echo "$res" | grep -Eq "^([0-9]{1,3}\.){3}[0-9]{1,3}$"; then
            echo "$res" && return 0
        fi
        count=$((count + 1))
        sleep 2
    done
    return 1
}

# --- 3. IPv6 暴力获取函数 ---
get_v6() {
    local count=0
    while [ $count -lt 10 ]; do
        local res=$(curl -s6 --max-time 5 "$CHECK_URL_V6" 2>/dev/null)
        if echo "$res" | grep -q ":"; then
            echo "${res}${V6_MASK}"
            return 0
        fi
        count=$((count + 1))
        sleep 4
    done
    
    ip6=$(ip -6 addr show scope global | grep inet6 | awk '{print $2}' | cut -d/ -f1 | grep -E '^(2|3)' | head -n 1)
    if [ -n "$ip6" ]; then
        echo "${ip6}${V6_MASK}"
        return 0
    fi
    
    return 1
}

# --- 4. 核心同步逻辑 ---
sync_ip() {

    # 刚收到日志时,给网络层 5-10 秒的初始化时间
    sleep 10
    
    # 获取当前 IP
    CURRENT_IP4=$(get_v4)
    CURRENT_IP6=$(get_v6)

    # 基础校验
    if [ -z "$CURRENT_IP4" ] && [ -z "$CURRENT_IP6" ]; then
        echo "$(date): [错误] 尝试10次后仍无法获取到任何有效IP" >> "$LOG_FILE"
        return 1
    fi

    # 读取缓存并对比
    . "$CACHE_FILE"
    if [ "$CURRENT_IP4" = "$LAST_IP4" ] && [ "$CURRENT_IP6" = "$LAST_IP6" ]; then
        echo "$(date): [跳过] IP 未发生变化,取消同步" >> "$LOG_FILE"
        return 0
    fi

    # 调用 API
    CMD="bash /root/update_fw.sh $CURRENT_IP4 $CURRENT_IP6"
    local RESPONSE_FULL=$(curl -s -w "%{http_code}" -X POST \
        --data "veid=${VEID}&api_key=${API_KEY}" \
        --data-urlencode "command=${CMD}" \
        "https://api.64clouds.com/v1/basicShell/exec")

    # 解析结果
    local HTTP_CODE="${RESPONSE_FULL:${#RESPONSE_FULL}-3}"
    local RESPONSE_JSON="${RESPONSE_FULL:0:${#RESPONSE_FULL}-3}"
    
    # 核心解析:压缩 JSON 并提取字段
    local CLEAN_JSON=$(echo "$RESPONSE_JSON" | tr -d '\n\r' | tr -s ' ')
    local ERROR_CODE=$(echo "$CLEAN_JSON" | sed -n 's/.*"error":[ ]*\([0-9]*\).*/\1/p')
    local MESSAGE=$(echo "$CLEAN_JSON" | sed -n 's/.*"message":[ ]*"\(.*\)"[ ]*}.*/\1/p' | sed 's/\\//g')

    # 写入日志
    if [ "$HTTP_CODE" = "200" ] && [ "$ERROR_CODE" = "0" ]; then
        echo "LAST_IP4=\"$CURRENT_IP4\"" > "$CACHE_FILE"
        echo "LAST_IP6=\"$CURRENT_IP6\"" >> "$CACHE_FILE"
        echo "$(date): [成功] 同步成功。IPv4: $CURRENT_IP4, IPv6: $CURRENT_IP6. VPS反馈: $MESSAGE" >> "$LOG_FILE"
    else
        [ -z "$MESSAGE" ] && MESSAGE="$CLEAN_JSON"
        echo "$(date): [失败] 同步失败。HTTP: $HTTP_CODE, ERROR: $ERROR_CODE, 原因: $MESSAGE" >> "$LOG_FILE"
    fi
}

# --- 5. 监听入口 ---
init_files

# 如果直接运行脚本(无管道输入),执行一次同步
if [ -t 0 ]; then
    sync_ip
else
    # 如果有管道输入(来自 socat),则监听日志触发
    while IFS= read -r line; do
        if echo "$line" | grep -qi 'local *IP address'; then
            echo "$(date): [触发] 检测到路由器拨号日志" >> "$LOG_FILE"
            sync_ip
        fi
    done
fi
EOF

粘貼之後有點混亂 不用管 直接回車就行了

然後

檢查一下 輸入 cat /root/checkip.sh 有內容就好了

授予運行權限 chmod +x /root/checkip.sh

然後輸入 ls -l /root/checkip.sh 查看多了三個x 就對了 如果像紅色框框沒有x 就不對 請重試 chmod +x /root/checkip.sh

然後你就可以輸入

/root/checkip.sh

如果第一次測試成功了 以後想測試的話運行這個 rm /etc/config/vps_last_ip && /root/checkip.sh

需要rm /etc/config/vps_last_ip刪除本地保留的ip記錄 就是說如果這個ip記錄和獲取到的一樣是不會運行api執行腳本的

測試一下 相當於是手動更新..不是自動更新

不會有內容輸出

需要查看日誌 cat /var/log/vps_sync.log

如果是下圖那樣就是成功了 要看紅色的 紅色框是vps伺服器返回的OK 就是真的成功了 Allowed:後面是成功添加的ip 一個是ipv4 一個是ipv6的

藍色框的成功說的是API使用成功 提交的IP是哪些


在此再次提醒您 使用時 保管好Veid和API_Key 你 看我打碼伺服器名字 然而還是在這裡漏了...以前的打碼就沒意義了

常見錯誤

身份驗證錯誤 一般代表 api_key 或 veid 的內容填寫錯誤 就是= 後面的 "" 裡面的內容 比如VEID="就這裡錯了" API_KEY="這裡"

配置開機啟動 和 自動更新

使用systemctl配置開機啟動和 自動根據日誌更新

修改以下代碼中的

UDP-RECV:514 把514換成你的路由器那裡設置的 遠程記錄伺服器埠 比如 515 就改成 UDP-RECV:515

ExecStart=/bin/sh -c "/usr/bin/socat -u UDP-RECV:514 STDOUT | /root/checkip.sh"

這裡面的 /root/checkip.sh

如果你的文件名改了的話 就需要修改… 沒改就不用改..

同理 /etc/systemd/system/router-log-watcher.service

這個地方你也應該輸入 ls /etc/systemd/system/router-log-watcher.service

查看文件是否存在.如果存在就改名字 如果不存在就不用改了 和上面伺服器部分的 ls /root/update_fw.sh 一樣

修改好 全部選擇 複製粘貼到ssh裡面 然後按回車

cat << 'EOF' > /etc/systemd/system/router-log-watcher.service
[Unit]
Description=Router Log Watcher
After=network.target

[Service]
Type=simple
# 管道命令:socat 接收 UDP 514,然后传给脚本
ExecStart=/bin/bash -c "/usr/bin/socat -u UDP-RECV:514 STDOUT | /root/checkip.sh"
Restart=always
RestartSec=5
# 如果脚本需要 root 权限,确保 User=root
User=root

[Install]
WantedBy=multi-user.target
EOF

這個不怎麼混亂 直接回車就行

然後 cat /etc/systemd/system/router-log-watcher.service 如果你的文件名字 如果不是這個的話請修改

輸入這個 意思是識別新服務

systemctl daemon-reload

在輸入這個 意思是 啟動並設置開機自啟

systemctl enable --now router-log-watcher.service

最後輸入

systemctl status router-log-watcher.service

藍色的框 是 已經運行了 綠色的是enabled 是 設置了 開機自動啟動

現在可以重啟路由器獲取新的ip試試了

輸入 cat /var/log/vps_sync.log 這樣就是成功了

如果ipv6獲取的速度慢 可能出現防火牆沒有允許ipv6的情況

遇到極端情況下 可以手動更新

rm -f /etc/config/vps_last_ip && /root/checkip.sh

刪除存儲的ip然後執行腳本 ip一直相同的話是不會調用api的

另外如果你修改了/root/checkip.sh 那麼還要輸入 systemctl restart router-log-watcher.service 重啟這個服務才生效

如果成功更新ip了 就可以把允許ssh的埠刪掉了就真正變成白名單模式了