Skip to content

[讨论] nat_output IPv6 DNS 劫持目标 ::/0 与 IPv4 的 127.0.0.1 不对称,想请教是否有特殊考量 #5258

Closed

Description

@ocoj

背景

在学习 OpenClash 的 nat_output 链实现时,注意到 IPv4 和 IPv6 的 DNS 劫持规则在目标地址上写法不同,想请教一下这是否有特殊考量。

IPv4(第 1359/1382 行)— 劫持目标为 loopback:

nft insert rule inet fw4 nat_output position 0 \
  meta nfproto {ipv4} meta l4proto {tcp,udp} th dport 53 \
  ip daddr {127.0.0.1} meta skgid != 65534 counter redirect to "$dns_port"

IPv6(第 1722/1743 行)— 劫持目标为 ::/0(所有地址):

nft insert rule inet fw4 nat_output position 0 \
  meta nfproto {ipv6} ip6 nexthdr {tcp,udp} th dport 53 \
  ip6 daddr {::/0} meta skgid != 65534 counter redirect to "$dns_port"

IPv4 只拦截目标是 127.0.0.1 的 DNS,理解这是为了配合 Dnsmasq 转发模式(server=127.0.0.1#7874)使用。IPv6 的 ::/0 覆盖了所有地址,和 IPv4 的行为范围不太一样。

想请教

  1. IPv6 的 ::/0 是否有特殊的设计考量需要覆盖比 IPv4 更广的范围?比如某些场景下必须劫持路由器发往外部 IPv6 DNS 的查询?

  2. 如果没有特殊考量,是否考虑像 IPv4 一样限定为 {::1}(loopback)?这样两种协议族的行为保持一致,也方便理解。

版本

当前 master 分支,行号参考:https://github.com/vernesong/OpenClash/blob/master/luci-app-openclash/root/etc/init.d/openclash

补充说明

在翻阅 #4244 和 #4806 时注意到两边的调试日志中 nat_output 都出现了 ip6 daddr ::/0 规则,但似乎没有针对这条规则本身的讨论,所以新开一个 issue 请教。如果已有相关讨论或设计文档被我漏掉了,还请指正。

Activity

  1. changed the title [-][代码疑问] nat_output IPv6 DNS 劫持目标:::/0 是否应为 ::1?[/-] [+][讨论] nat_output IPv6 DNS 劫持目标 ::/0 与 IPv4 的 127.0.0.1 不对称,想请教是否有特殊考量[/+] on Jul 28, 2026
  2. huazhaochen01 commented on Jul 29, 2026

    @huazhaochen01

    主要跟 openwrt 下 ipv6 的 dns 分发机制有关

    在 ipv4 场景下比较简单,openwrt 本地的 dnsmasq 默认监听在 127.0.0.1,系统本身的 resolv.conf 指向的基本上也是 127.0.0.1,所以 nat_output 里只限制 ip daddr {127.0.0.1} 就刚好能兜住路由器自身发出的所有 ipv4 dns 请求。

    但是到了 ipv6 场景,openwrt 的 wan6 接口通过 dhcpv6 odhcp6c 从运营商获取 ipv6 之后,系统自动生成的 resolv.conf 会把运营商下发的公网 ipv6 dns 地址比如 240e:x:x:x 或 2408:x:x:x 直接写进去。这时候路由器自身发起的 ipv6 dns 查询,目标地址并不是 ::1,而是直接发往外网的公网 ipv6 dns 地址。

    如果 ipv6 nat_output 也像 ipv4 一样只限制 ::1,那路由器自身发出的这些 ipv6 dns 请求就会直接绕过 openclash 跑去查公网 ipv6 dns,引发严重的 ipv6 dns 泄露,也就是常见的运营商 ipv6 dns 抢答问题。

    所以 ipv6 nat_output 必须用 ::/0 把所有目标为 53 端口的 ipv6 出站流量全量兜住重定向给 clash dns,同时配合 skgid != 65534 规则排除掉 clash 核心自身发起的请求,防止产生回环。

  3. morytyann commented on Jul 29, 2026

    @morytyann
    Contributor

    嗯?

    Image
  4. ocoj commented on Jul 29, 2026

    @ocoj
    Author

    感谢 @huazhaochen01 的详细解答,非常清晰。之前误以为 ::/0 是 IPv4 127.0.0.1 的对称 bug,没考虑到 OpenWrt 的 resolv.conf 会被运营商写入公网 IPv6 DNS 这一层。现在已经理解 ::/0 + skgid != 65534 的设计用意——防止路由器自身 IPv6 DNS 泄漏,同时排除 clash 自身流量防回环。

    已将之前的自定义防火墙脚本和定时巡检清理掉,恢复了默认设置。谢谢!

  5. GeekXtop commented on Jul 31, 2026

    @GeekXtop
    Contributor

    @huazhaochen01 @ocoj 这问题我也遇到了,已经提了修复 #5252,已合并到 dev。

    使用 AI 分析了一下原始提交,以下含 AI 生成内容。

    ::/0 不像刻意设计,更像手误。引入它的 d4cd7436 标题是 fix: dns hijack lo only for router self,意图明确是只劫持回环地址,IPv4 也确实写的 127.0.0.1,唯独 IPv6 写成了匹配全部地址的 ::/0,跟标题自相矛盾。而且那行连关键字都是从 v4 复制的——写的是 ip daddr 而不是 ip6 daddr,nft 根本解析不了,报错还被行尾的 2>/dev/null 吞掉,所以当时压根没装上。

    后来 #3175 把语法修成了 ip6 daddr,但 ::/0 被一起带了下来。而那时 v4/v6 还共用同一个"是否已存在 hijack 规则"的判断,不区分协议族——v4 分支先跑并下发规则后,v6 分支的判断就被顶掉、整段跳过。直到 #5209 分离了 v4/v6 guard,这条规则才第一次真正出现在 nftables 里(版本:v0.47.117)。

    我这边的具体故障:用 SmartDNS 做自定义 DNS,上游加了纯 IPv6 地址的服务器。规则生效后,SmartDNS 并发查询 v4/v6 上游时,v6 那一路就被 ::/0 劫持了——它会拦下本机除 OpenClash 核心(gid 65534)外所有发往任意 IPv6 地址的 53 请求,重定向到本地 clash,fake-ip 模式下秒回 198.18 段,比真正出网的 v4 请求快得多,于是被 SmartDNS 采纳。结果就是订阅地址和节点域名全解析成 fake-ip,订阅更新不了、节点也连不上。

  6. vernesong commented on Aug 1, 2026

    @vernesong
    Owner

    @GeekXtop 改了

  7. github-actions commented on Sep 30, 2026

    @github-actions
    Contributor

    This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 5 days

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions