IrineSistiana / mosdns

一个 DNS 转发器

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

[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.4black_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 它是工作的

image
image

可以在 GitHub 搜索下 openwrt mosdns luci 的配置文件,resp_ip + black_hole 它是工作的

如过你指的是这个
https://github.com/sbwml/luci-app-mosdns/blob/3ba579cf805795c407852d77607efa5fa1ac0e51/luci-app-mosdns/root/usr/share/mosdns/default.yaml#L141

那你可以尝试将图片中的“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'的影响