[Bug] 在经过 resp_ip 判断后 black_hole 无法替换 IPv4 或 IPv6 地址
Journalist-HK opened this issue · comments
在提交之前,请确认
- 我已经尝试搜索过 Issue ,但没有找到相关问题。
- 我正在使用最新的 mosdns 版本(或者最新的 commit),问题依旧存在。
- 我仔细看过 wiki 后仍然无法自行解决该问题。
- 我非常确定这是 mosdns 核心的问题。(如果是通过第三方衍生软件使用 mosdns 核心,不确定问题源头时,请先向衍生软件开发者提交问题。)
mosdns 版本
v5.3.1
操作系统
OpenWrt
Bug 描述和复现步骤
我想要实现:对于双栈域名,经过 black_hole 后能顺带实现 prefer_ipv4 的功能。prefer_ipv4 只能在 forward 前使用,而我想要先查询再判断。
根据文档,如果某一 IP 类型未指定,则不会生成对应类型的应答,那么 black_hole 1.2.3.4
和 black_hole 1.2.3.4 ::0
应当能够丢弃IPv6 地址。实际上并没有起作用,原始的AAAA记录将会保留,black_hole ::1
不会对结果产生任何影响。经过测试,问题只在前置判断有 resp_ip 才出现。
使用下方测试配置,测试域名 gh.api.99988866.xyz,正确 IP:
- 2606:4700:3038::6815:eb6e
- 2606:4700:3038::6815:eb6d
- 104.21.235.110
- 104.21.235.109
替换 begin 到 end 中的内容分别测试
以下 3 种均能替换 IP,说明没有 resp_ip 时 black_hole 能正常工作。
- exec: black_hole 127.0.0.1 ::1 # a
- exec: black_hole 127.0.0.1 # b ipv4 only
- exec: black_hole ::1 # c ipv6 only
当两条分开写时,也能分别替换 v4/v6,返回 v4/v6 两个 IP。两条的先后顺序不影响结果。这就有点问题了,按照文档中的描述,应该以最后一条为准。
- exec: black_hole 127.0.0.1
- exec: black_hole ::1
# d
加入 matches
- matches:
- resp_ip 0.0.0.0/0 ::/0
exec: black_hole 127.0.0.1 ::1
# 1 正常
- matches:
- resp_ip 0.0.0.0/0 ::/0
exec: black_hole 127.0.0.1
# 2 不会去除 v6
- matches:
- resp_ip 0.0.0.0/0 ::/0
exec: black_hole ::1
# 3 不会去除 v4
说明 resp_ip 会对 black_hole 的行为产生影响,这是意料之外的。下面进一步测试:
- matches:
- resp_ip 0.0.0.0/0
exec: black_hole 127.0.0.1
# 4 只替换 v4,v6 保留而不是删除
- matches:
- resp_ip 0.0.0.0/0
exec: black_hole 127.0.0.1 ::1
# 5 只替换 v4,v6 保留而不是替换
- matches:
- resp_ip 0.0.0.0/0
exec: black_hole ::1
# 6 什么都不会改
- matches:
- resp_ip ::/0
exec: black_hole ::1
# 7 只替换 v6,v4 保留而不是删除
- matches:
- resp_ip ::/0
exec: black_hole 127.0.0.1 ::1
# 8 能替换 v4/v6,和 5 的表现不一致
- matches:
- resp_ip ::/0
exec: black_hole 127.0.0.1
# 9 只替换 v4,v6 保留而不是删除,和 6 的表现不一致,和 4 的表现相同
4 和 7 组合能正确替换 IP,两个 matches 的先后顺序不影响结果。
- matches:
- resp_ip 0.0.0.0/0
exec: black_hole 127.0.0.1
- matches:
- resp_ip ::/0
exec: black_hole ::1
但是,将 5 和 c 组合,得到的结果是只能保留v6,和 d 的表现不一致。
- matches:
- resp_ip 0.0.0.0/0
exec: black_hole 127.0.0.1 ::1
- exec: black_hole ::1
# v6 only
理想的情况是只按最后一条 black_hole 为准,并且 resp_ip 不应对结果产生任何影响。也可以考虑将 black_hole 分为 black_hole_ipv4 和 black_hole_ipv6 两种。
使用的配置文件
log:
level: info
file: "./mosdns.log"
api:
http: "127.0.0.1:9091"
include: []
plugins:
- tag: main
type: sequence
args:
- exec: forward https://162.159.46.1/dns-query
# begin
- matches:
- resp_ip 0.0.0.0/0
exec: black_hole 127.0.0.1
- matches:
- resp_ip ::/0
exec: black_hole ::1
# end
- matches:
- has_resp
exec: accept
- tag: udp_server
type: udp_server
args:
entry: main
listen: ":53"
mosdns 的 log 记录
No response
可以在 GitHub 搜索下 openwrt mosdns luci 的配置文件,
resp_ip
+black_hole
它是工作的
那你可以尝试将图片中的“Cloudflare IP 范围”设置为只有 IPv4 或 IPv6 再试一试。
匹配范围 ipv4 only,这测试结果应该是对的吧。因为匹配 resp_ip
的 ipv4 地址,那么理所当然的也是更替应答结果的 ipv4 地址。
如果它连同 ipv6 也被更替了,我会觉得它是一个 bug。@IrineSistiana @sbwml 大佬们是不是这样的?
匹配范围 ipv4 only,这测试结果应该是对的吧。因为匹配
resp_ip
的 ipv4 地址,那么理所当然的也是更替应答结果的 ipv4 地址。如果它连同 ipv6 也被更替了,我会觉得它是一个 bug。
按照你的说法,如果我只匹配 IPv6 地址,是不是应该只替换 IPv6 地址,而不替换 IPv4 地址?但是 mosdns 并没有这样工作,你可以再试一试。
1、A记录和AAAA记录是不同的qtype,他们的查询并不在同一个请求里面。
2、// Response returns a response with given ips if query has corresponding qtypes.
// Otherwise, it returns nil.
如果提供了blackhole的地址,就会替换,否则不替换。
3、如果说可能的bug的话,在Match方法里面,::/0是包含了0.0.0.0/0,0.0.0.0/0不包含::/0。
而Contains是先把IP统一转换成IPv6格式再匹配:
// Contains reports whether the list includes the given netip.Addr.
func (list *List) Contains(addr netip.Addr) bool {
if !list.sorted {
panic("list is not sorted")
}
if !addr.IsValid() {
return false
}
addr = to6(addr)
i, j := 0, len(list.e)
for i < j {
h := int(uint(i+j) >> 1) // avoid overflow when computing h
if list.e[h].Addr().Compare(addr) <= 0 {
i = h + 1
} else {
j = h
}
}
if i == 0 {
return false
}
return list.e[i-1].Contains(addr)
}
所以这也算不上bug,如果你想匹配全部AAAA记录,应该用qtype 28
更高效。
所以这也算不上bug,如果你想匹配全部AAAA记录,应该用
qtype 28
更高效。
- matches:
- qtype 1
- has_wanted_ans
- resp_ip $cloudflare_ip
exec: jump blackhole_cloudflare
- matches:
- qtype 28
- has_wanted_ans
- resp_ip $cloudflare_ipv6
exec: jump blackhole_cloudflare_ipv6
这样写的话,能实现如果原来是 IPv4 only / IPv4 only / dual stack,替换后也是。
但是如何实现 prefer_ipv4?因为我希望只对命中规则的域名(在这里就是使用 CF CDN 的域名)实施 prefer_ipv4,其他的保留 IPv6。而 prefer_ipv4 只能放在 forward 之前,还没有请求上游前并不知道它是不是用了Cloudflare CDN。
“使用 CF CDN 的域名实施 prefer_ipv4",那你直接把解析结果命中 CF V6 CDN 的请求reject掉不就好了……
1、A记录和AAAA记录是不同的qtype,他们的查询并不在同一个请求里面。
这样会影响只返回 AAAA 而没有 A 记录的域名,虽然这样的域名应该不多。正因为不是同一个请求,才不好实现。
纯v6 cf cdn的域名。不能说不多,只能说除非是故意的。
我在尝试cf cdn优选ip时候发现了同样的问题
在没有'resp_ip'的情况下,'black_hole'是无视ipv4 ipv6完整替换的
但在'resp_ip'后,'black_hole'只会根据'resp_ip'匹配的ip类型,进行替换,'resp_ip'未匹配到的ip类型,保持不变。
感觉这种现象很割裂,按正常的想法,matchs只是条件筛选,而不应该影响后续的操作
而且不仅'black_hole'如此,'redirect'也会受到resp_ip的影响。
当然对于优选cf ip,不影响使用,cf cdn基本都是双栈
但还是希望能统一'black_hole'、'redirect'等的行为,不受'resp_ip'的影响