首页标签分类
015 实践文档:使用 Dnsmasq 和 Nginx 搭建 DNS 解锁服务
2024-04-11 · 更新 2026-03-03约 8 分钟 · 2209 字
开发工具包
000

目录

实践文档:使用 Dnsmasq 和 Nginx 搭建 DNS 解锁服务
1. 背景
2. 基本原理
3. 搭建步骤(服务端 - Debian 12)
3.1 安装必要的软件
3.2 配置 Dnsmasq
3.3 配置 Nginx
3.4 移除 Dnsmasq 的安全限制
3.5 配置防火墙 (UFW)
3.6 重启服务并进行服务端验证
4. 客户端配置与测试 (Debian 12)
4.1 配置客户端 DNS (systemd-resolved)
4.2 全面端到端测试
5. 常见问题 (FAQ)
6. 安全措施
7. 目前存在的问题
7.1 hk节点上自建的hysteria2无法访问dns解锁的服务,其他协议形式可以
附录:处理客户端因服务商默认 DNS 导致的解析冲突
诊断步骤
解决方案
最终验证

实践文档:使用 Dnsmasq 和 Nginx 搭建 DNS 解锁服务

1. 背景

在访问某些有地域限制的网络服务(如流媒体、AI 工具等)时,我们常常会因为 IP 地址不在服务区而受限。传统的代理或 VPN 方案虽然有效,但可能会影响整个网络的连接速度,或配置较为复杂。

本文档旨在提供一种高性能、低资源消耗且配置相对简单的解决方案:通过在有原生 IP 的 VPS 上搭建一个 DNS 解锁服务,实现对特定域名的“解锁”,同时不影响对其他网站的正常访问。

2. 基本原理

本方案的核心是 DNS 劫持SNI 代理 的结合,整个流程对客户端透明。

  1. DNS 劫持 (Dnsmasq)

    • 在 VPS 上部署一个 Dnsmasq 服务器。
    • 当客户端查询一个需要解锁的域名(如 aistudio.google.com)时,Dnsmasq 不返回其真实 IP,而是“欺骗”性地返回我们 VPS 自己的公网 IP。
    • 对于普通域名(如 www.debian.org),Dnsmasq 则正常返回其真实 IP。
  2. SNI 代理 (Nginx)

    • 客户端收到 VPS 的 IP 后,会向该 IP 的 443 端口发起 HTTPS 连接。
    • VPS 上的 Nginx 服务接收到这个连接,通过读取 TLS 握手初期未加密的 SNI (Server Name Indication) 字段,得知客户端真正想要访问的域名。
    • Nginx 不解密流量,而是作为“中间人”,将流量原封不动地透明转发给目标域名的真实服务器。
    • 目标服务器看到的是 VPS 的原生 IP,认为是合法访问,将内容返回给 Nginx,Nginx 再将其传回给客户端。

优点:

  • 性能卓越: Nginx 只做流量转发,不涉及 CPU 密集型的加解密操作。
  • 配置简单: 无需处理复杂的 SSL 证书。
  • 客户端透明: 客户端无需安装任何额外软件,只需修改 DNS 即可。

3. 搭建步骤(服务端 - Debian 12)

假设您的 VPS 公网 IPv4 地址为 a.a.a.a

3.1 安装必要的软件

bash
自动换行:关
放大阅读
展开代码
# 更新软件包列表 sudo apt update # 安装 Dnsmasq 和功能齐全的 Nginx Extras sudo apt install -y dnsmasq nginx-extras

3.2 配置 Dnsmasq

编辑 Dnsmasq 主配置文件:

bash
自动换行:关
放大阅读
展开代码
sudo nano /etc/dnsmasq.conf

将以下内容覆盖到文件中,并将所有 a.a.a.a 替换为您的 VPS 公网 IP

plaintext
自动换行:关
放大阅读
展开代码
# --- 基础配置 --- # 阻止不规范的 DNS 请求 domain-needed bogus-priv # 不读取系统的 resolv.conf,完全手动指定上游 no-resolv # 同时向上游查询,并使用最快返回的结果 all-servers # 手动指定上游 DNS 服务器 server=8.8.8.8 server=1.1.1.1 # 增加 DNS 缓存 cache-size=2048 # 为手动指定的 address 记录设置 60 秒 TTL local-ttl=60 # 明确指定监听的 IP 地址(本机和公网) listen-address=127.0.0.1,a.a.a.a # --- 自定义域名劫持列表 --- # GPT/OpenAI address=/.openai.com/a.a.a.a address=/openai.com/:: address=/.chatgpt.com/a.a.a.a address=/.chatgpt.com/:: # Google AI Studio address=/aistudio.google.com/a.a.a.a address=/aistudio.google.com/:: address=/.ai.google.com/a.a.a.a address=/.ai.google.com/:: # ... 在此添加更多您需要的域名 ...

配置解读:

  • address=/.openai.com/a.a.a.a表示匹配 openai.com域名规则的返回 a.a.a.a的ip;address=/openai.com/::匹配该规则的响应空回复(此处如此设置是因为dns服务器没有ipv6地址);当如此设置之后,一般服务请求时,如果同时存在ipv4,和ipv6,会优先选择ipv6进行响应,此时由于ipv6响应设置为空回复,会自动回退到使用ipv4请求服务;如果dns服务器具有ipv6地址,可以改为如下形式

    bash
    自动换行:关
    放大阅读
    展开代码
    # a.a.a.a为ipv4,b.b.b.b为ipv6 address=/.openai.com/a.a.a.a address=/openai.com/b.b.b.b

3.3 配置 Nginx

编辑 Nginx 主配置文件:

bash
自动换行:关
放大阅读
展开代码
sudo nano /etc/nginx/nginx.conf

将以下内容覆盖到文件中,并将所有 a.a.a.a 替换为您的 VPS 公网 IP

nginx
自动换行:关
放大阅读
展开代码
# 加载动态 stream 模块,必须放在文件最顶部 load_module modules/ngx_stream_module.so; user www-data; worker_processes auto; pid /run/nginx.pid; error_log /var/log/nginx/error.log info; events { worker_connections 1024; } stream { # 定义日志格式,方便调试 log_format main '[$time_local] $remote_addr to $server_addr' ' proto: $protocol status: $status' ' upstream: $upstream_addr'; access_log /var/log/nginx/stream-access.log main; # 核心的 SNI -> 后端映射规则 map $ssl_preread_server_name $filtered_sni_name { # GPT/OpenAI ~^(.*|)openai\.com$ $ssl_preread_server_name; ~^(.*|)chatgpt\.com$ $ssl_preread_server_name; # Google AI Studio ~^aistudio\.google\.com$ $ssl_preread_server_name; ~^(.*|)ai\.google\.com$ $ssl_preread_server_name; # ... 在此添加与 Dnsmasq 列表对应的域名 ... # 默认值:防止服务器被用作开放代理 default "127.255.255.255"; } # 监听和转发服务器 server { # 指定 DNS 解析器,仅做 IPv4 解析 resolver 8.8.8.8 1.1.1.1 ipv6=off; # 监听所有 IPv4 地址的 443 端口 listen 443; # 开启 SNI 预读 ssl_preread on; # 将流量转发到 map 计算出的后端地址 proxy_pass $filtered_sni_name:443; # 强制对外连接时使用此源 IP 地址 proxy_bind a.a.a.a; } }

3.4 移除 Dnsmasq 的安全限制

Debian 12 默认限制 Dnsmasq 只为局域网服务,我们需要移除此限制。

  1. 创建 systemd 覆盖文件:
    bash
    自动换行:关
    放大阅读
    展开代码
    sudo systemctl edit dnsmasq.service
  2. 在打开的文件中,粘贴以下内容:
    ini
    自动换行:关
    放大阅读
    展开代码
    [Service] ExecStart= ExecStart=/usr/sbin/dnsmasq -x /run/dnsmasq/dnsmasq.pid -u dnsmasq -7 /etc/dnsmasq.d,.dpkg-dist,.dpkg-old,.dpkg-new --no-resolv --conf-file=/etc/dnsmasq.conf
  3. 保存并退出。

3.5 配置防火墙 (UFW)

假设您允许访问的客户端 IP 为 b.b.b.b

bash
自动换行:关
放大阅读
展开代码
# 设置默认策略 sudo ufw default deny incoming sudo ufw default allow outgoing # 允许您自己的 IP 访问 SSH, DNS, Nginx sudo ufw allow from b.b.b.b to any port 22 proto tcp sudo ufw allow from b.b.b.b to any port 53 sudo ufw allow from b.b.b.b to any port 443 proto tcp # 允许本机测试 sudo ufw allow from 127.0.0.1 to any port 53 sudo ufw allow from 127.0.0.1 to any port 443 proto tcp # 启用防火墙 sudo ufw enable

3.6 重启服务并进行服务端验证

  1. 重启服务:
    bash
    自动换行:关
    放大阅读
    展开代码
    sudo systemctl daemon-reload sudo systemctl restart dnsmasq sudo nginx -t && sudo systemctl restart nginx
  2. 验证 Dnsmasq:
    bash
    自动换行:关
    放大阅读
    展开代码
    # 应返回 VPS 的 IP a.a.a.a dig @127.0.0.1 aistudio.google.com # 应返回 debian.org 的真实 IP dig @127.0.0.1 www.debian.org
  3. 验证 Nginx:
    bash
    自动换行:关
    放大阅读
    展开代码
    # 应成功完成 TLS 握手并返回网页内容 curl --resolve 'aistudio.google.com:443:127.0.0.1' https://aistudio.google.com -v

