(
更多自定义握手类型
Xray-core于26.6.1为“自定义握手(header-custom)”最终伪装层增加了更多变体,包括捕获、重用、变换(可编程表达式)等新特性,“变换”允许插入大/小端序数字与连接的元数据,执行拼接、切片、补全、截断、算术运算和按位运算。另外,此更新亦为UDP协议的同名伪装层支持自定义握手模式,行为与TCP协议的伪装层基本相同。
此次更新允许更准确地模仿其它协议之行为呀。
{
"streamSettings": {
"finalmask": {
"tcp": [
{
"type": "header-custom",
"settings": {
"clients": [],
"servers": []
}
}
],
"udp": [
{
"type": "header-custom",
"settings": {
"mode": "standalone", // 切换至“自定义握手”模式
"client": [],
"server": []
}
}
]
}
}
}
[!info] 信息
UDP协议处的“自定义握手”已经能够代替原本的“噪音(noise)”伪装层与Freedom出站的noise对象呀。
类型
由基本的范围随机数与变量存取开始。
范围随机数(randRange)
先前,数据包部分的对象中允许使用rand字段以表示“长度为rand的随机数据”,无法指定范围;更新后,便允许通过randRange字段指定随机数范围了:
{
"rand": "16",
"randRange": "32-126"
}
以上配置将随机生成16个ASCII可打印字符。
捕获(capture)与重用(reuse)
“捕获”与“重用”可理解为变量的“赋值”与“读取”,它们的意义便是其字面意思,将1段内容存储,便于后续重用。例如,部分协议需要客户端发送1段ID,而服务器需要于响应中包含此ID,此时便可使用捕获与重用。
{
"clients": [
[
{
"rand": 32,
"capture": "session_id"
}
]
],
"servers": [
[
{
"type": "str",
"packet": "回响Session ID:"
},
{
"reuse": "session_id"
}
]
]
}
以上配置中,客户端将于连接后发送32字节随机数据,服务器将其视为session_id,并以“回响Session ID:session_id”回应。
[!attention] 注意
变量的名称不应以数字开始,且应仅包含大小写拉丁字母、数字与下划线(_)。(这里并不知道为何那位贡献者添加了此校验……)
变换(transform)
“变换”应当是此特性最主要的更新,它提供了各种字节操作与运算功能呀。
{
"transform": {
"op": "操作",
"args": [
{
"参数名称": "值"
},
...
]
}
}
大端序数字(be16、be32)与小端序数字(le16、le32、le64)
此类操作需要1个参数,以上一系列操作类型用于插入大/小端序数字。
“端序(Endianness)”基本上是指2种排列字节的方式,小端表示将低位字节数据存储在低地址的模式,大端表示将高位字节数据存储在低地址的模式。例如,需要将数字0x01234567存储至内存的0x10、0x11和0x12字节位置,小端如此存储:67 45 23 01,大端则如此存储:01 23 45 67。
为插入数字,需要选择大端(Big Endian,be)或小端(Little Endian,le),以及数字的位数,不同位数允许的最值不相同。不过,无论选择何种位数,args数组应仅包含1个仅有u64字段的对象,指定1个十进制数字。
[!tip] 提示
不知为何,大端序64位整数(be64)并不存在。
{
"transform": {
"op": "be16",
"args": [
{
"u64": 123456
}
]
}
}
拼接(concat)
“拼接”操作没有参数数量限制,用于将多个值拼接于1处,它于外观上与不进行此操作并无区别,通常用于嵌套变换或捕获拼接后的数据。
{
"transform": {
"op": "concat",
"args": [
{
"transform": {
"op": "be16",
"args": [
{
"u64": 4660
}
]
}
},
{
"type": "hex",
"bytes": "AABB"
},
{
"type": "str",
"bytes": "Hello"
}
]
}
}
args数组中的数据将被连接,其内容为12 34 AA BB 48 65 6C 6C 6F。
[!attention] 注意
args数组中用于指定数据的字段不为packet,而是bytes。
切片(slice)
“切片”需要3个参数:源数据、偏移与切片长度。顾名思义,它将于源数据开始偏移至指定位置 + 1的位置,并取出开始切片之后的指定长度(不包含末字节)。
{
"transform": {
"op": "slice",
"args": [
{
"type": "str",
"bytes": "Hello"
},
{
"u64": 1
},
{
"u64": 2
}
]
}
}
以上操作将于源数据Hello的第2个字节开始,取得2个字节el。
补全(pad)
“补全”亦需要3个参数:源数据、目标长度和填充字节。若源数据长度小于目标长度,它会将“填充字节”补全至后方,补全后的序列长度等于目标长度。
{
"transform": {
"op": "pad",
"args": [
{
"type": "hex",
"bytes": "AABB"
},
{
"u64": 5
},
{
"type": "hex",
"bytes": "CCDD"
}
]
}
}
最终内容为AA BB CC DD CC。
截断(truncate)
此操作需要2个参数:源数据与长度。基本上,它仅取用源数据指定长度的前缀。
{
"transform": {
"op": "truncate",
"args": [
{
"type": "hex",
"bytes": "01020304"
},
{
"u64": 2
}
]
}
}
返回数据为01 02。
算术运算(add、sub)与按位运算(xor16、xor32、and、or、shl、shr)
运算需要2个参数:左操作数与右操作数。能够执行加法(add)、减法(sub)、异或(xor16、xor32)、按位与(and)、按位或(or)、左移(shl)和右移(shr)。
{
"transform": {
"op": "be16",
"args": [
{
"transform": {
"op": "add",
"args": [
{
"u64": 1
},
{
"u64": 2
}
]
}
}
]
}
}
连接的元数据(metadata)
metadata是1个字段而非参数,仅可于args数组中使用。它能够获取当前连接的本地/远程IP地址与端口。
[!info] 信息
由于此拉取请求被合并,目前UDP的header-custom最终伪装层暂时无法使用此功能。
{
"transform": {
"op": "concat",
"args": [
{
"transform": {
"op": "be32",
"args": [
{
"metadata": "local_ip4_u32"
}
]
}
},
{
"transform": {
"op": "be16",
"args": [
{
"metadata": "local_port"
}
]
}
},
{
"transform": {
"op": "be32",
"args": [
{
"metadata": "remote_ip4_u32"
}
]
}
},
{
"transform": {
"op": "be16",
"args": [
{
"metadata": "remote_port"
}
]
}
}
]
}
}
最终数据内容分别为本地IP地址 本地端口 远程IP地址 远程端口。
示例配置
《Minecraft》1.21.5版本登录正版验证服务器并开始加密连接:
{
"tcp": [
{
"type": "header-custom",
"settings": {
"clients": [
[
{
"transform": {
"op": "add",
"args": [
{
"u64": 14
},
{
"u64": 9
}
]
}
},
{
"type": "hex",
"packet": "008206"
},
{
"type": "hex",
"packet": "0e"
},
{
"type": "str",
"packet": "mc.hypixel.org"
},
{
"transform": {
"op": "be16",
"args": [
{
"u64": 25565
}
]
}
},
{
"type": "hex",
"packet": "02"
},
{
"transform": {
"op": "concat",
"args": [
{
"transform": {
"op": "add",
"args": [
{
"u64": 18
},
{
"u64": 5
}
]
}
},
{
"type": "hex",
"bytes": "00"
},
{
"type": "hex",
"bytes": "05"
},
{
"type": "str",
"bytes": "Steve"
},
{
"rand": 16
}
]
},
"delay": "1-50"
}
],
[
{
"type": "hex",
"packet": "8502018001"
},
{
"rand": 128
},
{
"type": "hex",
"packet": "8001"
},
{
"rand": 128
}
]
],
"servers": [
[
{
"type": "hex",
"packet": "ac010100a201"
},
{
"rand": 162
},
{
"type": "hex",
"packet": "04"
},
{
"rand": 4
},
{
"type": "hex",
"packet": "01"
}
],
[
{
"rand": 4
}
]
]
}
}
]
}
TUN自动配置
先前,使用TUN入站时 为防止数据包环路,通常需要手动于配置文件中指定各个出站所绑定的网络接口,或是配置系统路由表。Xray-core于26.6.1版本增加数个字段,以便自动配置路由表、网关、出站接口和DNS服务器等设置。
[!info] 信息
顺带一提,TUN入站目前会为ICMP回显请求(Echo Request)响应虚假的ICMP回显应答,并不会真正验证目标IP地址的可连通性。其它数据包则不予处理。
需要注意的是,除“DNS服务器”额外支持MacOS操作系统,“自动配置出站接口”支持几乎所有平台外,其它所有配置目前仅支持Windows操作系统:
| 配置项 | Windows | Linux | macOS | FreeBSD | Android |
|---|---|---|---|---|---|
| 接口IP地址 | ✓ | ||||
| DNS服务器 | ✓ | ✓ | |||
| 系统路由表 | ✓ | ||||
| 出站接口 | ✓ | ✓ | ✓ | ✓ |
配置
以下示例配置将指示Xray-core于启动时创建1个名为xray0的虚拟网络接口:
{
"inbounds": [
{
"port": 0,
"protocol": "tun",
"settings": {
"name": "xray0",
"mtu": 1500
}
}
]
}
自动配置出站接口
于settings对象的autoOutboundsInterface指定1个网络接口名称,便可自动将各个出站绑定至该接口。特殊值auto表示“自动寻找可用的物理网络接口”。
{
"name": "xray0",
"autoOutboundsInterface": "auto"
}
自动配置系统路由表
将特定IP地址加入autoSystemRoutingTable数组,Xray-core便会配置系统路由表,使操作系统将匹配的IP子网指向此虚拟网络接口。使用此配置时,无需担心创建重复的路由表规则,Xray-core不会对已被路由的IP子网创建规则。
指定此项时,若未指定“自动配置出站接口”,则自动设置后者的值为auto。
{
"name": "xray0",
"autoSystemRoutingTable": [
"0.0.0.0/0",
"::/0"
]
}
以上配置将使所有IPv4与IPv6地址路由至该接口。
接口IP地址
gateway数组用于配置此虚拟网络接口的网关与IP地址。
[!info] 信息
顺带一提,于MacOS操作系统的Xray-core将固定使用169.254.10.1/30作为接口地址,因为系统要求虚拟网络接口必须同时具备本地地址和远端地址才可进行路由。
{
"name": "xray0",
"gateway": [
"10.0.0.1/24",
"fd00::1/64"
]
}
以上配置将指定此接口的IPv4地址为10.0.0.1,子网掩码为255.255.255.0;而IPv6地址便是fd00::1/64。
DNS服务器
dns数组用于指定此接口应使用的DNS服务器。
{
"name": "xray0",
"dns": [
"1.1.1.1",
"1.0.0.1",
"2606:4700:4700::1111",
"2606:4700:4700::1001"
]
}
定时更新与热重载Geo文件
Xray-core在26.6.1版本中增加了Geo文件定时下载与热重载特性,以便及时获得更新的Geo文件。
配置
通过指定根对象中geodata对象的cron字段为1段Cron表达式,即可定期热重载Geo文件,通常适用于“已经使用其它脚本实施更新”的情况。
{
"geodata": {
"cron": "0 4 * * *"
},
"inbounds": [],
"outbounds": []
}
以上配置表示“于每天凌晨4点进行重载”。
若添加assets数组,便可从指定URL获取并重载文件。
{
"geodata": {
"cron": "0 0 1 * *",
"assets": [
{
"url": "https://github.com/Loyalsoldier/v2ray-rules-dat/raw/refs/heads/release/geoip.dat",
"file": "geoip.dat"
},
{
"url": "https://github.com/Loyalsoldier/v2ray-rules-dat/raw/refs/heads/release/geosite.dat",
"file": "geosite.dat"
}
]
}
}
通过outbound字段指定下载时所使用的出站。
{
"geodata": {
"cron": "0 0 1 * *",
"outbound": "proxy",
"assets": [
{
"url": "https://github.com/Loyalsoldier/v2ray-rules-dat/raw/refs/heads/release/geoip.dat",
"file": "geoip.dat"
},
{
"url": "https://github.com/Loyalsoldier/v2ray-rules-dat/raw/refs/heads/release/geosite.dat",
"file": "geosite.dat"
}
]
}
}
BBR拥塞控制配置
Xray-core于26.6.1版本同步Hysteria2特性,增加了“BBR配置”功能,其作用与Hysteria2处基本相同[1],用以调整BBR拥塞控制的激进程度。目前适用于“XHTTP+TLS(ALPN=HTTP/3)”组合与Hysteria等 使用或基于QUIC协议的连接。
配置
此配置十分简单,只需调整quicParams中的bbrProfile即可:
{
"streamSettings": {
"finalmask": {
"quicParams": {
"congestion": "bbr",
"bbrPofile": "standard"
}
}
}
}
允许3种取值:“激进(aggressive)”“标准(standard,默认值)”与“保守(conservative)”。
DNS出站规则
Xray-core在26.6.1更新中允许为DNS出站设置规则,以对命中规则的DNS记录类型和域名查询请求进行劫持、丢弃、放行或返回特定DNS响应代码。它被开发的最初目的则是“关闭请求海外网站时的ECH(通过拒绝HTTPS(65)查询请求实现)”。
配置
DNS出站需要接收传统DNS查询请求。以下示例配置通过路由规则,将发送至53端口的所有流量 引导至创建的DNS出站,并强制转向1.1.1.1发送查询请求。
{
"outbounds": [
{
"tag": "freedom",
"protocol": "freedom"
},
{
"tag": "_dns",
"protocol": "dns",
"settings": {
"rewriteNetwork": "udp",
"rewriteAddress": "1.1.1.1",
"rules": []
}
},
{
"tag": "blackhole",
"protocol": "blackhole"
}
],
"routing": {
"domainStrategy": "AsIs",
"rules": [
{
"type": "field",
"port": 53,
"outboundTag": "_dns"
}
]
}
}
DNS出站settings对象中的rules数组添加规则即可。通过qType指定1个或多个DNS查询类型编号,指定形式与端口相同;domain数组用于指定此规则所应用的域名。而action字段则指定处理方式,包括“劫持至内部DNS服务器(hijack)”“放行(direct)”“丢弃(drop)”和“返回(return)”。
“返回”表明“返回rCode字段指定的响应代码”,默认值为“成功(No Error,0)”。然而,响应中并不包含实际的响应内容,它本质上仍然拒绝了查询请求。于26.6.1版本之前,rCode字段并不存在,响应代码始终为“已拒绝(Refused,5)”。不过,由于不同应用对DNS响应代码的行为不同,部分手机系统可能因查询被拒绝而出现DNS泛洪,因此将默认响应代码更改为“成功”。
例如,以下配置将劫持所有查询中国域名A与AAAA记录的请求,并对所有非中国域名的HTTPS查询请求返回不包含实际内容的“成功”响应:
{
"rules": [
{
"action": "hijack",
"qType": "1,28",
"domain": [
"geosite:cn"
]
},
{
"action": "return",
"qType": 65,
"rCode": 0,
"domain": [
"!geosite:cn"
]
}
]
}
未命中任何规则时,将应用1个默认规则:将A与AAAA查询引入内部DNS服务器处理,其它类型的请求被丢弃。
Freedom出站规则
Xray-core在26.6.1版本中亦为Freedom出站增加规则,允许根据数据包的网络层协议类型(TCP协议和UDP协议)、目标IP地址和目标端口 而决定放行或阻止它。它被开发的初衷为“由于Freedom出站常常被作为最终数据包出口,相比使用路由规则,于此匹配能够更加彻底地封锁数据包”。
[!info] 信息
此功能与Blackhole出站的作用亦重叠,且更强大,因此最终将取代后者。
配置
于Freedom出站,其settings对象中的finalRules数组配置相关规则即可。
{
"protocol": "freedom",
"settings": {
"finalRules": [
{
"action": "block",
"network": "tcp",
"port": "25,465,587"
},
{
"action": "block",
"ip": ["geoip:cn"],
"blockDelay": "30-90"
}
]
}
}
匹配项包含网络层协议类型(network)、IP地址范围(ip)与目标端口(port)。规则由上至下匹配,命中时便对其执行action所指定的策略:放行(allow)或阻止(block)。上述示例配置将阻止发送至SMTP协议默认端口的数据,且阻止目标IP地址位于中国的数据。
blockDelay字段表示仅当规则命中且措施为阻止时有效,它意味着“将连接转变为黑洞的随机秒数”,默认值为30-90。当需要阻止连接时,若直接断开其连接,连接发起者可能不断重连,因此,Xray-core于此处选择“随机时间内静默丢弃数据,直到时间结束,断开连接,或客户端主动断开连接”[2],而非立刻断开连接。
对于未命中任何规则的数据,将执行以下策略:
- 若来自“VLESS反向代理”,则阻止所有目标IP地址。
- 若来自VLESS、VMESS、Shadowsocks、Trojan、Hysteria或WireGuard入站,则阻止所有私有IP地址(例如
192.168.0.0/16)与保留IP地址(例如127.0.0.1)。 - 否则放行。
如果需要避免默认规则生效,增加1个无匹配项的规则即可:
{
"finalRules": [
{
"action": "allow"
}
]
}
Realm最终伪装层
Xray-core于26.6.1版本推出Realm最终伪装层。它由Hysteria开发者推出,主要利用1个“牵线服务器(Rendezvous Server)”与客户端传输任意UDP协议数据,不依赖Hysteria。此功能主要面向NAT网络、移动网络等环境进行点对点连接呀。它目前适用于除WireGuard以外的任何使用UDP协议的组合,例如“VLESS+XHTTP+TLS(ALPN=HTTP/3)”“mKCP”“Hysteria”等。
Realms
“Realms”功能于Hysteria 2.9.0版本推出。使用“Realms”的Hysteria服务器将首先通过STUN[3]服务器 获知自身的IP地址,随后于牵线服务器注册当前服务器(包括服务器IP地址、穿透随机数与穿透混淆密钥),并保持与牵线服务器的长连接。
客户端需要打洞时,同样从STUN服务器得到自身的外部IP地址,同时得到服务器IP地址。随后,牵线服务器通过SSE协议 向服务器发送“穿透事件(PunchEvent)”,其中包含客户端IP地址,而后客户端与服务器便会互相发送加密后的“穿透数据包问候(PunchPacketHello)”,并互相向对方回应“穿透数据包确认(PunchPacketAck)”以进行打洞(建立点对点连接),开始传输数据。
牵线服务器仅用于记录双方的IP地址和穿透元数据,它不负责中继实际数据。
以下文本应当能够说明具体的启动与打洞流程呀。
C:Realm客户端
S:Realm服务器
RS:牵线服务器
SS:STUN服务器
获取自身的外部网络映射
S ←-----------------→ SS
Register(注册)
S -------------------→ RS
会话ID
S ←------------------- RS
获取自身的外部网络映射
C ←-----------------→ SS
Connect(连接)
C -------------------→ RS
服务器IP地址与穿透元数据
C ←------------------- RS
PunchPacketHello
C ←-----------------→ S
PunchPacketAck
S ←-----------------→ C
实际数据
C ←-----------------→ S
配置
使用Hysteria Realms需要1个牵线服务器,通常建议自行建立1个牵线服务器。此处使用Hysteria官方提供的公益服务器。
于此示例配置开始,它包含1个基本的Hysteria入站:
{
"log": {
"loglevel": "info"
},
"inbounds": [
{
"tag": "hysteria_realms"
"listen": "::",
"port": 48274,
"protocol": "hysteria",
"settings": {
"version": 2,
"users": [
{
"auth": "Hysteria密钥"
}
]
},
"streamSettings": {
"network": "hysteria",
"security": "tls",
"hysteriaSettings": {
"version": 2
},
"tlsSettings": {
"alpn": [
"h3"
],
"certificates": [
{
"certificateFile": "TLS证书文件路径",
"keyFile": "TLS私钥文件路径"
}
]
},
"finalmask": {
"udp": []
}
}
}
],
"outbounds": [
{
"protocol": "freedom"
}
]
}
于finalmask对象的udp数组添加realm类型的最终伪装层便可:
{
"udp": [
{
"type": "realm",
"settings": {
"url": "realm://[email protected]/<牵线服务器路径。建议使用1个随机字符串。>",
"stunServers": [
"stun.nextcloud.com:3478",
"global.stun.twilio.com:3478",
"stun.cloudflare.com:3478",
"stun.l.google.com:19302"
]
}
}
]
}
[!attention] 注意
realm最终伪装层必须位于最外层(数组最底部)。另外,显而易见地,它不可配合端口跳跃使用。
对于出站,通常与服务器同样配置即可呀:
{
"log": {
"loglevel": "info"
},
"inbounds": [
{
"listen": "127.0.0.1",
"port": 10808,
"protocol": "socks",
"settings": {
"auth": "noauth",
"udp": true
}
}
],
"outbounds": [
{
"protocol": "hysteria",
"settings": {
"version": 2,
"address": "127.0.0.1",
"port": 17462
},
"streamSettings": {
"network": "hysteria",
"security": "tls",
"hysteriaSettings": {
"version": 2,
"auth": "Hysteria密码"
},
"finalmask": {
"udp": [
{
"type": "realm",
"settings": {
"url": "realm://[email protected]/<牵线服务器路径>",
"stunServers": [
"stun.nextcloud.com:3478",
"global.stun.twilio.com:3478",
"stun.cloudflare.com:3478",
"stun.l.google.com:19302"
]
}
}
],
"quicParams": {
"keepAlivePeriod": 10
}
}
}
}
]
}
或许可以注意到,以上配置文件的quicParams中包含keepAlivePeriod,指定为10意味着“使用基于QUIC协议的传输方式时,每10秒向服务器发送1个QUIC Ping数据包”,添加此选项缘于大多数NAT主机存在的端口映射有效期——若1个映射长时间没有传输数据,NAT主机便会删除此映射——这意味着双方需要重新进行打洞过程,导致延迟增加。因此,添加此选项以保活NAT映射,往往可以增强Realm的使用体验。
)
对于UDP协议,则按目标地址与端口丢弃。 ↩︎
STUN(Session Traversal Utilities for NAT,NAT会话穿越应用程序)协议专用于 在NAT环境中发现自身的IP地址、当前NAT环境类型与NAT主机为当前本地端口所绑定的公网端口。 ↩︎