V2Ray 自定义路由规则写法:domain、ip、geosite 语法与匹配优先级

讲清 domain 前缀匹配、ip 与 geoip 段、geosite 分类的写法,以及规则自上而下的命中顺序和常见冲突的处理方式。

本文速览

适合已经能连接节点、准备调整分流的用户。本文以 V2Ray / Xray 的核心路由结构为基础,区分域名后缀、完整域名、IP 网段与分类数据,给出可以逐条推演的规则片段,再说明如何在 v2rayN 与 v2rayNG 中核对实际生效结果。重点不是堆叠规则,而是让每条请求有明确、可验证的出口。

路由入口:先确认请求进入内核

系统代理与路由规则处理不同环节。系统代理决定支持代理的应用是否把请求送到本地端口;内核路由决定已经进入内核的请求使用哪个出站。浏览器没有使用系统代理,或者程序自行直连时,再准确的 geosite 规则也不会接管这条连接。TUN 可以扩大流量接管范围,但是否开启 TUN 不会改变规则从上到下匹配的基本顺序。

应用请求本地入站目标识别路由匹配出站连接

v2rayN 是桌面客户端,负责生成并启动内核配置;v2rayNG 使用 Xray 内核,v2flyNG 使用 V2Fly 内核。客户端界面里的路由预设不等于协议本身:VMess、VLESS 描述代理连接使用的协议,domain、ip 与 outboundTag 则描述本地分流。更换节点协议,不会自动修复一条写错的域名规则。

domain 写法:后缀匹配不是字符串前缀匹配

规则中的 domain 是一个字符串数组,数组内满足任意一项即可满足该域名条件。字符串上的 domain:、full:、regexp: 是匹配方式标记。其中最容易误读的是 domain::它匹配指定域名及其子域名,并不是“域名以这一串字符开头”。

规则字符串匹配含义测试目标
domain:example.test自身及子域名匹配 example.test、api.example.test;不匹配 notexample.test
full:api.example.test完整域名精确匹配匹配 api.example.test;不匹配 img.api.example.test
example不带标记的子串匹配也可能匹配 another-example.test,范围较宽
regexp:^api[.]example[.]test$正则表达式匹配此写法限定为一个完整域名
geosite:cn匹配数据文件中的 cn 分类实际范围取决于当前 geosite 数据

需要直连一个服务及其全部子域名时,优先使用 domain:;只为一个接口设例外时,使用 full:。裸字符串适合明确知道匹配范围的关键字规则,不适合把一个短词当作精确域名。正则语法还受内核采用的正则实现限制,复杂表达式应先验证,不能照搬其他工具支持的全部特性。

{
  "type": "field",
  "domain": [
    "full:api.example.test",
    "domain:assets.example.test"
  ],
  "outboundTag": "direct"
}

这条规则会让指定接口或静态资源域名直连,但不会把整个 example.test 都纳入直连。正则放进 JSON 时还要注意反斜杠转义:如果用 \. 表示点,JSON 字符串里应写成 \\.。上表用 [.] 表示点,可以减少这一层转义带来的误写。

ip 与 geoip:地址范围、分类和条件组合

ip 数组可以容纳单个地址、CIDR 网段以及 geoip: 分类。CIDR 的斜杠后数字是网络前缀长度:192.168.0.0/16 表示这一私有地址范围,不是只匹配一个局域网地址;IPv6 同样可以写成 fc00::/7。分类方式依赖内核能够读取的 GeoIP 数据。

65,536
IPv4 /16 网段的地址总数
256
IPv4 /24 网段的地址总数
443
下方组合规则的目标端口

这些地址总数包括网段中的全部地址,不等于可分配给主机的数量。geoip:private 常用于内网与特殊本地地址分流,geoip:cn 用于数据文件中标记为中国的 IP 范围;后者不代表中文网站,也不保证与域名分类完全重合。一个域名可能使用多个地区的 CDN 地址,地址分类随解析结果变化。

{
  "type": "field",
  "ip": ["192.168.0.0/16"],
  "port": "443",
  "network": "tcp",
  "outboundTag": "direct"
}

结论:同一规则的不同字段按“并且”理解

