Skip to content

Repository files navigation

泠澈 LingChe

双上游混合本地 DNS 代理,基于 DoH 基准自动甄别域名污染,兼顾明文速度与加密防劫持能力。

泠澈在本机 53 端口提供 DNS 服务,同时向明文 UDP 上游加密 DoH 上游发起查询,通过比对两侧结果自动识别被污染的域名,并把这些域名路由到加密链路。整个过程无需手工维护规则列表。

产物为单个 exe,除 Windows 系统组件外零依赖——不需要附带任何 DLL 或 CA 证书包。


免责声明 / Disclaimer

作为中国公民,为遵守中国相关网络安全法律法规、规避发布可执行二进制软件带来的潜在法律风险,本项目仅发布完整源代码,不提供任何预编译的可执行文件

本项目仅用于技术研究及本地 DNS 优化,包括多节点 DNS 测速与抵御运营商 DNS 劫持。

程序的编译、修改与运行均由使用者自行完成。因不当使用本程序所产生的一切法律责任,均由最终使用者自行承担,作者不承担任何连带责任。

As a Chinese citizen, to comply with relevant cybersecurity laws of China and avoid potential legal risks related to distributing executable binary software, I only release the complete source code of this project, and will not provide any precompiled executable files. This project is developed solely for technical research and local DNS optimization, including multi-node DNS speed test and defense against carrier DNS hijacking. Any compilation, modification and operation of the program shall be completed by the user independently. All legal liabilities arising from improper use of the program shall be borne solely by the end user, and the author shall not bear any joint liability.


特性

能力 说明
双链路污染检测 首次访问某域名时并发探测全部上游,以 DoH 结果的并集为基准;明文结果与之无交集才判定污染,避免 CDN 多出口被误杀
污染标记可逆 空闲期慢速复验,域名恢复干净后自动解封,不会一次误判永久锁死
零第三方依赖 DoH 基于 Windows 自带 WinHTTP,TLS 校验交给系统证书存储。产物仅依赖 KERNEL32 / WS2_32 / WINHTTP / msvcrt,无需附带任何 DLL 或 CA 证书包
IPv4 / IPv6 双栈 同时监听 127.0.0.1:53[::1]:53;上游支持 IPv4、IPv6 及自定义端口;A / AAAA 记录均参与污染判定
上游自动测速 每 60 秒后台测量各上游延迟,干净域名优先走最快的可信明文节点
DoH 端点自指短路 DoH 端点域名由启动时的多上游共识投票 + 内置权威 IP 保底解析,杜绝"解析 DoH 域名又触发 DoH"的死锁
全量日志 每一次请求的收包、路由决策、上游来源、缓存命中、污染判定都同时打印到控制台与日志文件
静态 IP 覆盖 通过 ip_override.txt 把任意域名强制指向你指定的 IP 列表,完全绕过上游解析——当 DNS 解出的 IP 访问不了、被污染或你想钉死到某个节点时使用(Settings → IP Override Management 增删查)
后台 IP 竞速回退 对任意域名解析出的多个候选 IP(来自覆盖配置或上游应答),TCP 探测其可达性与延迟,应答中按「最快且可达」排序,出问题自动把健康 IP 排到首位("出问题了就先用哪个")
设置系统(省网/传统双模式) 通过 settings.ini(主菜单 2) Settings)切换运行模式:record 随访问记录最快节点(仅在域名被查询时才测速,省网,默认);race 传统后台竞速(周期性探测所有已知域名)。可选持久化每个域名的最佳 IP,下次访问前直接调用,应答更快
RST 防护(抗运营商伪造 RST) 内置 WinDivert 抓包层(主菜单 3) RST Guard),识别并丢弃运营商/中间盒伪造的 TCP RST。三层判定:① 时延粗筛(握手后采集基准 RTT,延迟低于 rtt_base×0.25 的 RST 判为本地伪造直接丢弃);② SEQ/ACK 合法性(RST-ACK 的 ACK 必须等于我方下一个发送序号,纯 RST 的 SEQ 必须落在对端合法接收窗口内);③ 流量行为取证(疑似 RST 不立即断连,缓冲等待;若在等待期内收到真实对端数据如 TLS ServerHello 则坐证实伪丢弃,超时无响应才按真实 RST 断连)。可设 rst_autostart 在代理启动时自动拉起
ECH / 加密 SNI 透传 DNS 代理自动把 HTTPS(65) / SVCB(64) 查询经最快 DoH 上游取回并原样回传,使支持 ECH 的浏览器能取得域名 ECH 公钥、加密 TLS 握手中的 SNI,对抗 SNI 级封锁(自动生效,无需菜单操作)。ECH 仅隐藏域名、无法隐藏目的 IP,对按目的 IP 丢弃的封锁无效

