历史背景与演化

  • 回溯源头: 早期互联网中, 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

  1. enable / listen / ipv6

    • enable: 决定是否启用 mihomo 自己的 DNS模块, 不启用就退回系统dns
    • listen: DNS服务监听地址, 支持 UDP/TCP
    • ipv6: 控制是否解析 AAAA
  2. enhanced-mode:

  3. hosts / use-hosts / use-system-hosts

    • dns 查询未必一上来就去外部 DNS 服务器
    • 本地 hosts 可能先拦截并直接回答
    • 在调试、内网、开发等场景很有用
  4. default-nameserver (引导 DNS)

    • 痛点: 假设你使用加密的 https://dns.google/dns-query 来查 DNS. 但是问题来了, 你要连这个加密的 dns, 首先要知道 dns.google 的 IP是多少
    • 机制: default-nameserver 就是打破死循环的. 它只能是纯数字的ip. 它唯一的任务是, 解析配置文件里出现的域名(代理节点服务器的域名、加密DNS的域名)
  5. nameserver(主DNS) + fallback (回退DNS)

    • nameserver 是默认的域名解析服务器(通常填国内dns)。fallback 是备用解析服务器(通常填国外加密dns, 确保可靠)
    • 当收到一个用户查询时,mihomo 会同时向 nameserver、fallback 发起询问
    • 根据 nameserver 的结果判断是否走 fallback, 有如下四类判断条件:
      1. geoip / geocode: 如果结果ip不属于中国(默认cn), 认为污染, 走 fallback
      2. ipcidr: 命中这些网段, 直接走 fallback
      3. domain: 命中这些域名, 直接走 fallback
  6. nameserver-policy (定向解析)

    • 优先级最高, 为特定域名查询指定解析服务器
    • 有些特殊域名(如内网域名, 或特定的应用) 必须用特定的DNS解析
  7. proxy-server-nameserver / proxy-server-nameserver-policy (代理服务器本身的寻路)

    • 只用于解析代理节点自身的域名
    • 例如你的节点地址本身是域名, 且这个域名必须通过特定DNS才能解析稳定, 那么你应该单独配置本项, 用以区分 “普通网站域名解析”
  8. direct-nameserver / direct-nameserver-follow-policy (直连出口DNS)

    • 当某条流量最终被判定为 DIRECT 时, 你可能不想继续用”代理路径下那套解析结果”, 而是希望按直连出口的视角重新解析一次
    • direct-nameserver-follow-policy 则是决定在使用 direct-nameserver 时, 是否遵循 nameserver-policy, 默认不遵循
  9. respect-rules

    • 定义: DNS 连接本身也遵守路由规则
    • 限制:
      1. 使用它需配置 proxy-server-nameserver
      2. 强烈不建议与 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:

  1. 应用发起 dns 查询
  2. mihomo 判断这个域名是否命中 fake-ip-filter; 若不命中, 就从 fake-ip-range 分配一个伪 ip 返回
  3. mihomo 记下 伪ip <-> 域名 映射
  4. 应用去连这个伪 ip
  5. mihomo 收到这条连接, 通过映射表找到原始域名
  6. 分析路由规则
    • 命中代理: 直接将域名交给代理服务器请求(本地无需进行dns解析)
    • 命中直连: 使用 nameserver 那一套链路拿到真实 ip 并请求

代价

你的系统中会出现一批 “看起来不真实” 的目标地址. 对某些应用、安全软件、局域网发现机制、对真实解析结果敏感的应用 可能会出现兼容性问题.

也可以通过 fake-ip-filter, fake-ip-filter-mode 单独配置某些域名不走 fake-ip

redir-host (域名嗅探)

让操作系统进行真实的 DNS 查询拿到真实IP, 代理软件拦截真实IP的数据包后, 再想办法(反查DNS缓存, TLS嗅探) 去猜/找回它原本对应的域名

一次请求的流向, 访问 example.com:

  1. 应用发起 dns 查询
  2. mihomo 按 nameserver-policy -> nameserver/fallback 这套链路拿到真实 IP
  3. 把真实 ip 返回给应用
  4. 应用直接去连那个真实 ip
  5. mihomo 收到这条连接, 通过 “dns反查 + 域名嗅探” 两套机制补回域名
    • dns反查: 在dns缓存中反查这个ip是不是刚刚某个域名回过的答案
    • 域名嗅探
  6. 匹配后续路由规则

代价

  1. 等真ip太慢: 必须实际发送dns请求
  2. DNS泄露风险: 若dns配置有误, 存在泄露风险
  3. 匹配吃力: 反查dns缓存, 域名嗅探, 消耗额外cpu算力
维度redir-hostfake-ip
本地DNS查询动作必须做. 需费力请求真实ip假装做. 直接返回内网假ip
分流判断依据基于真实ip, 反查出域名再判断基于假ip, 直接查字典对应域名判断
首包延迟较高(等待真实DNS返回)极低(0延迟)
DNS泄露风险较高极低(对于代理流量完全不发dns请求)
p2p/游戏兼容性优秀(拥有真实物理世界地址)极差(用假地址交换)

域名嗅探

事后补回域名

mihomo 直接对 HTTP/TLS/QUIC 协议进行嗅探, 原理如下:

  1. 拦截初始握手: 当 tcp/udp 连接刚刚建立时, mihomo会拦截客户端发出的第一个包含业务数据的数据包
  2. 特征匹配与提取
    • https (tls): 解析握手时的 Client Hello 报文, 提取其中的 SNI 字段
    • http: 直接看请求头中 Host, http2/3对应 :authority
    • QUIC: 来自 TLS ClientHello / SNI

应用场景

  • 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