上面的规则要求目标 IP 属于指定网段、目标端口为 443、网络为 TCP,三项同时成立才直连。要表达“指定域名或者指定 IP 直连”,应拆成两条规则;把 domain 与 ip 填在同一条里,不会得到二选一效果。

geosite 与 domainStrategy:域名分类和解析时机

geosite:cn 是数据文件内的分类名称,不是在线搜索指令。内核需要能够读取包含该分类的 geosite.dat。不同数据来源可能包含不同标签,更新节点订阅也不等于更新分类文件。启动日志出现“分类不存在”或“数据文件无法读取”时,应先修复资源,再讨论规则顺序。

域名优先,不额外解析

字段位置
routing.domainStrategy
取值
AsIs
域名请求
直接进行域名规则匹配
IP 条件
不因路由匹配主动解析域名

适合希望主要由明确域名规则决定出口的配置;出站连接本身仍可能需要解析域名。

域名未命中,补查 IP

字段位置
routing.domainStrategy
取值
IPIfNonMatch
解析触发
首次路由匹配没有命中规则
后续处理
解析后以地址参与再次匹配

适合域名分类之外再用 IP 分类补充分流,但必须给首次未命中保留机会。

另一个常见取值是 IPOnDemand:匹配过程中遇到需要 IP 的规则时,就可能触发解析。因此,靠前的 IP 规则可能在后面的域名例外之前命中。它与 IPIfNonMatch 的关键区别是触发时机,不是哪个选项“更强”。

这里说的是 routing 下的 domainStrategy。DNS 模块的查询策略、直连出站的域名策略与它不是同一设置。路由解析只提供匹配依据,不能单独保证 DNS 请求走指定出口;DNS 服务器可达性、解析路径与应用自身解析方式需要另行检查。

匹配优先级:具体例外在前,范围规则在后

核心按 rules 数组从上往下尝试,第一条满足条件的规则决定路由,不会因为后面的域名更长就自动改选。也就是说,“更具体”只是人为安排顺序的理由,不是内核自动计算的优先级。规则里的标签应指向已定义的出站;如果没有任何规则命中,通常使用 outbounds 数组中的第一个出站,而不是自动选择名为 proxy 的标签。

  1. 业务例外:先放明确需要代理或直连的完整域名、小范围后缀与特定网段。
  2. 局域网保护:再处理私有地址,避免本地设备请求被默认送往代理;若有企业内网的特殊出口,先写其更具体的例外。
  3. 分类规则:放置 geosite、geoip 等覆盖范围较大的分流条件。
  4. 兜底处理:最后明确剩余连接的去向,并检查它是否会改变解析策略的触发条件。

下面是一个 routing 片段:api.example.test 单独代理,同一父域名的其他请求直连,私有地址与中国分类直连,剩余 TCP / UDP 代理。采用 IPOnDemand,是为了让域名分类未覆盖的请求仍能在 IP 规则处获得解析结果。该片段需要合并到已有核心配置,不能当作完整配置文件启动。

{
  "routing": {
    "domainStrategy": "IPOnDemand",
    "rules": [
      {
        "type": "field",
        "domain": ["full:api.example.test"],
        "outboundTag": "proxy"
      },
      {
        "type": "field",
        "domain": ["domain:example.test"],
        "outboundTag": "direct"
      },
      {
        "type": "field",
        "ip": ["geoip:private"],
        "outboundTag": "direct"
      },
      {
        "type": "field",
        "domain": ["geosite:cn"],
        "outboundTag": "direct"
      },
      {
        "type": "field",
        "ip": ["geoip:cn"],
        "outboundTag": "direct"
      },
      {
        "type": "field",
        "network": "tcp,udp",
        "outboundTag": "proxy"
      }
    ]
  }
}
输入条件命中位置出口
域名 api.example.test第 1 条完整域名规则proxy
域名 img.example.test第 2 条域名后缀规则direct
目标 IP 为 192.168.1.10第 3 条私有地址规则direct
域名不在前面分类内,解析 IP 属于 geoip:cn第 5 条 IP 分类规则direct
其余未命中的 TCP 请求第 6 条兜底规则proxy

