用 https-dns-proxy 彻底解决 OpenWrt + TPROXY 的 DNS 卡顿/解析失败
网络环境踩坑后的完整复盘与做法,给同样用 OpenWrt + TPROXY 透明代理使用者作为参考。
问题现象
- Ubuntu Server 在
apt update
或ping
时先卡一阵再恢复。 关闭代理中的“仅 DNS 劫持(快速)”后,部分域名解析失败;但直接指定
1.1.1.1
就能解析,比如:nslookup www.ict.ac.cn 192.168.1.1 # 失败 nslookup www.ict.ac.cn 1.1.1.1 # 成功
- Ubuntu 侧
systemd-resolved
使用本机 stub(127.0.0.53
),上游指向路由器192.168.1.1
。
根因解析(两类问题叠加)
- UDP→TCP 回退没有被正确处理
部分域名响应很大或被截断(TC 位=1)时,客户端会从 UDP/53 改用 TCP/53 再查。如果路由/上游没接好 TCP/53(只拦或只代理了 UDP/53),就会超时或卡顿数秒。 - 路由器的“上游 DNS”质量/路径不可靠
关闭代理的 DNS 项后,LAN 客户端的查询经由 dnsmasq →(默认)ISP DNS,容易被劫持/污染/限速;于是出现“路由器上查不到,直接问 1.1.1.1 能查到”的错觉。
一句话:DNS 路径要么加密,要么把 TCP+UDP 53 都管好。否则就会时好时坏、时快时慢。
整体思路
- 让 DNS 专业的做 DNS:OpenWrt 上用 https-dns-proxy(DoH) 或同类(AdGuard Home/SmartDNS/dnsproxy)做加密上游;dnsmasq 只负责给 LAN 设备递送结果和缓存。
- 让TPROXY专心做透明代理:关闭代理软件的 DNS 劫持/接管,避免与 53 端口抢占或双链路冲突。
- (可选,不建议)把 LAN 的 TCP+UDP 53 都“收拢”到路由器,避免客户端绕路直连外部 DNS。
实施步骤
1) 在 OpenWrt 安装并启用 DoH
apk update # 注意,我采用的是OpenWrt snapshot版本,当前其他版本是opkg命令
apk add luci-i18n-https-dns-proxy-zh-cn luci-app-https-dns-proxy
- 打开 LuCI → Services → HTTPS DNS Proxy,选择一两个上游(Cloudflare/DNSPod 等)。
- 建议同时启用第二实例做备用端口(例如 5053/5054)。
2) 让 dnsmasq 只走本地 DoH 端口 (默认不需要手动设置此项)
uci set dhcp.@dnsmasq[0].noresolv='1'
uci -q del dhcp.@dnsmasq[0].resolvfile
uci -q del dhcp.@dnsmasq[0].server
uci add_list dhcp.@dnsmasq[0].server='127.0.0.1#5053'
uci add_list dhcp.@dnsmasq[0].server='127.0.0.1#5054'
uci commit dhcp
/etc/init.d/dnsmasq restart
之后 LAN 客户端依旧只填路由器做 DNS(192.168.1.1),但路由器上游已经是加密的 DoH 了。
3) 代理侧的设置
- 保持 TPROXY 透明代理(转发流量)。
- 关闭 DNS 劫持/防污染/接管 53(避免占用 53 端口或与 dnsmasq/DoH 双重解析)。
- (可选,不建议)如果你所在网络直连某个 DoH 端点不稳,将该端点域名加入 “走代理” 列表;同时排除本地回环与 代理自身流量,避免回环。
4) (可选,不建议)防止终端绕过:拦截 LAN 的 TCP+UDP 53
uci add firewall redirect
uci set firewall.@redirect[-1].name='Intercept-DNS'
uci set firewall.@redirect[-1].src='lan'
uci set firewall.@redirect[-1].proto='tcpudp'
uci set firewall.@redirect[-1].src_dport='53'
uci set firewall.@redirect[-1].dest_ip='192.168.1.1'
uci set firewall.@redirect[-1].dest_port='53'
uci set firewall.@redirect[-1].target='DNAT'
uci commit firewall
/etc/init.d/firewall restart
这样即使触发了 TCP 回退,流量也会被送回本地 dnsmasq→DoH 的链路,不会再出现“UDP 走本地、TCP 走外面”的分裂。
常见坑位与排查清单
- 53 端口冲突:确保只有 dnsmasq(或你选的 DNS 前端)在监听 53;关闭代理的 53 接管,以免撞端口。
- 只拦 UDP/53:务必同时处理 TCP/53(见“拦截 DNS”步骤),否则遇到回退就卡/超时。
- rebind_protection 误伤:只在公域名解析到私网地址时触发;如遇误报,可针对特定域名加白或暂关测试。
- 过滤 AAAA(filter_aaaa):不建议开启。开启后 IPv6 解析被截断,容易制造“能 ping 不能开网页”的错觉。
- TPROXY 只管转发流量:路由器本机进程(dnsmasq/DoH 客户端)默认不走透明代理。一般没问题(DoH 已加密)。若你确实需要让它也走代理,可在路由/策略中单独处理 OUTPUT 流量,并注意排除代理自身。
- 缓存与生效顺序:变更配置后记得
dnsmasq
、systemd-resolved
、客户端本地 DNS 缓存都要刷新/重启一次。
可选替代与拓展
- AdGuard Home / SmartDNS / dnsproxy:都能做加密上游(DoH/DoT/DoQ),也更易做分流、广告拦截。思路与本文一致:LAN→dnsmasq/AGH →(加密上游)。
- 仅明文上游(不推荐):起码要把 dnsmasq 上游显式改成 1.1.1.1/8.8.8.8,并确保网络放行 TCP/53。依旧可能被干扰,长期建议还是上 DoH/DoT。
最终效果
apt
、ping
不再卡顿,域名解析稳定、快速。- 指定
192.168.1.1
与1.1.1.1
的解析结果一致(无污染/无劫持)。 - 代理专注透明代理,DNS 由 DoH 专职处理,职责清晰、互不抢端口。