Repository navigation
Concurrent REALITY dials send only the first TCP segment of a ClientHello #7306
Description
Integrity requirements
- I have read all the comments in the issue template and ensured that this issue meet the requirements.
- I confirm that I have read the documentation, understand the meaning of all the configuration items I wrote, and did not pile up seemingly useful options or default values.
- I provided the complete config and logs, rather than just providing the truncated parts based on my own judgment.
- I searched issues and did not find any similar issues.
- The problem can be successfully reproduced in the latest Release
Description
Summary
A single VLESS REALITY dial with fingerprint chrome sends a complete ClientHello. A burst of dials from the same client leaves most connections with only the first TCP segment transmitted. The remainder is never sent and never retransmitted.
Client: Xray 26.10.29 (0afec66, go1.27.3 windows/amd64), v2rayN 7.25.6, Windows 10. Outbound is VLESS, network: raw, REALITY, xtls-rprx-vision, fingerprint chrome, mux off, VLESS encryption mlkem768x25519plus. Server: Xray 26.10.21. nginx 1.31.8 stream on public port 443 had ssl_preread on and proxy_protocol on, and forwarded that SNI to 127.0.0.1:8443. The inbound had acceptProxyProtocol true. preread_timeout is the default 30s. Path MSS was 1452.
The Chrome ClientHello is one TLS record of 1748 bytes. The SNI is inside the first 1452 bytes. The overflow is the tail of key_share (extension 51, 1263 bytes).
One connection through the same ssl_preread completes: 1452 bytes, then 264 bytes 1.7 ms later. nginx logs the SNI and Xray accepts the destination. firefox (1881 bytes) and safari (1529 bytes) also complete on a direct dial. The 517-byte fingerprints (ios, android, edge, 360, qq, random, randomized) are rejected with a real certificate. Every fingerprint this build accepts for REALITY is larger than the path MSS.
In a burst of a few hundred connections, about 280 stayed ESTABLISHED with Recv-Q 1452, bytes_received 1452, data_segs_in 1, and segs_in 3. No second segment and no retransmission. The window after the ACK was about 64 KB. Reading the 1452 bytes out later did not produce the tail. nginx waited 30s, logged an empty SNI, and sent the connection to the HTTPS vhost. Those sockets never appear in the Xray log.
nginx stream access for that client on the public 443 listener:
<client-ip> [31/Oct/2026:21:08:27 +0200] sni= -> nginx_https
<client-ip> [31/Oct/2026:21:24:05 +0200] sni=<reality server name> -> xray_reality
<client-ip> [31/Oct/2026:22:24:31 +0200] sni= -> nginx_https
Counts: 11793 empty SNI to the HTTPS vhost, 2180 sni=- to the same vhost, 117 with the REALITY name proxied to Xray. The empty line is repeated; those three lines are the distinct records. The stream error log has no line for the unfinished ClientHellos.
Single dial on a separate listener, same core and ssl_preread:
<client-ip> sni=<reality server name>
2026/10/31 23:06:19 [info] client <client-ip> connected to 0.0.0.0:18445
2026/10/31 23:06:19 [info] proxy 127.0.0.1 connected to 127.0.0.1:8443
2026/10/31 23:06:20 [info] client disconnected, bytes from/to client:9222/18819, bytes from/to upstream:18819/9222
Detailed info
A single VLESS REALITY dial with fingerprint chrome sends a complete ClientHello. A burst of dials from the same client leaves most connections with only the first segment transmitted. The remainder is never sent and never retransmitted.
Environment
- Client: Xray 26.10.29 (
0afec66,go1.27.3 windows/amd64), started by v2rayN 7.25.6 on Windows 10 - Outbound: VLESS,
network: raw,security: reality,flow: xtls-rprx-vision,fingerprint: chrome, mux off, VLESS encryptionmlkem768x25519plus - Server: Xray 26.10.21. During the failure, nginx 1.31.8
streamlistened on public port 443 withssl_preread onandproxy_protocol on, and forwarded that SNI to127.0.0.1:8443.preread_timeoutis the default 30s. The inbound hadacceptProxyProtocoltrue - Path MSS from the client SYN: 1452
The Chrome ClientHello is one TLS record of 1748 bytes (16 03 01, record length 1743). The SNI is inside the first 1452 bytes. The overflow is the tail of key_share (extension 51, 1263 bytes).
What happened
One connection through nginx 1.31.8 ssl_preread completed. The client sent 1452 bytes, then 264 bytes 1.7 ms later. nginx logged the SNI and proxied to Xray. Xray accepted the destination.
The same client, one dial at a time, directly to Xray:
| Fingerprint | ClientHello | Second segment | Result |
|---|---|---|---|
| chrome | 1748 | 296 bytes, +3.8 ms | REALITY accepted |
| firefox | 1881 | 429 bytes, +4.2 ms | REALITY accepted |
| safari | 1529 | 77 bytes, +2.1 ms | REALITY accepted |
| ios, android, edge, 360, qq, random, randomized | 517 | none | rejected: real certificate, reported as MITM |
chrome_120, firefox_120, safari_16, and ios_14 are not valid names in 26.10.29. Every fingerprint REALITY accepts is larger than this path MSS.
v2rayN then opened a few hundred connections to the nginx listener at once. About 280 stayed ESTABLISHED with Recv-Q 1452, bytes_received 1452, data_segs_in 1, and segs_in 3. No second segment arrived and the first was not retransmitted. The receive window after the ACK was about 64 KB. Reading those 1452 bytes out of the socket later did not make the client send the tail.
nginx held each socket until the 30s preread timeout, then logged an empty SNI and sent the connection to the HTTPS vhost. Xray logged no handshake error for those sockets, because the ClientHello never arrived whole. A few connections in the same burst did complete.
nginx logs
These are the stream access lines for that client while public port 443 was the preread proxy. The empty-SNI line is the same record; the file has 11793 copies of it. sni=- is 2180 copies. The REALITY name was logged 117 times.
<client-ip> [31/Oct/2026:21:08:27 +0200] sni= -> nginx_https
<client-ip> [31/Oct/2026:21:24:05 +0200] sni=<reality server name> -> xray_reality
<client-ip> [31/Oct/2026:22:24:31 +0200] sni= -> nginx_https
The single-dial test, on a separate nginx listener, port 18445, same ssl_preread and the same core:
<client-ip> sni=<reality server name>
2026/10/31 23:06:19 [info] client <client-ip> connected to 0.0.0.0:18445
2026/10/31 23:06:19 [info] proxy 127.0.0.1 connected to 127.0.0.1:8443
2026/10/31 23:06:20 [info] client disconnected, bytes from/to client:9222/18819, bytes from/to upstream:18819/9222
The stream error log has no line for these unfinished ClientHellos. Its only lines are the other client during Xray restarts.
Expected
Each concurrent REALITY dial writes the full ClientHello, as a single dial does. For chrome on this path that is two TCP segments, the second within a few milliseconds of the first.
Actual
Under a burst, most dials transmit exactly one MSS and never continue. ssl_preread waits for the whole TLS record before it reads the SNI (read the whole record before parsing), so those sockets sit until preread_timeout.
Reproduction Method
- Start the server config. It listens on
127.0.0.1:8443and expects a PROXY header, matching the failing setup. - Start this nginx in front of it. It is one complete file: the stream split from the failing setup, plus one HTTPS reverse-proxy vhost.
preread_timeoutis unset, so it stays 30s.www.example.comgoes to Xray. Any other name, including an empty one, goes to127.0.0.1:10443. Put a certificate and key at the twossl_certificatepaths before starting. nginx 1.31.8 accepted this file withnginx -t.
Details
worker_processes 1; error_log /tmp/nginx-preread-error.log info; pid /tmp/nginx-preread.pid;events {
worker_connections 256;
}stream {
map $ssl_preread_server_name $reality_upstream {
hostnames;
www.example.com xray_reality;
default nginx_https;
}upstream xray_reality { server 127.0.0.1:8443; } upstream nginx_https { server 127.0.0.1:10443; } log_format stream443 '$remote_addr [$time_local] sni=$ssl_preread_server_name -> $reality_upstream'; server { listen 18445; ssl_preread on; proxy_pass $reality_upstream; proxy_protocol on; proxy_timeout 7d; proxy_connect_timeout 5s; proxy_socket_keepalive on; access_log /tmp/nginx-preread-access.log stream443; }}
http {
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}map $server_port $forwarded_port { 10443 443; default $server_port; } server { listen 127.0.0.1:10443 ssl proxy_protocol; listen [::1]:10443 ssl proxy_protocol; server_name app.example.com; real_ip_header proxy_protocol; set_real_ip_from 127.0.0.1; set_real_ip_from ::1; absolute_redirect off; port_in_redirect off; ssl_certificate /etc/nginx/cert.pem; ssl_certificate_key /etc/nginx/key.pem; location / { proxy_pass http://127.0.0.1:8080; proxy_http_version 1.1; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Port $forwarded_port; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; } }
}
- Start the client config. On another machine, change
vnext.addressto the server and point the port at the nginx listener. One request through127.0.0.1:10808completes. The second ClientHello segment arrives within a few milliseconds, nginx logs the SNI, and Xray accepts the destination. - Open a few hundred connections at once through that same outbound. The captured failure was v2rayN in TUN mode, mux off, by reconnecting the tunnel or loading a page. Most sockets stay established with only the first 1452 bytes received. They do not appear in the Xray access log. After 30 seconds nginx logs an empty SNI.
Client config
Details
{
"log": {
"loglevel": "debug",
"dnsLog": true
},
"inbounds": [
{
"listen": "127.0.0.1",
"port": 10808,
"protocol": "socks",
"settings": {
"udp": true
},
"sniffing": {
"enabled": true,
"destOverride": [
"http",
"tls",
"quic"
]
}
}
],
"outbounds": [
{
"protocol": "vless",
"tag": "proxy",
"settings": {
"vnext": [
{
"address": "127.0.0.1",
"port": 18445,
"users": [
{
"id": "95c12c6f-cbd7-4426-aa62-ee1a138835d6",
"encryption": "mlkem768x25519plus.native.0rtt.RY2etTg949FuamI2FwFJOUYt_fhqd91WJseEo9LRNyc",
"flow": "xtls-rprx-vision"
}
]
}
]
},
"streamSettings": {
"network": "raw",
"security": "reality",
"realitySettings": {
"serverName": "www.example.com",
"fingerprint": "chrome",
"publicKey": "83Ze49cjQqKd5tihKPACggEnrLLjfKSToGEYDNW_qE4",
"shortId": "137503bab98b4f1a",
"spiderX": "/"
}
},
"mux": {
"enabled": false
}
}
]
}
Server config
Details
{
"log": {
"loglevel": "debug",
"dnsLog": true
},
"inbounds": [
{
"listen": "127.0.0.1",
"port": 8443,
"protocol": "vless",
"settings": {
"clients": [
{
"id": "95c12c6f-cbd7-4426-aa62-ee1a138835d6",
"flow": "xtls-rprx-vision",
"email": "user"
}
],
"decryption": "mlkem768x25519plus.native.600s.OFiiXhxuI3BKSNBrQIIL6BUYCOleh90PedrUbsyOu0g"
},
"streamSettings": {
"network": "raw",
"security": "reality",
"realitySettings": {
"show": false,
"target": "www.example.com:443",
"serverNames": [
"www.example.com"
],
"privateKey": "qI9BO6s1CIAUApmmxjnnMkxrHlOIYKxqcCqQ2cwzE1w",
"shortIds": [
"137503bab98b4f1a"
]
},
"tcpSettings": {
"acceptProxyProtocol": true,
"header": {
"type": "none"
}
}
}
}
],
"outbounds": [
{
"protocol": "freedom",
"tag": "direct"
}
]
}
Client log
Details
No Xray-core client log exists for this failure.
The outbound was running at loglevel warning, and v2rayN 7.25.6 did not write the core log to a file. The panel log has no dial, REALITY, or ClientHello lines, so it is not included.
Server log
Details
Captured from the server core on 31 Oct 2026. The process was Xray 26.10.21 at loglevel warning, so this is not a debug trace. dnsLog was on. A debug rerun has not been captured.The error log for that day is complete. It is only startup lines. There is no handshake error for the sockets that never sent the second segment, because those ClientHellos never reached Xray.
2026/10/31 19:27:18.872438 [Warning] transport/internet/tcp: accepting PROXY protocol
2026/10/31 19:27:18.873030 [Warning] core: Xray 26.10.21 started
2026/10/31 19:31:34.488423 [Warning] core: Xray 26.10.21 started
2026/10/31 20:58:50.954445 [Warning] transport/internet/tcp: accepting PROXY protocol
2026/10/31 20:58:50.954724 [Warning] core: Xray 26.10.21 started
2026/10/31 21:00:32.201279 [Warning] transport/internet/tcp: accepting PROXY protocol
2026/10/31 21:00:50.565875 [Warning] transport/internet/tcp: accepting PROXY protocol
2026/10/31 21:00:50.566079 [Warning] core: Xray 26.10.21 started
2026/10/31 21:02:45.531913 [Warning] transport/internet/tcp: accepting PROXY protocol
2026/10/31 21:02:45.532389 [Warning] core: Xray 26.10.21 started
2026/10/31 21:08:04.366207 [Warning] transport/internet/tcp: accepting PROXY protocol
2026/10/31 21:08:15.495144 [Warning] transport/internet/tcp: accepting PROXY protocol
2026/10/31 21:08:15.495623 [Warning] core: Xray 26.10.21 started
2026/10/31 22:24:22.552364 [Warning] core: Xray 26.10.21 startedThe access log is every acceptance from the affected client between 21:24 and 21:48, plus the single-dial check at 23:06:19. Destination hosts are replaced with dest.example. The unfinished sockets are not in this log.
2026/10/31 21:24:03.996184 from x.x.x.x:22523 accepted tcp:dest.example:443 [in-8443-tcp >> direct] email: user
2026/10/31 21:24:04.114164 from x.x.x.x:22525 accepted tcp:dest.example:80 [in-8443-tcp >> direct] email: user
2026/10/31 21:24:04.187543 from x.x.x.x:22520 accepted tcp:dest.example:80 [in-8443-tcp >> direct] email: user
2026/10/31 21:24:04.263625 from x.x.x.x:22519 accepted tcp:dest.example:443 [in-8443-tcp >> direct] email: user
2026/10/31 21:24:31.441901 from x.x.x.x:20716 accepted tcp:dest.example:443 [in-8443-tcp >> direct] email: user
2026/10/31 21:24:34.260264 from x.x.x.x:22506 accepted tcp:dest.example:7680 [in-8443-tcp >> direct] email: user
2026/10/31 21:24:38.511651 from x.x.x.x:16529 accepted tcp:dest.example:80 [in-8443-tcp >> direct] email: user
2026/10/31 21:24:38.777252 from x.x.x.x:31107 accepted tcp:dest.example:443 [in-8443-tcp >> direct] email: user
2026/10/31 21:24:39.043615 from x.x.x.x:16539 accepted tcp:dest.example:80 [in-8443-tcp >> direct] email: user
2026/10/31 21:24:39.339649 from x.x.x.x:31118 accepted tcp:dest.example:443 [in-8443-tcp >> direct] email: user
2026/10/31 21:24:39.366590 from x.x.x.x:31111 accepted tcp:dest.example:443 [in-8443-tcp >> direct] email: user
2026/10/31 21:24:41.000678 from x.x.x.x:16578 accepted tcp:dest.example:443 [in-8443-tcp >> direct] email: user
2026/10/31 21:24:46.190251 from x.x.x.x:22430 accepted tcp:dest.example:443 [in-8443-tcp >> direct] email: user
2026/10/31 21:24:46.770644 from x.x.x.x:31135 accepted tcp:dest.example:443 [in-8443-tcp >> direct] email: user
2026/10/31 21:24:46.864074 from x.x.x.x:31134 accepted tcp:dest.example:443 [in-8443-tcp >> direct] email: user
2026/10/31 21:24:47.859062 from x.x.x.x:31132 accepted tcp:dest.example:443 [in-8443-tcp >> direct] email: user
2026/10/31 21:24:47.876496 from x.x.x.x:31131 accepted tcp:dest.example:443 [in-8443-tcp >> direct] email: user
2026/10/31 21:24:48.311962 from x.x.x.x:22440 accepted tcp:dest.example:443 [in-8443-tcp >> direct] email: user
2026/10/31 21:24:50.142038 from x.x.x.x:22445 accepted tcp:dest.example:443 [in-8443-tcp >> direct] email: user
2026/10/31 21:25:50.145914 from x.x.x.x:22429 accepted tcp:dest.example:443 [in-8443-tcp >> direct] email: user
2026/10/31 21:25:58.237286 from x.x.x.x:22457 accepted tcp:dest.example:443 [in-8443-tcp >> direct] email: user
2026/10/31 21:26:01.182980 from x.x.x.x:20698 accepted tcp:dest.example:80 [in-8443-tcp >> direct] email: user
2026/10/31 21:26:20.706019 from x.x.x.x:16581 accepted tcp:dest.example:443 [in-8443-tcp >> direct] email: user
2026/10/31 21:26:23.370614 from x.x.x.x:22421 accepted tcp:dest.example:80 [in-8443-tcp >> direct] email: user
2026/10/31 21:26:31.828056 from x.x.x.x:22504 accepted tcp:dest.example:443 [in-8443-tcp >> direct] email: user
2026/10/31 21:26:40.329045 from x.x.x.x:20735 accepted tcp:dest.example:80 [in-8443-tcp >> direct] email: user
2026/10/31 21:26:41.285147 from x.x.x.x:22482 accepted tcp:dest.example:80 [in-8443-tcp >> direct] email: user
2026/10/31 21:26:47.455779 from x.x.x.x:22492 accepted tcp:dest.example:443 [in-8443-tcp >> direct] email: user
2026/10/31 21:26:48.137002 from x.x.x.x:16588 accepted tcp:dest.example:443 [in-8443-tcp >> direct] email: user
2026/10/31 21:26:50.600301 from x.x.x.x:16576 accepted tcp:dest.example:80 [in-8443-tcp >> direct] email: user
2026/10/31 21:26:57.699052 from x.x.x.x:16541 accepted tcp:dest.example:443 [in-8443-tcp >> direct] email: user
2026/10/31 21:26:58.542791 from x.x.x.x:22441 accepted tcp:dest.example:443 [in-8443-tcp >> direct] email: user
2026/10/31 21:28:21.905638 from x.x.x.x:16532 accepted tcp:dest.example:443 [in-8443-tcp >> direct] email: user
2026/10/31 21:37:36.763711 from x.x.x.x:16615 accepted tcp:dest.example:443 [in-8443-tcp >> direct] email: user
2026/10/31 21:39:37.622719 from x.x.x.x:16548 accepted tcp:dest.example:443 [in-8443-tcp >> direct] email: user
2026/10/31 21:42:02.576157 from x.x.x.x:16572 accepted tcp:dest.example:443 [in-8443-tcp >> direct] email: user
2026/10/31 21:48:02.835189 from x.x.x.x:16521 accepted tcp:dest.example:443 [in-8443-tcp >> direct] email: user
2026/10/31 23:06:19.656742 from 127.0.0.1:55768 accepted tcp:example.com:443 [in-8443-tcp >> direct] email: user
Activity