v2rayN / v2rayNG 笔记
TUN 模式下一次请求的完整流程:流量嗅探与本地协议栈、routeOnly、DNS 模块的分流与防污染、FakeIP、域名策略,以及 DNS 泄露的成因与最稳的配置组合。
速查
推荐配置
| 设置 | 推荐值 | 一句话原因 |
|---|---|---|
| 流量探测 | 开 | TUN 模式下靠它拿回域名,域名规则才生效 |
| routeOnly | 关 | 连接目标换成域名,由服务器重新解析,抗 DNS 污染 |
| 本地 DNS | 开 | 接管 App 的 DNS 查询,防污染、防泄露 |
| 虚拟 DNS | 按需 | 防探测优先时开;少数 App 不兼容 |
| 远程 DNS | https://1.1.1.1/dns-query | 经代理查询;DoH 走 TCP,兼容不支持 UDP 的节点 |
| 境内 DNS | 223.5.5.5 | 国内域名就近解析 |
| 域名策略 | 日常 IPIfNonMatch,防探测 AsIs | 见第六、七节 |
| Outbound 域名预解析 | 解析后添加至 DNS Hosts | 避免解析节点域名时的循环依赖 |
| 手机”私人 DNS”、Chrome”安全 DNS” | 关 | 加密 DNS 会绕过本地 DNS 和虚拟 DNS |
1、TUN 模式只拿得到 IP,嗅探和 FakeIP 都是为了把域名找回来
2、v2rayNG 不转发 IP 包,而是截断连接、重建连接,所以可以把 IP 换回域名交给服务器解析
3、所有连接都要过分流,包括 DNS 模块自己发出的查询
一、总览:一次请求的完整流程
① App 查询 DNS
├─ 本地 DNS 关 → 查询当作普通 UDP 流量,按路由转发
└─ 本地 DNS 开 → 交给 Xray DNS 模块
├─ 虚拟 DNS 开 → 返回假 IP(198.18.x.x)
└─ 虚拟 DNS 关 → 选远程 / 境内 DNS 查询真实 IP
② App 用 IP 建连接 → 进入虚拟网卡 tun0
③ 本地协议栈完成握手 → 嗅探出域名
④ 路由匹配(域名策略)→ 代理 / 直连
⑤ 出站建连(routeOnly 决定目标写域名还是 IP)二、流量截获:系统代理 vs TUN
App 访问一个网站分三步:
1、解析 DNS:查询 www.google.com,拿到 142.250.x.x
2、建立连接:connect(142.250.x.x, 443),从这一步起系统只认 IP
3、发包:IP 包头只有 IP 和端口,没有存放域名的位置
| 系统代理(应用层) | TUN / VPN 模式(网络层) | |
|---|---|---|
| 怎么接管 | App 主动把请求交给代理 | 虚拟网卡 tun0 强制接管所有流量 |
| 拿到什么 | 域名:CONNECT www.google.com:443 | 第 3 步产生的原始 IP 包 |
| 需要 App 配合 | 需要 | 不需要 |
| 需要嗅探 | 不需要 | 需要 |
手机上大多数 App 不理会代理设置,所以 v2rayNG 默认用 VPN 模式。电脑上的 TUN 模式原理相同。
三、嗅探与本地协议栈
连接与包
IP 层无状态,每个包单独发送。TCP 在其上把一串包组织成连接,由五元组唯一确定:
协议 + 源IP + 源端口 + 目标IP + 目标端口一次 HTTPS 访问:
包1 SYN ┐
包2 SYN-ACK ├ 三次握手,没有数据
包3 ACK ┘
包4 ClientHello ← 第一段数据,SNI 在这里
包5… 加密数据UDP 没有连接,内核把五元组相同的包在一段时间内视为一个会话。
流量嗅探
从每条连接最开头的数据里读出域名:
| 协议 | 域名位置 |
|---|---|
| HTTPS | ClientHello 中的 SNI(明文) |
| HTTP | 请求头 Host |
| QUIC | Initial 包中的 SNI(密钥可公开推导) |
- 只读开头,读到域名就停
- 读不到(ECH、自定义协议)就只能按 IP 匹配路由
- 同一网站的多个资源复用一条连接(HTTP/2),只嗅探一次
- URL 路径在加密之后,看不到,所以规则只能按域名或 IP 写
本地协议栈”冒充”目标
和 App 握手的不是真实服务器,而是 v2rayNG 内部的用户态协议栈(tun2socks)。不管目标 IP 是什么、真不真实,它都直接以那个 IP 的身份完成握手:
App ──SYN──→ 本地协议栈(假装自己是 1.2.3.4)
App ←SYN-ACK─ 本地协议栈
App ──ACK──→ 本地协议栈 ← 握手在本机完成
App ──ClientHello──→ 本地协议栈 ← 嗅探出 www.google.com之后 Xray 另起连接,让服务器去连域名:
App ⇄ 本地协议栈 ⇄ Xray ⇄ 代理服务器 ⇄ 真实的 google.com
(连接A:1.2.3.4) (连接B) (连接C:服务器解析的IP)备注
连接被重建,数据原样搬运。 连接 A、B、C 的 IP、端口、序列号各不相同;但 ClientHello 和后面的加密数据一个字节不改,TLS 仍是 App 和 google.com 端到端加密,Xray 只能读到明文 SNI。
代理 vs 传统 VPN
| 传统 VPN(WireGuard 等) | v2rayNG / Clash | |
|---|---|---|
| 层级 | IP 层(L3) | TCP/UDP 层(L4) |
| 做法 | 整个 IP 包加密后送到对端 | 截断连接,只转发数据 |
| TCP 握手 | App 与真实服务器端到端 | App 只和本地握手 |
| 按域名分流 | 难 | 可以 |
v2rayNG 的”VPN”只是借 Android 的流量截获接口,本质是代理。
TUN 的可检测性
提前握手会留下特征:本该连不上的地址瞬间”连上”再被断开;握手延迟接近 0ms。不过 App 检测 VPN 通常直接查 NetworkCapabilities.TRANSPORT_VPN,更简单。
四、routeOnly
决定 Xray 往外建连时,目标写域名还是原始 IP:
| routeOnly | 域名用于路由 | 域名替换连接目标 | 服务器去连 |
|---|---|---|---|
| 关(默认) | ✅ | ✅ | 域名,服务器自己解析 |
| 开 | ✅ | ❌ | App 原来的 IP |
- 关闭的好处:被污染的 IP 被丢掉,换成域名重新解析
- 开启的用途:IP 是有意选的,比如 Cloudflare 优选 IP、域前置
重要
routeOnly 对假 IP 不生效。 目标在 FakeDNS 地址池里(198.18.x.x)时,Xray 一定会换成域名。所以”虚拟 DNS + routeOnly 开”照样能用。
提示
保持关闭。只有”分流正确,但某个 App 走代理连不上、关代理就正常”时再试着打开。
五、DNS
1. DNS 模块:谁在用它
DNS 模块就是你在设置里填的远程 DNS、境内 DNS 和 DNS hosts。用到它的地方:
| 调用方 | 什么时候 | 说明 |
|---|---|---|
| 本地 DNS | App 发出 DNS 查询 | 回答 App,返回真实 IP 或假 IP |
| 路由域名策略 | IPIfNonMatch / IPOnDemand 需要 IP 匹配规则时 | 结果只用于判断,不用于连接 |
| 直连出站 | 目标是域名,要解析出 IP 才能连 | v2rayNG 模板写死 UseIP,所以一定用 DNS 模块 |
| 代理出站 | 节点地址是域名 | 预解析写进 hosts 后,用 UseIP 直接命中 hosts |
不用它的地方:代理出站把域名交给服务器后,由服务器自己解析,和手机上的 DNS 设置无关。
配置里有三个同名的 domainStrategy,别弄混:
| 位置 | 管什么 | 在哪设置 |
|---|---|---|
routing.domainStrategy | 路由要不要为 IP 规则解析域名 | 路由设置 → 域名策略 |
代理出站 sockopt.domainStrategy | 怎么解析节点域名 | 由”Outbound 域名预解析方式”间接决定 |
直连出站 settings.domainStrategy | 直连时怎么解析目标域名 | 写死在模板里,界面改不了 |
2. 一次查询发给哪个服务器
| 顺序 | 检查 | 结果 |
|---|---|---|
| 1 | DNS hosts 里有没有 | 有就直接返回 |
| 2 | 命中某个服务器绑定的域名列表 | 发给那个服务器 |
| 3 | 都没命中 | 发给列表第一个(默认服务器) |
绑定关系跟着路由规则走(v2rayNG 生成配置的逻辑):
- 远程 DNS 以纯地址放在第一个,作为默认
- 直连规则里的域名 → 绑给境内 DNS,带
domestic-dns标签和skipFallback - 代理规则里的域名 → 绑给远程 DNS
- 直连规则含
geosite:cn时,境内 DNS 还会加expectIPs: geoip:cn - 路由里没有域名规则(比如全局代理)时,只剩一个远程 DNS
“绕过大陆”路由导出的完整配置(分享 → 导出完整配置):
"servers": [
"1.1.1.1",
{ "address": "1.1.1.1", "domains": ["geosite:google"] },
{
"address": "223.5.5.5",
"domains": ["domain:alidns.com", "...", "geosite:cn"],
"expectIPs": ["geoip:cn"],
"skipFallback": true,
"tag": "domestic-dns"
}
]| 查询的域名 | 交给谁 |
|---|---|
geosite:google | 1.1.1.1 |
geosite:cn 等直连域名 | 223.5.5.5,结果必须是国内 IP |
| 其他 | 1.1.1.1(默认) |
两个防护字段:
| 字段 | 含义 | 效果 |
|---|---|---|
expectIPs | 结果必须在这个范围,否则丢弃并换下一个服务器 | 国内域名被污染成境外 IP 时,改问 1.1.1.1,防污染 |
skipFallback | 这个服务器不当别人的后备 | 境外域名查询失败时不会回退到 223.5.5.5,防泄露 |
3. DNS 查询本身也要分流
DNS 模块发出的查询也是普通连接,同样要过路由。查询带有标签,v2rayNG 自动加了两条规则:
{ "inboundTag": ["domestic-dns"], "outboundTag": "direct" }, // 境内 DNS 直连
{ "inboundTag": ["dns-module"], "outboundTag": "proxy" } // 其余 DNS 查询走代理按标签匹配,不管远程 DNS 填什么地址,它的查询都会走代理。
警告
规则从上往下匹配。如果前面有一条全匹配规则(比如”所有端口 → proxy”),这两条就永远轮不到。自己调整规则顺序时要注意 DNS 查询会先被哪条命中。
提示
为什么不直接让 VPS 用它自己的 DNS 回答?
代理协议(SOCKS5、VLESS、Trojan、ssh -D)只有”帮我连接 某域名:端口”一种请求,没有”帮我解析并把 IP 告诉我”。所以本地需要 IP 时,Xray 只能自己构造 DNS 查询、经隧道发给某个 DNS 服务器,远程 DNS 填的就是这个地址。
真正连接时,服务器用的本来就是它自己的 DNS。
4. FakeIP / 虚拟 DNS
DNS 查询和后续连接是两条无关的流量,靠”IP ↔ 域名”反查会遇到:一个 IP 对应多个域名(CDN)、DNS 被加密看不到、App 用了缓存不再查询。
FakeIP 的做法:给每个域名分配一个唯一的假 IP(如 198.18.0.5 ↔ www.google.com),App 用假 IP 连接时直接查表还原。映射是自己分配的,不需要反查。
- 依赖本地 DNS 开启(先接管查询才能返回假 IP)
- 依赖流量探测开启(
destOverride里的fakedns负责还原) - v2rayNG 中 FakeDNS 绑定的域名 =
geosite:cn+ 所有非阻断路由规则里的域名,其他域名仍查真实 IP - 地址池默认
poolSize: 10000,用满后回收旧的
| 缺点 | 表现 |
|---|---|
| 映射丢失 | 内核重启后 App 还拿着旧假 IP,连不上,等缓存过期 |
| 关代理后残留 | 系统缓存的 198.18.x.x 不存在,短时断网 |
| 需要真实 IP 的 App | 校验 IP 的银行、游戏,P2P/BT,SIP、FTP 可能异常 |
| 诊断失真 | ping、nslookup 只看得到假 IP |
备注
198.18.0.0/15 也在 geoip:private 里。假 IP 没还原成功时(探测关闭、映射丢失),会被判为直连然后失败。
5. 加密 DNS 的影响
- DoT(Android 私人 DNS):TCP 853
- DoH(Chrome 安全 DNS、部分 App 内置):HTTPS 443,看起来和网页一样
内核读不出加密查询的内容,本地 DNS 和 FakeIP 都接管不了,只能退回靠嗅探。处理方法:关掉私人 DNS 和安全 DNS,或在路由里拦截 853 端口和常见 DoH 域名,迫使 App 回退到明文 DNS。
6. Outbound 域名预解析
针对节点地址本身。节点是域名时有个循环依赖:解析它要经代理查远程 DNS,但代理还没连上。
| 选项 | 做法 |
|---|---|
| 解析后添加至 DNS Hosts(推荐) | 启动前解析好写进 hosts,代理出站加 UseIP 直接查表 |
| 解析后替换原域名 | 节点地址直接换成 IP |
| 不解析 | 交给 Xray 运行时处理 |
六、路由与域名策略
域名策略决定:目标是域名、规则却按 IP 写时,要不要解析域名去匹配。解析结果只用于判断,连接时仍交域名给服务器。
示例规则:
| 序号 | 规则 | 出站 |
|---|---|---|
| 1 | geoip:private | 直连 |
| 2 | geosite:google | 代理 |
| 3 | geosite:cn | 直连 |
| 4 | geoip:cn | 直连 |
| 5 | 都不匹配 | 代理(Xray 默认交给第一个出站) |
| 访问目标 | AsIs | IPIfNonMatch | IPOnDemand |
|---|---|---|---|
| 解析时机 | 从不 | 域名规则全没命中后 | 遇到第一条 IP 规则就解析 |
www.google.com | 规则 2 → 代理 | 规则 2 → 代理 | 规则 1 处先解析,规则 2 → 代理 |
www.taobao.com | 规则 3 → 直连 | 规则 3 → 直连 | 规则 1 处先解析,规则 3 → 直连 |
不在 geosite:cn 的国内小站 | 规则 5 → 代理 ❌ | 解析后规则 4 → 直连 ✅ | 解析后规则 4 → 直连 ✅ |
IPIfNonMatch 最常用:能用域名判断的不解析,漏网的用 IP 兜底。
geoip:private 包含什么
10.0.0.0/8、172.16.0.0/12、192.168.0.0/16(私有网段),127.0.0.0/8(回环),169.254.0.0/16(链路本地),100.64.0.0/10(CGNAT、Tailscale),198.18.0.0/15(测试段、FakeIP 池),以及组播、广播和保留段。
规则写法
| 写法 | 匹配 | 示例 |
|---|---|---|
domain: | 域名及所有子域名 | domain:google.com |
full: | 完全相同 | full:www.google.com |
keyword: | 包含该字符串(易误伤) | keyword:google |
regexp: | 正则 | regexp:\.goo.*\.com$ |
geosite: | 预置域名列表 | geosite:cn、geosite:private |
geoip: / CIDR | IP 范围 | geoip:cn、10.0.0.0/8 |
geoip:! | 取反 | geoip:!cn |
七、DNS 泄露
探测原理
1、网站让浏览器请求随机子域名,比如 a8f3k2.leaktest.com
2、只有网站自己的权威 DNS 能解析它
3、权威 DNS 能看到是哪个递归 DNS 来查的
4、出口在美国、查询却来自国内运营商 DNS,就暴露了
警告
查询到达过权威服务器就算数,结果用没用无所谓。查询本身就是特征。
各环节的风险
| 环节 | 风险 |
|---|---|
| 本地 DNS 关 | App 自己把查询发给运营商,最大漏洞 |
| 本地 DNS 开、虚拟 DNS 关 | 随机子域名交给远程 DNS 经代理发出,安全 |
| 本地 DNS + 虚拟 DNS | App 只拿假 IP,不产生真实查询,最干净 |
| 域名策略 AsIs | 路由不解析,不产生额外查询 |
| 域名策略 IPIfNonMatch / IPOnDemand | 会触发解析;只要查询发给远程 DNS 并经代理就安全 |
| 远程 DNS 被分流成直连 | 查询从本地发出,暴露国内 IP |
最稳组合:AsIs + 本地 DNS + 虚拟 DNS。代价是不在 geosite:cn 的国内网站会走代理。
验证方法
- 泄露检测:browserleaks.com/dns,结果里不应出现国内 DNS
- 分流检查:日志级别临时调成
info,看每条连接命中哪条规则
真实案例
内网域名
场景:节点是 ssh -D 搭建的 SOCKS5,要访问一个只有内网能解析的域名。
关键:域名必须原样交给 SSH 服务器,由它在内网解析。手机本地任何一次真实解析都会得到”域名不存在”,而 App 查不到 DNS 就根本不会发起连接。所以要先让 App 拿到一个 IP,再把域名还原出来交给 SSH。
不做处理时会怎样
App 查询内网域名 → DNS 模块交给 1.1.1.1 → 公网无记录 → NXDOMAIN → App 报错如果远程 DNS 是 UDP 格式,SSH 不转发 UDP,查询直接超时。两种情况都失败,因为问的是公网 DNS。
方案一:FakeIP(推荐)
| 设置 | 值 | 原因 |
|---|---|---|
| 本地 DNS + 虚拟 DNS | 开 | App 先拿到假 IP,绕过”无记录” |
| 流量探测 | 开 | 把假 IP 还原成域名 |
| 路由规则 | 最上面加 domain:内网域名 → 代理 | 防止被 geosite:cn 判直连;同时这个域名会自动加入 FakeDNS 绑定列表 |
| 路由规则 | 最上面加 ip:内网网段 → 代理 | 防止内网 IP 被 geoip:private 判直连 |
| 远程 DNS | tcp://1.1.1.1 或 DoH | SSH 不支持 UDP |
| 手机私人 DNS | 关 | 否则绕过虚拟 DNS |
警告
不加第一条路由规则时,FakeDNS 只覆盖 geosite:cn。不在其中的内网域名仍会发给 1.1.1.1,结果还是 NXDOMAIN。
方案二:DNS hosts
内网 IP 固定时,在 DNS hosts 里写 内网域名:10.x.x.x,不用开虚拟 DNS。同样要加 ip:10.x.x.x → 代理 规则,防止被 geoip:private 判直连。缺点是 IP 变了要手动改。
方案三:查询内网 DNS
把内网域名的查询指向 tcp://内网DNS地址,经 SSH 的 TCP 隧道直接问内网 DNS。需要用自定义配置写 dns.servers,并加 ip:内网DNS地址 → 代理 规则。
注意
ssh -D 不支持 UDP:
1、远程 DNS 必须用 tcp:// 或 https://,否则所有走远程 DNS 的查询都会失败
2、QUIC / HTTP3 不通,浏览器会回退到 TCP
3、SSH 服务器本身必须能解析内网域名(用的是它的 /etc/resolv.conf)
电脑上更简单
v2rayN 用系统代理模式,浏览器直接把域名交给代理,本地不解析,上面的问题都不存在。
评论
评论加载中……