抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

最近网络环境变化,NextDNS 和 AdGuard DNS 的 DoH 443 端口被封,导致无法正常使用。
本指南教你如何通过 Cloudflare Workers 自建可靠的 DNS-over-HTTPS 服务。


📦 准备工作

  1. Cloudflare 账户
  2. 自有域名
    • 可在 Cloudflare 托管或使用免费域名(如 .tk/.ml)
  3. GitHub 账户

🔧 部署步骤

第一步:获取项目代码

  1. 访问开源项目:https://github.com/tina-hello/doh-cf-workers
  2. 推荐做法:创建私有仓库
    1
    2
    3
    # 1. Fork 到自己的GitHub账户
    # 2. 进入仓库设置 → 修改为Private(私有)
    # 3. 这样你的DNS地址就不会公开

第二步:连接 GitHub 和 Cloudflare

  1. 在 GitHub 配置 Action 权限

    • 进入你的私有仓库 → Settings → Actions → General
    • 开启 Read and write permissions(必须!)
    • 保存设置
  2. 创建 Cloudflare API 令牌

    1. 登录 Cloudflare 控制台
    2. 右上角「我的个人资料」→「API 令牌」
    3. 创建令牌 → 使用模板 “编辑 Cloudflare Workers”
    4. 权限设置:
      • 账户:Workers 脚本 - 编辑
      • 区域:根据需要添加(绑定域名时需要)
    5. 生成令牌 → 立即复制保存(离开后无法查看)

第三步:配置 GitHub Secrets

在你的私有仓库中:

  1. Settings → Secrets and variables → Actions
  2. 添加以下两个机密:
    名称
    CLOUDFLARE_ACCOUNT_ID 你的Cloudflare账户ID(控制台右上角获取)
    CLOUDFLARE_API_TOKEN 上一步生成的API令牌

第四步:触发自动部署

  1. 修改代码后推送到仓库:
    1
    2
    3
    git add .
    git commit -m "更新DNS配置"
    git push origin main
  2. 查看 Action 状态:
    • 仓库 → Actions → 查看部署日志
    • 出现 ✅ Successfully published 表示成功

🔐 绑定自定义域名(可选但推荐)

避免使用默认的 *.workers.dev 地址,提升隐蔽性

  1. 在 Cloudflare 控制台:
    • Workers & Pages → 你的Worker → 触发器 → 自定义域
  2. 添加你的域名(如 doh.yourdomain.com
  3. 在域名DNS设置中添加CNAME记录:
    1
    2
    3
    4
    类型: CNAME
    名称: doh
    内容: your-worker-name.your-account.workers.dev
    TTL: 自动

🛡️ 增强安全性:IP访问控制

限制只有特定IP能访问你的DNS服务

方法1:在Worker代码中添加(推荐)

编辑 index.js,在顶部添加:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 允许访问的IP列表
const allowedIPs = [
'192.168.1.0/24', // 示例:家庭网络
'203.0.113.42', // 示例:办公IP
'2001:db8::/32' // IPv6示例
];

export default {
async fetch(request) {
const clientIP = request.headers.get('cf-connecting-ip');

// IP检查逻辑
if (!isIPAllowed(clientIP, allowedIPs)) {
return new Response('Access Denied', { status: 403 });
}

// 原有DNS处理代码...
}
};

// IP检查函数
function isIPAllowed(ip, allowedList) {
// 这里实现IP检查逻辑
// 可以使用ip-cidr等npm包
return true; // 示例
}

方法2:Cloudflare防火墙规则

  1. 控制台 → 安全 → WAF
  2. 创建新规则:
    • 字段:IP源地址
    • 运算符:不在
    • 值:填入你的IP列表(每行一个)
    • 操作:阻止

index.js配置实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
// SPDX-License-Identifier: 0BSD

// 定义上游DNS服务器列表(按优先级排序)
const dohUpstreams = [
// 一级:快速可靠的核心服务
'https://dns.cloudflare.com/dns-query',
'https://security.cloudflare-dns.com/dns-query',
'https://dns.google/dns-query',

// 二级:IPv6优化的备用服务
'https://[2606:4700:4700::1111]/dns-query',
'https://[2620:fe::fe]/dns-query',

// 三级:功能型服务
'https://dns.adguard-dns.com/dns-query',
'https://unfiltered.adguard-dns.com/dns-query',
'https://doh.opendns.com/dns-query',
'https://doh.quad9.net/dns-query'
];

const customPath = "/dns-query";
const timeoutMs = 3000; // 3秒超时
const maxRetries = dohUpstreams.length; // 尝试所有服务器

export default {
async fetch(request, env, ctx) {
return handleRequest(request);
},
};

// 带超时控制的fetch
async function fetchWithTimeout(url, options) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);