把第 2 条移到第 1 条前面后,api.example.test 会先命中父域名规则,结果变为直连。把兜底规则移到数组顶部后,后续规则基本失去作用。排查冲突时,应按实际目标从第一条开始推演,而不是只盯着希望命中的那一条。

客户端落地:检查生成配置,再做最小测试

v2rayN 可从「设置」→「路由设置」检查当前路由配置,入口文字随版本可能调整。重点确认编辑的是正在启用的配置,而不是另一个未选中的预设。「设置」→「参数设置」中核对本地监听端口;下面测试假定 SOCKS 监听在 127.0.0.1:10808,实际值不同就替换,不能把示例端口视为所有版本的固定默认值。

  1. 备份当前规则。记录当前核心、路由预设、域名策略与出站标签,先只新增一个可以辨认的域名例外。
  2. 保存并重新应用。按客户端当前流程重启核心或重连,使配置重新生成;只保存编辑窗口不一定已经切换运行配置。
  3. 检查运行日志。先排除 JSON 解析失败、分类缺失、出站标签不存在与监听端口占用。必要时临时提高日志级别,测试后恢复。
  4. 对照实际目标。观察连接日志或访问日志中的目标域名、目标 IP 和出站标签;日志格式由核心和日志级别决定。
  5. 再扩大范围。单条例外验证通过后,才加入 geosite / geoip 分类与兜底,避免一次改动多项导致无法定位。
curl --socks5-hostname 127.0.0.1:10808 \
  --connect-timeout 10 \
  --max-time 20 \
  -I https://v2rayworld.com/

--socks5-hostname 会将目标域名交给 SOCKS 端处理,有利于验证域名规则;--socks5 通常在本地先解析,可能让内核看到的是 IP。这里的 10 秒连接超时与 20 秒总超时用于限制测试等待,并非延迟或速度实测数据。HTTP 请求成功只能证明这次请求可用,是否命中指定规则还要看出站记录。

v2rayNG 中先检查当前路由模式,再进入「设置」内对应的路由配置入口;不同版本的菜单层级可能不同。安卓 VPN 接管也可能受按应用代理范围影响,测试浏览器必须在接管范围内。v2flyNG 的核心能力与资源文件要按 V2Fly 实际配置确认,不能把 Xray 专有字段原样搬过去。本文涉及的基础 field 规则仍应以当前核心启动结果为最终依据。

常见问题:域名没命中、分类报错与规则被覆盖

域名规则写对了,为什么还是走 IP 规则?

先查看入站收到的是域名还是 IP。用上面的 --socks5-hostname 测试建立对照;如果应用已自行解析,域名规则未必有匹配依据。嗅探可从部分 HTTP / TLS 流量中提取域名,但不是所有连接都能提取,也不能把它当成稳定覆盖全部流量的补丁。

geosite:cn 一保存就无法启动,怎么办?

查看核心日志是否提示 geosite 文件找不到或 cn 分类不存在。检查资源文件位置、读取权限以及客户端使用的数据来源;先恢复备份规则让核心启动,再用客户端提供的资源更新入口处理数据。不要通过删除全部路由掩盖资源错误。

添加 domain 和 ip 后,直连范围反而变小了?

检查是否把两种条件写在同一条 field 规则里。需要“域名符合或者地址符合”时,拆成两条、分别指定相同出口。同一条规则内两个字段要求同时满足,且域名请求要满足 IP 条件还受解析策略影响。

修改核心配置后,下次连接又恢复了?

客户端可能在启动时重新生成临时核心配置。应在当前路由预设或客户端支持的自定义配置入口修改,而不是只编辑运行时临时文件。保存后重新应用,再核对生成的 rules 顺序,确认没有被预设规则覆盖。

最终检查顺序可以固定为:请求是否进入内核、目标是否包含域名、分类资源是否可读、规则是否按预期排序、出站标签是否有效。浏览器可能复用已有连接,修改后应新建请求或重启测试应用。确认命中结果后,再分别测试普通域名、局域网 IP 和未分类目标,避免只验证一个网站就判断整套分流正确。

下载v2rayN查看平台与安装包