关于依赖:DNS 代理本体仍保持零第三方依赖——仅依赖 Windows 系统组件 KERNEL32 / WS2_32 / WINHTTP / msvcrt,无需附带任何 DLL 或证书包。RST 防护模块使用 WinDivert(经 WFP 的用户态抓包库,自带签名驱动 WinDivert64.sys + WinDivert.dll,随 build.bat 复制至 exe 同目录),仅在该功能启用时加载,二者解耦。

快速开始

1. 编译

需要任意支持 C++17 的 MinGW-w64 / TDM-GCC 工具链。

build.bat

或手动执行:

g++ -std=c++17 -O2 -static -Ithirdparty/WinDivert/include -o LingChe.exe src\lingche.cpp src\rstguard.cpp -Lthirdparty/WinDivert -lWinDivert -lwinhttp -lws2_32

-static 用于静态链接 libgcc / libstdc++,确保产物不依赖 MinGW 运行时 DLL。-lWinDivert 仅在使用 RST 防护功能时需要;若只想编译纯 DNS 代理,可去掉 src\rstguard.cpp-I/-L thirdparty/WinDivert-lWinDivert,回到上面的精简命令。

2. 运行

绑定 53 端口需要管理员权限:

LingChe.exe

RST 防护需要管理员权限:WinDivert 在 WinDivertOpen 时会自动安装/加载内核驱动 WinDivert64.sys,非管理员会返回 GetLastError=5,此时 RST Guard 启动失败但程序其余功能不受影响。建议直接以管理员身份运行 exe;build.bat 会把 WinDivert.dll / WinDivert32.sys / WinDivert64.sys 一并复制至 exe 同目录,缺任一文件都会导致 WinDivert 加载失败。

启动后进入菜单(主菜单精简,管理与诊断全部收进 Settings):

========== LingChe ==========
1) Start / Stop proxy
2) Settings
3) RST Guard (anti-spoofed RST, ON/OFF)
0) Exit

选择 1 启动服务。代理运行后,DNS 管理、IP 覆盖管理、运行模式开关都在 2) Settings 子菜单中:

===== Settings =====
1) DNS Server Management   (增/删/查 上游、查记录域名数)
2) IP Override Management  (增/删/查 静态 IP 覆盖)
3) Toggle mode (record <-> race)
4) Set race interval (seconds, >0)
5) Toggle persist best IP (0/1)
6) Toggle RST guard auto-start on proxy launch (0/1)
7) Back

**设置(2) Settings)**可在启动代理前调整运行模式与开关,修改即时落盘:

  • Toggle moderecord(随访问记录最快节点,省网,默认)↔ race(传统后台竞速)。
  • Set race interval:传统模式下后台竞速的轮次间隔(秒)。
  • Toggle persist best IP:是否持久化每个域名的最佳 IP(更快但更频繁写盘,不建议常开)。
  • Toggle RST guard auto-start:是否在代理启动时自动拉起 RST 防护(rst_autostart,需管理员权限才生效)。

RST 防护(3) RST Guard:在代理运行期随时启停,独立于 DNS 代理本体。首次开启会尝试加载 WinDivert 驱动;若失败(多为权限不足)会提示「RST Guard failed to start」而不影响 DNS 服务。详见下文「RST 防护」章节。

ECH / 加密 SNI 透传(无需菜单操作,自动生效):本程序作为 DNS 代理,自动把 HTTPS(65) / SVCB(64) 类型的查询经最快 DoH 上游取回并原样回给支持 ECH 的客户端。支持 ECH 的浏览器据此把 TLS 握手中的 SNI 加密,可对抗 SNI 级封锁。注意:ECH 只隐藏域名(SNI),无法隐藏目的 IP——对于按目的 IP 直接丢弃的「IP 级」封锁(如 Test-NetConnection github.com -Port 443 直连目的 IP 失败),ECH 无效,必须改用藏 IP 的合规隧道/专线。

3. 让系统使用它

把网络适配器的 DNS 服务器改为 127.0.0.1(IPv6 为 ::1)。

重要:Chrome / Edge / Firefox 默认开启的「安全 DNS / Secure DNS」会直连浏览器内置的 DoH 服务器,绕过系统 DNS 也就绕过本程序。请在浏览器设置 → 隐私与安全 中将其关闭,否则你不会看到任何浏览器产生的查询。


配置

上游列表 dns_servers.txt

