Parcourir la source

Add XTLS RPRX's Vision (#1235)

* Add XTLS RPRX's Vision

* Add helpful warning when security is wrong

* Add XTLS padding (draft)

* Fix  number of packet to filter

* Xtls padding version 1.0 and unpadding logic
yuhan6665 il y a 3 ans
Parent
commit
5e695327b1

+ 13 - 0
common/buf/multi_buffer.go

@@ -203,6 +203,19 @@ func SplitSize(mb MultiBuffer, size int32) (MultiBuffer, MultiBuffer) {
 	return mb, r
 }
 
+// SplitMulti splits the beginning of the MultiBuffer into first one, the index i and after into second one
+func SplitMulti(mb MultiBuffer, i int) (MultiBuffer, MultiBuffer) {
+	mb2 := make(MultiBuffer, 0, len(mb))
+	if i < len(mb) && i >= 0 {
+		mb2 = append(mb2, mb[i:]...)
+		for j := i; j < len(mb); j++ {
+			mb[j] = nil
+		}
+		mb = mb[:i]
+	}
+	return mb, mb2
+}
+
 // WriteMultiBuffer writes all buffers from the MultiBuffer to the Writer one by one, and return error if any, with leftover MultiBuffer.
 func WriteMultiBuffer(writer io.Writer, mb MultiBuffer) (MultiBuffer, error) {
 	for {

+ 4 - 4
infra/conf/vless.go

@@ -53,8 +53,8 @@ func (c *VLessInboundConfig) Build() (proto.Message, error) {
 		account.Id = u.String()
 
 		switch account.Flow {
-		case "", "xtls-rprx-origin", "xtls-rprx-direct":
-		case "xtls-rprx-splice":
+		case "", vless.XRO, vless.XRD, vless.XRV:
+		case vless.XRS:
 			return nil, newError(`VLESS clients: inbound doesn't support "xtls-rprx-splice" in this version, please use "xtls-rprx-direct" instead`)
 		default:
 			return nil, newError(`VLESS clients: "flow" doesn't support "` + account.Flow + `" in this version`)
@@ -182,8 +182,8 @@ func (c *VLessOutboundConfig) Build() (proto.Message, error) {
 			account.Id = u.String()
 
 			switch account.Flow {
-			case "", "xtls-rprx-origin", "xtls-rprx-origin-udp443", "xtls-rprx-direct", "xtls-rprx-direct-udp443":
-			case "xtls-rprx-splice", "xtls-rprx-splice-udp443":
+			case "", vless.XRO, vless.XRO + "-udp443", vless.XRD, vless.XRD + "-udp443", vless.XRV, vless.XRV + "-udp443":
+			case vless.XRS, vless.XRS + "-udp443":
 				if runtime.GOOS != "linux" && runtime.GOOS != "android" {
 					return nil, newError(`VLESS users: "` + account.Flow + `" only support linux in this version`)
 				}

+ 1 - 1
proxy/vless/encoding/addons.go

@@ -11,7 +11,7 @@ import (
 
 func EncodeHeaderAddons(buffer *buf.Buffer, addons *Addons) error {
 	switch addons.Flow {
-	case vless.XRO, vless.XRD:
+	case vless.XRO, vless.XRD, vless.XRV:
 		bytes, err := proto.Marshal(addons)
 		if err != nil {
 			return newError("failed to marshal addons protobuf value").Base(err)

+ 293 - 10
proxy/vless/encoding/encoding.go

@@ -3,11 +3,15 @@ package encoding
 //go:generate go run github.com/xtls/xray-core/common/errors/errorgen
 
 import (
+	"bytes"
 	"context"
+	"crypto/rand"
 	"fmt"
 	"io"
+	"math/big"
 	"runtime"
 	"syscall"
+	"time"
 
 	"github.com/xtls/xray-core/common/buf"
 	"github.com/xtls/xray-core/common/errors"
@@ -18,6 +22,7 @@ import (
 	"github.com/xtls/xray-core/features/stats"
 	"github.com/xtls/xray-core/proxy/vless"
 	"github.com/xtls/xray-core/transport/internet/stat"
+	"github.com/xtls/xray-core/transport/internet/tls"
 	"github.com/xtls/xray-core/transport/internet/xtls"
 )
 
@@ -25,6 +30,11 @@ const (
 	Version = byte(0)
 )
 
+var tls13SupportedVersions = []byte{0x00, 0x2b, 0x00, 0x02, 0x03, 0x04}
+var tlsClientHandShakeStart = []byte{0x16, 0x03}
+var tlsServerHandShakeStart = []byte{0x16, 0x03, 0x03}
+var tlsApplicationDataStart = []byte{0x17, 0x03, 0x03}
+
 var addrParser = protocol.NewAddressParser(
 	protocol.AddressFamilyByte(byte(protocol.AddressTypeIPv4), net.AddressFamilyIPv4),
 	protocol.AddressFamilyByte(byte(protocol.AddressTypeDomain), net.AddressFamilyDomain),
@@ -176,26 +186,89 @@ func DecodeResponseHeader(reader io.Reader, request *protocol.RequestHeader) (*A
 	return responseAddons, nil
 }
 
-func ReadV(reader buf.Reader, writer buf.Writer, timer signal.ActivityUpdater, conn *xtls.Conn, rawConn syscall.RawConn, counter stats.Counter, sctx context.Context) error {
+func ReadV(reader buf.Reader, writer buf.Writer, timer signal.ActivityUpdater, conn *xtls.Conn, rawConn syscall.RawConn, counter stats.Counter, ctx context.Context) error {
 	err := func() error {
 		var ct stats.Counter
 		for {
 			if conn.DirectIn {
 				conn.DirectIn = false
-				if sctx != nil {
-					if inbound := session.InboundFromContext(sctx); inbound != nil && inbound.Conn != nil {
+				if inbound := session.InboundFromContext(ctx); inbound != nil && inbound.Conn != nil {
+					iConn := inbound.Conn
+					statConn, ok := iConn.(*stat.CounterConnection)
+					if ok {
+						iConn = statConn.Connection
+					}
+					if xc, ok := iConn.(*xtls.Conn); ok {
+						iConn = xc.NetConn()
+					}
+					if tc, ok := iConn.(*net.TCPConn); ok {
+						if conn.SHOW {
+							fmt.Println(conn.MARK, "Splice")
+						}
+						runtime.Gosched() // necessary
+						w, err := tc.ReadFrom(conn.NetConn())
+						if counter != nil {
+							counter.Add(w)
+						}
+						if statConn != nil && statConn.WriteCounter != nil {
+							statConn.WriteCounter.Add(w)
+						}
+						return err
+					} else {
+						panic("XTLS Splice: not TCP inbound")
+					}
+				}
+				reader = buf.NewReadVReader(conn.NetConn(), rawConn, nil)
+				ct = counter
+				if conn.SHOW {
+					fmt.Println(conn.MARK, "ReadV")
+				}
+			}
+			buffer, err := reader.ReadMultiBuffer()
+			if !buffer.IsEmpty() {
+				if ct != nil {
+					ct.Add(int64(buffer.Len()))
+				}
+				timer.Update()
+				if werr := writer.WriteMultiBuffer(buffer); werr != nil {
+					return werr
+				}
+			}
+			if err != nil {
+				return err
+			}
+		}
+	}()
+	if err != nil && errors.Cause(err) != io.EOF {
+		return err
+	}
+	return nil
+}
+
+// XtlsRead filter and read xtls protocol
+func XtlsRead(reader buf.Reader, writer buf.Writer, timer signal.ActivityUpdater, conn *tls.Conn, rawConn syscall.RawConn, counter stats.Counter, ctx context.Context, userUUID []byte, numberOfPacketToFilter *int, isTLS13 *bool, isTLS12 *bool, isTLS *bool) error {
+	err := func() error {
+		var ct stats.Counter
+		filterUUID := true
+		shouldSwitchToDirectCopy := false
+		var remainingContent int32 = -1
+		var remainingPadding int32 = -1
+		currentCommand := 0
+		for {
+			if shouldSwitchToDirectCopy {
+				shouldSwitchToDirectCopy = false
+				if runtime.GOOS == "linux" || runtime.GOOS == "android" {
+					if inbound := session.InboundFromContext(ctx); inbound != nil && inbound.Conn != nil {
 						iConn := inbound.Conn
 						statConn, ok := iConn.(*stat.CounterConnection)
 						if ok {
 							iConn = statConn.Connection
 						}
-						if xc, ok := iConn.(*xtls.Conn); ok {
+						if xc, ok := iConn.(*tls.Conn); ok {
 							iConn = xc.NetConn()
 						}
 						if tc, ok := iConn.(*net.TCPConn); ok {
-							if conn.SHOW {
-								fmt.Println(conn.MARK, "Splice")
-							}
+							newError("XtlsRead splice").WriteToLog(session.ExportIDToError(ctx))
 							runtime.Gosched() // necessary
 							w, err := tc.ReadFrom(conn.NetConn())
 							if counter != nil {
@@ -214,12 +287,26 @@ func ReadV(reader buf.Reader, writer buf.Writer, timer signal.ActivityUpdater, c
 				}
 				reader = buf.NewReadVReader(conn.NetConn(), rawConn, nil)
 				ct = counter
-				if conn.SHOW {
-					fmt.Println(conn.MARK, "ReadV")
-				}
+				newError("XtlsRead readV").WriteToLog(session.ExportIDToError(ctx))
 			}
 			buffer, err := reader.ReadMultiBuffer()
 			if !buffer.IsEmpty() {
+				if filterUUID && (*isTLS || *numberOfPacketToFilter > 0) {
+					buffer = XtlsUnpadding(ctx, buffer, userUUID, &remainingContent, &remainingPadding, &currentCommand)
+					if remainingContent == 0 && remainingPadding == 0 {
+						if currentCommand == 1 {
+							filterUUID = false
+						} else if currentCommand == 2 {
+							filterUUID = false
+							shouldSwitchToDirectCopy = true
+						} else if currentCommand != 0 {
+							newError("XtlsRead unknown command ", currentCommand, buffer.Len()).WriteToLog(session.ExportIDToError(ctx))
+						}
+					}
+				}
+				if *numberOfPacketToFilter > 0 {
+					XtlsFilterTls13(buffer, numberOfPacketToFilter, isTLS13, isTLS12, isTLS, ctx)
+				}
 				if ct != nil {
 					ct.Add(int64(buffer.Len()))
 				}
@@ -238,3 +325,199 @@ func ReadV(reader buf.Reader, writer buf.Writer, timer signal.ActivityUpdater, c
 	}
 	return nil
 }
+
+// XtlsWrite filter and write xtls protocol
+func XtlsWrite(reader buf.Reader, writer buf.Writer, timer signal.ActivityUpdater, conn *tls.Conn, counter stats.Counter, ctx context.Context, userUUID *[]byte, numberOfPacketToFilter *int, isTLS13 *bool, isTLS12 *bool, isTLS *bool) error {
+	err := func() error {
+		var ct stats.Counter
+		filterTlsApplicationData := true
+		shouldSwitchToDirectCopy := false
+		for {
+			buffer, err := reader.ReadMultiBuffer()
+			if !buffer.IsEmpty() {
+				if *numberOfPacketToFilter > 0 {
+					XtlsFilterTls13(buffer, numberOfPacketToFilter, isTLS13, isTLS12, isTLS, ctx)
+				}
+				if filterTlsApplicationData && *isTLS {
+					var xtlsSpecIndex int
+					for i, b := range buffer {
+						if b.Len() >= 6 && bytes.Equal(tlsApplicationDataStart, b.BytesTo(3)) {
+							var command byte = 0x01
+							if *isTLS13 {
+								shouldSwitchToDirectCopy = true
+								xtlsSpecIndex = i
+								command = 0x02
+							}
+							filterTlsApplicationData = false
+							buffer[i] = XtlsPadding(b, command, userUUID, ctx)
+							break
+						} else if !*isTLS12 && !*isTLS13 && *numberOfPacketToFilter <= 0 {
+							//maybe tls 1.1 or 1.0
+							filterTlsApplicationData = false
+							buffer[i] = XtlsPadding(b, 0x01, userUUID, ctx)
+							break
+						}
+						buffer[i] = XtlsPadding(b, 0x00, userUUID, ctx)
+					}
+					if shouldSwitchToDirectCopy {
+						encryptBuffer, directBuffer := buf.SplitMulti(buffer, xtlsSpecIndex+1)
+						length := encryptBuffer.Len()
+						if !encryptBuffer.IsEmpty() {
+							timer.Update()
+							if werr := writer.WriteMultiBuffer(encryptBuffer); werr != nil {
+								return werr
+							}
+						}
+						buffer = directBuffer
+						writer = buf.NewWriter(conn.NetConn())
+						ct = counter
+						newError("XtlsWrite writeV ", xtlsSpecIndex, " ", length, " ", buffer.Len()).WriteToLog(session.ExportIDToError(ctx))
+						time.Sleep(5 * time.Millisecond) // for some device, the first xtls direct packet fails without this delay
+					}
+				}
+				if !buffer.IsEmpty() {
+					if ct != nil {
+						ct.Add(int64(buffer.Len()))
+					}
+					timer.Update()
+					if werr := writer.WriteMultiBuffer(buffer); werr != nil {
+						return werr
+					}
+				}
+			}
+			if err != nil {
+				return err
+			}
+		}
+	}()
+	if err != nil && errors.Cause(err) != io.EOF {
+		return err
+	}
+	return nil
+}
+
+// XtlsFilterTls13 filter and recognize tls 1.3
+func XtlsFilterTls13(buffer buf.MultiBuffer, numberOfPacketToFilter *int, isTLS13 *bool, isTLS12 *bool, isTLS *bool, ctx context.Context) {
+	for _, b := range buffer {
+		*numberOfPacketToFilter--
+		if b.Len() >= 6 {
+			startsBytes := b.BytesTo(6)
+			if bytes.Equal(tlsServerHandShakeStart, startsBytes[:3]) && startsBytes[5] == 0x02 {
+				total := (int(startsBytes[3])<<8 | int(startsBytes[4])) + 5
+				if b.Len() >= int32(total) {
+					if bytes.Contains(b.BytesTo(int32(total)), tls13SupportedVersions) {
+						*isTLS13 = true
+						*isTLS = true
+						newError("XtlsFilterTls13 found tls 1.3! ", buffer.Len()).WriteToLog(session.ExportIDToError(ctx))
+					} else {
+						*isTLS12 = true
+						*isTLS = true
+						newError("XtlsFilterTls13 found tls 1.2! ", buffer.Len()).WriteToLog(session.ExportIDToError(ctx))
+					}
+					*numberOfPacketToFilter = 0
+					return
+				}
+			} else if bytes.Equal(tlsClientHandShakeStart, startsBytes[:2]) && startsBytes[5] == 0x01 {
+				*isTLS = true
+				newError("XtlsFilterTls13 found tls client hello! ", buffer.Len()).WriteToLog(session.ExportIDToError(ctx))
+			}
+		}
+		if *numberOfPacketToFilter <= 0 {
+			newError("XtlsFilterTls13 stop filtering", buffer.Len()).WriteToLog(session.ExportIDToError(ctx))
+		}
+	}
+}
+
+// XtlsPadding add padding to eliminate length siganature during tls handshake
+func XtlsPadding(b *buf.Buffer, command byte, userUUID *[]byte, ctx context.Context) *buf.Buffer {
+	var length int32 = 0
+	if b.Len() < 900 {
+		l, err := rand.Int(rand.Reader, big.NewInt(500))
+		if err != nil {
+			newError("failed to generate padding").Base(err).WriteToLog(session.ExportIDToError(ctx))
+		}
+		length = int32(l.Int64()) + 900 - b.Len()
+	}
+	newbuffer := buf.New()
+	if userUUID != nil {
+		newbuffer.Write(*userUUID)
+		*userUUID = nil
+	}
+	newbuffer.Write([]byte{command, byte(b.Len() >> 8), byte(b.Len()), byte(length >> 8), byte(length)})
+	newbuffer.Write(b.Bytes())
+	newbuffer.Extend(length)
+	newError("XtlsPadding ", b.Len(), " ", length, " ", command).WriteToLog(session.ExportIDToError(ctx))
+	b.Release()
+	b = nil
+	return newbuffer
+}
+
+// XtlsUnpadding remove padding and parse command
+func XtlsUnpadding(ctx context.Context, buffer buf.MultiBuffer, userUUID []byte, remainingContent *int32, remainingPadding *int32, currentCommand *int) buf.MultiBuffer {
+	posindex := 0
+	var posByte int32 = 0
+	if *remainingContent == -1 && *remainingPadding == -1 {
+		for i, b := range buffer {
+			if b.Len() >= 21 && bytes.Equal(userUUID, b.BytesTo(16)) {
+				posindex = i
+				posByte = 16
+				*remainingContent = 0
+				*remainingPadding = 0
+				break
+			}
+		}
+	}
+	if *remainingContent == -1 && *remainingPadding == -1 {
+		return buffer
+	}
+	mb2 := make(buf.MultiBuffer, 0, len(buffer))
+	for i := 0; i < posindex; i++ {
+		newbuffer := buf.New()
+		newbuffer.Write(buffer[i].Bytes())
+		mb2 = append(mb2, newbuffer)
+	}
+	for i := posindex; i < len(buffer); i++ {
+		b := buffer[i]
+		for posByte < b.Len() {
+			if *remainingContent <= 0 && *remainingPadding <= 0 {
+				if *currentCommand == 1 {
+					len := b.Len() - posByte
+					newbuffer := buf.New()
+					newbuffer.Write(b.BytesRange(posByte, posByte+len))
+					mb2 = append(mb2, newbuffer)
+					posByte += len
+				} else {
+					paddingInfo := b.BytesRange(posByte, posByte+5)
+					*currentCommand = int(paddingInfo[0])
+					*remainingContent = int32(paddingInfo[1])<<8 | int32(paddingInfo[2])
+					*remainingPadding = int32(paddingInfo[3])<<8 | int32(paddingInfo[4])
+					newError("Xtls Unpadding new block", i, " ", posByte, " content ", *remainingContent, " padding ", *remainingPadding, " ", paddingInfo[0]).WriteToLog(session.ExportIDToError(ctx))
+					posByte += 5
+				}
+			} else if *remainingContent > 0 {
+				len := *remainingContent
+				if b.Len() < posByte+*remainingContent {
+					len = b.Len() - posByte
+				}
+				newbuffer := buf.New()
+				newbuffer.Write(b.BytesRange(posByte, posByte+len))
+				mb2 = append(mb2, newbuffer)
+				*remainingContent -= len
+				posByte += len
+			} else { // remainingPadding > 0
+				len := *remainingPadding
+				if b.Len() < posByte+*remainingPadding {
+					len = b.Len() - posByte
+				}
+				*remainingPadding -= len
+				posByte += len
+			}
+			if posByte == b.Len() {
+				posByte = 0
+				break
+			}
+		}
+	}
+	buf.ReleaseMulti(buffer)
+	return mb2
+}

+ 47 - 14
proxy/vless/inbound/inbound.go

@@ -442,7 +442,7 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s
 	var rawConn syscall.RawConn
 
 	switch requestAddons.Flow {
-	case vless.XRO, vless.XRD:
+	case vless.XRO, vless.XRD, vless.XRV:
 		if account.Flow == requestAddons.Flow {
 			switch request.Command {
 			case protocol.RequestCommandMux:
@@ -450,7 +450,14 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s
 			case protocol.RequestCommandUDP:
 				return newError(requestAddons.Flow + " doesn't support UDP").AtWarning()
 			case protocol.RequestCommandTCP:
-				if xtlsConn, ok := iConn.(*xtls.Conn); ok {
+				if requestAddons.Flow == vless.XRV {
+					if _, ok := iConn.(*xtls.Conn); ok {
+						return newError(`failed to use ` + requestAddons.Flow + `, vision "security" must be "tls"`).AtWarning()
+					}
+					if sc, ok := iConn.(*tls.Conn).NetConn().(syscall.Conn); ok {
+						rawConn, _ = sc.SyscallConn()
+					}
+				} else if xtlsConn, ok := iConn.(*xtls.Conn); ok {
 					xtlsConn.RPRX = true
 					xtlsConn.SHOW = xtls_show
 					xtlsConn.MARK = "XTLS"
@@ -494,6 +501,10 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s
 
 	serverReader := link.Reader // .(*pipe.Reader)
 	serverWriter := link.Writer // .(*pipe.Writer)
+	isTLS13 := false
+	isTLS12 := false
+	isTLS := false
+	numberOfPacketToFilter := 8
 
 	postRequest := func() error {
 		defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly)
@@ -508,7 +519,13 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s
 			if statConn != nil {
 				counter = statConn.ReadCounter
 			}
-			err = encoding.ReadV(clientReader, serverWriter, timer, iConn.(*xtls.Conn), rawConn, counter, nil)
+			if requestAddons.Flow == vless.XRV {
+				//TODO enable splice
+				ctx = session.ContextWithInbound(ctx, nil)
+				err = encoding.XtlsRead(clientReader, serverWriter, timer, iConn.(*tls.Conn), rawConn, counter, ctx, account.ID.Bytes(), &numberOfPacketToFilter, &isTLS13, &isTLS12, &isTLS)
+			} else {
+				err = encoding.ReadV(clientReader, serverWriter, timer, iConn.(*xtls.Conn), rawConn, counter, ctx)
+			}
 		} else {
 			// from clientReader.ReadMultiBuffer to serverWriter.WriteMultiBufer
 			err = buf.Copy(clientReader, serverWriter, buf.UpdateActivity(timer))
@@ -531,26 +548,42 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s
 
 		// default: clientWriter := bufferWriter
 		clientWriter := encoding.EncodeBodyAddons(bufferWriter, request, responseAddons)
-		{
-			multiBuffer, err := serverReader.ReadMultiBuffer()
-			if err != nil {
-				return err // ...
-			}
-			if err := clientWriter.WriteMultiBuffer(multiBuffer); err != nil {
-				return err // ...
+		userUUID := account.ID.Bytes()
+		multiBuffer, err1 := serverReader.ReadMultiBuffer()
+		if err1 != nil {
+			return err1 // ...
+		}
+		if requestAddons.Flow == vless.XRV {
+			encoding.XtlsFilterTls13(multiBuffer, &numberOfPacketToFilter, &isTLS13, &isTLS12, &isTLS, ctx)
+			if isTLS {
+				for i, b := range multiBuffer {
+					
+					multiBuffer[i] = encoding.XtlsPadding(b, 0x00, &userUUID, ctx)
+				}
 			}
 		}
-
+		if err := clientWriter.WriteMultiBuffer(multiBuffer); err != nil {
+			return err // ...
+		}
 		// Flush; bufferWriter.WriteMultiBufer now is bufferWriter.writer.WriteMultiBuffer
 		if err := bufferWriter.SetBuffered(false); err != nil {
 			return newError("failed to write A response payload").Base(err).AtWarning()
 		}
 
-		// from serverReader.ReadMultiBuffer to clientWriter.WriteMultiBufer
-		if err := buf.Copy(serverReader, clientWriter, buf.UpdateActivity(timer)); err != nil {
+		var err error
+		if rawConn != nil && requestAddons.Flow == vless.XRV {
+			var counter stats.Counter
+			if statConn != nil {
+				counter = statConn.WriteCounter
+			}
+			err = encoding.XtlsWrite(serverReader, clientWriter, timer, iConn.(*tls.Conn), counter, ctx, &userUUID, &numberOfPacketToFilter, &isTLS13, &isTLS12, &isTLS)
+		} else {
+			// from serverReader.ReadMultiBuffer to clientWriter.WriteMultiBufer
+			err = buf.Copy(serverReader, clientWriter, buf.UpdateActivity(timer))
+		}
+		if err != nil {
 			return newError("failed to transfer response payload").Base(err).AtInfo()
 		}
-
 		// Indicates the end of response payload.
 		switch responseAddons.Flow {
 		default:

+ 46 - 12
proxy/vless/outbound/outbound.go

@@ -5,7 +5,6 @@ package outbound
 import (
 	"context"
 	"syscall"
-	"time"
 
 	"github.com/xtls/xray-core/common"
 	"github.com/xtls/xray-core/common/buf"
@@ -25,6 +24,7 @@ import (
 	"github.com/xtls/xray-core/transport"
 	"github.com/xtls/xray-core/transport/internet"
 	"github.com/xtls/xray-core/transport/internet/stat"
+	"github.com/xtls/xray-core/transport/internet/tls"
 	"github.com/xtls/xray-core/transport/internet/xtls"
 )
 
@@ -128,15 +128,13 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
 	}
 
 	var rawConn syscall.RawConn
-	var sctx context.Context
-
 	allowUDP443 := false
 	switch requestAddons.Flow {
-	case vless.XRO + "-udp443", vless.XRD + "-udp443", vless.XRS + "-udp443":
+	case vless.XRO + "-udp443", vless.XRD + "-udp443", vless.XRS + "-udp443", vless.XRV + "-udp443":
 		allowUDP443 = true
 		requestAddons.Flow = requestAddons.Flow[:16]
 		fallthrough
-	case vless.XRO, vless.XRD, vless.XRS:
+	case vless.XRO, vless.XRD, vless.XRS, vless.XRV:
 		switch request.Command {
 		case protocol.RequestCommandMux:
 			return newError(requestAddons.Flow + " doesn't support Mux").AtWarning()
@@ -146,12 +144,18 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
 			}
 			requestAddons.Flow = ""
 		case protocol.RequestCommandTCP:
-			if xtlsConn, ok := iConn.(*xtls.Conn); ok {
+			if requestAddons.Flow == vless.XRV {
+				if _, ok := iConn.(*xtls.Conn); ok {
+					return newError(`failed to use ` + requestAddons.Flow + `, vision "security" must be "tls"`).AtWarning()
+				}
+				if sc, ok := iConn.(*tls.Conn).NetConn().(syscall.Conn); ok {
+					rawConn, _ = sc.SyscallConn()
+				}
+			} else if xtlsConn, ok := iConn.(*xtls.Conn); ok {
 				xtlsConn.RPRX = true
 				xtlsConn.SHOW = xtls_show
 				xtlsConn.MARK = "XTLS"
 				if requestAddons.Flow == vless.XRS {
-					sctx = ctx
 					requestAddons.Flow = vless.XRD
 				}
 				if requestAddons.Flow == vless.XRD {
@@ -176,6 +180,10 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
 
 	clientReader := link.Reader // .(*pipe.Reader)
 	clientWriter := link.Writer // .(*pipe.Writer)
+	isTLS13 := false
+	isTLS12 := false
+	isTLS := false
+	numberOfPacketToFilter := 8
 
 	if request.Command == protocol.RequestCommandUDP && h.cone && request.Port != 53 && request.Port != 443 {
 		request.Command = protocol.RequestCommandMux
@@ -196,17 +204,39 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
 		if request.Command == protocol.RequestCommandMux && request.Port == 666 {
 			serverWriter = xudp.NewPacketWriter(serverWriter, target)
 		}
-		if err := buf.CopyOnceTimeout(clientReader, serverWriter, time.Millisecond*100); err != nil && err != buf.ErrNotTimeoutReader && err != buf.ErrReadTimeout {
+		userUUID := account.ID.Bytes()
+		multiBuffer, err1 := clientReader.ReadMultiBuffer()
+		if err1 != nil {
+			return err1 // ...
+		}
+		if requestAddons.Flow == vless.XRV {
+			encoding.XtlsFilterTls13(multiBuffer, &numberOfPacketToFilter, &isTLS13, &isTLS12, &isTLS, ctx)
+			if isTLS {
+				for i, b := range multiBuffer {
+					multiBuffer[i] = encoding.XtlsPadding(b, 0x00, &userUUID, ctx)
+				}
+			}
+		}
+		if err := serverWriter.WriteMultiBuffer(multiBuffer); err != nil {
 			return err // ...
 		}
-
 		// Flush; bufferWriter.WriteMultiBufer now is bufferWriter.writer.WriteMultiBuffer
 		if err := bufferWriter.SetBuffered(false); err != nil {
 			return newError("failed to write A request payload").Base(err).AtWarning()
 		}
 
-		// from clientReader.ReadMultiBuffer to serverWriter.WriteMultiBufer
-		if err := buf.Copy(clientReader, serverWriter, buf.UpdateActivity(timer)); err != nil {
+		var err error
+		if rawConn != nil && requestAddons.Flow == vless.XRV {
+			var counter stats.Counter
+			if statConn != nil {
+				counter = statConn.WriteCounter
+			}
+			err = encoding.XtlsWrite(clientReader, serverWriter, timer, iConn.(*tls.Conn), counter, ctx, &userUUID, &numberOfPacketToFilter, &isTLS13, &isTLS12, &isTLS)
+		} else {
+			// from clientReader.ReadMultiBuffer to serverWriter.WriteMultiBufer
+			err = buf.Copy(clientReader, serverWriter, buf.UpdateActivity(timer))
+		}
+		if err != nil {
 			return newError("failed to transfer request payload").Base(err).AtInfo()
 		}
 
@@ -236,7 +266,11 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
 			if statConn != nil {
 				counter = statConn.ReadCounter
 			}
-			err = encoding.ReadV(serverReader, clientWriter, timer, iConn.(*xtls.Conn), rawConn, counter, sctx)
+			if requestAddons.Flow == vless.XRV {
+				err = encoding.XtlsRead(serverReader, clientWriter, timer, iConn.(*tls.Conn), rawConn, counter, ctx, account.ID.Bytes(), &numberOfPacketToFilter, &isTLS13, &isTLS12, &isTLS)
+			} else {
+				err = encoding.ReadV(serverReader, clientWriter, timer, iConn.(*xtls.Conn), rawConn, counter, ctx)
+			}
 		} else {
 			// from serverReader.ReadMultiBuffer to clientWriter.WriteMultiBufer
 			err = buf.Copy(serverReader, clientWriter, buf.UpdateActivity(timer))

+ 1 - 0
proxy/vless/vless.go

@@ -11,4 +11,5 @@ const (
 	XRO = "xtls-rprx-origin"
 	XRD = "xtls-rprx-direct"
 	XRS = "xtls-rprx-splice"
+	XRV = "xtls-rprx-vision"
 )