收起左侧

使用 Cloudflare Worker 实现 IPv4/IPv6 智能访问 NAS

14
回复
375
查看
[ 复制链接 ]

2

主题

9

回帖

0

牛值

江湖小虾

背景

很多 NAS 玩家都遇到了一个问题:家庭宽带只有公网 IPv6,没有公网 IPv4。这导致在外网通过 IPv4 访问家中的 NAS 会很麻烦。

之前受到论坛帖子的启发,我尝试了 Cloudflare Tunnel,但免费版的线路速度不理想。于是,我找到了一个替代方案:结合使用 Cloudflare Worker 和 FNOS 自带的 FN Connect 中继服务。

这个方案可以实现以下目标:

  • 统一访问入口:所有用户都通过同一个域名访问 NAS。
  • IPv6 访客:直接连接到家庭宽带的 IPv6 地址,实现全速访问。(重定向到 NAS 的公网 IPv6 地址(DDNS域名)。)
  • IPv4 访客:自动通过 FNOS 官方中继服务器访问,保证兼容性。(重定向到 FNOS 的官方中继服务器地址。)
  • 成本:免费。
  • 从而实现 IPv6 直连、IPv4 中继的智能分流。

准备工作 (Prerequisites)

在开始之前,请确保你已经完成以下设置:

  1. Cloudflare 域名:拥有一个托管在 Cloudflare 的域名。
  2. IPv6 配置
    • FNOS 已开启 IPv6。
    • 路由器已设置好端口转发(公网 80/443 -> NAS 80/443)。
  3. DDNS
    • FNOS 已开启 DDNS (例如使用 DDNS-GO)。
    • 你的域名 (yourdomain.com) 和一个子域名 (www.yourdomain.com) 都已通过 DDNS 解析到你的公网 IPv6 地址。
  4. 反向代理
    • 已在 NAS 上部署 Nginx Proxy Manager (NPM) 或其他反代工具。
    • 已成功配置 SSL 证书,可以通过 https://www.yourdomain.com 访问 FNOS。

核心要求:在进行下一步之前,请确保你的 IPv6 直连已经是完整、可用且带有 SSL 加密的状态。
拥有一个由Cloudflare托管的域名 (例如 yourdomain.com)。
FNOS已开启IPv6,并且您已经在路由器上设置好了端口转发 (公网的443端口转发到NAS的443端口,80端口转发到NAS的80端口)。
FNOS已开启DDNS,例如使用DDNS-GO,将您的根域名(yourdomain.com)和www子域名(www.yourdomain.com)都动态解析到您家宽带的公网IPv6地址(www子域名做CNAME解析到根域名也是可以的)。
(最关键!) 您已经在NAS上,通过Docker或其他方式,部署好了Nginx Proxy Manager (NPM) 或其他反向代理工具。 并且,您已经成功配置了它,实现了可以通过 https://www.yourdomain.com 这样带SSL安全证书的域名,来访问您FNOS的Web服务。
一句话总结:请确保您的IPv6直连访问已经是完美的、带SSL加密的状态。 如果这一步还没完成,请先在论坛里搜索相关教程。

操作步骤

1. 配置 Cloudflare DNS

我们需要在 Cloudflare DNS 中添加一条“诱饵”记录来触发 Worker 脚本。

  1. 登录 Cloudflare,进入你的域名 DNS 设置。
  2. 添加一条新的 AAAA 记录:
    • 类型 (Type): AAAA
    • 名称 (Name): nas (或其他你喜欢的名字,这将是你的统一访问入口)
    • IPv6 地址 (Content): 100:: (这是一个特殊的保留地址,照填即可)
    • 代理状态 (Proxy status): 已代理 (橙色云朵)
    • TTL: 自动
      屏幕截图2025-07-27013436.png

这条记录本身不会指向任何地方,但它能确保所有访问 nas.yourdomain.com 的流量都经过 Cloudflare,从而让我们的 Worker 脚本有机会执行。

2. 创建并配置 Cloudflare Worker

这是整个方案的核心。

  1. 在 Cloudflare 主菜单,进入 Workers路由

    屏幕截图2025-07-27013503.png

  2. 点击 创建应用程序 -> 创建 Worker

    屏幕截图2025-07-27013550.png

  3. 直接点击 部署,然后 编辑代码

    屏幕截图2025-07-27013618.png

  4. 删除编辑器里的所有默认代码,粘贴以下脚本(选择从hello word开始,点击开始使用后直接创建work,,随后在右上角就能看到编辑代码这个内容):