首次运行时自动生成,格式为纯文本,每行一条,可直接手工编辑:

UDP 223.5.5.5
UDP 114.114.114.114
UDP 2400:3200::1
UDP 223.5.5.5:5353
DOH https://1.1.1.1/dns-query
DOH https://dns.alidns.com/dns-query
  • UDP 明文上游,支持 IPIP:端口IPv6[IPv6]:端口 四种写法,端口缺省 53
  • DOH 加密上游,标准 RFC 8484 端点

通过菜单增删会立即落盘,无需退出程序。

静态 IP 覆盖 ip_override.txt

当某个域名「DNS 解出来的 IP 访问不了 / 被污染 / 想钉死到固定节点」时,可手工把它指向你信任的 IP。该表优先级高于一切上游解析——命中后程序直接用配置的 IP 合成应答,完全不查询任何上游

首次运行不会自动生成该文件;可直接复制 ip_override.example.txtip_override.txt,或在 Settings → IP Override Management 交互添加(立即落盘)。

# 格式:域名  IP1 [IP2 IP3 ...]   (IPv4 / IPv6 均可,空格分隔;# 起首为注释)
github.com 20.205.243.166 20.205.243.167
example.com 93.184.216.34 2606:2800:220:1:248:1893:25c8:1946
  • 仅对 A / AAAA 查询生效;MX / TXT / CNAME 等类型仍走常规链路。
  • 配置的 IP 字面量以外的内容会被加载时丢弃。
  • 同一域名的多个 IP 会进入后台竞速(见下),最终应答按竞速结果排序。

后台 IP 竞速(Race)

对任意域名「解析/配置出的多个候选 IP」,程序起一个独立后台线程(默认启动 30 秒后开始,每 120 秒一轮),逐个对候选 IP 做 TCP 连接探测(依次尝试 443、80,任一能建连即记录延迟;全失败标记不可达)。

探测结果用于两处决策:

  1. 应答排序:合成/转发 A / AAAA 应答时,把「可达且延迟最低」的 IP 排在首位(不可达的排末尾但保留,保证完整性)。操作系统 stub resolver 与浏览器通常优先使用首条记录,从而自动避开无响应的 IP——即「出问题了就先用哪个」。
  2. 竞速刷新:缓存按「域名 + 类型」存储原始应答,每次发送前都按最新竞速结果重排,因此网络状况变化后无需等缓存过期即可切换到健康 IP。

竞速仅在「纯地址应答」(应答段只含 A / AAAA,无 CNAME 链)时重排,避免破坏带 CNAME 的复杂应答。

设置 settings.ini

运行行为由 settings.ini 控制,首次运行自动生成默认值,也可复制 settings.example.ini 后手工编辑,或在主菜单 2) Settings 中交互修改(即时落盘)。

程序提供两种测速模式,应对不同网络习惯:

模式 行为 网络开销 适用
record(默认) 随访问记录最快节点:域名被实际查询后,后台线程才探测其候选 IP 并记录延迟/存活,应答按结果排序。网络环境不会瞬变,因此无需时刻探测所有域名 低(仅测被访问的域名) 大多数场景,省网
race 传统后台竞速:独立线程周期性探测所有已知域名的候选 IP(间隔见 race_interval 高(持续探测全部域名) 需要始终掌握全部节点状态

此外还有一个「更快但不建议常开」的开关:

  • persist_best_ip = 1:把每个域名测得的最佳 IP 持久化到 best_ip.ini。开启后,下一次访问在被测速之前直接调用已记录的最佳 IP(应答更快,无需等待探测);该值在每次测速时更新。代价:会额外写盘,且若网络已变,可能短暂用到旧的 IP——因此标注为「不建议常开」。设为 0 则不写盘,始终以当次测速结果为准。

无论哪种模式,缓存始终保存原始应答,发送前按最新测速结果重排,因此网络变化后无需等缓存过期即可切换到健康 IP。

可调参数

位于 src/lingche.cpp 顶部:

常量 默认值 含义
WORKER_THREADS 4 查询处理线程数
PROBE_WORKERS 3 污染探测线程数
QUERY_TIMEOUT_MS 1500 明文 UDP 查询超时
DOH_TIMEOUT_MS 8000 DoH 请求超时(含 TLS 握手)
CACHE_TTL_SEC 300 本地应答缓存有效期
SPEED_REFRESH_INTERVAL_SEC 60 上游测速间隔
IDLE_SWEEP_INTERVAL_SEC 600 空闲复验轮次间隔
PROBE_QUEUE_CAP 64 探测队列上限,超出丢弃以防积压
IP_RACE_START_SEC 30 启动后多久开始首轮 IP 竞速
IP_RACE_INTERVAL_SEC 120 IP 竞速轮次间隔
IP_RACE_PROBE_TIMEOUT_MS 1500 单 IP 连接探测超时
IP_RACE_PROBE_PORTS {443, 80} 探测端口优先级(443 优先,80 兜底)
ON_ACCESS_COOLDOWN_SEC 30 随访问测速每域名冷却,避免热门域名被反复探测浪费网络
g_mode / race_interval / persist_best_ip record / 120 / 0 运行模式、竞速间隔、最佳 IP 持久化,均可在 settings.ini 或主菜单 2) Settings 调整