try {
const response = await fetch(url, {
...options,
signal: controller.signal
});
return response;
} finally {
clearTimeout(timeoutId);
}
}

async function handleRequest(request) {
const { method, headers, url } = request;
const { searchParams, pathname } = new URL(url);
const now = new Date().toISOString();

if (pathname !== customPath) {
return new Response(`404 Not Found\n${now}`, { status: 404 });
}

// 健康检查端点
if (method === 'GET' && !searchParams.toString()) {
return new Response(
`DNS Worker - Priority-based Failover\nTime: ${now}\nUpstreams:\n${dohUpstreams.join('\n')}`,
{ headers: { 'Content-Type': 'text/plain' } }
);
}

try {
// DNS-over-HTTPS 二进制查询 (RFC 8484)
if (method === 'GET' && searchParams.has('dns')) {
const dnsParam = searchParams.get('dns');
let lastError = null;

// 按配置顺序尝试上游服务器
for (let i = 0; i < maxRetries; i++) {
try {
const upstream = dohUpstreams[i];
return await fetchWithTimeout(`${upstream}?dns=${dnsParam}`, {
method: 'GET',
headers: { 'Accept': 'application/dns-message' }
});
} catch (e) {
lastError = e;
console.warn(`[Failover] Upstream ${i} failed (${dohUpstreams[i]}): ${e.message}`);
// 继续尝试下一个服务器
}
}
throw lastError || new Error("All upstreams failed");

// DNS JSON API 查询
} else if (method === 'GET' && (searchParams.has('name') || searchParams.has('type'))) {
const params = new URLSearchParams(searchParams);
let lastError = null;

// 按优先级尝试JSON查询端点
for (let i = 0; i < maxRetries; i++) {
try {
// 特殊处理Google的JSON端点
const baseUrl = dohUpstreams[i].includes('dns.google')
? 'https://dns.google/resolve'
: dohUpstreams[i].replace('/dns-query', '');

return await fetchWithTimeout(`${baseUrl}?${params}`, {
method: 'GET',
headers: { 'Accept': 'application/dns-json' }
});
} catch (e) {
lastError = e;
console.warn(`[Failover] JSON upstream ${i} failed: ${e.message}`);
}
}
throw lastError || new Error("All JSON upstreams failed");

// POST请求处理
} else if (method === 'POST' && headers.get('content-type') === 'application/dns-message') {
const body = await request.arrayBuffer();
let lastError = null;

for (let i = 0; i < maxRetries; i++) {
try {
return await fetchWithTimeout(dohUpstreams[i], {
method: 'POST',
headers: {
'Accept': 'application/dns-message',
'Content-Type': 'application/dns-message',
},
body: body
});
} catch (e) {
lastError = e;
console.warn(`[Failover] POST upstream ${i} failed: ${e.message}`);
}
}
throw lastError || new Error("All POST upstreams failed");
}

return new Response(
'Invalid DNS request format\nRefer to RFC8484',
{ status: 400, headers: { 'Content-Type': 'text/plain' } }
);

} catch (e) {
return new Response(`DNS Resolution Failed\nError: ${e.message}`, {
status: 503,
headers: {
'Content-Type': 'text/plain',
'Cache-Control': 'no-store, max-age=0'
}
});
}
}

参考

评论



Powered by Hexo | Theme keep Volantis

本站总访问量 总访客数 🌎