6.9 KiB
luci-app-dnsguard —— DNS 防绕过(LuCI 应用)
把局域网内「绕过 AdGuard Home 的 DNS」拉回本机解析:53 明文用透明重定向(客户端无感), 853 DoT 用阻断(逼其回落 53),DoH 专用 IP 可选阻断。全部可在 WebUI 开关。
- 入口:LuCI → 服务 → DNS 防绕过(
admin/services/dnsguard) - 配置:
/etc/config/dnsguard - 渲染产物:
/etc/nftables.d/20-dns-guard.nft(由 fw4 在inet/fw4表上下文内 include) - 回滚点:
/mnt/data/backup-dnsguard/<时间戳>/(含20-dns-guard.nft+dnsguard.uci)
为什么需要它
OpenWrt 自带的「DNS 重定向」开关重定向到的是 dnsmasq 自己的端口。本机架构里 dnsmasq 已退居
5353、53 由 AdGuard Home 接管,所以打开那个开关会让全屋绕过 AGH、广告过滤失效;
而且它只匹配 udp dport 53,TCP 不覆盖。本应用是它的正确替代实现:
| 项 | OpenWrt 自带开关 | 本应用 |
|---|---|---|
| 重定向目标 | dnsmasq 的 port(本机 = 5353)❌ |
AdGuard Home 的 :53 ✅ |
| 协议覆盖 | 仅 UDP | TCP + UDP |
| 目标过滤 | 无(跨子网也劫持) | 排除内网/保留段 + 可配设备例外 |
| 前置校验 | 无 | nft -c + fw4 check + 应用后核对链 |
| 回滚 | 无 | 回滚点 + 延迟自动回滚 |
文件清单
| 路径(设备) | 作用 |
|---|---|
/etc/config/dnsguard |
UCI 配置(main / limits 两个节) |
/usr/bin/dnsguard-apply |
唯一写防火墙的地方:UCI → 渲染 → 校验 → 落盘 → fw4 reload |
/usr/bin/dnsguard-selftest |
健康自检(AGH 进程 / :53 监听 / 解析 / 链与计数 / 回滚倒计时) |
/usr/bin/dnsguard-rollback |
回滚点管理 + 延迟自动回滚 |
/etc/init.d/dnsguard |
reload / stop(boot 为空实现,开机不需要它做事) |
controller/dnsguard.lua、view/dnsguard/main.htm、acl.d/luci-app-dnsguard.json |
界面 |
关键技术点
1. 规则写在哪、优先级怎么排
/etc/nftables.d/*.nft 的文件是被 include 进 table inet fw4 内部的(见设备上 /etc/nftables.d/README),
所以文件里只写 set / chain,不要写 table 包裹。
fw4 自己的 DNAT 链是 dstnat(priority dstnat = -100)。我们的重定向链用 priority -105,
在它之前执行:
chain dns_guard_redirect {
type nat hook prerouting priority -105; policy accept;
iifname "br-lan" meta l4proto { tcp, udp } th dport 53 ip daddr != @dns_never_hijack4 counter redirect to :53
iifname "br-lan" meta l4proto { tcp, udp } th dport 53 ip6 daddr != @dns_never_hijack6 counter redirect to :53
}
redirect to :53(只给端口)= 改道到本机 :53。目标写死 AGH 的端口,不是 dnsmasq 的 5353。
2. 「永不劫持」集合里不要放 192.168.0.0/16
默认排除 192.168.1.0/24、172.16.0.0/12、127.0.0.0/8、169.254.0.0/16、224.0.0.0/4。
⚠️ 仓库里带的默认配置用的是
192.168.1.0/24这个占位网段。install.sh在首次安装时会自动读br-lan的 IPv4 地址、算出真实网段并替换它(不影响已有配置)。如果你不是用install.sh装的, 请到界面把「永不劫持(IPv4)」改成你自己的 LAN 网段。 故意不排除整个192.168.0.0/16:红米 K80 的静态 DNS 指向旧网段192.168.5.1(该网段已不存在), 若把 192.168/16 全排除,它会继续"DNS 打空";现在被重定向后反而恢复正常解析。
3. 安全闸门与三层校验
- 闸门:
redirect53=1而AdGuardHome进程不存在 → 直接拒绝应用(exit 3,不落盘) - 校验 1:渲染片段包进临时表名做
nft -c,不通过绝不落盘 - 校验 2:落盘后
fw4 check(fw4 自带的全量校验器),不通过立即还原旧文件 - 校验 3:
fw4 reload后核对dns_guard_redirect链真的存在,不存在则还原 + 重载
4. 延迟自动回滚
DNS 是全家网的命门:改错了人可能连这台路由器都访问不到(域名解析不了)。 所以「53 重定向」从关改到开时,控制器会:
- 先把当前 UCI + 当前规则文件拍成回滚点(必须在写 UCI 之前拍,否则快照里就是新配置)
- 起一个
setsid倒计时进程(默认 10 分钟) - 到点未点「确认保留」→ 自动还原 UCI + 规则文件 +
fw4 reload
/tmp/.dnsguard-rollback 是唯一真相:--disarm 删掉它,倒计时进程醒来发现记录没了就什么都不做(僵尸进程无害)。
5. 幂等
文件头有「生成时间」,所以比较时先剔除该行再 cmp——否则每次都比出差异、白白重载一次防火墙。
验收:连跑两次 dnsguard-apply,第二次必须报 file:"unchanged"。
命令行速查
# 渲染并应用(输出一行 JSON)
/usr/bin/dnsguard-apply
# 只渲染,不碰防火墙(界面「预览将要生成的规则」用的就是它)
/usr/bin/dnsguard-apply --render-only --out /tmp/x.nft
# 健康自检
/usr/bin/dnsguard-selftest
# 回滚
/usr/bin/dnsguard-rollback # 列出回滚点
/usr/bin/dnsguard-rollback latest # 还原到最近一个
/usr/bin/dnsguard-rollback 20260926-182913
/usr/bin/dnsguard-rollback --arm 10 # 挂 10 分钟自动回滚
/usr/bin/dnsguard-rollback --disarm # 确认保留
# 服务
/etc/init.d/dnsguard reload
/etc/init.d/dnsguard stop # 临时摘链(配置不动,fw4 reload 后恢复)
部署 / 回滚
export PATH="/c/Users/27970/.workbuddy/binaries/PortableGit/versions/1.2.0/usr/bin:$PATH"
sh deploy.sh --check # 静态检查(shell / JSON / Lua 控制器 / 模板内嵌 Lua)
sh deploy.sh --install # 打包上传安装(会先备份旧文件)
安装阶段不碰防火墙——规则文件只会在界面点「保存并应用」时生成。
回滚(install.sh 也会打印):
cp -r /mnt/data/backup-dnsguard/<安装时间戳>/* / \
&& rm -f /usr/lib/lua/luci/controller/dnsguard.lua \
&& rm -rf /usr/lib/lua/luci/view/dnsguard /usr/share/rpcd/acl.d/luci-app-dnsguard.json /etc/init.d/dnsguard \
&& rm -f /tmp/luci-indexcache*
# 再删掉重定向链:
nft delete chain inet fw4 dns_guard_redirect 2>/dev/null; fw4 reload
开发时的三个坑(都真踩过)
while read丢末行:printf '%s' "$list" | ... | while read e会把最后一个元素丢掉, 必须printf '%s\n'。症状是224.0.0.0/4、140.207.198.6之类的最后一个元素神秘消失。- Lua 白名单漏
/:控制器里清洗地址用的gsub("[^%w%.%:%-%s%,]", "")会把 CIDR 洗成192.168.1.024(打桩测试抓到的)。白名单必须含/。 - shell 里给变量赋值 PATH 在部分环境下有坑:
PATH=/x:$PATH cmd在某些封装 shell 里会被改写成 带$( )的形式而语法报错;测试时改用/usr/bin/env PATH=... cmd更稳。