工作原理

                 ┌──────────────┐
   客户端查询 ──▶ │  127.0.0.1:53 │
                 │   [::1]:53    │
                 └───────┬───────┘
                         │
              ┌──────────▼──────────┐
              │  是 DoH 端点域名?   │──是──▶ 预解析表直接合成应答(打破自指)
              └──────────┬──────────┘
                         否
              ┌──────────▼──────────┐
              │  查本地缓存(域名+类型)│──命中──▶ 改写事务 ID 后回包
              └──────────┬──────────┘
                       未命中
              ┌──────────▼──────────┐
              │  该域名已标记污染?   │
              └───┬──────────────┬──┘
                 是             否
                  │              │
            仅走 DoH        走最快可信明文上游
                  │              │
                  └──────┬───────┘
                         ▼
                  回包 + 写入缓存
                         │
                  首次访问则入队 ──▶ 后台探测线程比对全部上游 ──▶ 记录判定

污染判定规则:以所有 DoH 应答的 A/AAAA 并集为基准。某明文上游的结果与基准存在交集即视为干净;只有当确有明文上游应答、且无一与基准相交时才判定污染。明文上游全部超时不会被误判为污染。


RST 防护(抗运营商伪造 RST)

部分网络环境下,运营商 / 中间盒会对命中敏感特征的连接主动注入伪造的 TCP RST(SEQ/ACK 伪造得看似合法),强行掐断连接——典型表现为「刚连上就被断」「大文件下到一半断开」「某些站点怎么都打不开」。由于 DNS 代理本身不在 TCP 数据通路上,LingChe 通过 WinDivert(WFP 用户态抓包)旁路捕获本机收发的所有 TCP 报文,在 RST 真正生效前将其识别并丢弃。

WinDivert 在 WinDivertOpen 时自动安装已签名的内核驱动 WinDivert64.sys需要管理员权限;过滤表达式为 ip and tcp,覆盖本机所有进出 TCP 流(含 LingChe 自己的 DoH 上游连接)。

三层判定(已实现)

RST 防护按「宁可漏放、不可误杀」的保守原则串联三层,只有全部命中才会被当作可疑 RST 缓冲取证,最终由流量行为定夺:

  1. 时延粗筛(Layer 1 · 第一道筛选) TCP 握手完成后立即采集一次基准 RTT(rtt_base_ms = 收到 SYN-ACK 的时间 − 发出 SYN 的时间)。之后任意入向 RST,若其单程时延 delta 小于 rtt_base × 0.25,说明它根本没经过真实网络往返、是本地中间盒就地生成的 → 直接丢弃这是最便宜也最容易触发的一道,但只当作第一道粗筛:短连接(如单个 HTTP 请求一问一答)下合法 RST 也可能很「快」,因此绝不能单凭它就断连,必须叠加后续硬规则。

  2. SEQ/ACK 合法性(Layer 2 · 协议硬规则) DPI / 中间盒伪造 RST 时,往往只能猜 SEQ 或只能填一个窗口内的合法值,很难精确对齐我方真实状态:

    • RST-ACK:其 ACK 必须等于我方下一个待发送序号 our_snd_nxt,否则丢弃;
    • 纯 RST(无 ACK):其 SEQ 必须落在对端合法接收窗口 [rcv_nxt, rcv_nxt + rcv_win] 内(含 WINDOW_SLACK=32 容差),否则丢弃。 这是 DPI 难以凭空伪造的硬约束,作为第二道强校验。
  3. 流量行为取证(Layer 3 · 最终定夺) 当一层、二层都判为「可疑」时,不立即断连:把该 RST 缓冲 LAYER3_WAIT_MS = 1500ms,同时放行后续报文。等待期内——

    • 若收到真实对端数据(如 TLS ServerHello、HTTP 响应首字节)→ 坐证实伪,丢弃该 RST、连接继续;
    • 超时仍无对端响应 → 判定为真实 RST,放行断连。 用「真实数据是否还在来」作为终极证据,从根本上区分「连接其实还好、只是被塞了假 RST」与「连接真的死了」。