export default {
  async fetch(request, env, ctx) {
    // --- 用户配置 ---
    // 1. IPv6 目标地址 (你配置好 SSL 的反代域名)
    const IPV6_TARGET = "https://www.yourdomain.com";

    // 2. IPv4 目标地址 (FNOS 官方中继)
    const IPV4_LOGIN_TARGET = "https://你的FNConnectID.fnos.net"; // 用于 App 登录和管理
    const IPV4_SHARE_TARGET = "https://s.fnnas.net"; // 用于文件分享

    // --- 核心逻辑 (无需修改) ---
    const url = new URL(request.url);
    const clientIP = request.headers.get("CF-Connecting-IP");
    const originalPathAndQuery = url.pathname + url.search;

    // 判断是否为 IPv6 访客
    if (clientIP && clientIP.includes(":")) {
      return Response.redirect(IPV6_TARGET + originalPathAndQuery, 302);
    }

    // IPv4 访客:判断是否为分享链接
    if (url.pathname.startsWith('/s/')) {
      return Response.redirect(IPV4_SHARE_TARGET + originalPathAndQuery, 302);
    } else {
      return Response.redirect(IPV4_LOGIN_TARGET + originalPathAndQuery, 302);
    }
  },
};

重要: 请务必将脚本中的 yourdomain.com你的FNConnectID 替换为你自己的真实信息。

  1. 点击 保存并部署

3. 绑定 Worker 路由

