Unbound与TLS、proxy_protocol

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,如下所示:

{
    "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"
                  ]
                }
              ]
            }
          }
    }
}Code language: JSON / JSON with Comments (json)

因为要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 的示范配置如下:

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: 853Code language: CSS (css)

不需要设置 tls-port 和 tls证书。这样就可以顺利实现 DoT 跟普通 HTTPS 共用 443 端口而且 TLS 指纹都是 Caddy 了。