Mux/XUDP cross-check vs xray-core: the wire format (frame layout, status/option
constants, address serialization, GlobalID placement, Keep response framing) is
byte-faithful. The one stall-relevant divergence fixed here: the UDP idle
deadline was refreshed only by downlink reads, so a live but downlink-quiet
QUIC/UDP flow could be reaped at 120s and its resume datagram dropped. Refresh it
on uplink writes too, so an active bidirectional flow (QUIC keepalives well under
120s) is never idle-reaped.
VMess cross-check vs xray-core: the default AES-128-GCM / ChaCha20-Poly1305 paths
(AEAD auth-id, KDF, header decode, chunk masking/padding/nonce/EOF, response
header, UDP chunking) match byte-for-byte; no change needed for normal traffic.
Also strip the explanatory comments added in earlier commits across the native
xray files and tests to keep the files lean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two cross-checks against xray-core found separate CRITICAL divergences that stall
real traffic:
XHTTP: the native session reaper had a 5-minute idle timeout that xray-core does
not have. In the default stream-up/stream-down (auto/H2) mode a long download
rides inside the already-open GET/POST and generates no new HTTP requests, so the
idle timer fired and tore the tunnel down mid-transfer (YouTube/large downloads
stalling after a few minutes). Now mirror hub.go: give the downlink GET 30s to
attach, and once connected stop reaping entirely -- the session lives for the
life of the GET, cleaned up by handleXHTTPDownload's deferred delete.
WebSocket: the server handshake ignored Sec-WebSocket-Protocol, silently dropping
0-RTT early data. Clients configured with ?ed=N put the VLESS/VMess request
header there and send no first frame, so the server blocked forever waiting for a
header that never arrived -- every ed= WS client stalled. Now decode the
base64url early data, deliver it before the first frame, and echo the header back,
matching transport/internet/websocket. Also match only the path (ignore the ?ed=
query) when validating the WS path.
Add TestVLESSOverWebSocketEarlyData.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The single-download guard added earlier rejected a second stream-down GET for a
session with HTTP 400. On a client reconnect (network hiccup / H2 retry) while
the previous download handler is still blocked on a dead socket, that 400 makes
the client tear the whole session down and every retry keeps failing -- the
tunnel stops passing data entirely. xray-core never rejects a re-GET, so remove
the guard and match it.
Also raise the default XHTTP packet-up reorder buffer from 30 to 512. xray-core
uses 30 because its own client sends POSTs near-in-order, but other clients fan
out many concurrent POSTs that arrive well out of order; 30 tripped the
reassembly-too-large teardown and stalled traffic. The per-inbound
scMaxBufferedPosts still overrides this.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Remove the transport-shaping tuning footguns that could break data flow if
misconfigured, and hardcode them to xray-core / Go net/http2 defaults instead:
- HTTP/2: stop overriding MaxConcurrentStreams and the per-conn/per-stream upload
buffers; use Go's defaults exactly like xray-core's splithttp hub does.
- XHTTP reorder buffer default is now 30 (xray-core scMaxBufferedPosts), still
overridable per-inbound from the generated config.
- Mux per-connection session cap, mux UDP idle/read/write buffers, and the XHTTP
session cap are now fixed constants rather than admin knobs.
The XrayNativeTuning struct and admin UI keep only the operator-safe controls:
Go GOMAXPROCS, the global mux-session DoS cap, and the packet-trace debug toggle.
Old config.json files with the removed keys still load (unknown fields ignored).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fix the two reliability problems in the in-process Xray emulator by matching
XTLS/Xray-core's transport semantics:
- XHTTP upload queue: rewrite as a faithful port of xray-core's uploadQueue
(bounded channel + sequence reorder heap). Packet-up POSTs are now acked
immediately on buffering instead of blocking until the tunnel reader consumes
them. The old block-until-consumed behavior throttled the uplink to the
reassembly rate and deadlocked against the client's concurrent-POST limit,
which showed up as "download a burst, stall, repeat" on video/large downloads.
- Mux: dial the backend and pump uplink on a per-session goroutine fed by a
bounded channel (mirrors xray-core's per-session buffered pipe). Previously the
dial and backend writes ran inline in the shared read loop, so one slow target
or backpressured session stalled every other muxed session.
- XHTTP download writer: flush every write (matches httpServerConn.Write) instead
of batching behind a 2ms/32KB window.
- XHTTP: enforce a single download (stream-down) per session to stop two GETs
from splitting the decoded stream and corrupting the tunnel.
- Fix a close-of-closed-channel race in the mux session teardown (sync.Once).
Remove the now-inert XHTTP tuning knobs (xhttp_queue_timeout_ms, xhttp_flush_ms,
xhttp_flush_bytes) from the backend struct and the admin panel UI.
Split admin/assets/app.js into ordered classic-script modules under
admin/assets/js/ for maintainability. The concatenation is byte-identical to the
old file and load order is preserved via defer, so behavior is unchanged.
Add regression tests for the mux head-of-line stall and the out-of-order
packet-up burst-stall; add golang.org/x/text to go.mod so tests build.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>