pymumu / smartdns

A local DNS server to obtain the fastest website IP for the best Internet experience, support DoT, DoH. 一个本地DNS服务器,获取最快的网站IP,获得最佳上网体验,支持DoH,DoT。
https://pymumu.github.io/smartdns/
GNU General Public License v3.0
7.92k stars 1.05k forks source link

ttl 的默认值不正常 #1756

Closed scruel closed 3 days ago

scruel commented 2 weeks ago

问题现象

ttl 相关的默认值在文档中为“远程查询结果”: image 但实测却发现并非是这样,rr-ttl-min 的默认值貌似为 600。

另外对于上游 ttl 为一天的记录,设置 rr-ttl-min 60 后,下发的 ttl 也会变为 60……,这不是 rr-ttl 的效果嘛,怎么 rr-ttl-min 也这样……

运行环境

version: '3'

services:
  smartdns:
    container_name: smartdns
    image: pymumu/smartdns:latest
    restart: always
    network_mode: host
    volumes:
      - ./:/etc/smartdns

重现步骤

bind [::]:53

log-level debug
log-size 64K

speed-check-mode none
cache-persist no
server 223.5.5.5

本机配置 dns server 到 smartdns, 配置上游域名 test1m.scruel.com 的 ttl 为一分钟,随后执行 dig,可见 dig 结果中的 ttl 是十分钟即 600,而不是上游的 60:

$ dig test1m.scruel.com
; <<>> DiG 9.18.18-0ubuntu0.22.04.2-Ubuntu <<>> test1m.scruel.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 56757
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;test1m.scruel.com.             IN      A

;; ANSWER SECTION:
test1m.scruel.com.      600     IN      A       1.1.1.2

;; Query time: 289 msec
;; SERVER: 127.0.0.42#53(127.0.0.42) (UDP)
;; WHEN: Mon Jun 17 17:16:12 CST 2024
;; MSG SIZE  rcvd: 51

改用 1.1.1.1 做 dig 则正常获得 ttl 为 60 的结果:

$ dig test1m.scruel.com @1.1.1.1

; <<>> DiG 9.18.18-0ubuntu0.22.04.2-Ubuntu <<>> test1m.scruel.com @1.1.1.1
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 51053
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;test1m.scruel.com.             IN      A

;; ANSWER SECTION:
test1m.scruel.com.      60      IN      A       1.1.1.2

;; Query time: 299 msec
;; SERVER: 1.1.1.1#53(1.1.1.1) (UDP)
;; WHEN: Mon Jun 17 17:12:14 CST 2024
;; MSG SIZE  rcvd: 62

信息收集

