Xray-core新特性使用教程(XHTTP传输方式、Reality后量子签名验证、TLS ECH)

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连接后,它加入了以下流程:

  1. 客户端向代理服务器,发送需要访问的服务器(Proxy Request)。
  2. 客户端通过代理服务器,与目标服务器传输数据。
  3. 代理服务器检测“客户端是否正在与目标服务器建立TLS(版本1.3)连接”
  • 是:
    1. 代理服务器等待客户端完成与目标服务器的TLS握手。
    2. 客户端于第1个被内层TLS隧道加密的数据包中,插入UUID。
    3. 对于随后的数据包,客户端与代理服务器均不再进行二次加密,代理服务器直接将内容发送至目标服务器。
  • 不是:
    1. 客户端与代理服务器将继续加密互相之间的通信。

对于以上思路,尽管已经成功解决问题,却仅可用于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-one stream-up packet-up auto),通常使用auto即可。
  • xPaddingBytes:HTTP头部填充字节数,范围(<最小值>-<最大值>)表明范围内随机。
  • noSSEHeader:用于stream-onestream-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-onestream-uppacket-upauto),通常使用auto即可。
  • xPaddingBytes:HTTP头部填充字节数,范围(<最小值>-<最大值>)表明范围内随机。
  • noGRPCHeader:用于stream-onestream-up模式,设置为false时,向服务器请求时使用gRPC。
  • scMaxEachPostBytes:用于packet-up模式,表明“每个POST请求可包含的最大字节数”,此项可填写范围。
  • scMinPostsIntervalMs:用于packet-up模式,表明“相邻POST请求的最小间隔时间”,单位为毫秒。

或许注意到xmuxdownloadSettings的空缺,它们分别是“多路复用配置”与“下行配置”呀:

{
    "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中的mldsa65SeedSeed后的内容:

{
    "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服务器获取SVCBHTTPS记录。

ECH配置:

{
    "tlsSettings": {
        "echConfigList": "AFv+DQBXAAAgACDTVgijhpwsD/O0+fxXmTsSoBi2HMgXm02eT/m6b999LQAkAAEAAQABAAIAAQADAAIAAQACAAIAAgADAAMAAQADAAIAAwADAAhsaW51eC5kbwAA"
    }
}

从DNS服务器获取

对于证书所颁发域名的DNS服务商,按照以下格式添加SVCBHTTPS记录:

<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",
    }
}