(
XHTTP传输方式
emm,首先介绍XHTTP传输方式,它于2024年12月正式发布,由先前 1位伊朗贡献者所开发的“SplitHTTP”传输方式,并结合Tor网桥Meek的思路,经过一段时间的修改而来呀。
先决概念
了解它的特性之前,首先需要得知部分网络知识呀。
HTTP版本
在日常上网时,可以发现HTTP协议十分普及;而HTTP协议分为3个版本——HTTP/1.1、HTTP/2与HTTP/3,前2者的HTTPS由2个协议组成,即无加密的HTTP与TLS(Transfer Layer Security,传输层安全)_,简略地:前者用于传输具体数据,而后者用于对传输数据进行加密;HTTP/3则有许多更改,最重要的便是“抛弃TCP协议”,转而使用基于UDP的QUIC协议,QUIC本身设计考虑到加密。
TLS握手
TLS用于加密客户端与服务端的连接,它可以在不可信环境中,建立1个可信隧道(即“中间人无法解密其中所传输的内容,亦无法修改它”)。此处无需关心TLS是如何加密数据的,仅仅关注客户端与服务器进行TLS(版本1.3)握手的过程便可(以下部分参考了此文章)(“ACK”并不特指任何1种数据包结构,它用于向对方确认,通常意味着:“接下来将开始传输实际数据”):
当客户端与服务器建立TCP连接后:
客户端:C
服务器:S
Client Hello
C --------------> S
Server Hello
C <-------------- S
Finished
C --------------> S
Client Hello:你好,这里想要建立TLS连接,支持加密方式:……
Server Hello:好的,本次连接使用AES_128_GCM密码套件,此为我的证书:……
Finished:已完成。
接下来,应该是代理相关方面的问题呀:
SNI
在TLS握手过程开始时,SNI_(Server Name Indicator,服务器名称指示)_用于向服务器提供服务器名称(例如,因为1台服务器可能开启多个网站,客户端需要指示,自身欲访问的具体主机名),以便服务器返回正确的证书。SNI通常包含在“Client Hello”中。
TLS中的TLS
若使用代理上网,或许代理协议本身已经有TLS加密(例如“VLESS+TLS/Reality”组合或“VMESS”),此处举例说明:当连接节点时,客户端与代理服务器建立1次TLS连接,因此,其中的所有传输数据均被加密。但是,当客户端尝试要求代理服务器,发出使用TLS的连接时,便会与目标网站 再建立1次TLS连接:
客户端:C
代理服务器:S
目标服务器:D
C <┬--------------> S <--------┐
|代理协议TLS隧道(CS) ↓
└----┬------------------┬> D
|网站TLS隧道(CD) |
└------------------┘
观察“C”与“D”握手时的数据包。此时,“CS”隧道已建立,对于其中的所有数据,均已经被“CS”隧道加密,因此,中间人(例如GFW)无法得知具体传输内容,但是,它可以观察数据包长度:
回顾TLS握手过程,但此次握手发生于已建立的TLS隧道内:
客户端:C
代理服务器:S
目标服务器:D
Proxy Request
C --------------> S
ACK
C <-------------- S
Client Hello
C -----> S -----> D
Server Hello
C <----- S <----- D
Finished
C -----> S -----> D
“Proxy Request”:这里需要你访问服务器“D”。长度:短。长度变化程度:小。
“ACK”:已确认。长度:短。长度变化程度:基本固定。
“Client Hello”:长度:较短。长度变化程度:小。
“Server Hello”:长度:较长。长度变化程度:大。
“Finished”:长度:短。长度变化程度:基本固定。
可以见到,在TLS中传输TLS,将导致出现时序特征。审查者只需观察,是否出现特定范围的数据包长度序列,便可十分精准地识别代理流量呀。
XTLS Vision
于2022年10月时,Xray-core发布了XTLS Vision,以解决以上问题。当客户端与代理服务器成功建立TLS连接后,它加入了以下流程:
- 客户端向代理服务器,发送需要访问的服务器(Proxy Request)。
- 客户端通过代理服务器,与目标服务器传输数据。
- 代理服务器检测“客户端是否正在与目标服务器建立TLS(版本1.3)连接”
- 是:
- 代理服务器等待客户端完成与目标服务器的TLS握手。
- 客户端于第1个被内层TLS隧道加密的数据包中,插入UUID。
- 对于随后的数据包,客户端与代理服务器均不再进行二次加密,代理服务器直接将内容发送至目标服务器。
- 不是:
- 客户端与代理服务器将继续加密互相之间的通信。
对于以上思路,尽管已经成功解决问题,却仅可用于Raw(TCP)传输方法,不可配合WebSocket或gRPC使用。
自
25.9.5版本发布后,当开启“VLESS加密”时,XTLS Vision则能够配合WebSocket、gRPC与XHTTP使用啦。
特性
- 将代理协议伪装为正常的HTTP数据或gRPC数据,支持3种HTTP版本(因此支持TCP与UDP),均可穿过CDN。
- 使用“HTTP头部填充”,解决“TLS中的TLS”特征。
- 自带多路复用,降低延迟(不过,多线程测速时,速度可能并不达预期,但实际使用过程中,体验应当更好呀)。
- 上下行分离,可反审查,亦可用于优化路由路径以降低延迟。
另外,可参考开发者制作其的考虑:
我当年看好TLS以及TLS上流量的时序、长度特征混淆也是同理。多年经验告诉我们GFW不会永久封锁大型CDN的整个IP,否则会波及太多常规网站,那么对于XHTTP,我们最初的目标就是把它隐藏在众多各种各样的CDN后面 。
模式
XHTTP拥有3种模式:
- 双向流式上下行(Stream One)
- 流式上下行(Stream Up)
- 分包上行,流式下行(Packet Up)
通常来说,第3个模式泛用性最高,第2个模式泛用性次之,第1个模式亦次之;不过,目前来说,3种模式的速度,应当是差不多的(通常来说,设置mode字段为auto即可,服务器便会遵循客户端模式,而客户端则通过部分策略,选择合适的模式呀)。
“包”与“流”
此处的“包”并不指网络层的“数据包”,而是应用层的“HTTP请求/响应”。使用例子说明:当存在以下关系时:
客户端:C
内容分发网络:CDN
代理服务器:S
目标服务器:D
流 流
C <----> CDN <----> S <-> D
本质上,“CDN”像1个反向代理,假设“S”返回的响应体有大量数据,“CDN”不会等待数据全部发送完毕,而是将来自“S”的数据,即时地向“C”发送,因此不会牺牲速率呀。
而若是完全的“分包”(类似于Tor的Meek网桥),由于“S”必须得到“C”的所有POST请求,与“D”通信,再进行回应。
配置
入站
以下配置文件,包含1个“VLESS+Raw”组合入站:
{
"routing": [
{
"type": "field",
"ip": [
"geoip:private"
],
"outboundTag": "blackhole"
}
],
"inbounds": [
{
"tag": "vless",
"listen": "0.0.0.0",
"port": 443,
"settings": {
"clients": [
{
"id": "替换为具体的UUID",
"email": "[email protected]",
"flow": "none"
}
],
"decryption": "none"
},
"streamSettings": {
"network": "raw",
"security": "none"
}
}
],
"outbounds": [
{
"tag": "freedom",
"protocol": "freedom"
},
{
"tag": "blackhole",
"protocol": "blackhole"
}
]
}
关注streamSettings,设置network:
{
"network": "xhttp"
}
如此便可使用XHTTP。但若不进行其它设置,便无法感受其优势。可以选择,尝试加入Reality呢。
{
"security": "reality",
"realitySettings": {
"target": "www.bing.com:443",
"serverNames": [
"www.bing.com"
],
"shortIds": [
""
],
"privateKey": "通过命令“xray x25519”以生成私钥"
}
}
或者TLS(需要由CA机构签发的TLS证书)。
{
"security": "reality",
"tlsSettings": {
"certificates": [
{
"certificateFile": "证书文件路径"
"keyFile": "私钥文件路径"
}
]
}
}
emm,若前置Nginx等反向代理应用,则可由其处理TLS(对于Nginx,使用grpc_pass以反向代理流量)。
接下来,便是于streamSettings中,XHTTP的可选设置呀。
{
"xhttpSettings": {
"host": "",
"path": "/",
"mode": "auto",
"extra": {
"xPaddingBytes": "100-1000",
"noSSEHeader": false,
"scMaxEachPostBytes": 1000000,
"scMaxBufferedPosts": 30,
"scStreamUpServerSecs": "20-80"
}
}
}
host:允许的主机名,留空则意味着不限制客户端所发送的主机名。path:允许的路径。mode:模式(stream-onestream-uppacket-upauto),通常使用auto即可。xPaddingBytes:HTTP头部填充字节数,范围(<最小值>-<最大值>)表明范围内随机。noSSEHeader:用于stream-one与stream-up模式,设置为false时,将以Content-Type: text/event-stream回应客户端的gRPC请求。scMaxEachPostBytes:用于packet-up模式,表明“拒绝字节数大于此配置的客户端POST请求”。scMaxBufferedPosts:用于packet-up模式,表明“在单个代理请求中,服务器最多缓存的POST请求数量”。scStreamUpServerSecs:用于stream-up模式,当处于此模式时,服务器将发送xPaddingBytes所示的字节数,以保活连接。
于日常使用方面,基本上不必触碰extra对象呀。
出站
以下配置文件,包含基本的“VLESS+Raw”组合出站:
{
"routing": [
{
"type": "field",
"ip": [
"geoip:private",
"geoip:cn"
],
"outboundTag": "freedom"
}
],
"outbounds": [
{
"tag": "vless",
"protocol": "vless",
"settings": {
"vnext": [
{
"address": "代理服务器地址",
"port": 443,
"users": [
{
"id": "替换为具体的UUID",
"email": "[email protected]",
"encryption": "none",
"flow": "none"
}
]
}
]
},
"streamSettings": {
"network": "xhttp",
"security": "none"
}
},
{
"tag": "freedom",
"protocol": "freedom"
},
{
"tag": "blackhole",
"protocol": "blackhole"
}
]
}
尝试将其更改为XHTTP呀。
{
"network": "xhttp",
"xhttpSettings": {
"host": "",
"mode": "auto",
"extra": {
"xPaddingBytes": "100-1000",
"noGRPCHeader": false,
"scMaxEachPostBytes": 1000000,
"scMinPostsIntervalMs": 30,
"xmux": {},
"downloadSettings": {}
}
}
}
host:服务器中设置的主机名,留空意味着跟随address字段。path:服务器中设置的路径。mode:模式(stream-one、stream-up、packet-up、auto),通常使用auto即可。xPaddingBytes:HTTP头部填充字节数,范围(<最小值>-<最大值>)表明范围内随机。noGRPCHeader:用于stream-one与stream-up模式,设置为false时,向服务器请求时使用gRPC。scMaxEachPostBytes:用于packet-up模式,表明“每个POST请求可包含的最大字节数”,此项可填写范围。scMinPostsIntervalMs:用于packet-up模式,表明“相邻POST请求的最小间隔时间”,单位为毫秒。
或许注意到xmux与downloadSettings的空缺,它们分别是“多路复用配置”与“下行配置”呀:
{
"xmux": {
"maxConcurrency": 1,
"maxConnections": 0,
"cMaxReuseTimes": 0,
"hMaxRequestTimes": "600-900",
"hMaxReusableSecs": "1800-3000",
"hKeepAlivePeriod": 0
}
}
maxConcurrency:单连接可承载的最大代理请求数,与maxConnections互斥。此项可填写范围。maxConnections:最大连接数,与maxConcurrency互斥。此项可填写范围。cMaxReuseTimes:单连接可传输的最大代理请求次数(0表明“无限制”)。此项可填写范围。hMaxRequestTimes:单连接可传输的最大HTTP请求数(0表明“无限制”。通常来说,packet-up模式将产生多个请求,stream-up将产生2个请求,stream-one仅产生1个请求)。此项可填写范围。hMaxReusableSecs:单连接的最大可用时间(0表明“无限制”)。此项可填写范围。hKeepAlivePeriod:保活包发送间隔时间(秒数。0表明自动选择默认值)。此项不可填写范围。
对于downloadSettings,它用于上下行分离,设置下行的信息。不过,它有以下要求:
无论是CDN加反代,还是Reality加回落,只要最终以同一路径(Path)抵达同一代理服务器上的同一XHTTP入站即可。
(上下行分离包含更多内容,或许只得留待以后的文章呀)
好的,XHTTP应该已经完毕。
Reality后量子验证
Xray-core于2025年5月16日的发布中,若Reality的目标网站允许 X25519MLKEM768(1种后量子密钥交换方式),则代理服务器与客户端的加密亦会使用它 作为密钥交换方式。而2025年7月26日的发布中,Reality则增加了ML-DSA-65签名验证。
从此处引用:
为了避免攻击者拿到Reality客户端配置,再等到未来的量子计算机破解X25519后,对未来的Reality连接进行MITM,Reality协议新增抗量子的ML-DSA-65签名验签机制:执行
./xray mldsa65生成ML-DSA-65密钥对,服务端持有ML-DSA-65私钥(配置名mldsa65Seed) ,处理连接时会对“cert’s signature + raw Client Hello + raw Server Hello”的组合进行额外签名并填到cert’s ExtraExtensions;客户端若持有ML-DSA-65公钥(配置名mldsa65Verify),在原有的Reality证书验证阶段会进行额外验证。由于ML-DSA-65被设计为抵抗量子计算机,即使攻击者拿到了客户端配置,即公钥,也无法在未来反推出私钥,进而MITM。
目标网站
为了使用它,首先需要寻找1个合适的目标网站,网站满足以下要求:
- 支持使用 X25519MLKEM768。
- 证书长度达到3500字节以上
可以通过命令xray tls ping <目标网站>以检查是否满足需求,参考以下示例输出:
> xray tls ping github.io
TLS ping: github.io
Using IP: 185.199.111.153:443
-------------------
Pinging without SNI
Handshake succeeded
TLS Version: TLS 1.3
TLS Post-Quantum key exchange: true (X25519MLKEM768)
Certificate chain's total length: 4645 (certs count: 3)
Cert's signature algorithm: SHA256-RSA
Cert's publicKey algorithm: RSA
Cert's allowed domains: [*.github.io *.github.com *.githubusercontent.com github.com github.io githubusercontent.com www.github.com]
-------------------
Pinging with SNI
Handshake failure: read tcp 192.168.40.103:56763->185.199.111.153:443: wsarecv: An existing connection was forcibly closed by the remote host.
-------------------
TLS ping finished
关注以下文本:
TLS Post-Quantum key exchange: true (X25519MLKEM768)
Certificate chain's total length: 4645 (certs count: 3)
第1行表明“是否支持后量子密钥交换”,而第2行则为“证书总长度”,它需要大于3500字节呀。
配置
找到合适的网站后,通过命令xray mldsa65,便可生成ML-DSA-65的私钥与公钥。
> xray mldsa65
Seed: ************
Verify: **************************
对于入站,仅需设置realitySettings中的mldsa65Seed为Seed后的内容:
{
"realitySettings": {
"mldsa65Seed": "************"
}
}
对于出站,设置realitySettings中的mldsa65Verify,为Verify后的内容:
{
"realitySettings": {
"mldsa65Verify": "**************************"
}
}
在服务器设置ML-DSA-65私钥后,若客户端不设置mldsa65Verify,仍然可以成功连接,只是不会执行签名验证过程呀。
TLS ECH
在同样的25.7.26版本中,其为TLS增加了“加密客户端问候(Encrypted Client Hello)”,使得TLS握手过程中,能够加密Client Hello中先前的明文内容。
在Client Hello中包含了SNI,它虽然使服务器得知应该使用的证书,亦使得中间人得知客户端正在访问的网站域名。ECH向外部暴露1个主机名,而实际的主机名被放置于加密的Client Hello ECH扩展中。
配置
首先,需要通过命令xray tls ech --serverName <主机名>生成ECH服务器密钥(Server Key)与其配置(Config):
> xray tls ech --serverName linux.do
ECH config list:
AFv+DQBXAAAgACDTVgijhpwsD/O0+fxXmTsSoBi2HMgXm02eT/m6b999LQAkAAEAAQABAAIAAQADAAIAAQACAAIAAgADAAMAAQADAAIAAwADAAhsaW51eC5kbwAA
ECH server keys:
**********
对于入站,设置tlsSettings中的echServerKeys字段,为ECH server keys:后的内容便可:
{
"tlsSettings": {
"echServerKeys": "**********"
}
}
对于出站,可以选择填写ECH配置,或者从DNS服务器获取SVCB或HTTPS记录。
ECH配置:
{
"tlsSettings": {
"echConfigList": "AFv+DQBXAAAgACDTVgijhpwsD/O0+fxXmTsSoBi2HMgXm02eT/m6b999LQAkAAEAAQABAAIAAQADAAIAAQACAAIAAgADAAMAAQADAAIAAwADAAhsaW51eC5kbwAA"
}
}
从DNS服务器获取
对于证书所颁发域名的DNS服务商,按照以下格式添加SVCB或HTTPS记录:
<SvcPriority(记录优先级,若没有其它“SVCB”或“HTTPS”,可填写“1”)> <TargetName(目标名称,填写“.”)> alpn=h3,h2 ech=<ECH配置>
假若linux.do的DNS管理者需要配置ECH,他可能如此添加记录:
名称:linux.do
类型:HTTPS
内容:
1 . alpn=h3,h2 ech=AFv+DQBXAAAgACDTVgijhpwsD/O0+fxXmTsSoBi2HMgXm02eT/m6b999LQAkAAEAAQABAAIAAQADAAIAAQACAAIAAgADAAMAAQADAAIAAwADAAhsaW51eC5kbwAA
于echConfigList字段填写DNS服务器即可:
{
"tlsSettings": {
// 使用DoH
"echConfigList": "https://1.1.1.1/dns-query",
// 使用DNS
// "echConfigList": "udp://1.1.1.1",
// 使用DoH,并使用指定域名的DNS记录
// "echConfigList": "linux.do+https://1.1.1.1/dns-query",
}
}
)