[2024-06-17 09:16:07,735][NOTICE][       smartdns.c:640 ] smartdns starting...(Copyright (C) Nick Peng <pymumu@gmail.com>, build: 1.2024.05.09-1452)
[2024-06-17 09:16:07,737][ INFO][     dns_server.c:9013] bind ip [::]:53, type 0
[2024-06-17 09:16:07,737][ INFO][     dns_server.c:9013] bind ip [::]:853, type 2
[2024-06-17 09:16:07,747][ INFO][     dns_server.c:6404] ICMP ping is disabled, no ipv6 icmp check feature
[2024-06-17 09:16:07,747][ INFO][     dns_server.c:9330] IPV6 is not ready or speed check is disabled, disable IPV6 features
[2024-06-17 09:16:07,749][ INFO][     dns_client.c:1272] add server 223.5.5.5:53, type: udp
[2024-06-17 09:16:11,427][DEBUG][     dns_server.c:7155] recv query packet from fe80::eb23:cfdf:83ae:3adf, len = 46, type = 0
[2024-06-17 09:16:11,427][DEBUG][     dns_server.c:7171] request qdcount = 1, ancount = 0, nscount = 0, nrcount = 0, len = 46, id = 23986, tc = 0, rd = 1, ra = 0, rcode = 0
[2024-06-17 09:16:11,427][DEBUG][     dns_server.c:7196] query test1m.scruel.com from fe80::eb23:cfdf:83ae:3adf, qtype: 1, id: 23986, query-num: 1
[2024-06-17 09:16:11,427][DEBUG][     dns_client.c:3855] send query to server 223.5.5.5:53
[2024-06-17 09:16:11,427][ INFO][     dns_client.c:4252] request: test1m.scruel.com, qtype: 1, id: 44466, group: default
[2024-06-17 09:16:11,457][DEBUG][     dns_server.c:7155] recv query packet from 192.168.1.22, len = 46, type = 0
[2024-06-17 09:16:11,457][DEBUG][     dns_server.c:7171] request qdcount = 1, ancount = 0, nscount = 0, nrcount = 0, len = 46, id = 23986, tc = 0, rd = 1, ra = 0, rcode = 0
[2024-06-17 09:16:11,457][DEBUG][     dns_server.c:7196] query test1m.scruel.com from 192.168.1.22, qtype: 1, id: 23986, query-num: 2
[2024-06-17 09:16:11,503][DEBUG][     dns_server.c:7155] recv query packet from fe80::eb23:cfdf:83ae:3adf, len = 32, type = 0
[2024-06-17 09:16:11,503][DEBUG][     dns_server.c:7171] request qdcount = 1, ancount = 0, nscount = 0, nrcount = 0, len = 32, id = 20183, tc = 0, rd = 1, ra = 0, rcode = 0
[2024-06-17 09:16:11,712][DEBUG][     dns_client.c:1848] domain: test1m.scruel.com qtype: 1  qclass: 1
[2024-06-17 09:16:11,712][DEBUG][     dns_server.c:4644] query result from server 223.5.5.5:53, type: 0, domain: test1m.scruel.com qtype: 1 rcode: 0, id: 23986
[2024-06-17 09:16:11,712][DEBUG][     dns_server.c:3865] domain: test1m.scruel.com TTL: 60 IP: 1.1.1.2
[2024-06-17 09:16:11,712][ INFO][     dns_server.c:2613] result: test1m.scruel.com, qtype: 1, rtt: -0.1 ms, 1.1.1.2
[2024-06-17 09:16:11,712][DEBUG][     dns_server.c:2336] reply test1m.scruel.com qtype: 1, rcode: 0, reply: 1
[2024-06-17 09:16:11,712][DEBUG][     dns_server.c:1189] result: test1m.scruel.com, rtt: -0.1 ms, 1.1.1.2
[2024-06-17 09:16:11,712][DEBUG][     dns_server.c:1666] cache test1m.scruel.com qtype: 1 ttl: 600
[2024-06-17 09:16:11,712][ INFO][     dns_server.c:2391] result: test1m.scruel.com, client: fe80::eb23:cfdf:83ae:3adf, qtype: 1, id: 23986, group: default, time: 286ms
[2024-06-17 09:16:11,712][ INFO][     dns_server.c:4500] result: test1m.scruel.com, client: 192.168.1.22, qtype: 1, id: 23986, group: default, time: 255ms
[2024-06-17 09:16:11,712][DEBUG][     dns_client.c:1600] result: test1m.scruel.com, qtype: 1, has-result: 1, id 44466
[2024-06-17 09:16:11,856][DEBUG][     dns_client.c:2609] recv udp packet from 223.5.5.5:53, len: 103, ttl: 118, latency: 352
[2024-06-17 09:16:11,856][DEBUG][     dns_client.c:1836] qdcount = 1, ancount = 0, nscount = 1, nrcount = 0, len = 103, id = 35659, tc = 0, rd = 1, ra = 1, rcode = 3, payloadsize = 1408
scruel commented 2 weeks ago

另外假设这个问题已被解决,默认的 ttl 变为同上游一致,那么缓存的预获取功能,究竟会被哪些选项所影响? 例如,上游 ttl=60,配置中写入 rr-ttl-min 3600cache-persist yes,我目前理解的流程应该如下:

client dns query -> record uncached -> upstream query -> cache record -> return record
client dns query -> record cached -> record ttl unexpired -> return record
client dns query -> record cached -> record ttl expired -> return expired record with ttl=3 -> query upstream -> update cached record

如果追加写入以下配置:

serve-expired yes
serve-expired-ttl 345600

那么 smartdns 会主动进行轮询,效果应当如下:

smartdns -> iterate cached records -> record excceed serve-expired-ttl -> delete record

考虑文档说明:

开启过期缓存的情况下,仅当域名要从缓存中过期时,才进行预读取,而不是TTL超时获取。 所以,开启了过期缓存的情况下,推荐开启域名预获取功能。

如果追加写入以下配置:

serve-expired yes
serve-expired-ttl 345600
prefetch-domain yes

效果应当如下:

client dns query -> record uncached -> upstream query -> cache record -> return record
client dns query -> record cached -> record cache unexpired -> return record
client dns query -> record cached -> record cache expired -> return expired record with ttl=3 -> query upstream -> update cached record
smartdns -> iterate cached records -> record excceed serve-expired-ttl -> query upstream -> update cached record

