OpenWrt 25 安装 AdGuardHome 接管 IPv4 / IPv6 DNS

搞了个新路由器 Tenda BE12 Pro,顺手把全屋 DNS 接到 AdGuardHome 上 —— 一处屏蔽,全屋去广告,顺带把那些"私自加点广告"的运营商 DNS 全甩开。本文记录在 OpenWrt 25 (snapshot) 上把 AGH 装好、让 dnsmasq 让位、IPv6 也走 AGH 的完整步骤。

设计原则:

  • 改动最小:不重装 dnsmasq,不动 LAN/DHCP/网段,只调几个配置项
  • 可逆:§0 做整套 UCI 备份,任何一步炸了能一键回滚
  • 每步可验证:节末给一条 cmd → 期望输出,不过就回头查

§0 准备

环境(本文示例,你按自己实际改):

  • 路由器:Tenda BE12 Pro
  • 系统:OpenWrt 25 snapshot(包管理已切 apk)
  • 上游运营商:日本软银 SoftBank(SB)宽带,入户 NTT 光猫

    • IPv4 走 PPPoE 自己拨号(SB 的 IPoE / MAP-E 只认官方光猫,第三方路由器用不了)
    • IPv6 由 NTT 光猫从 SB 拿原生前缀,SLAAC 透传给下游 —— 路由器走 wan6 + LAN relay,详见 §4.2
  • LAN:10.0.0.0/16,路由器 10.0.0.1
  • LAN IPv6:wan6 relay + LAN relay,客户端直接拿原生 GUA(2400::xxx),不经路由器 NAT66

§1 刷 OpenWrt(略写)

Tenda BE12 Pro WiFi7

根据恩山的教程免拆机刷入 过渡固件 过渡固件再刷 openwrt


§2 安装 AdGuardHome

OpenWrt 25 snapshot 已收录官方 adguardhome 包,直接装。

2.1 装包

apk update
apk add adguardhome luci-app-adguardhome

包关系:

  • adguardhome —— 二进制 + procd init(/etc/init.d/AdGuardHome)+ 默认 /etc/adguardhome.yaml
  • luci-app-adguardhome —— LuCI 上的 "AdGuard Home" 页面(状态、配置入口、日志)

验证:

which AdGuardHome
ls /etc/init.d/AdGuardHome
ls /etc/adguardhome.yaml || echo "首次启动后生成"

2.2 首次启动(走 3000 端口的 Web UI)

先后顺序很关键:AGH 默认想监听 0.0.0.0:53,但此时 dnsmasq 还占着 53。如果让 AGH 直接占 53,启动会失败。思路:先让 AGH 跑起来(管理界面在 3000 端口),配置好之后再到 §3 让 dnsmasq 搬家,最后 AGH 才接管 53。

启 AGH:

/etc/init.d/AdGuardHome enable
/etc/init.d/AdGuardHome start
sleep 2
netstat -tnlp 2>/dev/null | grep -E ':3000|AdGuard'
# 期望: 看到 AdGuardHome 监听 :3000

浏览器打开 http://10.0.0.1:3000:

  • 安装向导第一屏:管理界面 = All interfaces / 端口 3000
  • 第二屏:DNS 服务器 = All interfaces / 端口 53(就算此时端口冲突也照填,§3 把 53 让出来后会自动 OK)
  • 设管理员账号 / 密码
  • 完成
如果向导第二屏立刻报 53 被占,先填 5353 应付过去,§3 做完后回到设置改回 53。

2.3 配置 AGH 上游 DNS

AGH 主页 → 设置 → DNS 设置 → 上游 DNS 服务器(按需,推荐至少配 2 家做冗余):

https://dns10.quad9.net/dns-query
https://cloudflare-dns.com/dns-query
https://doh.pub/dns-query
https://dns.adguard-dns.com/dns-query
https://dns.google/dns-query

并行模式选 "并行请求""快速",体验最好。

下面这三行先加上,留给 §3 用(让 .lan 域名 + 内网反查走 dnsmasq):

[/in-addr.arpa/]127.0.0.1:12353
[/ip6.arpa/]127.0.0.1:12353
[/lan/]127.0.0.1:12353

[/lan/] 这种语法是 AGH 的"分域上游" —— 仅 .lan 后缀的查询走指定上游,其它走主上游。我们让 .lan 走 dnsmasq(下一节会把它搬到 12353),这样客户端 ping printer.lan / ssh nas.lan 这些内网域名照常解析。

