久光本光的主页 久光本光的主页
  • 首页
  • 知识区
  • 影视区
  • 游戏区
  • 软件区

久光本光

管理员
听不见音乐的人以为跳舞的人疯了

热门标签

bitiful (1) cloudflare (1) cs (1) Debian (1) fps游戏 (2) IPTV (1) OpenWrt (1) rclone (1) rime输入法 (1) stun穿透 (1) windows (6) X-Lite (1) X-Lite中文 (1) X-Lite汉化 (1) XOS (2) 人物志 (4) 优化 (2) 光猫 (1) 公网 (1) 分手 (1) 刘慈欣 (3) 北宋 (4) 历史 (5) 原版windows (2) 反恐精英 (1) 失恋 (1) 宝塔 (1) 小狼毫 (1) 小说解读 (3) 文人 (5) 游戏剧情 (1) 游戏教程 (1) 游戏解读 (1) 王安石 (1) 王荆公 (1) 白霜词库 (1) 科幻 (3) 精简 (5) 系统优化 (3) 缤纷云 (1) 苏东坡 (2) 苏轼 (2) 遗憾 (1) 雾凇拼音 (1) 项脊轩志 (1)
久光本光
3 月前

stun打洞+cloudflare回源规则,将本地服务放进公网

文章字数:5835
阅读时间: 15 分钟

原理及效果

先说效果:本地路由器或NAS上部署的如vaultwarden这类服务,即使没有公网ipv4,只要有nat1(也就是fullcone全锥形网络),即可通过cloudflare的动态dns及回源规则,配合stun打洞实现近似公网使用的效果

原理:通过stun打洞将本地服务暴露至公网(本方案使用lucky工具,通过触发脚本实现动态dns更新及回源规则更新,将打洞获得的公网端口更新至cloudflare回源规则)

流量路径:用户访问 -> cloudflare -> origin rules的动态端口 -> lucky主机的ip:穿透通道本地端口 -> 部署服务的主机ip:本地服务端口

准备工作

  1. 本地网络开启fullcone,iStoreOS及大多数openwrt固件、iKuai均可一键开启
    stun打洞+cloudflare回源规则,将本地服务放进公网-久光本光的主页

  2. 部署好本地服务后,在路由器的防火墙规则添加对应的端口转发规则(这里是局域网的固定端口)
    stun打洞+cloudflare回源规则,将本地服务放进公网-久光本光的主页

  3. 确保lucky运行的终端已安装curl及jq

    opkg update
    opkg install curl
    opkg install jq
  4. 安装lucky,iStoreOS可以在软件商城一件安装,其余请参考lucky安装文档

操作步骤

  1. 获得cloudflare的zone_id并创建一个api令牌
    api令牌创建:https://dash.cloudflare.com/profile/api-tokens
    权限需要两个:DNS和Origin Rules
    stun打洞+cloudflare回源规则,将本地服务放进公网-久光本光的主页

zone_id在进入cloudflare的域名管理页面右下角
stun打洞+cloudflare回源规则,将本地服务放进公网-久光本光的主页

  1. 确认需要暴露的服务已参考准备工作2添加好防火墙转发规则

  2. 通过lucky开启stun打洞并填写触发脚本
    stun打洞+cloudflare回源规则,将本地服务放进公网-久光本光的主页

务必全部按图设置,不要使用lucky内置端口转发,而必须通过路由器的防火墙端口转发,触发脚本需要填写4个位置,分别是服务名称(随意)、域名、zoneid及api令牌,以下是完整脚本:

SERVICE_NAME="<填写你的服务名称>"
DOMAIN_NAME="<填写你的域名如aa.bb.com>"
CF_ZONE_ID="<cloudflare的zone_id>"
CF_TOKEN="<刚才获取的api令牌>"

# 最大重试次数
MAX_RETRIES=10
RETRY_DELAY=5

# =======================================================
# 2. 接收 Lucky 变量并更新“最新状态”
# =======================================================
# 接收 Lucky 传入的原始变量
INPUT_IP="${ip}"
INPUT_PORT="${port}"

# 定义状态文件路径 (每个服务独立)
STATE_FILE="/tmp/lucky_state_${SERVICE_NAME}.info"

# 【核心修复步骤 1】
# 脚本一启动,立即将最新收到的参数写入状态文件。
# 无论后续排队多久,所有排队的脚本最终读取的都是最后一次写入的文件内容。
echo "${INPUT_IP} ${INPUT_PORT}" > "$STATE_FILE"

# =======================================================
# 3. 全局锁与日志
# =======================================================
GLOBAL_LOCK_FILE="/tmp/lucky_cloudflare_global_update.lock"
LOG_FILE="/tmp/lucky_cf_update.log"

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

safe_curl() {
    local method="$1"
    local url="$2"
    local data="$3"
    local count=0
    local response=""

    while [ $count -lt $MAX_RETRIES ]; do
        if [ -n "$data" ]; then
            response=$(curl -s -X "$method" "$url" \
                -H "Authorization: Bearer $CF_TOKEN" \
                -H "Content-Type: application/json" \
                --data "$data")
        else
            response=$(curl -s -X "$method" "$url" \
                -H "Authorization: Bearer $CF_TOKEN" \
                -H "Content-Type: application/json")
        fi

        if echo "$response" | grep -q "success"; then
            echo "$response"
            return 0
        fi

        count=$((count + 1))
        # 如果是连接被拒绝等严重网络错误,稍微多等一会
        sleep $RETRY_DELAY
    done

    log "错误: API请求失败 ($url)"
    return 1
}