而如果此时增加 serve-expired-prefetch-time 43200 则主动查询部分将变为,客户端发起的被动查询则不变:

smartdns -> iterate cached records -> record excceed serve-expired-ttl -> delete record
smartdns -> iterate cached records -> record unexcceed serve-expired-ttl -> excceed serve-expired-prefetch-time -> query upstream -> update cached record

即一旦配置预获取,ttl 就会变得无意义?不知道理解是否有误?

smartdns 在何时会进行预获取?或者说 record expired ttl 的值此时应该等于多少? 最终的目的无非是当上游 IP 更新时,希望 smartdns 能尽快返回更新后的 IP,直接查询主上游服务器的话(不考虑全球分发,只考虑主上游),返回的记录应当总是最新的 IP,所以判断是否记录过期用的 ttl 应当越低越好(不考虑性能消耗)。 那么,是否有办法能让 smartdns 以上游 ttl 为预获取的基准值,或者能否有个选项能让是否请求上游的判断基准值变为 min(配置的 ttl, 上游 ttl)?预期如下:

client dns query -> record uncached -> upstream query -> cache record -> return record
client dns query -> record cached -> MIN unexpired -> return record
client dns query -> record cached -> MIN expired -> return expired record with ttl=3 -> query upstream -> update cached record
smartdns -> iterate cached records -> record excceed serve-expired-ttl -> delete record
smartdns -> iterate cached records -> record unexcceed serve-expired-ttl -> excceed serve-expired-prefetch-time -> query upstream -> update cached record

另外文档提到频繁更新的 dns 记录会有问题,请问具体是什么问题呢?没太读懂,还望解释一下,谢谢。

PikuZheng commented 2 weeks ago

首先我不主张修改ttl,ttl应该有域名所有者自主决定。

我的设置

rr-ttl-min 1
rr-ttl-max 3600
rr-ttl-reply-max 3600

以域名 ntp.17xiu8.com 为例,域名所有者(也就是我)设置ttl为3600。使用上游 119.29.29.29 查询ttl为3600。 使用smartdns,将上游设置为doh.pub时,首次查询(无缓存时)返回给客户端的ttl也是3600;间隔2秒后再次查询,ttl为3598。 以域名 blog.17xiu8.com 为例,域名所有者(也就是我)设置ttl为86400。使用上游 119.29.29.29 查询ttl为86400。 使用smartdns,将上游设置为doh.pub时,首次查询(无缓存时)返回给客户端的ttl也是3600;间隔2秒后再次查询,ttl为3598。

综上根据我的测试结果,我认为rr-ttl-min没有将所有的域名ttl改为1,符合预期;rr-ttl-max 和 rr-ttl-reply-max 也符合预期。

scruel commented 2 weeks ago

并不希望修改 ttl,主要是 rr-ttl-min 的默认值并非是预期的。

PikuZheng commented 2 weeks ago

并不希望修改 ttl,主要是 rr-ttl-min 的默认值并非是预期的。

你就把它设的足够小嘛。

scruel commented 2 weeks ago

你就把它设的足够小嘛。

关于 workaround 的话,https://github.com/pymumu/smartdns/issues/1756#issuecomment-2172944595 里已经补充过了,但非预期行为属于 bug,可以看下怎么能修掉。

PikuZheng commented 2 weeks ago

rr-ttl-min 3600 会导致写入缓存的ttl最小值为3600 serve-expired-ttl 345600 是指缓存的记录ttl降到0后仍会保留 345600 秒,和记录的ttl值无关

最终的目的无非是当上游 IP 更新时,希望 smartdns 能尽快返回更新后的 IP,

建议将这部分经常改变的域名不缓存,且不能使用阿里作为上游

但非预期行为属于 bug,可以看下怎么能修掉。

能否明确的说一下 哪个配置导致了哪个非预期行为。1楼说得当上游返回ttl大于 rr-ttl-min 时也会被改成rr-ttl-min 在我这没法重现,我自己也没遇到过这个问题。

serve-expired yes serve-expired-ttl 345600 prefetch-domain yes serve-expired-prefetch-time 43200

这样设置的话会有两种可能。一是在缓存的ttl降到0后的43200秒发起预读,此期间如果有客户端查询将收到ttl=5(由serve-expired-reply-ttl 定义)的结果;二是这个域名一段时间没有客户端查询不触发预读,缓存ttl降到0后的345600(会生效吗?就我所知最大有效值应该是65534)秒从缓存中删掉这个记录。

