From bdfd1d27b548e7941db294765c01de99c88fdfc2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 11 Jan 2026 10:33:21 +0000 Subject: [PATCH] Fix occasional SSL protocol error in XTLS Vision by deferring direct copy when rawInput has pending data Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com> --- proxy/proxy.go | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/proxy/proxy.go b/proxy/proxy.go index 29548d9f..5bfc026e 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -224,7 +224,8 @@ func (w *VisionReader) ReadMultiBuffer() (buf.MultiBuffer, error) { switchToDirectCopy = &w.trafficState.Outbound.DownlinkReaderDirectCopy } - if *switchToDirectCopy { + if *switchToDirectCopy && w.input == nil { + // Already switched to direct copy mode if w.directReadCounter != nil { w.directReadCounter.Add(int64(buffer.Len())) } @@ -257,11 +258,18 @@ func (w *VisionReader) ReadMultiBuffer() (buf.MultiBuffer, error) { if *switchToDirectCopy { // XTLS Vision processes TLS-like conn's input and rawInput + // input contains decrypted application data - safe to merge if inputBuffer, err := buf.ReadFrom(w.input); err == nil && !inputBuffer.IsEmpty() { buffer, _ = buf.MergeMulti(buffer, inputBuffer) } - if rawInputBuffer, err := buf.ReadFrom(w.rawInput); err == nil && !rawInputBuffer.IsEmpty() { - buffer, _ = buf.MergeMulti(buffer, rawInputBuffer) + // rawInput may contain encrypted bytes for the next TLS record + // If rawInput is not empty, we should NOT switch to direct mode yet + // because those bytes need to be processed by the TLS layer first + if w.rawInput != nil && w.rawInput.Len() > 0 { + // rawInput has pending data - defer direct copy to next read + // Keep *switchToDirectCopy as true so we retry on next read + // This ensures we don't mix encrypted bytes with application data + return buffer, err } *w.input = bytes.Reader{} // release memory w.input = nil