# =======================================================
# 4. 后台执行逻辑
# =======================================================
(
    # 随机延时 (保留原有逻辑,缓解并发)
    RANDOM_DELAY=$(awk 'BEGIN{srand(); print int(rand()*3)}')
    sleep $RANDOM_DELAY

    # --- 获取全局锁 ---
    LOCK_WAIT_COUNT=0
    while [ -f "$GLOBAL_LOCK_FILE" ]; do
        LOCK_TIME=$(date -r "$GLOBAL_LOCK_FILE" +%s)
        NOW_TIME=$(date +%s)
        # 锁超时检查 (120秒)
        if [ $((NOW_TIME - LOCK_TIME)) -gt 120 ]; then
            log "检测到死锁,强制释放"
            rm -f "$GLOBAL_LOCK_FILE"
            break
        fi

        if [ $LOCK_WAIT_COUNT -gt 60 ]; then
             log "排队超时,放弃本次执行"
             exit 0
        fi

        sleep 2
        LOCK_WAIT_COUNT=$((LOCK_WAIT_COUNT + 1))
    done

    touch "$GLOBAL_LOCK_FILE"
    trap "rm -f '$GLOBAL_LOCK_FILE'; exit" EXIT TERM INT

    # 日志轮转
    [ -f "$LOG_FILE" ] && [ $(wc -c < "$LOG_FILE") -gt 100000 ] && echo "" > "$LOG_FILE"

    # 【核心修复步骤 2】
    # 拿到锁之后,不使用自己的变量,而是从状态文件读取“真正的最新值”
    if [ -f "$STATE_FILE" ]; then
        read TARGET_IP TARGET_PORT < "$STATE_FILE"
    else
        log "错误: 状态文件丢失"
        rm -f "$GLOBAL_LOCK_FILE"
        exit 1
    fi

    if [ -z "$TARGET_IP" ] || [ -z "$TARGET_PORT" ]; then
        log "错误: 状态文件内容为空"
        rm -f "$GLOBAL_LOCK_FILE"
        exit 1
    fi

    log "开始处理 (最新目标): $DOMAIN_NAME -> $TARGET_IP:$TARGET_PORT"

    # --- A. 更新 DNS A 记录 ---
    DNS_RES=$(safe_curl "GET" "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/dns_records?type=A&name=$DOMAIN_NAME" "")
    if [ $? -ne 0 ]; then rm -f "$GLOBAL_LOCK_FILE"; exit 1; fi

    DNS_ID=$(echo "$DNS_RES" | jq -r '.result[0].id')
    CURRENT_DNS_IP=$(echo "$DNS_RES" | jq -r '.result[0].content')

    if [ "$DNS_ID" = "null" ]; then
        log "DNS记录不存在,创建中..."
        safe_curl "POST" "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/dns_records" \
            "{\"type\":\"A\",\"name\":\"$DOMAIN_NAME\",\"content\":\"$TARGET_IP\",\"ttl\":60,\"proxied\":true}" > /dev/null
    elif [ "$CURRENT_DNS_IP" != "$TARGET_IP" ]; then
        log "更新 DNS IP ($CURRENT_DNS_IP -> $TARGET_IP)..."
        safe_curl "PATCH" "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/dns_records/$DNS_ID" \
            "{\"content\":\"$TARGET_IP\"}" > /dev/null
    else
        # log "DNS IP 无需更新" # 减少日志噪音
        :
    fi

    # --- B. 更新 Origin Rules ---
    PHASE_RES=$(safe_curl "GET" "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/rulesets/phases/http_request_origin/entrypoint" "")
    if [ $? -ne 0 ]; then rm -f "$GLOBAL_LOCK_FILE"; exit 1; fi
    RULESET_ID=$(echo "$PHASE_RES" | jq -r '.result.id')

    if [ "$RULESET_ID" != "null" ]; then
        RULES_RES=$(safe_curl "GET" "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/rulesets/$RULESET_ID" "")
        if [ $? -ne 0 ]; then rm -f "$GLOBAL_LOCK_FILE"; exit 1; fi

        # 查找同名规则
        # 获取 Rule ID 和当前规则中设定的端口
        TARGET_RULE_DATA=$(echo "$RULES_RES" | jq -r --arg name "$SERVICE_NAME" '(.result.rules // [])[] | select(.description == $name) | "\(.id)|\(.action_parameters.origin.port // 0)"')

        # 处理多条规则重复的情况,只取最后一条,其他的并在后面逻辑清理
        TARGET_RULE_ID=$(echo "$TARGET_RULE_DATA" | tail -n 1 | cut -d "|" -f 1)
        CURRENT_RULE_PORT=$(echo "$TARGET_RULE_DATA" | tail -n 1 | cut -d "|" -f 2)

        # 构造 Payload
        PAYLOAD=$(jq -n \
                    --arg desc "$SERVICE_NAME" \
                    --arg domain "$DOMAIN_NAME" \
                    --argjson port "$TARGET_PORT" \
                    '{
                        description: $desc,
                        expression: ("(http.host eq \"" + $domain + "\")"),
                        action: "route",
                        action_parameters: {origin: {port: $port}}
                    }')

        if [ -z "$TARGET_RULE_ID" ]; then
            log "规则不存在,新建规则..."
            safe_curl "POST" "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/rulesets/$RULESET_ID/rules" "$PAYLOAD" > /dev/null

        else
            # 【优化】只有当 CF 里的端口 和 目标端口 不一致时才调用 API
            if [ "$CURRENT_RULE_PORT" != "$TARGET_PORT" ]; then
                log "端口变更 ($CURRENT_RULE_PORT -> $TARGET_PORT),更新规则..."
                safe_curl "PATCH" "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/rulesets/$RULESET_ID/rules/$TARGET_RULE_ID" "$PAYLOAD" > /dev/null
            else
                log "规则端口 ($CURRENT_RULE_PORT) 已是最新,跳过更新。"
            fi

            # 清理重复规则 (如果有多个同名规则)
            ALL_IDS=$(echo "$RULES_RES" | jq -r --arg name "$SERVICE_NAME" '(.result.rules // [])[] | select(.description == $name) | .id')
            for id in $ALL_IDS; do
                if [ "$id" != "$TARGET_RULE_ID" ]; then
                     log "发现冗余规则,删除 ID: $id"
                     safe_curl "DELETE" "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/rulesets/$RULESET_ID/rules/$id" "" > /dev/null
                fi
            done
        fi
    else
        log "错误: 无法获取 Ruleset ID"
    fi

    rm -f "$GLOBAL_LOCK_FILE"

) >/dev/null 2>&1 &