即一旦配置预获取,ttl 就会变得无意义?不知道理解是否有误?

我认为ttl值在记录没有过期时是有意义的,它指示客户端应保留记录(缓存)多久。预读是增加有效缓存,加快下游查询应答速度的手段,不能改变或代替记录ttl值的功能。

pymumu commented 2 weeks ago

tll-min的值默认设置为600这个是为了避免上游返回TTL过小,导致的客户端频繁查询问题。 大部分情况,上游返回一个IP,不会说10分钟就失效的,大部分情况IP失效,生效都是要48~72小时。

如果是你自己内部的一些域名要IP频繁变化,可以用domain-rule来设置ttl值。

scruel commented 2 weeks ago

@PikuZheng 缓存的记录ttl 是指什么呢?是指经过 rr-ttl-xxx 调整的 ttl?

建议将这部分经常改变的域名不缓存,不能使用阿里作为上游

大部分域名倒是不会频繁改动,这里主要是顺便多问一些,毕竟目前没有监看面板等功能,也没法知道哪些域名会频繁改变。另外,多久变一次算得上经常改变?是否能简单说一下原因为何不能?cloudflare 呢?

能否明确的说一下 哪个配置导致了哪个非预期行为。1楼说得当上游返回ttl大于 rr-ttl-min 时也会被改成rr-ttl-min 在我这没法重现,我自己也没遇到过这个问题。

owner 已回复该问题,既然算预期行为,那么便属于是文档未同步,修改下即可。

一是在缓存的ttl降到0后的43200秒发起预读,此期间如果有客户端查询将收到ttl=5(由serve-expired-reply-ttl 定义)的结果;

那么在缓存的 ttl 降到 0 后,未到 serve-expired-prefetch-time 规定的秒数,客户端就发起了查询,是否也会收到 ttl=5(由 serve-expired-reply-ttl 定义),并同时向上游做查询? 那这里若是将 serve-expired-prefetch-time 改为零,是否一旦缓存 ttl 超时,就会发起预读?例如将会有如下循环:

smartdns -> check_and_update -> check_and_update -> check_and_update
check_and_update: cache ttl expired -> serve-expired-prefetch-time(=0) reached -> upstream query -> update ttl

@pymumu

tll-min的值默认设置为600这个是为了避免上游返回TTL过小,导致的客户端频繁查询问题。

看来默认值确实为 600,建议把文档更新一下,否则容易产生误导,谢谢。

pymumu commented 2 weeks ago

https://pymumu.github.io/smartdns/config/cache/

scruel commented 2 weeks ago

@pymumu 这个已经读过了,就是读后才有上面的疑问

PikuZheng commented 2 weeks ago

@PikuZheng 缓存的记录ttl 是指什么呢?是指经过 rr-ttl-xxx 调整的 ttl?

建议将这部分经常改变的域名不缓存,不能使用阿里作为上游

大部分域名倒是不会频繁改动,这里主要是顺便多问一些,毕竟目前没有监看面板等功能,也没法知道哪些域名会频繁改变。另外,多久变一次算得上经常改变?是否能简单说一下原因为何不能?cloudflare 呢?

是的,大部分域名解析都不会频繁改变。腾讯云认为“频繁改变的域名,应(域名所有者)将ttl设为60”(但是允许最小设置为1)。 我认为多久变一次不重要,但变了以后能及时更新很重要。阿里云和移动在这做得就非常不好,因为它有巨大的过期缓存,这使得(尤其是ddns)记录变化后不能及时更新。根据我自己的使用经验,一般ddns域名的记录改变2-4小时后才能从移动获得到新的结果,阿里云需要十几分钟。cf我自己不用,不了解。

一是在缓存的ttl降到0后的43200秒发起预读,此期间如果有客户端查询将收到ttl=5(由serve-expired-reply-ttl 定义)的结果;

那么在缓存的 ttl 降到 0 后,未到 serve-expired-prefetch-time 规定的秒数,客户端就发起了查询,是否也会收到 ttl=5(由 serve-expired-reply-ttl 定义),并同时向上游做查询?

正确

那这里若是将 serve-expired-prefetch-time 改为零,是否一旦缓存 ttl 超时,就会发起预读?例如将会有如下循环:

smartdns -> check_and_update -> check_and_update -> check_and_update
check_and_update: cache ttl expired -> serve-expired-prefetch-time(=0) reached -> upstream query -> update ttl

设置为0逻辑上不通,应该是无效值

