unbound 是支持提供 DNS-over-HTTPS 和 DNS-over-TLS 的,但是为了能够用上 subnetcache 这个模块来给国内用户提供正确的国内IP结果(附上 edns client subnet 并发送给国内的公共DNS),如果处于 Caddy 后面做 upstream server,就不得不使用 proxy_protocol 了,否则 ecs 地址不是 client 的 IP 地址。
首先最重要的是要用 Caddy,而且是构建了 caddy-l4 这个插件的 Caddy,因为 nginx 不支持对 upstream 发起proxy_protocol v2。此外,对于用作DoT的域名,l4这个插件的 handle 部分需要先 terminate TLS 然后再 proxy v2,如下所示:
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
{
"apps": {
"layer4": {
"servers": {
"majorHTTPS": {
"listen": [
"0.0.0.0:443"
],
"routes": [
{
"match": [
{
"tls": {
"sni": [
"DoT域名"
]
}
}
],
"handle": [
{
"handler": "tls"
},
{
"handler": "proxy",
"proxy_protocol": "v2",
"upstreams": [
{
"dial": [
"unbound监听的proxy_protocol地址和端口"
]
}
]
}
]
}
]
}
}
},
"tls": {
"certificates": {
"load_files": [
{
"certificate": "证书文件路径",
"key": "证书私钥文件路径",
"tags": [
"随便给个tag"
]
}
]
}
}
}
}
因为要terminate TLS,所以要单独写一个 tls 来加载证书。至于如何把Caddyfile转成json然后跟这个json做合并,再启动 Caddy,就不在本文范围内了(问 ChatGPT 怎么用 Python 手动合并两个 json ,而且某个 key 需要进行 append 而不是 replace ,它就会给你答案)。
但是根据 DoT 的定义以及 unbound 的配置文件,难道不是应该直接让 Caddy 把数据流 proxy 到 unbound 的 proxy-protocol-port:
并且设置 tls-port:
才对吗?答案是不对,虽然这配置看起来对,但是结果就是 unbound 疯狂报错 SSL wrong version number (0A00010B),意思就是 unbound 并没有期待 TLS 但是 client 在向它发送 TLS 数据流。后来我悟了,DoT 本质上是把 DNS 数据包封装在 TLS 里面,所以如果 unbound 拒绝处理 TLS 那我们就让 Caddy 把 TLS 层去了,再 proxy 给 unbound。
unbound 的示范配置如下:
1
2
3
4
5
6
7
server:
module-config: "subnetcache iterator"
interface: 127.0.0.1@853
interface: ::1@853
# 自己配置 access-control 为你自己的IP地址,照抄可是不行的哦
access-control: 1.1.1.1./32 allow
proxy-protocol-port: 853
不需要设置 tls-port 和 tls证书。这样就可以顺利实现 DoT 跟普通 HTTPS 共用 443 端口而且 TLS 指纹都是 Caddy 了。