历史背景与演化
- 回溯源头: 早期互联网中, DNS(域名系统)就是一个公共电话薄. 记录
google.com -> 142.250.x.x, 你拿到ip, 直接连过去 - 前身与痛点: 随着网络环境的复杂化(GFW的出现), 直接查询变成了灾难
- DNS污染: 你问运营商的DNS google.com 在呢, 它故意给你一个不存在的ip, 连接直接失败
- CDN解析错误(绕远路): 为了防污染, 你强行把所有dns查询都发给国外的代理服务器去做. 结果你访问国内的淘宝, 国外的dns给你返回了一个淘宝在美国的服务器ip, 导致你的速度奇慢无比
- 演变
- 早期的代理软件 (如 shadowsocks) 只是简单的无脑圈代理.
- 现代代理软件(如Surge, Clash, mihomo) 为了实现”国内直连, 国外代理”的分流, 进化出一套极其复杂的DNS系统
核心概念
在代理环境中, 解析域名是一个多重嵌套的 “先有鸡还是先有蛋” 的依赖问题
dns server
-
enable / listen / ipv6
- enable: 决定是否启用 mihomo 自己的 DNS模块, 不启用就退回系统dns
- listen: DNS服务监听地址, 支持 UDP/TCP
- ipv6: 控制是否解析 AAAA
-
enhanced-mode:
-
hosts / use-hosts / use-system-hosts
- dns 查询未必一上来就去外部 DNS 服务器
- 本地 hosts 可能先拦截并直接回答
- 在调试、内网、开发等场景很有用
-
default-nameserver (引导 DNS)
- 痛点: 假设你使用加密的 https://dns.google/dns-query 来查 DNS. 但是问题来了, 你要连这个加密的 dns, 首先要知道
dns.google的 IP是多少 - 机制: default-nameserver 就是打破死循环的. 它只能是纯数字的ip. 它唯一的任务是, 解析配置文件里出现的域名(代理节点服务器的域名、加密DNS的域名)
- 痛点: 假设你使用加密的 https://dns.google/dns-query 来查 DNS. 但是问题来了, 你要连这个加密的 dns, 首先要知道
-
nameserver(主DNS) + fallback (回退DNS)
nameserver是默认的域名解析服务器(通常填国内dns)。fallback是备用解析服务器(通常填国外加密dns, 确保可靠)- 当收到一个用户查询时,mihomo 会同时向 nameserver、fallback 发起询问
- 根据 nameserver 的结果判断是否走 fallback, 有如下四类判断条件:
- geoip / geocode: 如果结果ip不属于中国(默认cn), 认为污染, 走 fallback
- ipcidr: 命中这些网段, 直接走 fallback
- domain: 命中这些域名, 直接走 fallback
-
nameserver-policy (定向解析)
- 优先级最高, 为特定域名查询指定解析服务器
- 有些特殊域名(如内网域名, 或特定的应用) 必须用特定的DNS解析
-
proxy-server-nameserver / proxy-server-nameserver-policy (代理服务器本身的寻路)
- 只用于解析代理节点自身的域名
- 例如你的节点地址本身是域名, 且这个域名必须通过特定DNS才能解析稳定, 那么你应该单独配置本项, 用以区分 “普通网站域名解析”
-
direct-nameserver / direct-nameserver-follow-policy (直连出口DNS)
- 当某条流量最终被判定为 DIRECT 时, 你可能不想继续用”代理路径下那套解析结果”, 而是希望按直连出口的视角重新解析一次
- direct-nameserver-follow-policy 则是决定在使用 direct-nameserver 时, 是否遵循 nameserver-policy, 默认不遵循
-
respect-rules
- 定义: DNS 连接本身也遵守路由规则
- 限制:
- 使用它需配置
proxy-server-nameserver - 强烈不建议与 perfer-h3 一起使用
- 使用它需配置
| 概念 | 解释 | |
|---|---|---|
| 1. | default-nameserver | 解析”DNS服务器自己的域名” |
nameserver | 解析”目标网站域名” | |
| 2. | nameserver-policy | 事前指定 “某个域名使用哪些DNS” |
fallback | 事后判断 “默认查询结果是否可疑, 是否换fallback” | |
| 3. | proxy-server-nameserver | 给节点域名用 |
direct-nameserver | 给走 DIRECT 的目标域名用 |
dns 模块处理流程
pass
fake-ip 与 redir-host
shadowsocks 时期
- 通常是在浏览器或软件内部手动配置代理(http/socks)
- 代理软件直接拿到域名, 然后把域名发给海外服务器, 一切顺利成章
透明代理 (tun模式) 时期
- 为了接管那些不支持设置代理的软件(例如: 游戏, 命令行工具), Surge/Clash 的 TUN 模式(透明代理) 诞生了, 直接接管网卡层面的流量, 而不需要在软件里做任何设置
- 但是遇到一个致命的问题: 当流量到达网卡(网络层)后, 只有IP地址. 原本应用层知道的那个域名, 在经过系统 dns 的解析后, 变成了一串干瘪的 ip
- 然而我们配置的分流规则是 (遇到 xx 域名 → 走代理)
为了解决这个找回域名的元问题, 演化出两条截然不同路线: redir-host 和 fake-ip
fake-ip (假ip)
代理软件劫持 DNS 查询, 直接发给操作系统一个 “假IP(占位符)” 并记录映射关系. 当系统向这个假ip发送数据时, 反查还原出真实的域名
一次请求的流向, 访问 example.com:
- 应用发起 dns 查询
- mihomo 判断这个域名是否命中
fake-ip-filter; 若不命中, 就从fake-ip-range分配一个伪 ip 返回 - mihomo 记下
伪ip <-> 域名映射 - 应用去连这个伪 ip
- mihomo 收到这条连接, 通过映射表找到原始域名
- 分析路由规则
- 命中代理: 直接将域名交给代理服务器请求(本地无需进行dns解析)
- 命中直连: 使用 nameserver 那一套链路拿到真实 ip 并请求
代价
你的系统中会出现一批 “看起来不真实” 的目标地址. 对某些应用、安全软件、局域网发现机制、对真实解析结果敏感的应用 可能会出现兼容性问题.
也可以通过
fake-ip-filter,fake-ip-filter-mode单独配置某些域名不走 fake-ip
redir-host (域名嗅探)
让操作系统进行真实的 DNS 查询拿到真实IP, 代理软件拦截真实IP的数据包后, 再想办法(反查DNS缓存, TLS嗅探) 去猜/找回它原本对应的域名
一次请求的流向, 访问 example.com:
- 应用发起 dns 查询
- mihomo 按
nameserver-policy -> nameserver/fallback这套链路拿到真实 IP - 把真实 ip 返回给应用
- 应用直接去连那个真实 ip
- mihomo 收到这条连接, 通过 “dns反查 + 域名嗅探” 两套机制补回域名
- dns反查: 在dns缓存中反查这个ip是不是刚刚某个域名回过的答案
- 域名嗅探
- 匹配后续路由规则
代价
- 等真ip太慢: 必须实际发送dns请求
- DNS泄露风险: 若dns配置有误, 存在泄露风险
- 匹配吃力: 反查dns缓存, 域名嗅探, 消耗额外cpu算力
| 维度 | redir-host | fake-ip |
|---|---|---|
| 本地DNS查询动作 | 必须做. 需费力请求真实ip | 假装做. 直接返回内网假ip |
| 分流判断依据 | 基于真实ip, 反查出域名再判断 | 基于假ip, 直接查字典对应域名判断 |
| 首包延迟 | 较高(等待真实DNS返回) | 极低(0延迟) |
| DNS泄露风险 | 较高 | 极低(对于代理流量完全不发dns请求) |
| p2p/游戏兼容性 | 优秀(拥有真实物理世界地址) | 极差(用假地址交换) |
域名嗅探
事后补回域名
mihomo 直接对 HTTP/TLS/QUIC 协议进行嗅探, 原理如下:
- 拦截初始握手: 当 tcp/udp 连接刚刚建立时, mihomo会拦截客户端发出的第一个包含业务数据的数据包
- 特征匹配与提取
- https (tls): 解析握手时的
Client Hello报文, 提取其中的 SNI 字段 - http: 直接看请求头中
Host, http2/3对应:authority - QUIC: 来自 TLS ClientHello / SNI
- https (tls): 解析握手时的
应用场景
- TUN + Redir-Host: 绝对主场
- 旁路由/软路由 透明代理: 路由器必须靠嗅探才能精确分流
- APP内置的防抓包/直连IP行为: 某些APP直接向硬编码的IP发送请求(绕过DNS). 嗅探可以把这些流量抢救回来, 便于分析
不适用
- 系统代理模式: 浏览器已经把域名发过来了, 无需嗅探
- 加密SNI (ECH/eSNI): 若浏览器开启了 ECH, SNI也是加密的, 嗅探机制将彻底失效
配置说明
sniffer:
enable: true # 是否开启嗅探
force-dns-mapping: true # 对 redir-host 的流量进行强制嗅探
parse-pure-ip: true # 对所有未获取到域名的流量进行强制嗅探
override-destination: false # 是否使用嗅探结果作为实际访问
sniff: # 仅支持 HTTP / TLS / QUIC 协议嗅探
HTTP:
ports: [80, 8080-8880]
override-destination: true
TLS:
ports: [443, 8443]
QUIC:
ports: [443, 8443]
force-domain: # 强制嗅探的域名列表
- +.v2ex.com
skip-domain: # 跳过嗅探的域名列表
- Mijia Cloud
skip-src-address: # 跳过指定源ip段的流量
- 192.168.0.3/32
skip-dst-address: # 跳过指定目标ip段的流量
- 192.168.0.3/32