PikuZheng commented 2 weeks ago

https://github.com/pymumu/smartdns/blob/07c13827bb523519a638214ed7ad76180f71a40a/src/dns_server.c#L1611-L1616

serve-expired-prefetch-time 0 时,将其值改为 serve-expired-ttl 的一半且不超过28800

scruel commented 2 weeks ago

阿里云需要十几分钟。cf我自己不用,不了解。

原来如此,阿里云的具体缓存行为倒是没注意到,cf 生效非常快,可算是即时更新的。

设置为 0 逻辑上不通,应该是无效值 serve-expired-prefetch-time 0 时,将其值改为 serve-expired-ttl 的一半且不超过28800

了解,这里只是假设,意思就是一旦缓存 ttl 过期,就尽快发起预读,比如假设这个值是 1 的话,是不是有一个周期为 ttl+1 的循环,不断进行预读?

PikuZheng commented 2 weeks ago

一旦缓存 ttl 过期,就尽快发起预读,比如假设这个值是 1 的话,是不是有一个周期为 ttl+1 的循环,不断进行预读?

时间上是正确的,但是否预读取决于下游查询这个域名的频率。一段时间没有下游查询,就不预读了

scruel commented 2 weeks ago

一段时间没有下游查询,就不预读了

这个应该意思是缓存超时后就不预读吧,还是另有其他值,比如在未过期前预读 n 次?伪代码应该类似下面这样?

while 1:
  if cache_timedout:
    break
  check
  sleep ttl+1

然后其中的 ttl 的值会被外部改变,即当客户端请求时,依据 rr-ttl 和上游 ttl 做更新。

PikuZheng commented 2 weeks ago

一段时间没有下游查询,就不预读了

这个应该意思是缓存超时后就不预读吧,还是另有其他值,比如在未过期前预读 n 次?伪代码应该类似下面这样?

while 1:
  if cache_timedout:
    break
  check
  sleep ttl+1

然后其中的 ttl 的值会被外部改变,即当客户端请求时,依据 rr-ttl 和上游 ttl 做更新。

就我所知是否预读有复杂算法 参考 https://github.com/pymumu/smartdns/issues/1016#issuecomment-1211830056

scruel commented 3 days ago

@PikuZheng 感谢解惑,还有一个问题,如果禁用缓存,是不是意味着每次客户端查询,都将会进行一次向上游的查询,并且会附带 经过计算的 ttl

scruel commented 3 days ago

下面给出我的配置和理解(如果有误欢迎指正),供以后他人参考:

rr-ttl-min 60
cache-persist yes
serve-expired yes
serve-expired-ttl 345600
prefetch-domain yes
serve-expired-prefetch-time 43200
serve-expired-reply-ttl 3

不考虑是否做预读的这一层判断,假设过期前会一直更新。

rr-ttl-min 60 是为了防止类似 ddns 之类“更新频繁/更新需要尽快刷新”的域名,无法及时更新 有些主机频繁更新的域名的 ttl 会很小(=60),而启用预读后是依照计算后的 ttl 为间隔做循环的,所以如果这类域名很多,将会增加 CPU 的负担,故而建议为这类域名禁用缓存(虽看起来光禁用预读就够了,毕竟如果应用于子网络中的设备,某一时刻某个设备能访问,而同时某个又不能访问,行为就会比较奇怪)。 serve-expired-ttl 345600,过久没访问的域名就释放缓存 serve-expired-prefetch-time 43200,表示意味着当 ttl 过期后,经过多久时间做预读。用于冷处理,对于客户端来说,这个值越小就越不可能获取到过期记录,必须大于 0 serve-expired-reply-ttl 3 如果缓存的 ttl 已过期(不过期的话当然就直接返回记录了),先返回指定 ttl 为 3 的记录(不确定是否过期),并且同时向上游进行查询,如果此时记录已经更新,那么客户端将会在 3 秒后重查记录,从而获得上游更新后的记录,这个操作同时会刷新下一次的预读时间(serve-expired-prefetch-time 的值)。这个值如果很小,可能会导致查询风暴,如果很大的话,一旦发生过期,则会导致客户端较长时间无法访问。

PikuZheng commented 3 days ago

@PikuZheng 感谢解惑,还有一个问题,如果禁用缓存,是不是意味着每次客户端查询,都将会进行一次向上游的查询,

正确

并且会附带 经过计算的 ttl

查询没有ttl,应答才有。在不缓存的情况下,ttl值应该是上游应答的值