过滤规则(广告列表)默认开了 "AdGuard DNS filter" 一条就够日常,愿意加可以再加 "Anti-AD"、"halflife list"、"EasyList China" 等。

保存。


§3 dnsmasq 让出 53 端口

目标:

  • AGH 占 53,对外总入口
  • dnsmasq 搬到 12353,负责 DHCP + .lan 域名解析
  • AGH 把 .lan / 内网反查转发给 127.0.0.1:12353(§2.3 已配)

3.1 改 dnsmasq 监听端口为 12353

LuCI:网络 → DHCP/DNS → 高级设置 → "DNS 服务器端口" 改成 12353

UCI:

uci set dhcp.@dnsmasq[0].port='12353'
uci commit dhcp
service dnsmasq restart

验证:

netstat -unlp 2>/dev/null | grep ':12353'
# 期望: dnsmasq 监听 :12353
netstat -unlp 2>/dev/null | grep -E ':53 .* dnsmasq'
# 期望: 空(dnsmasq 不再占 53)

3.2 关掉 DNS 重定向(fw4 强制劫持)

这一步装 AGH 必做,漏了 AGH 等于白装 —— 流量会被 fw4 提前劫持到 dnsmasq,绕过 AGH 全部过滤。

LuCI:网络 → DHCP/DNS → 常规设置 → 取消勾选 "DNS 重定向"(DNS Redirect)

UCI:

# OpenWrt / ImmortalWrt 都用这个字段, 字段名可能因版本不同稍异
uci set dhcp.@dnsmasq[0].dnsredirect='0'
uci commit dhcp
service firewall restart
service dnsmasq restart

验证:

nft list ruleset | grep -E 'dport.*53.*(redirect|dnat)'
# 期望: 空 (没有任何把 53 端口 redirect / dnat 走的规则)

为什么必须关:

OpenWrt(尤其 ImmortalWrt 等改版)默认开启 DNS 重定向 —— 在 fw4 层加一条规则,把客户端去往任何外部 53 端口的查询统统 DNAT 回本机 dnsmasq 监听端口。原本(dnsmasq 在 53)的设计意图是"客户端硬编码 8.8.8.8 也强制走本机 DNS,顺带挡运营商劫持"。

问题在 §3.1:dnsmasq 端口改成 12353 后,这条 fw4 规则跟着把 DNAT 目标改成 12353,直接把客户端流量塞给 dnsmasq:12353,完全绕开 AGH:53。结果是:

  • 客户端 DNS = 10.0.0.1 的查询 → 命中 fw4 规则 → DNAT 到 dnsmasq:12353 → 绕过 AGH
  • 客户端硬编码 8.8.8.8 的查询 → 同上 → 绕过 AGH

广告过滤、查询日志、过滤规则统统看不到流量,AGH 仪表盘空空如也。

关掉这条 fw4 规则后:

  • 客户端 DNS = 10.0.0.1 → 直达本机 53 → AGH ✓
  • 客户端硬编码 8.8.8.8 → 走外网,不进 AGH(也不再被劫持去 dnsmasq)

想"无论客户端怎么设都走 AGH"?那要自己起一条 fw4 规则,把外部 53 显式 DNAT 到本机 53(AGH),不是开着固件那个自动开关 —— 那个会指向 12353:

uci add firewall redirect
uci set firewall.@redirect[-1].name='Force-DNS-to-AGH'
uci set firewall.@redirect[-1].src='lan'
uci set firewall.@redirect[-1].proto='tcp udp'
uci set firewall.@redirect[-1].src_dport='53'
uci set firewall.@redirect[-1].dest_port='53'   # AGH 监听的 53, 不是 12353
uci set firewall.@redirect[-1].target='DNAT'
uci commit firewall
service firewall restart

代价:IoT / 公司 VPN / 部分游戏服务想用自己 DNS 时会失败。看个人取舍。


§4 IPv6 DNS:不需要单独接管(原理 + 防御 + 验证)

直觉上"全屋 DNS 接管"会让人想"v6 那边肯定也得专门搞一套吧" —— 不用。DNS 协议跟 IP 协议是解耦的,v4 一份 AGH 已经能解所有记录类型(A / AAAA / CNAME / ...)。本章先讲为啥不需要,然后讲本机 SB + NTT 环境的实测情况,再加一条防御性 fw4 规则挡掉硬编码绕过,最后验证。

