diff --git a/go.mod b/go.mod index f0797bc0bfbb..a39162cca6dd 100644 --- a/go.mod +++ b/go.mod @@ -25,6 +25,7 @@ require ( golang.org/x/net v0.48.0 golang.org/x/sync v0.19.0 golang.org/x/sys v0.39.0 + golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 google.golang.org/grpc v1.78.0 google.golang.org/protobuf v1.36.11 @@ -50,7 +51,6 @@ require ( golang.org/x/text v0.32.0 // indirect golang.org/x/time v0.12.0 // indirect golang.org/x/tools v0.39.0 // indirect - golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/proxy/proxy.go b/proxy/proxy.go index 29548d9fb120..aea5c1da5f75 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 + // *switchToDirectCopy remains true (unchanged), so we will retry on the next ReadMultiBuffer call + // This ensures we don't mix encrypted bytes with application data + return buffer, err } *w.input = bytes.Reader{} // release memory w.input = nil