4. 客户端配置与测试 (Debian 12)

4.1 配置客户端 DNS (systemd-resolved)

我们将配置 systemd-resolved 来使用我们的 VPS DNS,并实现自动故障回退和与 NetBird 等工具的兼容。

  1. 编辑主配置文件
    bash
    自动换行:关
    放大阅读
    展开代码
    sudo nano /etc/systemd/resolved.conf
    [Resolve] 部分添加或修改,a.a.a.a 替换为您的 VPS 公网 IP
    ini
    自动换行:关
    放大阅读
    展开代码
    [Resolve] DNS=a.a.a.a 8.8.8.8 1.1.1.1
  2. systemd-resolved 接管系统 DNS
    bash
    自动换行:关
    放大阅读
    展开代码
    sudo ln -sf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf
  3. 重启服务:
    bash
    自动换行:关
    放大阅读
    展开代码
    sudo systemctl restart systemd-resolved

4.2 全面端到端测试

在客户端上执行以下测试:

  1. 验证解锁: dig aistudio.google.com (应返回 a.a.a.a)
  2. 验证常规访问: dig www.debian.org (应返回其真实 IP)
  3. (如适用) 验证与 VPN/Mesh 兼容性: 让 VPN/Mesh 客户端(如 NetBird)自动管理其接口的 DNS,然后 dig 一个内部域名,应能正常解析。
  4. 验证故障回退:
    • VPS 上执行 sudo systemctl stop dnsmasq
    • 客户端上执行 sudo resolvectl flush-caches
    • dig aistudio.google.com 应该能够正常解析,但是解析地址变为真正的公网地址,而不是a.a.a.a(dns解锁服务器的ip)。
    • dig www.debian.org 应该依然能够成功解析。
    • 测试完毕后,务必在 VPS 上执行 sudo systemctl start dnsmasq 恢复服务。
  5. 恢复主 DNS:
    • 当 VPS 上的 Dnsmasq 恢复后,如果客户端没有立即切回,可执行 sudo systemctl restart systemd-resolved 来强制重置状态。

5. 常见问题 (FAQ)

  • Q1: 在 VPS 上测试 dig @127.0.0.1 ... 返回 REFUSED

    • A1: 这是因为 Debian 12 的 Dnsmasq 默认开启了 --local-service 限制。请参考 3.4 节 的步骤,通过创建 systemd 覆盖文件来移除此限制。
  • Q2: 在 VPS 上测试 curl --resolve ... 返回 Connection refused

    • A2: 这是因为 Nginx 没有在 127.0.0.1:443 上监听。请参考 3.3 节,将 listen 指令修改为 listen 443;listen 0.0.0.0:443;,使其监听所有 IPv4 地址。
  • Q3: 客户端 DNS 配置重启后被覆盖?

    • A3: 不要直接编辑 /etc/resolv.conf。请严格按照 4.1 节 的步骤,通过配置 systemd-resolved 并创建符号链接来实现持久化。
  • Q4: VPS 上的 Dnsmasq 恢复后,客户端没有立即切回使用它?

    • A4: 这是 systemd-resolved 的正常行为,它会暂时“记住”失效的服务器。要立即恢复,请在客户端上执行 sudo systemctl restart systemd-resolved 来重置其内部状态。

6. 安全措施

  • 部署完毕后应该对特定的ip范围限制使用,放置服务的滥用

    bash
    自动换行:关
    放大阅读
    展开代码
    # 此处使用防火墙限制,b.b.b.b为你允许访问服务的ip sudo ufw allow from b.b.b.b to any port 53 sudo ufw allow from b.b.b.b to any port 443 proto tcp

7. 目前存在的问题

7.1 hk节点上自建的hysteria2无法访问dns解锁的服务,其他协议形式可以

目前未解决,原因不详

附录:处理客户端因服务商默认 DNS 导致的解析冲突

在客户端上配置完 systemd-resolved 后,有时会发现 DNS 解析结果不稳定,在我们的 VPS DNS 地址和另一个公共 DNS 地址(如 1.1.1.1)之间随机跳变。这是因为客户端的网络接口(如 eth0)从更高优先级的配置源(如云服务商的 DHCP、netplanNetworkManager)获取了 DNS 设置,与我们设置的全局 DNS 发生了冲突。

以下是如何诊断并从根源上解决此问题的步骤。