真要给 LAN 客户端通告 v6 DNS 的(国内电信 / 联通有 PD 前缀的 server 模式场景,或纯 v6-only 客户端的特殊需求),看本文末尾的 附录 A

4.1 为什么 v4 一个 AGH 够全屋用

关键认识:很多人下意识把"v6 DNS 服务器"理解为"解析 v6 地址的 DNS",其实不是 —— DNS 协议本身是载体无关的:

  • 客户端用 v4 transport 问 AGH(10.0.0.1)www.youtube.comAAAA 记录
  • AGH 通过上游 DoH 拿到 2607:f8b0:...
  • AGH 用 v4 transport 把这个 v6 地址回给客户端
  • 客户端拿到 v6 地址,直接走 v6 transport 连过去

全程没有任何"v6 DNS 服务器"参与,但 v6 访问完美工作。

不通告 v6 DNS 实际影响表:

场景影响
客户端解析 AAAA 记录✅ 不影响,AGH 照样返回 v6 地址
客户端通过 v6 访问网站✅ 不影响,拿到 v6 地址就能连
Happy Eyeballs 双栈选择✅ 不影响,客户端早拿到 v4+v6 两个地址自己选
纯 v6-only 客户端(无 v4 stack)❌ 需要 v6 DNS,但家庭网几乎不存在
强制只用 v6 DNS 的 IoT❌ 罕见,真碰上单独配它

家用场景下,事实上不需要给 LAN 通告 v6 DNS。这能避开 odhcpd 在 relay 模式下 DNS 选项覆写的不确定性,配置最简,行为最可预测。

4.2 本机环境:SoftBank + NTT 光猫 + relay

为啥单开一节讲本机环境:典型的"没拿到 PD 前缀,只能 relay"场景,而且实测验证了 §4.1 的结论 —— 这种场景下 v6 DNS 已经事实上是空的,不用做任何动作。

  • IPv4:SB 的 v4 走 IPoE 上的 MAP-E / DS-Lite —— 只有 SB 官方光猫 / 认证路由器能用。第三方路由器(本机 OpenWrt)只能退一步用 PPPoE 自己拨号(慢一点延迟高一点,但能用)。
  • IPv6:NTT 光猫从 SB IPoE 拿到原生 v6 前缀(2400::/29 段),在自己 LAN 口下发 SLAAC + 无状态 DHCPv6(地址走 SLAAC,DNS 等附加项走 DHCPv6),但不下发 DHCPv6-PD(没把前缀切给下游)。OpenWrt 拿不到可分配子前缀,只能 relay 把光猫的 RA / DHCPv6 透传给 LAN。
想自己确认光猫下发模式,看 ifstatus wan6data 段:有 ipv6-prefix 字段 = 有 PD,可以走 server / hybrid;只有 passthru = 纯 SLAAC + 无状态 DHCPv6,只能 relay。

最终拓扑:

[SB IPoE 网络]
       │ 光纤
       ▼
   NTT 光猫    ──┐ v6 SLAAC + 光猫自己的 v4 网络
                │
                ▼
        OpenWrt(本机)
        ├── wan  = PPPoE(自己拨, v4 出口)
        └── wan6 = DHCPv6 客户端 → SLAAC 拿 GUA
                │
                ▼
        LAN(odhcpd 全 relay)
        └── 客户端走 SLAAC 直接拿原生 GUA `2400:xxx::xxx`

UCI(/etc/config/dhcp 里 LAN 部分):

config dhcp 'lan'
    option interface 'lan'
    option ra 'relay'
    option dhcpv6 'relay'
    option ndp 'relay'
    option master '0'

LuCI:网络 → 接口 → LAN → DHCP 服务器 → IPv6 设置,三个下拉全设 relay(中继)。

实测客户端 v6 DNS 状态(Windows 主机):

PS> Get-DnsClientServerAddress -AddressFamily IPv6 | ?{$_.InterfaceAlias -in '以太网','WLAN'}

InterfaceAlias    ServerAddresses
--------------    ---------------
以太网            {}
WLAN              {}