让 Worker 在我们设定的 nas 子域名上生效。

  1. 在 Worker 的管理页面,点击 触发器 (Triggers) 标签页。
  2. 路由 (Routes) 部分,点击 添加路由 (Add route)
    • 路由 (Route): nas.yourdomain.com/* (确保 nas 和你之前 DNS 设置的名称一致,/* 通配符必须有)

    • 区域 (Zone): 选择 yourdomain.com

      屏幕截图2025-07-27014208.png

  3. 点击 添加路由

4. (可选) 统一 FNOS 分享链接

为了让体验更统一,可以修改 FNOS 的默认分享链接。

  1. 登录 FNOS 后台。
  2. 进入 远程访问 -> 外链分享 -> 设置
  3. 选择 自定义链接,填入你的统一入口地址:https://nas.yourdomain.com
  4. 点击 确认

完成

现在,你可以将 https://nas.yourdomain.com 作为你的唯一入口地址了。

  • IPv6 用户: 享受直连速度,共享的文件也会通过ipv6直接访问你的域名。
  • IPv4 用户: 通过官方中继稳定访问,共享文件通过飞牛的中间访问(可购买飞牛服务提高中继速度)。

希望这个教程对你有帮助。如果有任何问题,欢迎在下面留言讨论。


收藏
送赞
分享

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

6

主题

64

回帖

0

牛值

fnOS系统内测组

码了,有空试试

我修改了一下代码逻辑 现在做了个ipv6和v4的联通性check,并且根据浏览器和app的agent的不同,做了不同的分流  详情 回复
3 天前

2

主题

9

回帖

0

牛值

江湖小虾

3 天前 楼主 显示全部楼层

我修改了一下代码逻辑
现在做了个ipv6和v4的联通性check,并且根据浏览器和app的agent的不同,做了不同的分流

2

主题

9

回帖

0

牛值

江湖小虾

3 天前 楼主 显示全部楼层
// === 目标地址配置区 ===
// 建议将这些值配置到Cloudflare Worker的环境变量中,也可以直接复制到worker代码中 (Settings > Variables)
const IPV6_TARGET = "https://www.yourdomain.com";
const IPV4_LOGIN_TARGET = "https://你的飞牛ID.fnos.net";
const IPV4_SHARE_TARGET = "https://s.fnnas.net";

// === 探测配置 ===
const PROBE_FILE = "/favicon.ico";
const TIMEOUT = 2500;

// === 浏览器识别关键词 ===
const BROWSER_UA_KEYWORDS = ['Mozilla', 'Chrome', 'Firefox', 'Safari', 'Edge', 'Opera'];

// 辅助函数:根据路径获取IPv4目标地址
function getIPv4TargetByPath(pathname) {
  if (pathname.startsWith('/s/')) {
    return IPV4_SHARE_TARGET;
  }
  return IPV4_LOGIN_TARGET;
}

export default {
  async fetch(request, env, ctx) {
    const url = new URL(request.url);
    const clientIP = request.headers.get("CF-Connecting-IP");
    const userAgent = request.headers.get("User-Agent") || "";
    const originalPathAndQuery = url.pathname + url.search;

    // 【关键修复】定义一个禁止缓存的响应头对象,用于所有动态响应
    const noCacheHeaders = new Headers({
      'Cache-Control': 'private, no-store, no-cache, must-revalidate',
    });

    const isBrowser = BROWSER_UA_KEYWORDS.some(keyword => userAgent.includes(keyword));

    if (isBrowser) {
      // 对于浏览器,返回带有JS探测的HTML页面
      const html = `
      <!DOCTYPE html>
      <html>
      <head>
        <title>智能路径分析中...</title>
        <meta charset="utf-8">
        <noscript>
          <meta http-equiv="refresh" content="0;url=${IPV4_LOGIN_TARGET}${originalPathAndQuery}">
        </noscript>
        <style>
          body { font-family: sans-serif; display: flex; flex-direction: column; justify-content: center; align-items: center; height: 100vh; margin: 0; background-color: #f0f2f5; color: #555; }
          .loader { border: 4px solid #f3f3f3; border-top: 4px solid #3498db; border-radius: 50%; width: 40px; height: 40px; animation: spin 1s linear infinite; margin-bottom: 20px; }
          @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
        </style>
      </head>
      <body>
        <div class="loader"></div>
        <p>正在为您选择最佳访问路径...</p>
        <script>
          (async function() {
            const ipv6Url = "${IPV6_TARGET}";
            const ipv4LoginUrl = "${IPV4_LOGIN_TARGET}";
            const ipv4ShareUrl = "${IPV4_SHARE_TARGET}";
            const originalPath = "${originalPathAndQuery}";
  
            function checkIPv6() {
              return new Promise(resolve => {
                const controller = new AbortController();
                const timeoutId = setTimeout(() => { controller.abort(); resolve(false); }, ${TIMEOUT});
                const probeUrl = ipv6Url + "${PROBE_FILE}" + "?_t=" + Date.now();
                fetch(probeUrl, { method: 'HEAD', signal: controller.signal, mode: 'no-cors' })
                  .then(() => { clearTimeout(timeoutId); resolve(true); })
                  .catch(() => { clearTimeout(timeoutId); resolve(false); });
              });
            }

            const isIPv6Available = await checkIPv6();
            let finalUrl = "";

            if (isIPv6Available) {
              finalUrl = ipv6Url + originalPath;
            } else {
              if (originalPath.startsWith('/s/')) {
                finalUrl = ipv4ShareUrl + originalPath;
              } else {
                finalUrl = ipv4LoginUrl + originalPath;
              }
            }
            window.location.replace(finalUrl);
          })();
        </script>
      </body>
      </html>
      `;
  
      // 在返回HTML时,加入禁止缓存的头
      noCacheHeaders.set('Content-Type', 'text/html;charset=UTF-8');
      return new Response(html, { headers: noCacheHeaders });

    } else {
      // 对于非浏览器(App等),直接进行服务器端重定向
      let targetUrl;
      if (clientIP && clientIP.includes(":")) {
        targetUrl = IPV6_TARGET + originalPathAndQuery;
      } else {
        const ipv4Target = getIPv4TargetByPath(url.pathname);
        targetUrl = ipv4Target + originalPathAndQuery;
      }
  
      // 【关键修复】手动创建302响应,并附上禁止缓存的头
      noCacheHeaders.set('Location', targetUrl);
      return new Response(null, {
        status: 302,
        headers: noCacheHeaders,
      });
    }
  },
};

不懂worker,有个问题咨询一下,worker里能否获取 dns的配置 比如说 我通过ddns给 某个 text记录 传了当前nas的ipv4地址和内网穿透端口,那么在worker里能否读取到这条解析记录,如果能的话是不是可以在v4情况下不走  详情 回复
昨天 10:36
按新的这个修改之后会一直卡在 正在为你选择最佳路径 一直在重新请求。  详情 回复
前天 20:42

6

主题

64

回帖

0

牛值

fnOS系统内测组

ipv6的80,443端口如何开启

运营商把你的给屏蔽了吗?  详情 回复
前天 14:02

2

主题

9

回帖

0

牛值

江湖小虾

前天 14:02 楼主 显示全部楼层
Jimboo7339 发表于 2025-7-30 09:11
ipv6的80,443端口如何开启

运营商把你的给屏蔽了吗?
不知道是否屏蔽,部署lucky和nginx指定80和443会报错,可能是系统限制的  详情 回复
昨天 09:22

0

主题

4

回帖

0

牛值

江湖小虾

对于每个服务一个子域名的情况好像也不适用,路由器的端口转发有上限

11

主题

21

回帖

0

牛值

初出茅庐

一蓑烟雨任 发表于 2025-7-29 16:41
// === 目标地址配置区 ===
// 建议将这些值配置到Cloudflare Worker的环境变量中,也可以直接复制到worker ...

按新的这个修改之后会一直卡在 正在为你选择最佳路径 一直在重新请求。
试一下无痕模式?估计是浏览器cookies缓存导致的 但是,我这个脚本已经设置每次删除缓存了,强制刷新,在选择线路的时候避免了cookies导致的bug  详情 回复
昨天 02:31

2

主题

9

回帖

0

牛值

江湖小虾

昨天 02:31 楼主 显示全部楼层
鸽子王奈奈 发表于 2025-7-30 20:42
按新的这个修改之后会一直卡在 正在为你选择最佳路径 一直在重新请求。

试一下无痕模式?估计是浏览器cookies缓存导致的
但是,我这个脚本已经设置每次删除缓存了,强制刷新,在选择线路的时候避免了cookies导致的bug

6

主题

64

回帖

0

牛值

fnOS系统内测组

一蓑烟雨任 发表于 2025-7-30 14:02
运营商把你的给屏蔽了吗?

不知道是否屏蔽,部署lucky和nginx指定80和443会报错,可能是系统限制的

6

主题

64

回帖

0

牛值

fnOS系统内测组

部署好了可用,给楼主点个赞

6

主题

64

回帖

0

牛值

fnOS系统内测组

一蓑烟雨任 发表于 2025-7-29 16:41
// === 目标地址配置区 ===
// 建议将这些值配置到Cloudflare Worker的环境变量中,也可以直接复制到worker ...

不懂worker,有个问题咨询一下,worker里能否获取 dns的配置
比如说 我通过ddns给 某个 text记录 传了当前nas的ipv4地址和内网穿透端口,那么在worker里能否读取到这条解析记录,如果能的话是不是可以在v4情况下不走FNID,走ipv4的内网穿透
我这个worker脚本没有考虑到内网穿透的情况,我修改一下!尝试走一下p2p打洞! 周末我多测试一下,然后把稳定的脚本放出来  详情 回复
昨天 15:01

2

主题

9

回帖

0

牛值

江湖小虾

昨天 15:01 楼主 显示全部楼层
Jimboo7339 发表于 2025-7-31 10:36
不懂worker,有个问题咨询一下,worker里能否获取 dns的配置
比如说 我通过ddns给 某个 text记录 传了当 ...

我这个worker脚本没有考虑到内网穿透的情况,我修改一下!尝试走一下p2p打洞!
周末我多测试一下,然后把稳定的脚本放出来

3

主题

6

回帖

0

牛值

江湖小虾

我是电信千兆上行50,有公网IP,IPV4和6都有,现在用的免费的DDNS,XXX.f3322.org免费动态域名,感觉速度不行,能怎么提升速度么,公司的联通宽带不不支持IPV6,有文件共享访问需求
和DDNS没有任何关系,DDNS就是一个动态解析的电话本而已 你的NAS在家里放着吗?有公网ipv4,上传慢,应该是运营商限速了,建议/path用speedtest之类的,可以破解限速 但是,运营商绝大多数时候是不对ipv6上传限速的  详情 回复
昨天 21:26

2

主题

9

回帖

0

牛值

江湖小虾

昨天 21:26 楼主 显示全部楼层
niqiu8 发表于 2025-7-31 15:26
我是电信千兆上行50,有公网IP,IPV4和6都有,现在用的免费的DDNS,XXX.f3322.org免费动态域名,感觉速度不 ...

和DDNS没有任何关系,DDNS就是一个动态解析的电话本而已
你的NAS在家里放着吗?有公网ipv4,上传慢,应该是运营商限速了,建议/path用speedtest之类的,可以破解限速
但是,运营商绝大多数时候是不对ipv6上传限速的,可以使用WireGuard搭一个隧道,也就是IPv6 traffic in IPv4 tunnel
你可以查一下相关的技术攻略,如果有不懂的,我们及时交流
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则