DNSdist 实施 DOT/DOH 记录

2022-05-07 571点热度 2人点赞

dnsmasq 不支持 doh 和 dot 怎么办,想自建 doh 和 dot 服务端怎么办,总有 ddos 想限速怎么办,快用 dnsdist


1 、 dnsdist 是什么

dnsdist 是一个抗 DoS 的 DNS 负载均衡器。其设计目标是路由流量到优选的服务器,同时分流或阻止恶意流量。 [ 链接 ]


2 、 dnsdist 基础配置

使用 Nginx 作为 dot/doh 网关 [ 链接 ]

DOH 服务搭建 [ 链接 ]

Nginx+doh [ 链接 ]

集群 DNS 基础解析 [ 链接 ]

2.1 、安装 DNSdist

因为官方源中的 dnsdist 是 v1.4 版本,太老了,所以需要换源 [ 链接 ]

举个例子,v1.4 不能使用 DoT DoH 请求其他 DNS 服务器。(newServer 通通 UDP)
而且最麻烦的是配置多了没有使用也不会提醒错误。


2.2 、启用 DoT 与 DoH

首先绑定端口,同时配置 客户端列表 ACL

-- Bind Address
addLocal("0.0.0.0:53")
addLocal("[::]:53")
addLocal("0.0.0.0:5300")
addLocal("[::]:5300")
-- Query ACL
setACL({'0.0.0.0/0','::/0'})

然后配置运行时的 Buffer 和 缓冲块,以及 请求来源 TCP 的 最大线程数

-- Running Processes
setRingBuffersSize(1024000, 100)
setMaxTCPClientThreads(8)

创建 DoT/DoH 配置,默认 DoT 使用 853/tcp ,DoH 使用 443/tcp
需要提前准备相应的证书,以及 DoH 的请求 URL 后缀,默认 /dns-query

-- DoT
addTLSLocal("0.0.0.0:853", 'fullchain.pem', 'privkey.pem', {provider="openssl"})
-- DoH
addDOHLocal('0.0.0.0:443', 'fullchain.pem', 'privkey.pem', "/dns-query")
addDOHLocal("127.0.0.1:8053", nil, nil, "/resolve", {reusePort=true})

如果希望实现 curl 请求的 DoH 服务器,则需要一个 Nginx 前端展示,后端直接转发,同时去除证书
dnsdist 不支持 使用 json 格式的 doh,有关信息查阅 [ 链接 ]

location /dns-query {
  proxy_http_version 1.0;
  proxy_cache doh_cache;
  proxy_cache_key $scheme$proxy_host$uri$is_args$args$request_body;
  proxy_pass http://127.0.0.1:8053;
}

最后准备一个缓存,通常来说缓存 65535 条就够了,如果有需求修改 TTL 缓存,则需要扩大此值
默认 newServer 的 Pool 是 "" ,所以缓存需要应用在 getPool("") 上。

-- Cache
getPool(""):setCache(newPacketCache(65535, {maxTTL=86400, minTTL=0, temporaryFailureTTL=60, staleTTL=60, dontAge=false}))

2.3 、查看运行状态

-- Console Bind Address
controlSocket("127.0.0.1:5300")
setKey("console_password")
setConsoleACL({'127.0.0.1/32','::1/128'})
-- WebServer
webserver("0.0.0.0:8083")
setWebserverConfig({password="website_password",apiKey="api_password",acl="10.179.0.0/16"})

2.4 、配置 DNS 来源服务器

上游是 53/UDP 的可以直接请求,QPS 是限制最大并发请求数,Name 是别名

 newServer({address="1.0.0.1:53",qps=100,name="CF-DNS-UDP"})
 newServer({address="1.1.1.1:53",qps=100,name="CF-DNS-UDP"})

如果考虑 53/UDP 污染的情况,可以使用 DoT/DoH 的方式从来源请求
tcponly 是限定使用 tcp 协议,subjectName 是请求报文的 SNI 值,checkInterval 是周期检查时间

 newServer({address="1.0.0.1:443",qps=500,name="CF-DNS-DoH",tcponly=true,tls="openssl",subjectName="one.one.one.one",validateCertificates=true,dohPath="/dns-query",useClientSubnet=true,checkInterval=10,checkTimeout=3000})
 newServer({address="1.1.1.1:443",qps=500,name="CF-DNS-DoH",tcponly=true,tls="openssl",subjectName="one.one.one.one",validateCertificates=true,dohPath="/dns-query",useClientSubnet=true,checkInterval=10,checkTimeout=3000})
 newServer({address="1.0.0.1:853",qps=500,name="CF-DNS-DoT",tcponly=true,tls="openssl",subjectName="one.one.one.one",validateCertificates=true,checkInterval=10,checkTimeout=3000})
 newServer({address="1.1.1.1:853",qps=500,name="CF-DNS-DoT",tcponly=true,tls="openssl",subjectName="one.one.one.one",validateCertificates=true,checkInterval=10,checkTimeout=3000})