echo "后台更新任务已排队触发 (State: $INPUT_PORT)"
exit 0
  1. lucky穿透成功后,可以去cloudflare后台确认dns解析和origin rules是否生效
    stun打洞+cloudflare回源规则,将本地服务放进公网-久光本光的主页

  2. 确认是否可以通过域名直接访问你的本地服务

补充说明

使用本方案进行stun穿透,必须保证本地网络连接stun服务器是通过直连(保证3478端口直连)

  • 软件区
  • cloudflare
  • stun穿透
  • 公网
等 人表示很赞
久光本光
久光本光
2 月前

推荐 【微软原版】Windows11 LTSC 24h2 简体中文版镜像ISO下载地址

提供Goolge Drive和百度网盘两个下载地址: MD5:1a13ade0178082432f90df951a88842f SHA-256:2cb21649590c8cf770cd93556596dff4fd800f24d267a9be ...
  • 软件区
久光本光
久光本光
1 年前

推荐 Debian服务器通过rclone挂载缤纷云(或其他s3兼容云存储),适用宝塔备份任务

安装rclone并配置好缤纷云或其他s3兼容存储 在缤纷云或其他云存储后台获取Access Key和Secret Key 随后ssh进入你的服务器,安装rclone并配置缤纷云存储桶 依次运行 apt update apt instal ...
  • 软件区
  • bitiful
  • Debian
  • rclone
  • 宝塔
  • 缤纷云
久光本光
久光本光
1 月前

推荐 久光专版优化Windows11镜像下载地址

以下2种下载方式任选一种就可以,内容完全一致 ...
  • 软件区
  • windows
  • 精简
  • 系统优化
久光本光
久光本光
2 月前

推荐 原版Windows11优化/系统整备流程

适用于原版windows的优化及系统整备流程,主要包含安装过程:跳过联网账户;系统启动后:遥测服务禁用、VBS关闭、Edge浏览器卸载、小组件卸载 ...
  • 软件区
  • windows
  • 优化
  • 原版windows
  • 精简
久光本光
久光本光
2 年前

推荐 rime输入法+白霜词库配置说明

输入法介绍 Rime是一款主打隐私保护、跨平台的“极客向”输入法,通过非联网词库而是词频记录算法达到越用越顺手的效果,长久以来广受好评,唯一的问题在于他的配置文件需要用户具备一定的编程基础。但现在由于许多开发者维护的开源词库,我们通过简单的 ...
  • 软件区
  • rime输入法
  • 小狼毫
  • 白霜词库
  • 雾凇拼音

自媒体

随机推荐

  • 历史上真实存在的圣人【范仲淹人物志】
  • 优化版Windows11精简镜像:低延迟、无阉割、很纯净【X-Lite中文优化版】
  • 我本来不想当宰相【王安石人物志】
  • 久光专版优化Windows11镜像下载地址
  • Debian服务器通过rclone挂载缤纷云(或其他s3兼容云存储),适用宝塔备份任务
  • 魂穿北宋三天,苏轼治好了我的精神内耗
  • 刘慈欣《带上她的眼睛》解读,爱情、勇气不过是最肤浅的注解
  • wordpress主题 Farallon 调整记录
  • windows11镜像推荐:X-Lite 26h1 V3汉化版
  • iptables规则持久化指南
  • stun打洞+cloudflare回源规则,将本地服务放进公网
  • Windows11 LTSC究极优化+美化指南
  • 原版Windows11优化/系统整备流程
  • 不仅是精简:XOS PlayBook —— 可能是目前最专业的Windows深度优化方案
  • 铁甲柔情 男人的终极浪漫 《泰坦陨落2》剧情向解读
Copyright © 2026 久光本光的主页. All rights reserved. Designed by nicetheme. 苏公网安备32100302010906号 苏ICP备2024106004号
  • 首页
  • 知识区
  • 影视区
  • 游戏区
  • 软件区