Highlights
- Removing a user, an inbound or an outbound now ends its traffic. Before, HandlerService removal only deleted the entry, and open connections kept relaying (XTLS#5844 reports a user with a 1 GB quota who used 18 GB). Now:
- uTLS ClientHellos come from one builder (#54).
randomizedandrandomizednoalpnalways offer X25519MLKEM768 with its key share. Before, about a third of processes sent a hello that REALITY rejects.- Firefox fingerprints complete against servers that select P-256.
- WebSocket session resumption works.
- The REALITY spider browses over TCP again.
- REALITY servers can require the hybrid key share.
realitySettings.requireMlkem: truerestores upstream's rule. It isfalseby default (#48). - Opt-in HTTP/2 flow control for XHTTP. A governor sizes each stream's HTTP/2 window by what its reader consumes, and a governed server bounds what one connection's client holds. It is off unless enabled (#17, #24, #31, #36, #50, #52).
- Hysteria 2.13.0 and its QUIC stack. The same stack also carries XHTTP/3, MASQUE and DNS-over-QUIC. The QUIC and TLS sniffers reassemble split ClientHellos. Hysteria clients of stopped instances, and canceled dials, no longer linger (#23, #30, #40).
- Upstream v26.10.10 and
mainup toc77a4d0e. This brings:- TLS clients that verify against Xray's bundled roots by default;
- Lua routing and DNS scripts;
- the TUN inbound on Xray's own TCP/IP stack;
- MASQUE WARP and XMUX.
- Go 1.27.2,
golang.org/x/netv0.61.0, and a bounded shutdown. After SIGINT or SIGTERM,xray runexits within 10 s even when a feature'sClosehangs (#29, #38).
Fork fixes
XHTTP flow control (opt-in)
- HTTP/2 flow-control governor (#17, #24, #31, #50, #52). Go's HTTP/2 grants fixed windows: 1 MiB per stream and per connection on the server, 4 MiB per stream on a Go client. A slow reader therefore holds that much unread data per stream, and on a shared XHTTP connection that data queues in front of every other stream.
- Windows. The governor rewrites
SETTINGS_INITIAL_WINDOW_SIZEandWINDOW_UPDATEand never grants more than the stack does. A stream's window starts at 256 KiB, doubles while its reader keeps up, and shrinks by a quarter when the reader slows. Once the reader has settled, the window follows a path model built from LinuxTCP_INFOor from the governor's own PINGs. - Stalled streams. They no longer use up the 1 MiB of connection credit kept for new uploads.
- Enabling.
XRAY_XHTTP_FLOW=onturns the governor on process-wide."h2Flow": {"enabled": true}inxhttpSettingsor itsextraturns it on per inbound or outbound, andfalsethere overrides the variable. - Scope. It covers XHTTP over HTTP/2 on TCP: TLS, REALITY and h2c. With it off, the HTTP/2 wiring is stock.
- Before the client preface. The first PING now waits for the client preface, so a prober that completes TLS and sends nothing sees only SETTINGS (#24).
- Credit quanta. #36 had them; they were withdrawn before release because they slowed a download that followed an upload (#50).
- Windows. The governor rewrites
- Per-connection send window (#36). Many downloads that start at once on a fast link each look fast for a moment, and the client holds their sum. Twenty slow readers at 1 Gbit/s took a client past 40 MiB within about a second, with or without the governor.
- Limit. A governed server now lets one connection's client hold at most
h2Flow.maxConnectionSendWindowbytes of its data, unread or as open window. - Values. Unset or
0means 4 MiB (4194304).-1lifts the limit. Otherwise the value is 65535 to 1 GiB. - Exception. A client that announces the default 65535-byte window, as a governed client does, is not held to the limit.
- Measured. In the author's stand (1 Gbit/s, RTT 50 ms, twenty downloads read at 100 KB/s, client killed above 40 MiB), the client peaked at 24.1 MB instead of 42.7 MB. It was killed in 0 of 5 runs instead of 5 of 5.
- Limit. A governed server now lets one connection's client hold at most
- Receive windows (#17).
h2Flow.maxStreamReceiveWindowandh2Flow.maxConnectionReceiveWindowset the windows this side grants. Values are 65535 to 1 GiB, and0or unset keeps the default. With the governor these are ceilings; without it they are fixed windows. Unset, a governed server grants 6 MiB (6291456), an ungoverned server Go's 1 MiB, and a client Go's 4 MiB per stream. - Later fixes, each measured against the build before it.
REALITY and uTLS fingerprints
-
One ClientHello builder (#54). Every uTLS client is now created in
transport/internet/tls/uclient.go. That covers the TCP, WebSocket, HTTPUpgrade, XHTTP, gRPC, MASQUE and xdrive dialers, the REALITY client, DoH, geodata andxray tls ping.- The builder makes the ClientHello when the client is created, returns any error, and closes the connection on failure.
- Before, the hello was built during the handshake. REALITY ignored a build error and read the server's certificates through
unsafe. - REALITY's
*_pskfingerprints panicked withslice bounds out of range; they now fail withtls: empty psk detected. - Snapshot tests pin every byte of the first ClientHello of every fingerprint on each path.
-
randomizedandrandomizednoalpn(#54). uTLS chose once per process, and independently, whether to offer the X25519MLKEM768 group and whether to send its key share. As a result:- about a third of processes offered the group without the key share, and REALITY rejected them whatever
requireMlkemsaid; - Go 1.27 and OpenSSL 3.5 servers answered with a HelloRetryRequest that uTLS aborted;
- about one process in seven sent a key share outside
supported_groups, which OpenSSL rejects withillegal_parameter.
Both presets now always offer X25519MLKEM768 with its key share. They send key shares for X25519MLKEM768 and X25519 only, and drop P-256 as Chrome does. The raw
hellorandomized*forms keep uTLS's behavior. - about a third of processes offered the group without the key share, and REALITY rejected them whatever
-
Firefox fingerprints against P-256 servers (#54).
firefox,hellofirefox_120andhellofirefox_148send key shares for X25519 and P-256, but uTLS kept only the first one's private key. A server that selected P-256 failed the handshake withinvalid server key shareand a cleartext alert right after the ServerHello, where Firefox goes on (refraction-networking/utls#402). uTLS now comes fromgithub.com/Jolymmiles/utls, a backport of refraction-networking/utls#416 that keeps those keys. The ClientHello is unchanged. -
WebSocket ALPN on a resumed session (#54). For a fingerprint without ALPN, the WebSocket dialer appended its
http/1.1ALPN extension afterpre_shared_key. A handshake that resumed a session then failed withremote error: tls: error decoding message. The extension now goes beforepre_shared_key. A hello without a ticket is unchanged. -
REALITY spider over TCP (#54). When the server shows a real certificate, the REALITY client hands the connection to a spider that browses the site. That happens when the server forwarded the hello to its target, or when a middlebox intercepted it.
- Problem. Since v26.7.24, the fork's TCP dialer closed that connection right after the handshake (
d26e9439), so over TCP the spider sent nothing. - Fix. The spider browses over TCP again. It owns the connection for a lease drawn from 660-720 s, then closes it without GOAWAY or close_notify.
- One connection. It keeps to that connection only. A redirect to another host is refused instead of opening a second HTTP/2 session on it, which the site rejected.
- Problem. Since v26.7.24, the fork's TCP dialer closed that connection right after the handshake (
-
requireMlkemfor REALITY servers (#48). The fork's REALITY server accepts a Client Hello that offers no X25519MLKEM768 at all, through its X25519 key share, and an operator had no way to get upstream's rule back.- Option. Server-side
realitySettings.requireMlkem,falseby default. Withtrue, every hello without an X25519MLKEM768 key share is rejected and sent to the cover target, as upstream does. - Turned away with
true. sing-box v1.14.3, Mihomo v1.19.32 with default options, and Xray'sedge,ios,qqand olderhello*fingerprints.randomis turned away on some process starts. - Accepted with
true.chrome,firefox,safari,randomizedandrandomizednoalpn.
- Option. Server-side
Removal of users, inbounds and outbounds
- Removed VLESS and Trojan users (#43).
RemoveUser(xray api rmu) deleted the user from the validator and left every connection it had open running: plain TCP and UDP, Mux.Cool, XUDP and SMUX carriers, and VLESS reverse bridges.- Fix. Each inbound now tracks the connections its
Processserves for users with an email. A removal cancels and closes them, without waiting for the closes. - Races. A connection that authenticated just before the removal is refused when it registers.
- Shared reverse tags. A reverse tag that another user of the inbound still uses is kept.
- Tested. The process tests pass 37 of 37 cells: direct, SMUX, Vision, WebSocket, HTTPUpgrade, gRPC, XHTTP and UDP. They use Xray, sing-box and Mihomo clients where each cell supports them.
- Fix. Each inbound now tracks the connections its
- Removed VMess users (#44). VMess had the same gap:
RemoveUserremoved the user from its email index and AEAD validator and left its connections running. They now close. The Shadowsocks, Shadowsocks 2022 and Hysteria inbounds still keep a removed user's connections. - Removed inbounds and shutdown (#45).
RemoveInbound(xray api rmi) and instance shutdown closed only the inbound's listeners, so every accepted connection kept relaying until it ended on its own.- Fix. The TCP, Unix-socket and UDP workers now cancel and close their live connections and sessions before the listener closes. They refuse connections that arrive afterwards.
- UDP. An ended UDP session no longer removes a newer session of the same source.
- XHTTP. The XHTTP listener over HTTP/1.1 and HTTP/2 now also closes its HTTP server. That server kept serving the connections a client reused for its next sessions.
- Removed outbounds (#42).
RemoveOutbound(xray api rmo) dropped the handler without closing it.- Impact. A removed WireGuard outbound kept its TUN and device; goroutines grew from 11 to 171 over five add-and-remove cycles. A VLESS reverse kept dialing its server every 2 s, and MASQUE kept its tunnel.
- Fix. The manager now closes a removed handler once, after unpublishing it and without its lock (port of XTLS#7112).
- VLESS reverse. Its
Closecancels its context and closes its bridges. A bridge stalled in a TLS, REALITY or VLESS encryption handshake no longer hangs removal or shutdown. testpre. Pre-connects stop onClose, and a secondCloseno longer panics.
httpTCP header and WebSocket early data (#42, #43). Closing from another goroutine, as removal now does, exposed two races.- A server connection with the
httpTCP header could send both its error header and its response header (HTTP/1.1 400 Bad Request, thenHTTP/1.1 200 OK). The firstWriteor theClose, whichever comes first, now claims the header, and only that header is sent. - The WebSocket early-data connection published its dialed connection without synchronization. It now publishes it under a lock and closes a connection dialed after
Close. A deadline set before the first write returns an error instead of panicking.
- A server connection with the
- VLESS inbound reader (#49). On return, the VLESS inbound gave its first read's buffers back to the pool, even after it had handed the reader to a reverse bridge's Mux.Cool worker, a dispatched link or a fallback copy.
- Impact. Removing the user or closing the instance could end the handler while that consumer still read. That data race could hand a pooled buffer out again while it was in use.
- Fix. The buffers are now released only when the handler returns before handing the reader on.
- Found by.
Pre-release Validationhit the race under-raceon an earlier candidate of this release.
Hysteria and QUIC
- Hysteria 2.13.0 (#23). The in-tree Hysteria implementation is synced with Hysteria 2.13.0.
apernet/quic-gomoves to that release's pin,v0.63.1-0.20261004180939-a10df75c260c. It is also the QUIC stack of XHTTP/3, MASQUE and DNS-over-QUIC.- Masquerade proxy URLs accept Unix sockets.
xray tls echvalidates the public name, randomizes the config ID and takes--maxNameLength, ashysteria echdoes.
- QUIC and TLS sniffing (#23). The QUIC sniffer reassembles a ClientHello split across Initial packets, shuffled, retransmitted or padded, and reads packet numbers as a receiver does.
- Before. A packet number truncated to one byte (256 read as 0) was skipped, and a later overlapping CRYPTO frame overwrote earlier bytes. Either way, a client could be routed by a server name the destination never reads.
- Now. A ClientHello whose repeated CRYPTO data differs is not sniffed. The sniffer no longer writes into the datagrams the dispatcher forwards.
- TLS. The TLS sniffer reassembles a ClientHello fragmented across handshake records.
- Clients of stopped instances (#30). The Hysteria client pool is process-wide.
- Problem. An application that restarts Xray in one process, such as a mobile client on every config change, kept one QUIC connection with keepalives, and one pool entry, per stopped instance (XTLS#7100).
- Fix. The cleaner now closes and removes those clients with one
CONNECTION_CLOSE(H3_NO_ERROR), as the official client does. - Unlike upstream. The cleaner releases the pool lock while it waits, and removes a client only if the pool still holds it.
- Data race.
Instance.IsRunningandAddFeatureno longer raceClose.
- Dial cancellation (#40). A dial waiting for a pooled client ignored its context, and the dial holding the client ran the QUIC handshake and authentication on
context.Background(). A rejected authentication also leaked an HTTP/3 goroutine (XTLS#7120). Waiting dials, handshakes and authentication now honor the dial's context. A rejected authentication still closes the connection with H3_GENERAL_PROTOCOL_ERROR (0x101), as the official client does.
XDNS, mKCP and Gecko
- XDNS server replies and client table (#23).
- Replies. A rejected query that parses gets a minimal well-formed reply: the header, the question and the server's own OPT record. Before, the reply echoed the query's records, so a 510-byte spoofed query could draw a 3.5 KB reply.
- Apex and matching. The zone apex gets an authoritative NOERROR, and only names below the domain are tunnel queries.
- Client table. At most 4096 clients are tracked, each with 128 queued downlink packets. When the table is full, the least recently used client is evicted. Before, a flood of new client IDs grew the table without bound.
- XDNS domain names (#25). The configured domain's ASCII form was computed and then ignored. A trailing dot (
t.example.) panicked the client on its first poll, and internationalised names went out as raw UTF-8.- Fix. Domains are packed as absolute ASCII names. One trailing dot is accepted, and IDN names go out in punycode.
- Rejected names. A name whose ASCII form has an empty label, a label over 63 bytes, more than 255 bytes, or an
xn--label that decodes to nothing fails at config load, with an error that names it.
- mKCP
dnsheader (#28). The header packed its configured domain as raw bytes plus a dot. A trailing dot, an IDN name or an empty label produced a malformed question. A 255-character name panicked the server at inbound start and the client at its first dial. Such names now fail at config load with a suggested spelling, such asxn--bcher-kva.example; names that load pack the same bytes as before and as upstream. - XDNS fragment splicing (#33). The server reassembled upload fragments by client ID and a one-byte fragment ID that repeats every 256 packets, so after a lost fragment a later packet completed the earlier one.
- Impact. mKCP without a mask passed the spliced packet into the TCP stream. With about 14 % receive drops on loopback, a 1 MiB echo through VLESS over mKCP over XDNS came back corrupted in 5 of 5 runs.
- Fix. The reassembly key now includes the nonce that every client repeats in each fragment of a packet. The wire format is unchanged, so existing clients are protected once the server is updated.
- XDNS client
Close(#33). Upstream's synchronous upload (XTLS#7095) holds the client mutex across a resolver write that can block until the client closes.Closeneeds that mutex, so it waited forever. The fork sends without holding it. - Gecko reassembly (#33). Gecko is the
salamandermask withpacketSize. It reassembles by remote address and a one-byte sequential message ID, so after a lost chunk a later message completed the earlier one.- Fix. A message ID that receives a chunk that does not fit is now quarantined until its deadline.
- New rule. A mix that stays consistent cannot be detected at that layer, so Gecko now needs whole-datagram integrity above it. It is accepted only under hysteria, xhttp or masque, or with an
mkcp-legacymask without a header listed before it. - Refused. Every other configuration, and the UDP dialer, the UDP hub and WireGuard whatever the stream network.
DNS outbound
- Timeout-only sessions (#39). For timeout-only sessions, such as XUDP flows, the DNS outbound replaced the session context with
context.Background()and lost its values.sendThrough. A Direct query over XUDP withsendThroughset panicked in the dial with an index out of range. In this fork the panic left the query unanswered, with two goroutines blocked per session.- Own queries. The DNS client's own queries were sent back into the DNS client.
- Fix. The session values are now kept (port of XTLS#6425), so those dials honor
sendThrough, sockopt anddialerProxy.
Lua scripts (upstream feature)
- Scripts in the fork's route picker (#33). Upstream added its routing-script hook only to
Router.PickRoute, while the fork's dispatcher routes through its allocation-freePickRouteTag, sorouting.scriptwould have done nothing. The hook is now inPickRouteTagas well. The dispatcher keeps sniffed HTTP attributes while a script is loaded. - Scripts before
Start(#46). Upstream routes and resolves through a compiled script as soon as the config loads, but the script's Lua pool exists only afterStart. The inbound manager starts before the router, so an early connection or the TUN DNS takeover probe could panic the process. A script now decides only once its pool runs; until then, routing uses the JSON rules and DNS lookups go to the servers.
WireGuard
- TUN close (#33). After a failed bind, wireguard-go closes the TUN itself, and its owner then closes it again. The netstack TUN panicked on that second close and took down a running instance that had added such an inbound. The kernel TUN repeated its teardown and closed its netlink handle concurrently. Both now close once.
Mux.Cool
- Reuse budget (#37). Outbound
mux.maxReuseTimessets how many sessions one Mux.Cool connection of the main pool admits over its lifetime before new sessions open another connection. Until now that was a fixed 128.- Values.
0or omitted keeps 128, and1to60000sets the budget. - Invalid values. Any other value fails the config, from JSON, protobuf or the HandlerService API alike.
- XUDP. The XUDP pool keeps 128.
- Default. This ports XTLS#4231, which raises the default to 60000. The fork keeps 128, because connection lifetime is part of what a censor sees.
- Values.
Shutdown
- Bounded wait for
Close(#38). After SIGINT or SIGTERM,xray runwaited forInstance.Closewithout a bound. When a feature'sClosenever returned, such asapp/geodatawaiting for a download that nothing cancels, the process kept relaying established connections and ignored further signals until SIGKILL.- Fix.
xray runnow waits at most 10 s. On timeout it printsTimed out while closing Xray.and exits 1; aCloseerror printsFailed to close:and exits 1. - Port. This ports XTLS#6164. Unlike that PR, the exit waits at most 1 s for its message, so a stdout that nobody reads cannot hold the process.
- Fix.
Build and tooling
- Go 1.27.2 and
golang.org/x/netv0.61.0 (#29). Go 1.27.2's security fixes reach the XHTTP server: crypto/tls ECH, net/http CONNECT and trailers, net/textproto, and HTTP/2 flow control and HPACK. x/net's matching fixes reach the code that-tags http2legacyselects.x/crypto,x/sync,x/sys,x/textandx/toolsmove with it. - Tooling only (#26, #32, #35, #41, #47, #51, #53). Test, CI and agent changes; shipped binaries are unaffected. The release gate's
-runpattern had skipped the 36-cell VLESS TCP process matrix sincef8f6266f(August 2026); the gate runs it again (#47).
Upstream changes
Merged upstream up to c77a4d0e (includes v26.10.10): v26.10.10 (701af607) through #33, and the seven commits after it through #46.
- TLS client roots (XTLS#7105). TLS clients now verify servers against Xray's built-in root bundle by default, on every platform, instead of the OS store. The bundle is
golang.org/x/crypto/x509roots/fallback, frozen at 2026-10-05.tlsSettings.useSystemCA: truerestores the OS store. Taken as is. - Lua scripts (XTLS#6823, XTLS#7132).
routing.scriptanddns.scripttake a Lua file or an inline script (base64:orbase64+gzip:).- A routing script cannot be combined with
domainStrategyorrules. A DNS script cannot be combined withdisableFallback,disableFallbackIfMatchorenableParallelQuery, or with a server'sdomains,skipFallbackorfinalQuery. - A new command,
xray base64 encode/decode [-gzip]. - Scripts ship enabled, as upstream does. The fork's wiring fixes are under Fork fixes.
- TUN inbound.
- MASQUE.
- The client supports Cloudflare WARP through
warp.privateKey,publicKeyandaddress(XTLS#6878).warp.publicKeyis ignored whentlsSettingssetspinnedPeerCertSha256orverifyPeerCertByName, which allows domain fronting (aa7aa9ad). - The client gets XMUX; without
xmuxit usesmaxConnections1 andhMaxReusableSecs1800-3000 (XTLS#7121). - The client sends Chrome's User-Agent by default.
- The MASQUE outbound now accepts
mux, and MASQUE inbounds accept only XUDP over Mux.Cool. XHTTP inbounds keep accepting Mux.Cool TCP sessions, a fork deviation.
- The client supports Cloudflare WARP through
- Hysteria client (XTLS#7107, XTLS#7119, XTLS#7122). Fork #30 and #40 had ported these first, and both syncs kept the fork's versions:
- the cleaner snapshots the pool, releases its lock, and deletes a client only if the pool still holds it;
- a dial rechecks its context after it takes the client lock;
- a rejected authentication closes the connection with 0x101 before the response body and the transport. #46 reverted an auto-merged upstream body close that ran first.
- From XTLS#7107 only
OmitMaxDatagramFrameSize: truewas taken. Upstream had applied it by date from 2026-09-01, so the change shows only on a client whose clock is set earlier.
- XDNS upload (XTLS#7095). The client sends uploads synchronously instead of through a 16-packet queue. The server drops incomplete fragments after 4 s instead of 8 s, and the per-client fragment cap is gone. It is taken with the fork's
Closefix. In #33's loopback capture, the client also sent a run of about 50 empty polls, 20 ms apart, in the first second after a transfer. - Mux.Cool (XTLS#6822). A UDP packet's destination could be overwritten by the next frame's.
- SOCKS (XTLS#7101). UDP Associate traffic is now counted in stats.
- DNS (XTLS#7091). The pubsub cleanup no longer sizes every topic's slice by the number of topics.
- ECH (XTLS#6246). An ECH config fetched over DoH h2c passes the DoH host to the dialer.
verifyPeerCertByName: "fromMitm"therefore works for it behinddialerProxyin a RAW outbound'stlsSettings. golang.org/x/net0.60.0 (XTLS#7133). Superseded by the fork's 0.61.0 (#29).
Compatibility notes
- Toolchain. Release assets are built with Go 1.27.2 and
golang.org/x/netv0.61.0, and keep-tags http2legacy.- The tag keeps x/net's own
http2.Transportandhttp2.Server. Without it, the XHTTP client's first SETTINGS frame advertisesMAX_FRAME_SIZE1048576 instead of 16384. - Building from source needs Go 1.27.2. With
GOTOOLCHAIN=local, Go 1.27.1 refuses to build.
- The tag keeps x/net's own
- uTLS dependency.
go.modreplacesgithub.com/refraction-networking/utlswithgithub.com/Jolymmiles/utls(v0.0.0-20261013215225-23f702376002, branchfix/keep-secondary-key-shares).- The fork is upstream
aa6edf4b11afplus a backport of refraction-networking/utls#416. The module path stays the same, and the replace is dropped once upstream ships the fix. - Go applies
replaceonly in the main module. A program that imports this tree as a library gets upstream uTLS, without the P-256 fix, unless it adds the samereplace.
- The fork is upstream
- ClientHello changes.
randomizedandrandomizednoalpnnow always carry X25519MLKEM768 with its key share. Their key shares are X25519MLKEM768 and X25519 only, with no P-256 share, which makes the hello about 1.5 KB. A server that accepts only P-256 answers them with a HelloRetryRequest.- For every other fingerprint, the first ClientHello is byte-identical, as the snapshot tests show. The WebSocket ALPN position changes only on resumed sessions.
- The hello is now built when the client is created, so the resumption ticket is taken then.
- The gRPC dialer completes the uTLS handshake under gRPC's connect context instead of on gRPC's first write.
- REALITY spider. Over TCP, the spider now browses after a handshake that showed the cover certificate, as on the other transports. A peer that keeps the connection open sees the client close it after 11-12 minutes, without GOAWAY or close_notify.
- QUIC. The quic-go Chrome parrot now offers the trust_anchors extension (0xca34). It has not been compared with a capture of Chrome.
- XHTTP governor, when enabled.
- Fingerprint. A governed server's SETTINGS carry
INITIAL_WINDOW_SIZE1048576, as stock Go does, andMAX_FRAME_SIZE16384 instead of Go's 1048576. It then sends one PING once the client preface arrives. - Client fingerprint. A governed client announces
INITIAL_WINDOW_SIZE65535, sends a PING, and sends aWINDOW_UPDATEafter a stream's HEADERS. - PINGs. They carry 8 random bytes: one per connection on direct Linux paths, one per second behind a proxy or without
TCP_INFO. - Lowered window. A peer that lowers
SETTINGS_INITIAL_WINDOW_SIZEbelow credit already forwarded has its connection closed, where a stock server applies the change. On a plain TLS inbound an unauthenticated client can trigger this; under REALITY such a client is handed to the cover target before HTTP/2. - Not measured against a traffic classifier.
- Throughput. With the default 4 MiB limit, eight parallel downloads on one connection at RTT 300 ms read 31 MB/s instead of 75 MB/s. Raise
maxConnectionSendWindowor set it to-1. - Download after upload. Under CUBIC, a download that follows an upload on the same connection takes 0.72 s for its first 3 MB, against 0.67 s with the governor off (100 Mbit/s, RTT 50 ms, #50).
- Fingerprint. A governed server's SETTINGS carry
- Go 1.27.2 replies to probes. These replies match any Go 1.27.2 server on the same handlers.
- An HTTP/1.1
CONNECTto the XHTTP server is answered withConnection: close, and the connection is closed. - The Hysteria masquerade
filemode answers aRangeheader with more than 200 ranges with 200 and the whole file. - The
proxymode answersCONNECTwith its own 502 instead of passing through the site's reply.
- An HTTP/1.1
- Upstream defaults.
- TLS roots. A config that relies on a private CA in the OS store needs
"useSystemCA": true, or the CA in its own certificates. - TUN. The inbound runs a new TCP/IP stack that no upstream tagged release has shipped yet.
- Lua. Each concurrent routing or DNS call through a script creates a Lua state of about 177 KiB plus the script's data, with no bound. Nothing is allocated without
script. - Binary size. gopher-lua, gopher-luar and the root bundle grew the Linux/amd64 binary by 3.35 %, as measured in #33.
- TLS roots. A config that relies on a private CA in the OS store needs
- Removal and shutdown.
rmu,rmi,rmoand instance shutdown now cut live traffic. A panel that updates an entry by removing and re-adding it cuts that entry's connections each time, and its clients reconnect.- Still not cut:
- in-flight connections of outbounds without a
Close: VLESS without reverse ortestpre, VMess, Trojan; - legacy Mux.Cool workers, on
rmo; - a removed user's connections on Shadowsocks, Shadowsocks 2022 and Hysteria inbounds;
- REALITY handshakes and cover forwarding already under way, on
rmi.
- in-flight connections of outbounds without a
- No email. Users without an email cannot be removed.
- close_notify. A connection closed during a saturated download can end without the TLS close_notify, as in 2 of 5 loopback captures.
- Cost. Each accepted connection costs one more allocation (160 → 215 ns in
BenchmarkTCPWorkerAcceptedConnection). Each connection of a user with an email costs about 240 ns and 6 allocations.
- Still not cut:
- Exit codes. After a signal,
xray runexits 1 whenClosefails or takes over 10 s. A unit withRestart=on-failure, such as the XTLS/Xray-install unit, then restarts Xray when the SIGTERM came from something other than systemd. - Stricter configuration.
- xdns must be the last
finalmask.udpentry. - Resolver addresses need a dialable port, and an empty address is rejected; write
:53for the local resolver. - The XDNS and mKCP
dnsheader domain rules above apply. IDN XDNS names now go out in punycode, so a client and a server that use one must be upgraded together. - Gecko needs datagram integrity above it; upstream accepts these configurations.
mux.maxReuseTimesoutside 0-60000 fails the config. A nonzero value, ignored before, now takes effect.
- xdns must be the last
- New options.
- Fork.
realitySettings.requireMlkem, defaultfalse.mux.maxReuseTimes; 0 means 128.xhttpSettings.h2Flow:enabled(off by default, or fromXRAY_XHTTP_FLOW=on),maxStreamReceiveWindow,maxConnectionReceiveWindow, andmaxConnectionSendWindow(4 MiB by default on a governed server).
- Upstream.
tlsSettings.useSystemCA(defaultfalse),routing.script,dns.script, and MASQUEwarpandxmux. - Fork-owned protobuf fields. REALITY
Configfield 100,MultiplexingConfigfield 30 and splithttpConfigfield 32.docs/FORK.mdsays what to do if upstream claims them.
- Fork.
- Wire bytes. VLESS, Trojan, REALITY server, Vision, SMUX and Mux.Cool framing bytes are unchanged.
- REALITY version bytes. The REALITY version bytes are now 26.10.13, up from 26.10.6. The HHMM stamp is display-only.
- Fork deviations.
docs/FORK.mdlists every intentional deviation from upstream and the tests that guard it.
Validation
All local gates ran on Linux/amd64 with Go 1.27.2 and GOFLAGS=-tags=http2legacy, in a clean worktree.
-
Release gate on the candidate
ca79f41c.testing/release/structural_presence.sh standardpassed:- vformat,
go vet ./...; - repository-wide
go test, race and checkptr; - the VLESS TCP process matrix 36/36 and the SMUX and H2MUX matrices 72/72 each, three runs each, with Xray, sing-box and Mihomo clients against an Xray server;
- the direct, legacy-mux, XUDP, reverse and WireGuard version-skew gates;
- the presence, XUDP, RVS and WireGuard lifecycle gates.
A first run failed only in
TestUnixInboundProxyProtocolRealityCloseWriteContract, withbind: invalid argument: the local temporary directory made the Unix socket path 108 bytes long, over the kernel's limit. With a shorter temporary directory the whole gate passed. - vformat,
-
Release commit
4ab04d0d. It differs from the candidate only in the stamp. The version test passed, andxray versionprints26.10.13-2347. -
Pre-release Validation passed on
4ab04d0d: 568 tests passed, none failed or were skipped. It covers:- repository-wide unit, race and checkptr;
- the SMUX and H2MUX matrices, three runs each;
- stress, reconnect and hardening profiles;
- the version-skew gates;
- the candidate-versus-v26.8.25-1457 performance and resource budgets, three runs: median ratios 0.953-0.999;
- the RemnaNode configuration contract;
- a 30-minute mixed-path soak, 60 cycles.
-
Tests and Checkings passed on the same commit.
-
Release-flag Linux/amd64 build: static,
-tags=http2legacy,GOAMD64=v1,CGO_ENABLED=0,-trimpath. uTLS resolves togithub.com/Jolymmiles/utls v0.0.0-20261013215225-23f702376002. -
Code review. #54 went through five rounds of independent code review, the last three clean, and through the review bots. Each bot finding was either fixed with a test or answered with a written reason in the pull request.
-
These results are correctness and compatibility evidence. Apart from the measurements quoted above, with their conditions, no performance or traffic-camouflage gain is claimed.