服务器之间的轮询方式,默认是顺序轮询,可以修改为 qps 均值轮询

-- QPS
setServerPolicy(firstAvailable)

2.5 、 EDNS 携带用户源地址

当我们希望用户请求得到的 DNS 是他们最近的服务器时,我们就需要携带用户源地址
这种请求方式经常会在 CDN 中体现,并且大多数隐私保护限制了携带位数。

-- EDNS 截断用户源地址的位数
setECSSourcePrefixV4(24)
setECSSourcePrefixV6(64)
-- 执行 EDNS 查询时,保留查询者的 IP 信息并传递给来源服务器
newServer({address="8.8.8.8",useClientSubnet=true})

2.6 、安全防护

如果不限制用户请求数,那么 DNS 服务器很容易被打瘫

-- Limit 
local dbr = dynBlockRulesGroup()
dbr:setQueryRate(100, 10, "Exceeded query rate", 60)
dbr:setRCodeRate(DNSRCode.NXDOMAIN, 20, 10, "Exceeded NXD rate", 60)
dbr:setRCodeRate(DNSRCode.SERVFAIL, 20, 10, "Exceeded ServFail rate", 60)
dbr:setQTypeRate(DNSQType.ANY, 5, 10, "Exceeded ANY rate", 60)
dbr:setResponseByteRate(100000, 10, "Exceeded resp BW rate", 60)
dbr:excludeRange({"192.168.0.0/16","fc00::/16"})
function maintenance()
dbr:apply()
end
  • 10 秒内,请求数超过 100,则触发 60s 封禁,提示 "Exceeded query rate"
  • 10 秒内,请求结果中包含 NXDOMAIN 的超过 20,则触发 60s 封禁,提示 "Exceeded NXD rate"
  • 10 秒内,请求结果中包含 SERVFAIL 的超过 20,则触发 60s 封禁,提示 "Exceeded ServFail rate"
  • 10 秒内,请求类型中包含 ANY 的超过 5,则触发 60s 封禁,提示 "Exceeded ANY rate"
  • 10 秒内,回应总字节超过 100Kb ,则触发 60s 封禁,提示 "Exceeded resp BW rate"
  • 白名单放行 192.168.0.0/16 和 fc00::/16 不进行限制。

由于 DNS 是基础服务之一,一旦出现安全隐患影响极大,所以会周期的检查最新版本
如果我们自己内部使用,则不需要频繁的检查,所以可以关闭安全更新。

-- 禁用安全更新通知
setSecurityPollSuffix("")


2.7 、服务器状态检查

newServer({address="1.1.1.1", 
healthCheckMode='lazy', 
lazyHealthCheckMinSampleCount=10, 
lazyHealthCheckThreshold=30, 
lazyHealthCheckSampleSize=100, 
checkInterval=10, 
checkTimeout=3000, 
rise=2, 
maxCheckFailures=3, 
lazyHealthCheckFailedInterval=30, 
lazyHealthCheckMaxBackOff=3600, 
lazyHealthCheckMode='TimeoutOnly'})
  • 首次运行,前 lazyHealthCheckMinSampleCount=10 个包什么都不做
  • 每当处理 lazyHealthCheckSampleSize=100 个包中有 lazyHealthCheckThreshold=30 个包出现问题时,视为服务器连接状态出现问题
  • 当认为出现问题时,每 checkInterval=10 s 发送一个检查包,每个包的超时时间为 checkTimeout=3000 ms,发送 rise=2 次后依然不可用认为此服务器故障,标记为 down
  • 当服务器被标记为 down 时,每 lazyHealthCheckFailedInterval=30 s 周期性进行服务器状态检查
  • 当服务器被标记为 down 超过 lazyHealthCheckMaxBackOff=3600 s 时,服务器重新视为 up 状态。
  • 服务器状态检测的内容为 TimeoutOnly 仅超时,还可以设置其他内容。

2.8 、测试服务器响应

配置完毕后想测试,可以使用以下测试命令(apt 安装包名分别为 curl 和 getdns-utils)

getdns_query @1.0.0.1~cloudflare-dns.com -m -s -L -A www.twitter.com
curl --doh-url https://1.0.0.1/dns-query https://www.twitter.com
curl -H 'accept: application/dns-json' 'https://1.0.0.1/dns-query?name=www.twitter.com&type=A'

推荐使用一个 DNS 压力测试小工具 dnsperf [ 链接 ],DNS 测试列表参考 [ 链接 ]

dnsperf -s 1.1.1.1 -p 53 -f inet -m udp -d opendns-top-domains.txt -c 100 -T 4 -t 30 -n 1 -l 10 -D -q 100 -Q 200

StarryVoid

Have a good time