诊断步骤

在客户端上运行以下命令,查看详细的 DNS 状态:

bash
自动换行:关
放大阅读
展开代码
resolvectl status

如果看到类似下面的输出,即在 Global 配置之外,Link 2 (eth0) (或您的主网卡) 下也出现了 DNS Servers,那么就证明存在配置冲突:

plaintext
自动换行:关
放大阅读
展开代码
Global DNS Servers a.a.a.a ... <-- 这是我们期望的全局配置 Link 2 (eth0) Current Scopes: DNS ... Protocols: +DefaultRoute ... DNS Servers: 1.1.1.1 ... <-- 这是不期望的、冲突的配置源 DNS Domain: ...

解决方案

解决方案的核心是阻止网络管理工具为网卡设置 DNS,让 systemd-resolved 的全局配置成为唯一来源。您需要根据客户端使用的网络管理工具选择对应的方案。

1. 首先,判断客户端使用的网络管理工具

  • 运行 sudo systemctl status NetworkManager。如果服务 active (running)
  • 如果 NetworkManager 不存在,运行 sudo systemctl status systemd-networkd。如果服务 active (running),请检查 systemd-networkd 的日志 sudo journalctl -u systemd-networkd,如果日志中提到 netplan,即为使用netplan进行网络管理。

2.客户端使用 Netplan (常见于云服务器或 Ubuntu 移植环境)

Netplansystemd-networkd 的一个上层管理工具。我们需要修改 Netplan 的源配置文件。

  1. 找到 .yaml 配置文件: 配置文件位于 /etc/netplan/,例如 50-cloud-init.yaml

  2. 检查文件内容

    • 如果是 DHCP 配置,添加 dhcp4-overrides: {use-dns: false}
    • 如果是静态 IP 配置 (如下面的示例),则需要禁用 cloud-init 的网络管理功能,并创建我们自己的永久性 Netplan 配置。

    示例:处理由 cloud-init 生成的静态配置

    a. 禁用 cloud-init 的网络管理:

    bash
    自动换行:关
    放大阅读
    展开代码
    # 创建一个新文件 sudo nano /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg

    粘贴 network: {config: disabled} 并保存。

    b. 创建自己的 Netplan 配置文件:

    bash
    自动换行:关
    放大阅读
    展开代码
    # (可选) sudo rm /etc/netplan/50-cloud-init.yaml sudo nano /etc/netplan/01-custom-network.yaml

    c. 粘贴修改后的配置:复制原始 .yaml 文件的内容,但彻底删除 nameservers 整个部分

    yaml
    自动换行:关
    放大阅读
    展开代码
    network: version: 2 ethernets: eth0: addresses: - ipv4地址 - ipv6地址 match: macaddress: mac地址 # nameservers: 部分已被完全删除 routes: - to: default via: ipv4网关 - on-link: true to: default via: ipv6网关 set-name: eth0

    d. 应用 Netplan 配置:

    bash
    自动换行:关
    放大阅读
    展开代码
    sudo netplan apply

最终验证

在执行完适合您系统的方案后,重启 systemd-resolved (sudo systemctl restart systemd-resolved),然后再次运行 resolvectl status。此时,您应该会看到主网卡下的 DNS Servers 部分已经消失,冲突彻底解决。

bash
自动换行:关
放大阅读
展开代码
hedeoer@FzZ0HW0YJikn:~$ resolvectl status Global Protocols: +LLMNR +mDNS -DNSOverTLS DNSSEC=no/unsupported resolv.conf mode: stub Current DNS Server: a.a.a.a DNS Servers a.a.a.a 1.1.1.1 2606:4700:4700::1111 Link 2 (eth0) Current Scopes: LLMNR/IPv4 LLMNR/IPv6 Protocols: -DefaultRoute +LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported

resolvectl status 结果解释:

  • 优先使用: 它会始终优先使用列表中的第一个服务器 (a.a.a.a)。
  • 故障检测: 当它发现第一个服务器无响应(超时)或返回错误时,它会在内部将其标记为“降级 (degraded)”。
  • 自动切换: 一旦主服务器被降级,systemd-resolved 会自动地、无缝地切换到列表中的下一个服务器(例如 1.1.1.1)来处理后续的 DNS 请求。
  • 自动恢复: 它会定期(通常是几分钟后)“试探”一下那个发生过故障的主服务器。一旦发现它恢复正常,就会自动地将它重新设为首选,切回到使用 a.a.a.a;但是经过测试在debian12系统上该功能没有检验通过,实际情况是只有后续设置的dns都失效了,才能正常使用第一个设置的dns,目前应对这种情况是多部署几台所需的dns服务器,提高容错。

本文作者:hedeoer

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!