物理网卡的 v6 DNS 列表是空的,因为:

  • NTT 光猫 RA 里 M=0 / O=1,叫客户端用 DHCPv6 stateless 拿 DNS
  • OpenWrt odhcpd 在 relay 模式下转发 DHCPv6 INF-REQ 出去,但 reply 在 relay 链路上没能正确送回客户端
  • 结果:客户端 v6 DNS 实际空 → 所有 DNS 自动走 v4 → 全部进 AGH

阴差阳错,这套环境已经处于"v4-only AGH 完美生效"状态,不用碰任何 odhcpd 配置。

别担心"全是公网 v6 = 内网裸奔"。OpenWrt 的 fw4 默认对 wan6 → lan 方向的 forward 是 REJECT,跟 v4 一模一样 —— 内网设备虽有真公网 v6,外网照样进不来。需要外部访问(NAS / Web 服务)得自己加 forward 放行,等价于 v4 的端口转发,只是不用 NAT。验证:nft list ruleset | grep -A2 'forward_wan',看到 meta nfproto ipv6 ... reject 就放心。

如果你不在 NTT 场景而是国内电信 / 联通拿到了完整 PD 前缀,LAN 应该用 serverhybrid 模式。这种场景下要不要走"不通告 v6 DNS"路线看你 —— 偷懒走本章路径完全可以(原理一样),洁癖追求 v6 DNS 也接管的看附录 A

4.3 防御性兜底:阻断 LAN v6 DNS 出站(强烈推荐)

虽然 §4.2 实测客户端默认拿不到 v6 DNS,但有几种情况会绕过:

  • 用户手动给某台设备配了 v6 公共 DNS(2606:4700:4700::1111 Cloudflare 等)
  • 安卓 9+ 的"私人 DNS"用 DoT 到外部
  • 某些 IoT 内置 hardcoded v6 DNS

加一条 fw4 规则,把 LAN 出方向的 v6 53 流量统统 REJECT,强制 fallback 到 v4(AGH):

uci add firewall rule
uci set firewall.@rule[-1].name='Block-LAN-v6-DNS-outbound'
uci set firewall.@rule[-1].src='lan'
uci set firewall.@rule[-1].dest='wan'
uci set firewall.@rule[-1].family='ipv6'
uci set firewall.@rule[-1].proto='tcp udp'
uci set firewall.@rule[-1].dest_port='53'
uci set firewall.@rule[-1].target='REJECT'
uci commit firewall
service firewall restart

为什么 REJECT 不 DROP:让客户端立刻收到 ICMP unreachable,马上 fallback 到 v4,不傻等超时(典型 30 秒+)。体验差异肉眼可见。

验证:

nft list ruleset | grep -A2 'Block-LAN-v6-DNS'
# 期望: 看到 ip6 ... udp/tcp dport 53 reject 规则

这条只挡明文 53 端口的 DNS —— DoH(443)/ DoT(853)拦不住。要进一步封 DoH,得在客户端关浏览器的"安全 DNS"开关,或者在 fw4 加黑名单封 known DoH IP(1.1.1.1 / 2606:4700:4700::1111 / 8.8.8.8 / 2001:4860:4860::8888 等),代价是这些 IP 不能再用,看个人取舍。详见 §7.3。

4.4 验证

客户端断开 WiFi / 拔网线再连,然后逐项过:

1. 物理网卡 v6 DNS 应为空

# Windows:
PS> Get-DnsClientServerAddress -AddressFamily IPv6 | Select InterfaceAlias, ServerAddresses
# 期望: 物理网卡(以太网 / WLAN)ServerAddresses 是 {}
# Linux:
resolvectl status | grep -A3 'DNS Servers'
# 期望: 看到 v4 (10.0.0.1), 没有 v6 (2404::xxx 或 fd::xxx 都不应有)

# macOS:
scutil --dns | grep nameserver
# 期望: 全是 10.0.0.1, 没有 v6

2. v4 transport 解析 AAAA 记录工作正常

nslookup -type=AAAA youtube.com 10.0.0.1
# 期望: 返回 AAAA 记录 (2607:f8b0:xxxx::xxxx)
# 说明: v4 transport 问 AGH, AGH 返回 v6 地址 → 客户端能走 v6 出去

3. AGH 屏蔽 AAAA 记录也生效

nslookup -type=AAAA googleadservices.com 10.0.0.1
# 期望: AGH 屏蔽 → 返回 :: 或 NXDOMAIN
# 说明: 广告域的 v6 也被挡住, 不会通过 v6 路径加载广告

4. fw4 兜底真的拦截 v6 DNS

# 在 LAN 客户端:
nslookup baidu.com 2606:4700:4700::1111
# 期望: 失败(Connection refused / 立刻报错, 不超时)
# 说明: fw4 REJECT 工作正常, 想绕的也绕不了

四条全过 = §4 完工,全屋 DNS(v4 + AAAA 解析 + v6 屏蔽)都在 AGH 手里。


§5 切换 AGH 到 53 端口 + 端到端验证

5.1 让 AGH 接管 53

如果 §2.2 安装向导那一步你填的是 53,这步只需重启:

/etc/init.d/AdGuardHome restart
sleep 2
ss -nlp 2>/dev/null | grep -E ':53 ' | grep -i adguard
# 期望: 同时看到 *:53 (v4) 和 [::]:53 (v6)

如果 §2.2 你填了 5353 应付,现在去 AGH 设置 → DNS 设置 → 监听端口改回 53,保存,然后上面的 restart + 验证。

5.2 客户端端到端验证

客户端断开 WiFi / 拔网线再连,重新拿 DHCP/RA,然后:

1. 客户端拿到的 DNS 是路由器

  • Windows:ipconfig /all 看 "DNS 服务器"
  • macOS:scutil --dns | grep nameserver
  • Linux:resolvectl status
  • Android:设置 → 关于本机 → IPv4/IPv6 地址栏旁边 / 或装 PingTools 看
  • 期望:IPv4 = 10.0.0.1,IPv6 = 空 / 未分配(§4 解释了为什么不通告 v6 DNS;空才对,有 v6 DNS 反而要查是不是有客户端硬编码绕过)

2. AGH 真的看到查询

LuCI → AdGuard Home → 查询日志,期望刷出客户端 IP + 实时查询记录。

3. 广告真被屏蔽

  • AGH 仪表盘 "Blocked by filters" 计数在涨
  • 浏览器开任意广告大站,广告位空了 / 占位

4. .lan 内网域名照常解析

nslookup router.lan 10.0.0.1
# 期望: 解析回 10.0.0.1

四条全过 = 完工。


§6 自用脚本(占位,后补)

预留几个口子,后面补具体内容:


§7 踩坑速查

7.1 装完瞬间全屋 DNS 炸

90% 是 dnsmasq 已经搬走但 AGH 没占上 53。看一眼谁占着:

netstat -tnlp 2>/dev/null | grep ':53 '
netstat -unlp 2>/dev/null | grep ':53 '

应急还原(让 dnsmasq 先把 53 接回去,慢慢排查):

/etc/init.d/AdGuardHome stop
uci set dhcp.@dnsmasq[0].port='53'
uci commit dhcp
service dnsmasq restart

DNS 立刻恢复,然后回 §3 重做。

7.2 IPv6 客户端有 v6 DNS,且不是 AGH

按 §4 的设计,客户端 v6 DNS 应该是。如果实际看到客户端 v6 DNS 是某个非 10.0.0.1 的地址(典型是 2404:1a8:7f01:b::3 NTT,或者 2606:4700:4700::1111 Cloudflare),说明有东西绕过了 AGH:

  1. odhcpd 在 relay 模式下把上游的 DNS 选项透传给了客户端:uci show dhcp.lan | grep ra_dns,如果没设,把它显式关掉:uci set dhcp.lan.ra_dns='0'; uci commit dhcp; service odhcpd restart
  2. 客户端手动配了 v6 公共 DNS:Windows 在 "网络与 Internet → 高级网络设置 → 网卡属性 → IPv6" 里看;Linux 在 systemd-networkd / NetworkManager 配置里看;手机在 "私人 DNS / Private DNS" 设置里看。逐个关掉。
  3. fw4 阻断规则没生效:回 §4.3 重做那条 Block-LAN-v6-DNS-outbound,然后 nslookup baidu.com 2606:4700:4700::1111 测一下应该失败。
  4. 某 IoT 内置硬编码 DNS:fw4 规则会把它的 v6 53 出站请求 REJECT,设备应该自动 fallback 到 v4。如果设备不 fallback(IoT 死硬),它就上不了网,这时要么换设备,要么单独给那 MAC 放行(自己拿主意)。

7.3 某些设备(iOS / Android / 浏览器)继续绕过 AGH

iOS 私有中继、Android 9+ 私人 DNS、Chrome / Edge 内置 DoH,默认都会绕过本地 DNS。逐个关:

  • iOS:设置 → 自己头像 → iCloud → 私人中继 → 关
  • Android:设置 → 网络与互联网 → 私人 DNS → "自动" 或 "关闭"
  • Chrome:设置 → 隐私和安全 → 安全 → "使用安全 DNS" → 关
  • Edge:同上(edge://settings/security)

想强行兜底就把 §3.2 选项 B 反向开着(fw4 劫持所有 53),但 DoH(443 端口)还是拦不住,要彻底封需要黑名单 known DoH 服务器 IP(cloudflare-dns.com / dns.google 一长串),代价是误伤,看个人取舍。

7.4 sysupgrade 升级后 AGH 配置丢

升级前把这几条加到 /etc/sysupgrade.conf:

/etc/adguardhome.yaml
/usr/share/AdGuardHome/
/var/AdGuardHome/

具体哪个目录是 AGH 工作目录看你装的版本 —— 找含 filters/ data/ sessions.db 的那个就是。sysupgrade -l | grep -i adguard 确认保留名单。

7.5 时间不准导致 DoH/DoT 上游 TLS 握手失败

部分硬件无 RTC,断电时间归零,AGH 连 https://dns.alidns.com/dns-query 会因证书 "not valid yet" 失败。

service sysntpd status   # 应 running
date                      # 看时间合理

不准的话 service sysntpd restart 强制同步;snapshot 里没 sysntpd 就装 chrony(apk add chrony && /etc/init.d/chronyd enable && start)。


§8 关键文件位置

文件 / 目录作用谁维护
/etc/adguardhome.yamlAGH 主配置(上游、监听、过滤、用户)AGH / LuCI
/usr/share/AdGuardHome//var/AdGuardHome/工作目录(filters / querylog / stats / sessions)AGH 运行时
/etc/init.d/AdGuardHomeprocd init 脚本
/etc/config/dhcpdnsmasq 端口、DNS 重定向开关、LAN v6 ra/dhcpv6 模式uci(§3 / §4.2)
/etc/config/firewall阻断 LAN v6 DNS 出站规则(§4.3)、(可选)强制 53 DNAT 规则(§3.2)uci
/etc/config/networkwan PPPoE、wan6 DHCPv6 客户端、LAN IPv6 ULA 前缀uci
/root/agh-backup/§0.1 备份 + §6.1 自动备份(待补)

附录 A:想给 v6 客户端通告 DNS 的扩展阅读

正文 §4 主张"不通告也不影响 v6 访问",这是 NTT relay 场景实测验证过的结论。本附录留给以下三类读者:

  • 在国内电信 / 联通环境,光猫给到 OpenWrt 完整 PD 前缀,自然走 server / hybrid 模式 —— 顺便通告 v6 DNS 成本几乎为 0
  • 纯 v6-only 客户端(无 v4 stack),必须有 v6 DNS 才能解析
  • 单纯追求"v6 DNS 也接管"的纯粹路径

A.1 IPv6 工作模式速览

LuCI 里 LAN 接口的"DHCP 服务器 → IPv6 设置"几个下拉:

字段取值行为
路由通告服务(RA)server路由器自己生成 RA,通告给 LAN(包含前缀、网关、DNS)
relay把上游 wan6 收到的 RA 原样转发给 LAN
hybrid有 PD 前缀走 server,没有走 relay,自动
disabled不发 RA
DHCPv6 服务server / relay / hybrid / disabled同上,管 DHCPv6
NDP 代理hybrid / relay / disabled邻居发现代理
DHCPv6 模式(server 模式才有意义)statelessSLAAC(客户端用 EUI-64 / 隐私扩展自己生成 IP,从 RA 学前缀)
statefulDHCPv6 显式分配 IP(像 v4 DHCP 那样)
stateless+stateful两者并行

Server vs Relay 关键差异:

  • Server:路由器有自己的 v6 前缀(ULA 或 PD 给的 GUA),自己当 v6 网关 + DNS。"通告哪个 DNS"完全路由器说了算 —— v6 DNS 接管最简单。
  • Relay:路由器是透明转发,上游 RA 写啥就传啥。能不能改 DNS 选项取决于 odhcpd 版本,较新版本支持覆写,但行为不稳定,实测可能失效(本文 NTT 环境就是栽在这里)。

A.2 Server / hybrid 模式下接管 v6 DNS

适用于:LuCI → 网络 → 接口 → LAN → DHCP → IPv6 设置 → RA / DHCPv6 = serverhybrid,且 br-lan 上有稳定的 v6 地址(ULA fd*::1 或 PD 给的 GUA)。

A.2.1 关掉 odhcpd 默认的 DNS 通告

LuCI:网络 → 接口 → LAN → 编辑 → DHCP 服务器 → IPv6 设置 → 取消勾选 "本地 IPv6 DNS 服务器"

UCI:

uci set dhcp.lan.ra_dns='0'
uci commit dhcp

A.2.2 选一个稳定的 v6 地址作为 AGH 的"v6 入口"

ip -6 addr show br-lan | grep inet6
# 优先取 fd**::1 这种 ULA;没有的话用 GUA(光猫前缀给路由器自己的)

ULA 没自动生成的话手动建一个(RFC 4193 标准):

ULA=fd$(head -c 5 /dev/urandom | hexdump -e '5/1 "%02x"' | sed -E 's/(..)(....)(.*)/\1:\2:\3::\/48/')
uci set network.globals.ula_prefix="$ULA"
uci commit network
service network reload

记下这个地址,下文统一写作 <ROUTER_V6>

A.2.3 写入 RA / DHCPv6 通告 DNS

LuCI:同一页 → "通告 DNS 服务器"填 <ROUTER_V6>

UCI:

uci add_list dhcp.lan.dns='<ROUTER_V6>'
uci commit dhcp
service odhcpd restart

A.2.4 确认 AGH 在 v6 上监听

sed -n '/^dns:/,/^[a-z]/p' /etc/adguardhome.yaml | head -10
# 期望 bind_hosts 包含 "::"

不对就在 AGH 设置 → DNS 设置 → "监听接口" 选 "所有接口",/etc/init.d/AdGuardHome restart

A.2.5 客户端验证

# Linux:
resolvectl status | grep -A3 'DNS Servers'
# 期望: 列表里有 <ROUTER_V6>, 没有运营商 DNS

A.3 Relay 模式下硬尝试覆写 DNS(可能失效)

如果你和 NTT 场景一样在 relay 模式但就是想试,做完 A.2.1 + A.2.2 + A.2.3 后,抓 DHCPv6 reply 看 DNS 选项:

tcpdump -i br-lan -nvve 'ip6 and (udp port 547 or udp port 546)' -c 20

让一台 LAN 设备断 WiFi 重连触发 DHCPv6,reply 里 DNS-server 字段:

  • <ROUTER_V6> → 生效,完成
  • 还是上游 2404::xxx / 2001:240::xxx → 没生效

没生效的话只剩两条路:

  1. 切 hybrid 模式碰运气:uci set dhcp.lan.ra='hybrid'; uci set dhcp.lan.dhcpv6='hybrid'。有 PD 就 server,没有就 relay —— 但你既然没 PD,这等于没改。
  2. 回正文 §4 路线(不通告 v6 DNS,fw4 兜底)—— 已经是最优解。
不推荐 NAT66:server 模式 + 自己起 ULA 段 + nft 做 v6 NAT 出去。性能 / 隐私 / 连通性都让步,只换来一个"v6 DNS 也走 AGH"的虚名,得不偿失。

A.4 v6 反查转给 dnsmasq(可选)

让 LAN 内 v6 设备的反向 PTR 解析(根据 IP 反查主机名)也能查到:

AGH 设置 → DNS 设置 → 上游 DNS,加一行(<ULA_PREFIX_REV> 替换为你 ULA 前缀的 nibble 反转):

[/<ULA_PREFIX_REV>.ip6.arpa/]127.0.0.1:12353

例:ULA 是 fd5a:abcd:1234::/64,反查段就是 0.0.0.0.0.0.0.0.4.3.2.1.d.c.b.a.a.5.d.f.ip6.arpa

嫌算麻烦就跳过,内网 v6 反查走 AGH 主上游会失败,不影响正向解析。


完工。

Last modification:June 7, 2026
If you think my article is useful to you, please feel free to appreciate