Skip to content

reality: propagate the dest's close to the client in fallback - #6

Open
huangyingw wants to merge 1 commit into
MetaCubeX:v1.7.0-mod-metafrom
huangyingw:reality-propagate-dest-close
Open

huangyingw wants to merge 1 commit into
MetaCubeX:v1.7.0-mod-metafrom
huangyingw:reality-propagate-dest-close

Conversation

@huangyingw

Copy link
Copy Markdown

When REALITY falls back to relaying an unauthenticated connection to the dest, it forwards the dest's bytes faithfully but never forwards the close.

Teardown sits behind waitGroup.Wait(), which waits on the upload io.Copy — and that copy only returns once the client closes. A client that simply never closes keeps the connection open indefinitely, long after the real dest has dropped it.

This is remotely observable, and it distinguishes a REALITY server from the site it is imitating.

Measurement

Against sing-box 1.13.14 (which pins metacubex/utls v1.8.4), dest = www.intel.com.
Probe: one connection, send a 5-byte record header plus 32 random bytes, read the alert, then wait.

alert afterwards
REALITY server (unpatched) 1503030002020a still open at 12s (2/2)
real www.intel.com 1503030002020a closes immediately (2/2)

The alert itself comes back byte-identical — the relay forwards it faithfully. Only the shutdown is missing. Cost to an attacker: one connection, 37 bytes, wait a second.

Fix

Half-close towards the client once the download copy returns, so the client observes the same shutdown the dest performed. Guarded by a type assertion, so it is a no-op where the underlying conn cannot half-close.

After the patch, same probe, same config:

alert afterwards
REALITY server (patched) 1503030002020a closes at 0ms (2/2)
real www.intel.com 1503030002020a closes at 1ms

Verification

  • Built sing-box v1.13.14 against this branch via go mod edit -replace.
  • Behaviour above reproduced on that build; unpatched build of the same version still hangs.
  • Regression: a normal VLESS + REALITY + xtls-rprx-vision client through the patched server proxies fine, 4/4 requests.
  • go build ./... clean, gofmt clean.

Prior art

XTLS/REALITY already does exactly this — see the underlying.CloseWrite() call right after the download io.Copy in its tls.go, where underlying is asserted to CloseWriteConn, with a comment stating the intent. This change brings the same behaviour here with a minimal, non-invasive assertion rather than changing the type of underlying.

When REALITY falls back to relaying an unauthenticated connection to the
dest, it forwards the dest's bytes faithfully but never forwards the
close. Teardown sits behind waitGroup.Wait(), which waits on the upload
io.Copy, and that copy only returns once the client closes. A client that
never closes therefore keeps the connection open indefinitely, long after
the real dest has dropped it.

That is remotely observable and distinguishes a REALITY server from the
site it imitates. Measured against sing-box 1.13.14 (metacubex/utls
v1.8.4) with dest www.intel.com: send a 5-byte record header plus 32
random bytes; both the REALITY server and the real dest return the same
alert 1503030002020a, but the real dest closes immediately while the
REALITY server is still open after 12 seconds. One connection, 37 bytes.

Propagate the close with a half-close once the download copy returns, so
the client observes the same shutdown the dest performed. Guarded by a
type assertion, so it is a no-op where the underlying conn cannot
half-close.

XTLS/REALITY already does this; see the underlying.CloseWrite() call
after the download io.Copy in its tls.go, where underlying is asserted to
CloseWriteConn.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant