因为一些众所周知的原因, 我们可以尝试自己搭建干净/安全的DNS服务,一切都只要掌握一些简单知识/技巧.
## 懒人提示
对于绝大多数不愿意折腾的人而言, 223.5.5.5作为dns服务器就够了
## 搭建原理
首先呢, 地球上除了少数地方以外, Google家的8.8.8.8的DNS是非常理想的选择, 我们要做的,就是自建DNS服务, 通过DoH转发Google的DNS服务, 但是呢, 直接转发肯定是不行的,我们可以通过cf的workers无服务函数进行中转, 中转的时候顺便把自己的IP地址打码后传给Google DNS, 这样就能获得一个支持EDNS的DNS服务了(如果不传EDNS IP的话, 很多域名解析出来就是海外链路了, 速度必然受到影响).
## 开始实操
整个过程总共分为六步, 一切顺利的话, 30分钟内就能完成搭建:
- 使用Technitium DnsServer自建一台DNS服务器, 详情见"使用docker轻松搭建dns服务器"
- 建立一个cf的Workers(其它厂商无服务函数也可以), 具体代码见附录, 这里要注意把EDNS的IP设置成你当地的IP(记得打码噢)
- 因为Workers的顶级域名被XX了, 这里需要在触发器自定义域添加一个自己的二级域名, 比如dns.demo.com
- 在Technitium DnsServer里设置Forwarder, Forwarder Protocol选DNS-over-HTTPS (JSON), 转发地址就是https://dns.demo.com/, 保存设置. (这里需要注意的是, Technitium DnsServer从v11版本开始去除了DNS-over-HTTPS (JSON)这个协议, 所以目前需要把版本锁定至v10)
- (可选)在DnsServer的日志里检查Forwarder转发是否成功
- (可选) DnsServer开启DNS-over-TLS, 需要自行准备ssl证书,对外开放一个public.demo.com:853服务, 这样就可以将其在安卓手机里设置为"私人DNS"了
## 附录
```js
addEventListener('fetch', function(event) {
const { request } = event
const response = handleRequest(request)
event.respondWith(response)
})
const doh = 'https://dns.google/dns-query'
const dohjson = 'https://dns.google/resolve'
const contype = 'application/dns-message'
const jstontype = 'application/dns-json'
async
function handleRequest(request) {
const { method, headers, url } = request
const searchParams = new
URL(url).searchParams
if (method == 'GET' && searchParams.has('dns')) {
return
await fetch(doh + '?dns=' + searchParams.get('dns')+'&edns_client_subnet=6.6.6.0/24', {
method: 'GET',
headers: {
'Accept': contype,
}
});
} else
if (method == 'POST' && headers.get('content-type')==contype) {
return
await fetch(doh, {
method: 'POST',
headers: {
'Accept': contype,
'Content-Type': contype,
},
body: await request.arrayBuffer()
});
} else
if (method== 'GET' && headers.get('Accept')==jstontype) {
const search = new
URL(url).search
const result = await fetch(dohjson + search + '&edns_client_subnet=6.6.6.0/24', {
method: 'GET',
headers: {
'Accept': jstontype,
}
});
const secondCheck = await result.clone().json();
if (secondCheck['Status'] == 0) {
return result
} else {
// Some request name can't work with edns
// "Status": 5 /* REFUSED */,
return
await fetch(dohjson + search, {
method: 'GET',
headers: {
'Accept': jstontype,
}
});
}
} else {
return
new
Response("", {status: 404})
}
}
```
如果有bug, 可以到我fork的仓库里提issues(https://github.com/justid/doh-cf-workers)