Cloudflare Worker 部署指南:IPv6 P2P 直连 + IPv4 CDN 代理

Cloudflare Worker 部署指南:IPv6 P2P 直连 + IPv4 CDN 代理

站点只有V6公网,公司只有V4上网环境,如何访问家内只有V6公网的NAS。 本文教程,利用免费的cloudflare计划实现V6网络P2P访问纯V6站点,V4网络使用国内优选IP加速访问纯V6站点。

 次点击
69 分钟阅读

核心架构说明(所有图片内容均为个人设置,请自行更改)

本方案需要 两条 DNS 记录配合

记录

类型

用途

代理状态

a.example.com

AAAA

Worker 内部连接源站

仅 DNS(灰云)

b.example.com

CNAME

用户访问入口

已代理(橙云)

原理

  1. 用户访问 b.example.com,流量被 Cloudflare 代理拦截

  2. Worker 根据用户 IP 类型分流:

    • IPv6 用户 → 302 重定向到 a.example.com:端口,浏览器直连源站

    • IPv4 用户 → Worker 内部请求 a.example.com:端口,通过 CDN 转发响应

准备工作

  1. Cloudflare 账号

  2. 域名一个,DNS 托管在 Cloudflare

  3. 源服务器已配置好服务,拥有公网 IPv6 地址(如 240e:xxx:xxx:xxx::1

第一步:创建两条 DNS 记录

假设主域名为 example.com,源站 IPv6 为 240e:123:456:789::1

1.1 创建 AAAA 记录(内部源站)

这条记录供 Worker 内部使用,不需要开启代理

  • 类型AAAA

  • 名称a(或任意内部标识,如 origin-v6

  • IPv6 地址240e:123:456:789::1

  • 代理状态:关闭(灰色云朵,仅 DNS)

1.2 创建 CNAME 记录(用户入口)

这条记录是用户实际访问的地址,必须开启代理

  • 类型CNAME

  • 名称b(你的服务子域名,如 pannas

  • 目标a.example.com

  • 代理状态:开启(橙色云朵,已代理)

⚠️ 关键理解:用户访问的是 b.example.com(走 CDN),Worker 内部连接的是 a.example.com(直连源站 IPv6)。两者通过 CNAME 关联,但代理状态不同。

我这里用nas作为演示


第二步:部署 Worker

2.1 创建 Worker

  1. 进入 Cloudflare 仪表盘 → Workers 和 Pages

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

  3. 随便起个名字,点击 部署(先部署默认代码)

2.2 粘贴完整代码

点击 编辑代码,清空原有内容,粘贴以下代码:

// ==========================================
// Cloudflare Worker - IPv6 P2P 直连版
// IPv6用户直连 + IPv4走CDN代理
// ==========================================

// 变量命名规则:
//   IPV6_HOST     -> 全局IPv6主机地址(默认:a.example.com)
//   PORT_子域名    -> 目标端口  
//   SSL_子域名     -> 协议类型 (https/http,默认https)

// ==========================================
// 配置区
// ==========================================

// 缓存配置
const CACHE_CONFIG = {
    static: 86400,    // 静态资源:24小时
    dynamic: 300,     // 动态内容:5分钟
    api: 0            // API请求:不缓存
};

// 静态资源后缀
const STATIC_EXTENSIONS = [
    'css', 'js', 'jpg', 'jpeg', 'png', 'gif', 'svg', 'ico',
    'woff', 'woff2', 'ttf', 'eot', 'mp4', 'webp', 'avif'
];

// 必要请求头
const ESSENTIAL_HEADERS = [
    'Accept', 'Accept-Encoding', 'Accept-Language',
    'Authorization', 'Cache-Control', 'Content-Type',
    'Cookie', 'Range', 'User-Agent', 'Referer'
];

// 必要响应头
const ESSENTIAL_RESPONSE_HEADERS = [
    'Content-Type', 'Content-Length', 'Content-Encoding',
    'Cache-Control', 'ETag', 'Last-Modified',
    'Set-Cookie', 'WWW-Authenticate', 'Content-Disposition',
    'Location', 'Access-Control-Allow-Origin'
];

// ==========================================
// 工具函数
// ==========================================

function isIPv6(ip) {
    return ip && ip.includes(':');
}

function isStaticResource(pathname) {
    const ext = pathname.split('.').pop().toLowerCase();
    return STATIC_EXTENSIONS.includes(ext);
}

function isAPIRequest(pathname) {
    return pathname.startsWith('/api/') || pathname.startsWith('/_api/');
}

/**
 * 从环境变量读取服务配置
 */
function getServiceConfig(subdomain) {
    const ipv6Host = globalThis['IPV6_HOST'] || 'a.example.com';
    const port = globalThis[`PORT_${subdomain}`];
    const ssl = globalThis[`SSL_${subdomain}`] || 'https';
    
    if (!port) return null;
    
    return {
        host: ipv6Host,
        port: port,
        protocol: ssl.toLowerCase()
    };
}

/**
 * 获取缓存TTL
 */
function getCacheTTL(pathname) {
    if (isStaticResource(pathname)) return CACHE_CONFIG.static;
    if (isAPIRequest(pathname)) return CACHE_CONFIG.api;
    return CACHE_CONFIG.dynamic;
}

// ==========================================
// 核心处理函数
// ==========================================

/**
 * IPv6 P2P 直连(302重定向到内网地址)
 * 让客户端直接连接,Worker不做代理中转
 */
function handleIPv6Direct(request, serviceConfig) {
    const url = new URL(request.url);
    const directUrl = `${serviceConfig.protocol}://${serviceConfig.host}:${serviceConfig.port}${url.pathname}${url.search}`;
    
    // 方案1:302重定向(推荐,真正的P2P直连)
    return new Response(null, {
        status: 302,
        statusText: 'IPv6 Direct Connect',
        headers: {
            'Location': directUrl,
            'X-Connection-Type': 'IPv6-P2P-Direct',
            'X-Direct-Host': serviceConfig.host,
            'X-Direct-Port': serviceConfig.port,
            'X-Direct-Protocol': serviceConfig.protocol,
            'Cache-Control': 'no-cache'
        }
    });
}

/**
 * IPv6 透明代理(备选方案:Worker中转但保持连接)
 */
async function handleIPv6Transparent(request, serviceConfig, clientIP) {
    const url = new URL(request.url);
    const originUrl = `${serviceConfig.protocol}://${serviceConfig.host}:${serviceConfig.port}${url.pathname}${url.search}`;
    
    console.log(`[IPv6透明代理] ${clientIP} -> ${originUrl}`);
    
    // 构建最小化请求头
    const headers = new Headers();
    ESSENTIAL_HEADERS.forEach(header => {
        const value = request.headers.get(header);
        if (value) headers.set(header, value);
    });
    headers.set('Host', serviceConfig.host);
    headers.set('X-Real-IP', clientIP);
    headers.set('X-Connection-Type', 'IPv6-P2P');
    
    const fetchOptions = {
        method: request.method,
        headers: headers,
        redirect: 'follow'
    };
    
    if (['POST', 'PUT', 'PATCH', 'DELETE'].includes(request.method)) {
        fetchOptions.body = request.body;
    }
    
    const response = await fetch(originUrl, fetchOptions);
    
    // 构建响应
    const responseHeaders = new Headers();
    ESSENTIAL_RESPONSE_HEADERS.forEach(header => {
        const value = response.headers.get(header);
        if (value) responseHeaders.set(header, value);
    });
    
    responseHeaders.set('X-Connection-Type', 'IPv6-Transparent');
    responseHeaders.set('X-Direct-Host', serviceConfig.host);
    
    return new Response(response.body, {
        status: response.status,
        statusText: response.statusText,
        headers: responseHeaders
    });
}

/**
 * IPv4 CDN 代理
 */
async function handleIPv4CDN(request) {
    const url = new URL(request.url);
    const cacheTTL = getCacheTTL(url.pathname);
    
    const headers = new Headers(request.headers);
    headers.set('X-Proxy-Done', '1');
    headers.set('X-Connection-Type', 'IPv4-CDN');
    
    const modifiedRequest = new Request(request, {
        headers: headers,
        cf: {
            cacheTtl: cacheTTL,
            minify: {
                javascript: true,
                css: true,
                html: false
            },
            polish: 'lossy',
            mirage: true,
        }
    });
    
    return fetch(modifiedRequest);
}

// ==========================================
// 主请求处理
// ==========================================

async function handleRequest(request) {
    try {
        const url = new URL(request.url);
        const subdomain = url.hostname.split('.')[0];
        const clientIP = request.headers.get('CF-Connecting-IP') || '';
        
        // 健康检查端点
        if (url.pathname === '/health' || url.pathname === '/_health') {
            const isIPv6Client = isIPv6(clientIP);
            return new Response(JSON.stringify({
                status: 'ok',
                region: 'Suzhou-CN',
                connection: isIPv6Client ? 'IPv6-P2P' : 'IPv4-CDN',
                clientIP: clientIP,
                timestamp: Date.now(),
                p2p: isIPv6Client ? 'direct' : 'cdn'
            }), {
                headers: { 'Content-Type': 'application/json' }
            });
        }
        
        // 获取服务配置
        const serviceConfig = getServiceConfig(subdomain);
        if (!serviceConfig) {
            return fetch(request);
        }
        
        // IPv6 用户:P2P 直连
        if (isIPv6(clientIP)) {
            // 检查是否强制使用透明代理模式
            const useTransparent = url.searchParams.get('proxy') === 'transparent';
            
            if (useTransparent) {
                // 透明代理模式(Worker中转)
                return await handleIPv6Transparent(request, serviceConfig, clientIP);
            } else {
                // P2P 直连模式(302重定向,默认)
                return handleIPv6Direct(request, serviceConfig);
            }
        }
        
        // IPv4 用户:CDN代理
        return await handleIPv4CDN(request);
        
    } catch (error) {
        console.error('Worker错误:', error);
        
        // 降级处理
        try {
            return await fetch(request);
        } catch (e) {
            return new Response(JSON.stringify({
                error: '服务暂时不可用',
                timestamp: Date.now()
            }), {
                status: 502,
                headers: { 'Content-Type': 'application/json' }
            });
        }
    }
}

// ==========================================
// 事件监听
// ==========================================

addEventListener('fetch', event => {
    event.passThroughOnException();
    event.respondWith(handleRequest(event.request));
});

2.3 配置环境变量

在代码编辑器中,点击顶部 变量和机密 选项卡,添加以下环境变量:

变量名

示例值

说明

IPV6_HOST

a.example.com

内部 IPv6 源站地址

PORT_b

8080

子域名 b 对应的端口

SSL_b

http

子域名 b 的协议(http/https)

📌 变量命名规则PORT_SSL_ 后面的部分必须与你的用户访问子域名一致。

  • 访问 b.example.com → 配置 PORT_b

  • 访问 pan.example.com → 配置 PORT_pan

  • 访问 nas.example.com → 配置 PORT_nas

我以NAS为例

2.4 部署 Worker

点击右上角 部署 按钮,确认部署成功。


第三步:绑定 Worker 路由

最后一步,让访问 b.example.com 的流量经过 Worker。

  1. 进入域名管理页 → Worker 路由(某些版本在 Workers 菜单下)

  2. 点击 添加路由

  3. 配置:

    • 路由b.example.com/*

    • Worker:选择你刚刚创建的 Worker

    • 环境production

  4. 保存

同样以NAS为例

如果想增免端口访问(自行放行局域网V6防火墙

完整数据流图解

用户浏览器
    │
    ├─ 访问 b.example.com(CNAME → a.example.com,橙色云朵)
    │
    ▼
Cloudflare CDN 边缘节点
    │
    ├─ 触发 Worker 路由
    │
    ├─ 判断客户端 IP 类型
    │
    ├─── IPv6 客户端 ──→ 302 重定向至 http://a.example.com:8080
    │                         │
    │                         └──→ 浏览器直接连接源站 IPv6(灰云记录)
    │
    └─── IPv4 客户端 ──→ Worker 内部 fetch http://a.example.com:8080
                              │
                              └──→ CDN 节点代理转发响应给用户

多服务扩展

如果你有多个服务需要代理,只需添加对应的环境变量:

IPV6_HOST = a.example.com

PORT_pan  = 8080     # 网盘服务
SSL_pan   = http

PORT_nas  = 5000     # NAS管理面板
SSL_nas   = https

PORT_tv   = 8096     # Jellyfin
SSL_tv    = http

然后分别为每个子域名创建 CNAME 记录(都指向 a.example.com,开启代理),并添加对应的 Worker 路由即可。一个 Worker 搞定所有服务。

© 本文著作权归作者所有,未经许可不得转载使用。