第四层(前端釜底抽薪,未实现)

最根本的解法是不让 DPI 有机会识别并发包:把 ClientHello 做 TCP 分段(首字节单独成段、或拆成小段并控制发送节奏),使 DPI 在重组窗口内看不到完整特征,从而不注入 RST。这要求 DoH 从当前的 WinHTTP 迁到自己管理的 socket(WinHTTP 不允许控制分段),属于一次较大的链路改造——当前版本尚未实现,列为后续真实迭代项(非占位 TODO)。

可调参数

位于 src/rstguard.cpp 顶部:

常量 默认值 含义
LAYER3_WAIT_MS 1500 疑似 RST 缓冲取证等待期(毫秒)
FLOW_IDLE_MS 60000 流表项无活动后的回收间隔(毫秒)
WINDOW_SLACK 32 纯 RST 接收窗口判定容差(字节)
时延粗筛阈值 rtt_base × 0.25 Layer 1 触发丢弃的上限系数(0.2~0.3 可调)

已知局限

  • 短连接可能漏放:Layer 1 在连接往返极短(RTT 本身 < 数毫秒的本地/内网连接)时区分度下降,此时主要依赖 Layer 2/3。
  • 误杀风险极低但非零:Layer 3 以「等待期内有无真实数据」为证据,若对端恰好在窗口内无数据且连接本就半死,可能把真实 RST 误判为伪造而多撑 1.5s;属可接受权衡。
  • 需管理员权限:WinDivert 驱动加载失败则整功能不可用(程序其余部分不受影响)。
  • 仅 IPv4 严密处理:RST 防护对非 IPv4 报文(IPv6 / 回环)采取直接转发、不判定,避免误伤。

生成的文件

程序在工作目录下读写以下文件(均已在 .gitignore 中排除):

文件 内容
dns_servers.txt 上游列表配置
visited_domains.txt 已访问域名记录
domain_status.txt 各域名的污染判定与最优明文节点
polluted_domain.txt 被判定为污染、强制走加密的域名
speed_record.ini 上游延迟测速记录
ip_override.txt 静态 IP 覆盖配置(用户手工维护,不在仓库内)
settings.ini 运行模式 / 竞速间隔 / 持久化开关(首次自动生成)
best_ip.ini 持久化的最佳 IP(persist_best_ip=1 时写入,不在仓库内)
lingche.log 完整运行日志

⚠️ 隐私提醒visited_domains.txtdomain_status.txtpolluted_domain.txtlingche.log 会记录你实际访问过的域名。提交代码或分享目录前请确认这些文件未被携带。


已知限制

  • 仅支持 Windows。DoH 依赖 WinHTTP、监听依赖 Winsock2,跨平台需要替换这两层。
  • DoH 无法指定连接 IP。WinHTTP 不提供 libcurl CURLOPT_RESOLVE 那样的地址覆盖能力,因此 DoH 端点域名改由预解析表短路处理。这带来一个副作用:若某 DoH 端点的真实 IP 发生变更,需等待下一轮测速周期(默认 60 秒)刷新。
  • UDP 报文上限 512 字节,未实现 EDNS0 与 TCP 回退,超大应答会被截断。
  • 污染判定只覆盖 A / AAAA 记录,不检测 CNAME 链路劫持。
  • RST 防护需管理员权限且捆绑 WinDivert:该功能依赖 WinDivert 内核驱动(随 build.bat 复制的 WinDivert64.sys / WinDivert.dll),非管理员无法加载;DNS 代理本体不依赖它。第四层 ClientHello 分段(前端规避)尚未实现,详见「RST 防护」章节。
  • ECH 仅抗 SNI 级封锁:ECH 透传隐藏的是 TLS 握手中的域名(SNI),无法隐藏目的 IP。对于按目的 IP 直接选择性丢弃的封锁(如 Test-NetConnection <host> -Port 443 直连目的 IP 失败),ECH 无效,必须改用藏 IP 的合规隧道/专线。ECH 还需站点发布 ECH 配置且客户端浏览器支持,现实覆盖率有限。

许可证

MIT

本项目仅以源代码形式发布,不含任何预编译可执行文件。使用条款与责任边界详见 免责声明

About

泠澈 - 双上游混合本地 DNS 代理,基于 DoH 基准自动甄别域名污染,兼顾明文速度与加密防劫持能力。

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages