Skip to content

mapdns重构#283

Closed
zheshinicheng wants to merge 3 commits intoheiher:mainfrom
zheshinicheng:main
Closed

mapdns重构#283
zheshinicheng wants to merge 3 commits intoheiher:mainfrom
zheshinicheng:main

Conversation

@zheshinicheng
Copy link
Contributor

在windows 11上使用hev-socks5-tunnel搭建的透明网关时,发现windows 11上系统的dnscache缓存策略不符合rfc规范ipconfig /displaydns。windows 11默认dns缓存延长到86400秒。
mapdns对于这种不规范的缓存客户端,需要建立ttl持续时间更长的[ip->domain]映射方法。

以下是这个提交的实现原理:

假设

mapdns:
  network: 192.168.1.0
  netmask: 255.255.255.0
  cache-size: 10000

等效于 192.168.1.0/24
1.已知hev-socks5-tunnel只需要 [ipv4-> domain]单向映射。
2.mapdns可用ip 192.168.1.0 ~ 192.168.0.255,,广播地址 192.168.1.255 网络地址 192.168.1.0,实际可用地址 192.168.1.1 ~ 192.168.1.254
3.为了方便管理 每个映射分配一个 char[256],使用位运算 NOT ~netmask 申请一块 char[~netmask][256]大小的内存作为环形缓冲区。

处理流程:
1.收到 “github.com”的DNS A请求,分配环形缓冲区首个地址192.168.1.1,将DNS请求数据包的域名写入char[1][256]
2.tun设备收到192.168.1.1的tcp链接,(uint32_t)(192.168.1.1 - 192.168.1.0) = index
3.取地址运算&(char[index][0]) 得到映射域名的 char[256]的缓冲区地址,完成ip->域名的映射。

缺点:
1.为了方便管理 每个缓冲区都是rfc1035规定的域名最长值,需要耗费更多的内存 。(每个域名映射256字节)
2.每个dns请求会分配一个映射块(配合前置缓存服务器,这甚至是个优点) [mapdns <- dnsmasq chche]

优点:
1.避免内存管理的开销,环形缓冲区初始化时申请一次大块内存,直到程序运行结束释放。不存在mapdns运行产生的内存碎片。
2.快速映射,一些简单的指针运算就能直接得到映射到对应域名 O(1)
3.更长的TTL,在环形缓冲区对应的char[256]没被覆盖之前,ip映射的域名始终有效。避免不规范的缓存客户端导致的映射失败。

要点:
1.cache-size用于限制最大的char[256]块数量
假设cache-size: 10000 network: 192.168.0.0/16 ~mask =65536 但会被限制为 10000 char[10000][256] 大约需要 2.44MiB内存
2.增加hev_mapped_dns_cache_ttl函数,需要作者加入yaml配置选项,可以控制前置缓存的ttl 例如dnsmasq

因为不太熟练,如果代码中有换行 代码对齐之类的格式问题,麻烦作者合并代码后帮忙修改一下。

@heiher
Copy link
Owner

heiher commented Jan 18, 2026

mapdns对于这种不规范的缓存客户端,需要建立ttl持续时间更长的[ip->domain]映射方法。

相比之前的实现,如何做到维持更长时间的ip -> domain的映射了?

另外,现在的实现对于同一个域名的多次解析请求,是不是就会重复占用多条记录?

@zheshinicheng
Copy link
Contributor Author

在windows11上系统默认的缓存直接把解析记录ttl调整成一天,安卓手机我就不太清楚了。
想修改windows11就是一座屎山,我尝试注册表限制最大ttl,居然没用。
更长时间的映射实现原理就是应对这些把规范的客户端实现的。
假设现在windows发送了一个域名查询 “github.com”, mapdns回复 192.168.1.2。
后续windows对 “github.com"的请求都使用缓存中的192.168.1.2,。

如果规范的dns缓存客户端,就会在域名映射的环形缓冲区出现多个记录。

@heiher
Copy link
Owner

heiher commented Jan 18, 2026

假设现在windows发送了一个域名查询 “github.com”, mapdns回复 192.168.1.2。
后续windows对 “github.com"的请求都使用缓存中的192.168.1.2,。

那就是说原先的实现相对更容易造成 192.168.1.2 -> github.com 的映射被释放,是什么原因?

@zheshinicheng
Copy link
Contributor Author

windows和安卓的dns缓存策略就像个黑盒子,只能用这种笨方法防止不规范的缓存。
之前的映射我测试时候发现有一些问题,一个ip地址可能对应多个域名。我有一个域名 一个ip地址下面有将近10个 x.x.x这样的三级域名

@zheshinicheng
Copy link
Contributor Author

就像CDN一样 一个ip可能对应几十上百个域名,他们通过”SSL SNI“ ”ALPN“之类的扩展。就能通过一个ip访问很多网站

@zheshinicheng
Copy link
Contributor Author

就像CDN一样,一个ip可能对应几十个域名,它们通过“SSL SNI”“ALPN”之类的扩展很多。可以通过一个ip访问网站
最常见的例子就是 ngx_stream_ssl_preread_module, 通过 SNI 让一个ip 443端口拥有多个域名
https://nginx.org/en/docs/stream/ngx_stream_ssl_preread_module.html

@heiher
Copy link
Owner

heiher commented Jan 18, 2026

主线现在的实现,只要缓存记录的总数没有超过cache-size,就不会释放映射记录,已经建立的映射关系就不会改变。如果超过了cache-size,最不常被访问(根据记录中的Fake IP统计的)的那条记录会被释放。

理论上这样的场景会出现错误映射,一个域名被解析并分配了一个Fake IP,之后很长时间没有再访问过这个Fake IP,使得这条记录很冷。然后随着系统的运行,解析的域名越来越多,超过了cache-size,之前那条映射记录由于最冷被释放了,系统DNS缓存没有失效,最后这个Fake IP又被访问,出现错误的映射关系。

我觉得问题的关键是不能在系统DNS缓存没超时前出现回收,这个PR的环形记录也是一样会回收的,而且还存在同域名重复记录(有的应用会绕过带缓存的DNS自己直接发DNS请求的)。应该在现有的基础上调整更大的cache-size来避免问题。

@zheshinicheng
Copy link
Contributor Author

主线现在的实现,只要服务器记录的总数没有超过cache-size,就不会释放映射记录,已经建立的映射关系就不会改变。如果超过了cache-size,最不常被访问(根据记录中的假IP统计的)的那条记录会被释放。

理论上这样的场景会出现错误映射,一个域名被解析并分配了一个假IP,之后很长时间没有再访问过这个假IP,使得这条记录很冷。然后随着系统的运行,解析的域名越来越多,超过了缓存大小,那条映射记录由于最冷被释放了,系统DNS缓存没有失效,最后这个假IP又被访问,出现之前错误的映射关系。

我觉得问题的关键是不能在系统DNS服务器超时前出现恢复,这个PR的环形记录同样会恢复的,而且还存在同域重复记录(有的应用会绕过服务器的DNS自己直接发DNS请求的)。应该在现有的基础上调整更大的缓存大小来避免问题。

是的,我也是想避免系统发出ip包时域名映射失效。但是系统的客户端缓存策略就是个黑盒。根本找不到多少能参考的资料。
我的场景时透明的局域网linux软件路由。我在mapdns前面有一个dnsmasq缓存。按照ttl缓存。这样就不会出现在ttl有效期内产生同一个域的多个映射块,也许我的方法只适合这种结构 。

@heiher
Copy link
Owner

heiher commented Jan 18, 2026

嗯。当前的PR+前置dnsmasq去重叠加后的效果,与主线的实现对比,如果没达到cache-size,映射都是唯一且稳定的;如果都超过了cache-size,都会回收旧记录。看起来都差不多,不能缓解问题吧?

@zheshinicheng
Copy link
Contributor Author

稍等一会,我突然想到一个事。我之前在调试v2.14.1的时候,mapdsn出现过50ms+的延迟。我去找一份域名列表。编译以下主线的软件和改过的对比以下mapdns响应速度

@heiher
Copy link
Owner

heiher commented Jan 18, 2026

稍等一会,我突然想到一个事。我之前在调试v2.14.1的时候,mapdsn出现过50ms+的延迟。我去找一份域名列表。编译以下主线的软件和改过的对比以下mapdns响应速度

好。性能上来说,主线的实现解析记录是动态内存分配(后端有个带缓存的分配器);domain -> ip的查找(因为要去重)是O(log n);ip -> domain的查找是O(1)。

@heiher
Copy link
Owner

heiher commented Jan 18, 2026

在i7-7700k上测试了默认mapdns配置,按顺序解析(time nslookup)文件accelerated-domains.china.conf中的所有域名,real时间绝大部分在8-9ms,最长的是13ms。

@zheshinicheng
Copy link
Contributor Author

image 我真是小丑,上面的是主线,下面的是我的分支。我重写的比原来的还慢。太尴尬了。。。我应该关闭PR,然后好好反省

@heiher
Copy link
Owner

heiher commented Jan 19, 2026

平常心,重构和性能对比本来就是不断试错的过程,这次的结论同样很有价值。非常感谢你的投入和探索,期待你后续更多的贡献

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants