v2rayN / v2rayNG 笔记

TUN 模式下一次请求的完整流程:流量嗅探与本地协议栈、routeOnly、DNS 模块的分流与防污染、FakeIP、域名策略,以及 DNS 泄露的成因与最稳的配置组合。

速查

推荐配置

设置推荐值一句话原因
流量探测开TUN 模式下靠它拿回域名,域名规则才生效
routeOnly关连接目标换成域名,由服务器重新解析,抗 DNS 污染
本地 DNS开接管 App 的 DNS 查询,防污染、防泄露
虚拟 DNS按需防探测优先时开;少数 App 不兼容
远程 DNShttps://1.1.1.1/dns-query经代理查询;DoH 走 TCP,兼容不支持 UDP 的节点
境内 DNS223.5.5.5国内域名就近解析
域名策略日常 IPIfNonMatch,防探测 AsIs见第六、七节
Outbound 域名预解析解析后添加至 DNS Hosts避免解析节点域名时的循环依赖
手机”私人 DNS”、Chrome”安全 DNS”关加密 DNS 会绕过本地 DNS 和虚拟 DNS

1、TUN 模式只拿得到 IP,嗅探和 FakeIP 都是为了把域名找回来

2、v2rayNG 不转发 IP 包,而是截断连接、重建连接,所以可以把 IP 换回域名交给服务器解析

3、所有连接都要过分流,包括 DNS 模块自己发出的查询

一、总览:一次请求的完整流程

flow

① 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 没有连接,内核把五元组相同的包在一段时间内视为一个会话。

流量嗅探

从每条连接最开头的数据里读出域名:

协议域名位置
HTTPSClientHello 中的 SNI(明文)
HTTP请求头 Host
QUICInitial 包中的 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

v2rayNG 设置截图

1. DNS 模块:谁在用它

DNS 模块就是你在设置里填的远程 DNS、境内 DNS 和 DNS hosts。用到它的地方:

调用方什么时候说明
本地 DNSApp 发出 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. 一次查询发给哪个服务器

顺序检查结果
1DNS hosts 里有没有有就直接返回
2命中某个服务器绑定的域名列表发给那个服务器
3都没命中发给列表第一个(默认服务器)

绑定关系跟着路由规则走(v2rayNG 生成配置的逻辑):

  • 远程 DNS 以纯地址放在第一个,作为默认
  • 直连规则里的域名 → 绑给境内 DNS,带 domestic-dns 标签和 skipFallback
  • 代理规则里的域名 → 绑给远程 DNS
  • 直连规则含 geosite:cn 时,境内 DNS 还会加 expectIPs: geoip:cn
  • 路由里没有域名规则(比如全局代理)时,只剩一个远程 DNS

“绕过大陆”路由导出的完整配置(分享 → 导出完整配置):

json
"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:google1.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 自动加了两条规则:

json
{ "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 写时,要不要解析域名去匹配。解析结果只用于判断,连接时仍交域名给服务器。

示例规则:

序号规则出站
1geoip:private直连
2geosite:google代理
3geosite:cn直连
4geoip:cn直连
5都不匹配代理(Xray 默认交给第一个出站)
访问目标AsIsIPIfNonMatchIPOnDemand
解析时机从不域名规则全没命中后遇到第一条 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: / CIDRIP 范围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 + 虚拟 DNSApp 只拿假 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 判直连
远程 DNStcp://1.1.1.1 或 DoHSSH 不支持 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 用系统代理模式,浏览器直接把域名交给代理,本地不解析,上面的问题都不存在。

评论

评论加载中……

登录后再评论

注册要用邮箱收个验证码,只为确认邮箱能收信,不会拿去做别的。账号设置