专栏

非标 DoH 部署教程:绕过 DPI 的 DNS 加密方案

校园网 / 企业网 DPI 环境下的 DNS 加密方案。

教程DNSOpenWrt

校园网/企业网的 DPI 设备通常不会放过 UDP 53 端口的明文 DNS 查询。这篇教程教你用 非标准端口 + DoH 加密 把 DNS 流量伪装成普通 HTTPS,让运营商 DPI 彻底认不出。

架构一览

客户端 → smartdns(:53) → DoH TLS :8443 → 云服务器 dnsproxy → 223.5.5.5
                              ↑
                         非标准端口
                         绕过了 DPI 的 53/853 端口特征库

核心思路三句话:

  1. 端口伪装:不用 53(明文 DNS)也不用 853(DoT 标准端口),用 8443——看起来就是普通 HTTPS
  2. 内容加密:DoH 把 DNS 查询装进 HTTP/2 TLS 隧道,抓到包也只能看到加密载荷
  3. IP 无特征:云服务器 IP 不在任何 DNS 特征库里,DPI 不认识这个地址

第一步:在云服务器上部署 dnsproxy

1.1 下载 dnsproxy

# 云服务器 (本例:阿里云 ECS, Ubuntu 26.04)
mkdir -p /usr/local/dnsproxy
cd /usr/local/dnsproxy

# 下载预编译二进制 (AdGuardTeam/dnsproxy)
wget https://github.com/AdguardTeam/dnsproxy/releases/download/v0.73.4/dnsproxy-linux-amd64-v0.73.4.tar.gz
tar xzf dnsproxy-linux-amd64-*.tar.gz

1.2 生成自签名证书

⚠️ 自签名证书的 CN 用 IP 地址,不是域名。

mkdir -p /etc/dnsproxy/ssl
cd /etc/dnsproxy/ssl

# 生成 CA 私钥
openssl genrsa -out ca.key 2048

# 生成自签名 CA 证书(CN 填服务器公网 IP)
openssl req -new -x509 -days 3650 -key ca.key -out ca.crt \
  -subj "/CN="

# 生成服务端证书(同样用 IP)
openssl genrsa -out server.key 2048
openssl req -new -key server.key -out server.csr \
  -subj "/CN="

# 签发
openssl x509 -req -days 3650 -in server.csr \
  -CA ca.crt -CAkey ca.key -set_serial 01 -out server.crt

# 合并证书链
cat server.crt ca.crt > cert.pem

1.3 创建 systemd 服务

cat > /etc/systemd/system/dnsproxy.service << 'EOF'
[Unit]
Description=DNS Proxy (DoH)
After=network.target

[Service]
Type=simple
ExecStart=/usr/local/dnsproxy/dnsproxy \
  --https-port=8443 \
  --tls-crt=/etc/dnsproxy/ssl/cert.pem \
  --tls-key=/etc/dnsproxy/ssl/server.key \
  --port=5353 \
  --listen=0.0.0.0 \
  --upstream=223.5.5.5 \
  --upstream=119.29.29.29 \
  --bootstrap=223.5.5.5
Restart=always

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable --now dnsproxy
参数说明:
--https-port=8443:DoH 监听端口(非标,避开 DPI)
--port=5353:本地 DNS 转发端口(不对外)
--upstream:上游 DNS,阿里 + DNSPod 双保险
--bootstrap:启动时解析 bootstrap DNS 用的初始上游

1.4 开放防火墙

# UFW
ufw allow 8443/tcp
ufw reload

# 或者 iptables
iptables -A INPUT -p tcp --dport 8443 -j ACCEPT

1.5 验证 DoH 端点

# 从任意能访问公网的机器测试
curl -sk "https://:8443/dns-query?name=baidu.com&type=A"
# 应返回 DNS JSON 响应

# 或测试 POST 格式(RFC 8484)
curl -sk -H "Content-Type: application/dns-message" \
  "https://:8443/dns-query?dns=AAABAAABAAAAAAAABGJhaWQDY29tAAABAAE"

第二步:在 OpenWrt 软路由上配置 smartdns

2.1 安装 smartdns

# OpenWrt 软路由 (SSH)
opkg update
opkg install luci-app-smartdns
# 自动依赖 smartdns

2.2 停掉 AdGuardHome(如有)

⚠️ AdGuardHome 是 Go 写的,不认系统 CA 证书。自签名证书 + Go 程序 = 一定翻车。用 C 写的 smartdns 替代。
/etc/init.d/adguardhome stop
/etc/init.d/adguardhome disable

2.3 配置 smartdns 上游

编辑 /etc/smartdns/custom.conf(不是 UCI!UCI 改了可能不生效):

# ===== 主上游:自建 DoH 服务器 =====
server-https https://:8443/dns-query \
  -no-check-certificate \
  -host-name cloud-dns.aliyun.com

# ===== 备用上游:公共 DoH =====
server-https https://dns.alidns.com/dns-query \
  -host-name www.aliyun.com

server-https https://doh.pub/dns-query \
  -host-name www.dnspod.cn

# ===== 优化参数 =====
speed-check-mode ping,tcp:443
cache-size 4096
prefetch-domain yes
serve-expired yes
force-AAAA-SOA yes
dualstack-ip-selection yes
rr-ttl-min 60
edns-client-subnet 0.0.0.0/0
关键参数:
-no-check-certificate:跳过自签名证书验证(smartdns 支持这个,不像 Go 程序那样认死理)
-host-name:伪装 TLS SNI 为阿里云/CDN 域名,DPI 看到的是 cloud-dns.aliyun.com 而不是你的 IP

2.4 重启生效

/etc/init.d/smartdns restart

# 测试
nslookup baidu.com 127.0.0.1

第三步:验证与加固

3.1 确认 DNS 走 DoH 而非明文

# 抓 WAN 口的 UDP 53 包(DNS 明文)
tcpdump -i eth1 -c 5 "udp port 53"

# 如果输出为空 → 全走 DoH 加密 ✅
# 如果有包 → 存在明文泄露,检查 smartdns 配置

3.2 确认 DoH 连接正常

# 抓 WAN 口到云服务器的 TLS 流量
tcpdump -i eth1 "host  and port 8443"

# 触发几次 DNS 查询
nslookup baidu.com 127.0.0.1
nslookup qq.com 127.0.0.1

# 应该看到 TLS 握手 + 加密数据传输

3.3 持久化:确保自启

# smartdns 开机自启
/etc/init.d/smartdns enable

# dnsmasq 只做 DHCP(端口 5353,不处理上游 DNS)
uci set dhcp.@dnsmasq[0].port='5353'
uci commit dhcp
/etc/init.d/dnsmasq restart

第四步:可选——加上 DPI 流量混淆

DoH 保护了 DNS 查询内容,但 TLS SNI 仍然是明文的。ISP 的 DPI 设备可以知道你连接了哪个 IP(虽然不知道查询内容)。

如果网络环境特别严苛,可以在软路由上部署 nfqws(zapret 工具链)对 TCP 流量做 DPI 混淆:

# nfqws 示例策略(TLS split + 随机 TTL)
nfqws --qnum=100 \
  --filter-l7=tls \
  --dpi-desync=split2 \
  --dpi-desync-fooling=md5sig \
  --dpi-desync-autottl=1:3-30

# iptables 劫持流量到 NFQUEUE
iptables -t mangle -I FORWARD 1 -p tcp --dport 443 \
  -j NFQUEUE --queue-num 100 --queue-bypass

详细编译部署步骤见《DNS 安全升级 + DPI 对抗实录》

踩坑记录

坑 #1:Go 程序 + 自签名证书 = 翻车

AdGuardHome、早期版本 dnsproxy 等 Go 写的 DNS 客户端不走系统 CA 路径。即使把自签名证书加到 /etc/ssl/certs/ 并运行 update-ca-certificates,Go 的 TLS 栈依然报 bad certificate。解决方案:换 C 写的(smartdns),或使用 -no-check-certificate 选项。

坑 #2:dnsproxy 只接受 POST

浏览器地址栏直接访问 https://IP:8443/dns-query?name=baidu.com 会返回 400 Bad Request。这不代表服务挂了——dnsproxy 的 DoH 端点只接受 application/dns-message 格式的 POST 请求(RFC 8484),浏览器 GET 自然不行。

坑 #3:OpenWrt 的 busybox grep 没有 -P

如果在 OpenWrt 上写脚本解析路由表,不要用 grep -oP(Perl 正则),用 awk 代替。BusyBox 的 grep 不支持 PCRE。

坑 #4:校园网 DHCP 可能推送多个死网关

部分校园网 DHCP 会同时推送一个可用网关和一个不可达的备用网关。Linux 会在两者间负载均衡,导致一半流量丢包。需要写 hotplug 脚本在 WAN 上线后清理 ARP 不可达的网关。用 awk 解析路由,不要用 grep -P。

总结

这套方案的核心优势:

唯一的前提:有一台能跑 dnsproxy 的云服务器。

← 返回专栏
Collaplex · 克拉普莱克斯 collaplex.me · 2026 浙ICP备2026080865号