mirror of
				https://github.com/optim-enterprises-bv/Xray-core.git
				synced 2025-10-30 18:18:04 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			563 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			563 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Package proxy contains all proxies used by Xray.
 | |
| //
 | |
| // To implement an inbound or outbound proxy, one needs to do the following:
 | |
| // 1. Implement the interface(s) below.
 | |
| // 2. Register a config creator through common.RegisterConfig.
 | |
| package proxy
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"context"
 | |
| 	"crypto/rand"
 | |
| 	"io"
 | |
| 	"math/big"
 | |
| 	"runtime"
 | |
| 	"strconv"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/pires/go-proxyproto"
 | |
| 	"github.com/xtls/xray-core/app/dispatcher"
 | |
| 	"github.com/xtls/xray-core/common/buf"
 | |
| 	"github.com/xtls/xray-core/common/errors"
 | |
| 	"github.com/xtls/xray-core/common/net"
 | |
| 	"github.com/xtls/xray-core/common/protocol"
 | |
| 	"github.com/xtls/xray-core/common/session"
 | |
| 	"github.com/xtls/xray-core/common/signal"
 | |
| 	"github.com/xtls/xray-core/features/routing"
 | |
| 	"github.com/xtls/xray-core/features/stats"
 | |
| 	"github.com/xtls/xray-core/transport"
 | |
| 	"github.com/xtls/xray-core/transport/internet"
 | |
| 	"github.com/xtls/xray-core/transport/internet/reality"
 | |
| 	"github.com/xtls/xray-core/transport/internet/stat"
 | |
| 	"github.com/xtls/xray-core/transport/internet/tls"
 | |
| )
 | |
| 
 | |
| var (
 | |
| 	Tls13SupportedVersions  = []byte{0x00, 0x2b, 0x00, 0x02, 0x03, 0x04}
 | |
| 	TlsClientHandShakeStart = []byte{0x16, 0x03}
 | |
| 	TlsServerHandShakeStart = []byte{0x16, 0x03, 0x03}
 | |
| 	TlsApplicationDataStart = []byte{0x17, 0x03, 0x03}
 | |
| 
 | |
| 	Tls13CipherSuiteDic = map[uint16]string{
 | |
| 		0x1301: "TLS_AES_128_GCM_SHA256",
 | |
| 		0x1302: "TLS_AES_256_GCM_SHA384",
 | |
| 		0x1303: "TLS_CHACHA20_POLY1305_SHA256",
 | |
| 		0x1304: "TLS_AES_128_CCM_SHA256",
 | |
| 		0x1305: "TLS_AES_128_CCM_8_SHA256",
 | |
| 	}
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	TlsHandshakeTypeClientHello byte = 0x01
 | |
| 	TlsHandshakeTypeServerHello byte = 0x02
 | |
| 
 | |
| 	CommandPaddingContinue byte = 0x00
 | |
| 	CommandPaddingEnd      byte = 0x01
 | |
| 	CommandPaddingDirect   byte = 0x02
 | |
| )
 | |
| 
 | |
| // An Inbound processes inbound connections.
 | |
| type Inbound interface {
 | |
| 	// Network returns a list of networks that this inbound supports. Connections with not-supported networks will not be passed into Process().
 | |
| 	Network() []net.Network
 | |
| 
 | |
| 	// Process processes a connection of given network. If necessary, the Inbound can dispatch the connection to an Outbound.
 | |
| 	Process(context.Context, net.Network, stat.Connection, routing.Dispatcher) error
 | |
| }
 | |
| 
 | |
| // An Outbound process outbound connections.
 | |
| type Outbound interface {
 | |
| 	// Process processes the given connection. The given dialer may be used to dial a system outbound connection.
 | |
| 	Process(context.Context, *transport.Link, internet.Dialer) error
 | |
| }
 | |
| 
 | |
| // UserManager is the interface for Inbounds and Outbounds that can manage their users.
 | |
| type UserManager interface {
 | |
| 	// AddUser adds a new user.
 | |
| 	AddUser(context.Context, *protocol.MemoryUser) error
 | |
| 
 | |
| 	// RemoveUser removes a user by email.
 | |
| 	RemoveUser(context.Context, string) error
 | |
| 
 | |
| 	// Get user by email.
 | |
| 	GetUser(context.Context, string) *protocol.MemoryUser
 | |
| 
 | |
| 	// Get all users.
 | |
| 	GetUsers(context.Context) []*protocol.MemoryUser
 | |
| 
 | |
| 	// Get users count.
 | |
| 	GetUsersCount(context.Context) int64
 | |
| }
 | |
| 
 | |
| type GetInbound interface {
 | |
| 	GetInbound() Inbound
 | |
| }
 | |
| 
 | |
| type GetOutbound interface {
 | |
| 	GetOutbound() Outbound
 | |
| }
 | |
| 
 | |
| // TrafficState is used to track uplink and downlink of one connection
 | |
| // It is used by XTLS to determine if switch to raw copy mode, It is used by Vision to calculate padding
 | |
| type TrafficState struct {
 | |
| 	UserUUID               []byte
 | |
| 	NumberOfPacketToFilter int
 | |
| 	EnableXtls             bool
 | |
| 	IsTLS12orAbove         bool
 | |
| 	IsTLS                  bool
 | |
| 	Cipher                 uint16
 | |
| 	RemainingServerHello   int32
 | |
| 
 | |
| 	// reader link state
 | |
| 	WithinPaddingBuffers     bool
 | |
| 	ReaderSwitchToDirectCopy bool
 | |
| 	RemainingCommand         int32
 | |
| 	RemainingContent         int32
 | |
| 	RemainingPadding         int32
 | |
| 	CurrentCommand           int
 | |
| 
 | |
| 	// write link state
 | |
| 	IsPadding                bool
 | |
| 	WriterSwitchToDirectCopy bool
 | |
| }
 | |
| 
 | |
| func NewTrafficState(userUUID []byte) *TrafficState {
 | |
| 	return &TrafficState{
 | |
| 		UserUUID:                 userUUID,
 | |
| 		NumberOfPacketToFilter:   8,
 | |
| 		EnableXtls:               false,
 | |
| 		IsTLS12orAbove:           false,
 | |
| 		IsTLS:                    false,
 | |
| 		Cipher:                   0,
 | |
| 		RemainingServerHello:     -1,
 | |
| 		WithinPaddingBuffers:     true,
 | |
| 		ReaderSwitchToDirectCopy: false,
 | |
| 		RemainingCommand:         -1,
 | |
| 		RemainingContent:         -1,
 | |
| 		RemainingPadding:         -1,
 | |
| 		CurrentCommand:           0,
 | |
| 		IsPadding:                true,
 | |
| 		WriterSwitchToDirectCopy: false,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // VisionReader is used to read xtls vision protocol
 | |
| // Note Vision probably only make sense as the inner most layer of reader, since it need assess traffic state from origin proxy traffic
 | |
| type VisionReader struct {
 | |
| 	buf.Reader
 | |
| 	trafficState *TrafficState
 | |
| 	ctx          context.Context
 | |
| }
 | |
| 
 | |
| func NewVisionReader(reader buf.Reader, state *TrafficState, context context.Context) *VisionReader {
 | |
| 	return &VisionReader{
 | |
| 		Reader:       reader,
 | |
| 		trafficState: state,
 | |
| 		ctx:          context,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (w *VisionReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
 | |
| 	buffer, err := w.Reader.ReadMultiBuffer()
 | |
| 	if !buffer.IsEmpty() {
 | |
| 		if w.trafficState.WithinPaddingBuffers || w.trafficState.NumberOfPacketToFilter > 0 {
 | |
| 			mb2 := make(buf.MultiBuffer, 0, len(buffer))
 | |
| 			for _, b := range buffer {
 | |
| 				newbuffer := XtlsUnpadding(b, w.trafficState, w.ctx)
 | |
| 				if newbuffer.Len() > 0 {
 | |
| 					mb2 = append(mb2, newbuffer)
 | |
| 				}
 | |
| 			}
 | |
| 			buffer = mb2
 | |
| 			if w.trafficState.RemainingContent > 0 || w.trafficState.RemainingPadding > 0 || w.trafficState.CurrentCommand == 0 {
 | |
| 				w.trafficState.WithinPaddingBuffers = true
 | |
| 			} else if w.trafficState.CurrentCommand == 1 {
 | |
| 				w.trafficState.WithinPaddingBuffers = false
 | |
| 			} else if w.trafficState.CurrentCommand == 2 {
 | |
| 				w.trafficState.WithinPaddingBuffers = false
 | |
| 				w.trafficState.ReaderSwitchToDirectCopy = true
 | |
| 			} else {
 | |
| 				errors.LogInfo(w.ctx, "XtlsRead unknown command ", w.trafficState.CurrentCommand, buffer.Len())
 | |
| 			}
 | |
| 		}
 | |
| 		if w.trafficState.NumberOfPacketToFilter > 0 {
 | |
| 			XtlsFilterTls(buffer, w.trafficState, w.ctx)
 | |
| 		}
 | |
| 	}
 | |
| 	return buffer, err
 | |
| }
 | |
| 
 | |
| // VisionWriter is used to write xtls vision protocol
 | |
| // Note Vision probably only make sense as the inner most layer of writer, since it need assess traffic state from origin proxy traffic
 | |
| type VisionWriter struct {
 | |
| 	buf.Writer
 | |
| 	trafficState      *TrafficState
 | |
| 	ctx               context.Context
 | |
| 	writeOnceUserUUID []byte
 | |
| }
 | |
| 
 | |
| func NewVisionWriter(writer buf.Writer, state *TrafficState, context context.Context) *VisionWriter {
 | |
| 	w := make([]byte, len(state.UserUUID))
 | |
| 	copy(w, state.UserUUID)
 | |
| 	return &VisionWriter{
 | |
| 		Writer:            writer,
 | |
| 		trafficState:      state,
 | |
| 		ctx:               context,
 | |
| 		writeOnceUserUUID: w,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (w *VisionWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
 | |
| 	if w.trafficState.NumberOfPacketToFilter > 0 {
 | |
| 		XtlsFilterTls(mb, w.trafficState, w.ctx)
 | |
| 	}
 | |
| 	if w.trafficState.IsPadding {
 | |
| 		if len(mb) == 1 && mb[0] == nil {
 | |
| 			mb[0] = XtlsPadding(nil, CommandPaddingContinue, &w.writeOnceUserUUID, true, w.ctx) // we do a long padding to hide vless header
 | |
| 			return w.Writer.WriteMultiBuffer(mb)
 | |
| 		}
 | |
| 		mb = ReshapeMultiBuffer(w.ctx, mb)
 | |
| 		longPadding := w.trafficState.IsTLS
 | |
| 		for i, b := range mb {
 | |
| 			if w.trafficState.IsTLS && b.Len() >= 6 && bytes.Equal(TlsApplicationDataStart, b.BytesTo(3)) {
 | |
| 				if w.trafficState.EnableXtls {
 | |
| 					w.trafficState.WriterSwitchToDirectCopy = true
 | |
| 				}
 | |
| 				var command byte = CommandPaddingContinue
 | |
| 				if i == len(mb)-1 {
 | |
| 					command = CommandPaddingEnd
 | |
| 					if w.trafficState.EnableXtls {
 | |
| 						command = CommandPaddingDirect
 | |
| 					}
 | |
| 				}
 | |
| 				mb[i] = XtlsPadding(b, command, &w.writeOnceUserUUID, true, w.ctx)
 | |
| 				w.trafficState.IsPadding = false // padding going to end
 | |
| 				longPadding = false
 | |
| 				continue
 | |
| 			} else if !w.trafficState.IsTLS12orAbove && w.trafficState.NumberOfPacketToFilter <= 1 { // For compatibility with earlier vision receiver, we finish padding 1 packet early
 | |
| 				w.trafficState.IsPadding = false
 | |
| 				mb[i] = XtlsPadding(b, CommandPaddingEnd, &w.writeOnceUserUUID, longPadding, w.ctx)
 | |
| 				break
 | |
| 			}
 | |
| 			var command byte = CommandPaddingContinue
 | |
| 			if i == len(mb)-1 && !w.trafficState.IsPadding {
 | |
| 				command = CommandPaddingEnd
 | |
| 				if w.trafficState.EnableXtls {
 | |
| 					command = CommandPaddingDirect
 | |
| 				}
 | |
| 			}
 | |
| 			mb[i] = XtlsPadding(b, command, &w.writeOnceUserUUID, longPadding, w.ctx)
 | |
| 		}
 | |
| 	}
 | |
| 	return w.Writer.WriteMultiBuffer(mb)
 | |
| }
 | |
| 
 | |
| // ReshapeMultiBuffer prepare multi buffer for padding structure (max 21 bytes)
 | |
| func ReshapeMultiBuffer(ctx context.Context, buffer buf.MultiBuffer) buf.MultiBuffer {
 | |
| 	needReshape := 0
 | |
| 	for _, b := range buffer {
 | |
| 		if b.Len() >= buf.Size-21 {
 | |
| 			needReshape += 1
 | |
| 		}
 | |
| 	}
 | |
| 	if needReshape == 0 {
 | |
| 		return buffer
 | |
| 	}
 | |
| 	mb2 := make(buf.MultiBuffer, 0, len(buffer)+needReshape)
 | |
| 	toPrint := ""
 | |
| 	for i, buffer1 := range buffer {
 | |
| 		if buffer1.Len() >= buf.Size-21 {
 | |
| 			index := int32(bytes.LastIndex(buffer1.Bytes(), TlsApplicationDataStart))
 | |
| 			if index < 21 || index > buf.Size-21 {
 | |
| 				index = buf.Size / 2
 | |
| 			}
 | |
| 			buffer2 := buf.New()
 | |
| 			buffer2.Write(buffer1.BytesFrom(index))
 | |
| 			buffer1.Resize(0, index)
 | |
| 			mb2 = append(mb2, buffer1, buffer2)
 | |
| 			toPrint += " " + strconv.Itoa(int(buffer1.Len())) + " " + strconv.Itoa(int(buffer2.Len()))
 | |
| 		} else {
 | |
| 			mb2 = append(mb2, buffer1)
 | |
| 			toPrint += " " + strconv.Itoa(int(buffer1.Len()))
 | |
| 		}
 | |
| 		buffer[i] = nil
 | |
| 	}
 | |
| 	buffer = buffer[:0]
 | |
| 	errors.LogInfo(ctx, "ReshapeMultiBuffer ", toPrint)
 | |
| 	return mb2
 | |
| }
 | |
| 
 | |
| // XtlsPadding add padding to eliminate length signature during tls handshake
 | |
| func XtlsPadding(b *buf.Buffer, command byte, userUUID *[]byte, longPadding bool, ctx context.Context) *buf.Buffer {
 | |
| 	var contentLen int32 = 0
 | |
| 	var paddingLen int32 = 0
 | |
| 	if b != nil {
 | |
| 		contentLen = b.Len()
 | |
| 	}
 | |
| 	if contentLen < 900 && longPadding {
 | |
| 		l, err := rand.Int(rand.Reader, big.NewInt(500))
 | |
| 		if err != nil {
 | |
| 			errors.LogDebugInner(ctx, err, "failed to generate padding")
 | |
| 		}
 | |
| 		paddingLen = int32(l.Int64()) + 900 - contentLen
 | |
| 	} else {
 | |
| 		l, err := rand.Int(rand.Reader, big.NewInt(256))
 | |
| 		if err != nil {
 | |
| 			errors.LogDebugInner(ctx, err, "failed to generate padding")
 | |
| 		}
 | |
| 		paddingLen = int32(l.Int64())
 | |
| 	}
 | |
| 	if paddingLen > buf.Size-21-contentLen {
 | |
| 		paddingLen = buf.Size - 21 - contentLen
 | |
| 	}
 | |
| 	newbuffer := buf.New()
 | |
| 	if userUUID != nil {
 | |
| 		newbuffer.Write(*userUUID)
 | |
| 		*userUUID = nil
 | |
| 	}
 | |
| 	newbuffer.Write([]byte{command, byte(contentLen >> 8), byte(contentLen), byte(paddingLen >> 8), byte(paddingLen)})
 | |
| 	if b != nil {
 | |
| 		newbuffer.Write(b.Bytes())
 | |
| 		b.Release()
 | |
| 		b = nil
 | |
| 	}
 | |
| 	newbuffer.Extend(paddingLen)
 | |
| 	errors.LogInfo(ctx, "XtlsPadding ", contentLen, " ", paddingLen, " ", command)
 | |
| 	return newbuffer
 | |
| }
 | |
| 
 | |
| // XtlsUnpadding remove padding and parse command
 | |
| func XtlsUnpadding(b *buf.Buffer, s *TrafficState, ctx context.Context) *buf.Buffer {
 | |
| 	if s.RemainingCommand == -1 && s.RemainingContent == -1 && s.RemainingPadding == -1 { // initial state
 | |
| 		if b.Len() >= 21 && bytes.Equal(s.UserUUID, b.BytesTo(16)) {
 | |
| 			b.Advance(16)
 | |
| 			s.RemainingCommand = 5
 | |
| 		} else {
 | |
| 			return b
 | |
| 		}
 | |
| 	}
 | |
| 	newbuffer := buf.New()
 | |
| 	for b.Len() > 0 {
 | |
| 		if s.RemainingCommand > 0 {
 | |
| 			data, err := b.ReadByte()
 | |
| 			if err != nil {
 | |
| 				return newbuffer
 | |
| 			}
 | |
| 			switch s.RemainingCommand {
 | |
| 			case 5:
 | |
| 				s.CurrentCommand = int(data)
 | |
| 			case 4:
 | |
| 				s.RemainingContent = int32(data) << 8
 | |
| 			case 3:
 | |
| 				s.RemainingContent = s.RemainingContent | int32(data)
 | |
| 			case 2:
 | |
| 				s.RemainingPadding = int32(data) << 8
 | |
| 			case 1:
 | |
| 				s.RemainingPadding = s.RemainingPadding | int32(data)
 | |
| 				errors.LogInfo(ctx, "Xtls Unpadding new block, content ", s.RemainingContent, " padding ", s.RemainingPadding, " command ", s.CurrentCommand)
 | |
| 			}
 | |
| 			s.RemainingCommand--
 | |
| 		} else if s.RemainingContent > 0 {
 | |
| 			len := s.RemainingContent
 | |
| 			if b.Len() < len {
 | |
| 				len = b.Len()
 | |
| 			}
 | |
| 			data, err := b.ReadBytes(len)
 | |
| 			if err != nil {
 | |
| 				return newbuffer
 | |
| 			}
 | |
| 			newbuffer.Write(data)
 | |
| 			s.RemainingContent -= len
 | |
| 		} else { // remainingPadding > 0
 | |
| 			len := s.RemainingPadding
 | |
| 			if b.Len() < len {
 | |
| 				len = b.Len()
 | |
| 			}
 | |
| 			b.Advance(len)
 | |
| 			s.RemainingPadding -= len
 | |
| 		}
 | |
| 		if s.RemainingCommand <= 0 && s.RemainingContent <= 0 && s.RemainingPadding <= 0 { // this block done
 | |
| 			if s.CurrentCommand == 0 {
 | |
| 				s.RemainingCommand = 5
 | |
| 			} else {
 | |
| 				s.RemainingCommand = -1 // set to initial state
 | |
| 				s.RemainingContent = -1
 | |
| 				s.RemainingPadding = -1
 | |
| 				if b.Len() > 0 { // shouldn't happen
 | |
| 					newbuffer.Write(b.Bytes())
 | |
| 				}
 | |
| 				break
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	b.Release()
 | |
| 	b = nil
 | |
| 	return newbuffer
 | |
| }
 | |
| 
 | |
| // XtlsFilterTls filter and recognize tls 1.3 and other info
 | |
| func XtlsFilterTls(buffer buf.MultiBuffer, trafficState *TrafficState, ctx context.Context) {
 | |
| 	for _, b := range buffer {
 | |
| 		if b == nil {
 | |
| 			continue
 | |
| 		}
 | |
| 		trafficState.NumberOfPacketToFilter--
 | |
| 		if b.Len() >= 6 {
 | |
| 			startsBytes := b.BytesTo(6)
 | |
| 			if bytes.Equal(TlsServerHandShakeStart, startsBytes[:3]) && startsBytes[5] == TlsHandshakeTypeServerHello {
 | |
| 				trafficState.RemainingServerHello = (int32(startsBytes[3])<<8 | int32(startsBytes[4])) + 5
 | |
| 				trafficState.IsTLS12orAbove = true
 | |
| 				trafficState.IsTLS = true
 | |
| 				if b.Len() >= 79 && trafficState.RemainingServerHello >= 79 {
 | |
| 					sessionIdLen := int32(b.Byte(43))
 | |
| 					cipherSuite := b.BytesRange(43+sessionIdLen+1, 43+sessionIdLen+3)
 | |
| 					trafficState.Cipher = uint16(cipherSuite[0])<<8 | uint16(cipherSuite[1])
 | |
| 				} else {
 | |
| 					errors.LogInfo(ctx, "XtlsFilterTls short server hello, tls 1.2 or older? ", b.Len(), " ", trafficState.RemainingServerHello)
 | |
| 				}
 | |
| 			} else if bytes.Equal(TlsClientHandShakeStart, startsBytes[:2]) && startsBytes[5] == TlsHandshakeTypeClientHello {
 | |
| 				trafficState.IsTLS = true
 | |
| 				errors.LogInfo(ctx, "XtlsFilterTls found tls client hello! ", buffer.Len())
 | |
| 			}
 | |
| 		}
 | |
| 		if trafficState.RemainingServerHello > 0 {
 | |
| 			end := trafficState.RemainingServerHello
 | |
| 			if end > b.Len() {
 | |
| 				end = b.Len()
 | |
| 			}
 | |
| 			trafficState.RemainingServerHello -= b.Len()
 | |
| 			if bytes.Contains(b.BytesTo(end), Tls13SupportedVersions) {
 | |
| 				v, ok := Tls13CipherSuiteDic[trafficState.Cipher]
 | |
| 				if !ok {
 | |
| 					v = "Old cipher: " + strconv.FormatUint(uint64(trafficState.Cipher), 16)
 | |
| 				} else if v != "TLS_AES_128_CCM_8_SHA256" {
 | |
| 					trafficState.EnableXtls = true
 | |
| 				}
 | |
| 				errors.LogInfo(ctx, "XtlsFilterTls found tls 1.3! ", b.Len(), " ", v)
 | |
| 				trafficState.NumberOfPacketToFilter = 0
 | |
| 				return
 | |
| 			} else if trafficState.RemainingServerHello <= 0 {
 | |
| 				errors.LogInfo(ctx, "XtlsFilterTls found tls 1.2! ", b.Len())
 | |
| 				trafficState.NumberOfPacketToFilter = 0
 | |
| 				return
 | |
| 			}
 | |
| 			errors.LogInfo(ctx, "XtlsFilterTls inconclusive server hello ", b.Len(), " ", trafficState.RemainingServerHello)
 | |
| 		}
 | |
| 		if trafficState.NumberOfPacketToFilter <= 0 {
 | |
| 			errors.LogInfo(ctx, "XtlsFilterTls stop filtering", buffer.Len())
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // UnwrapRawConn support unwrap stats, tls, utls, reality and proxyproto conn and get raw tcp conn from it
 | |
| func UnwrapRawConn(conn net.Conn) (net.Conn, stats.Counter, stats.Counter) {
 | |
| 	var readCounter, writerCounter stats.Counter
 | |
| 	if conn != nil {
 | |
| 		statConn, ok := conn.(*stat.CounterConnection)
 | |
| 		if ok {
 | |
| 			conn = statConn.Connection
 | |
| 			readCounter = statConn.ReadCounter
 | |
| 			writerCounter = statConn.WriteCounter
 | |
| 		}
 | |
| 		if xc, ok := conn.(*tls.Conn); ok {
 | |
| 			conn = xc.NetConn()
 | |
| 		} else if utlsConn, ok := conn.(*tls.UConn); ok {
 | |
| 			conn = utlsConn.NetConn()
 | |
| 		} else if realityConn, ok := conn.(*reality.Conn); ok {
 | |
| 			conn = realityConn.NetConn()
 | |
| 		} else if realityUConn, ok := conn.(*reality.UConn); ok {
 | |
| 			conn = realityUConn.NetConn()
 | |
| 		}
 | |
| 		if pc, ok := conn.(*proxyproto.Conn); ok {
 | |
| 			conn = pc.Raw()
 | |
| 			// 8192 > 4096, there is no need to process pc's bufReader
 | |
| 		}
 | |
| 	}
 | |
| 	return conn, readCounter, writerCounter
 | |
| }
 | |
| 
 | |
| // CopyRawConnIfExist use the most efficient copy method.
 | |
| // - If caller don't want to turn on splice, do not pass in both reader conn and writer conn
 | |
| // - writer are from *transport.Link
 | |
| func CopyRawConnIfExist(ctx context.Context, readerConn net.Conn, writerConn net.Conn, writer buf.Writer, timer *signal.ActivityTimer, inTimer *signal.ActivityTimer) error {
 | |
| 	readerConn, readCounter, _ := UnwrapRawConn(readerConn)
 | |
| 	writerConn, _, writeCounter := UnwrapRawConn(writerConn)
 | |
| 	reader := buf.NewReader(readerConn)
 | |
| 	if runtime.GOOS != "linux" && runtime.GOOS != "android" {
 | |
| 		return readV(ctx, reader, writer, timer, readCounter)
 | |
| 	}
 | |
| 	tc, ok := writerConn.(*net.TCPConn)
 | |
| 	if !ok || readerConn == nil || writerConn == nil {
 | |
| 		return readV(ctx, reader, writer, timer, readCounter)
 | |
| 	}
 | |
| 	inbound := session.InboundFromContext(ctx)
 | |
| 	if inbound == nil || inbound.CanSpliceCopy == 3 {
 | |
| 		return readV(ctx, reader, writer, timer, readCounter)
 | |
| 	}
 | |
| 	outbounds := session.OutboundsFromContext(ctx)
 | |
| 	if len(outbounds) == 0 {
 | |
| 		return readV(ctx, reader, writer, timer, readCounter)
 | |
| 	}
 | |
| 	for _, ob := range outbounds {
 | |
| 		if ob.CanSpliceCopy == 3 {
 | |
| 			return readV(ctx, reader, writer, timer, readCounter)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	for {
 | |
| 		inbound := session.InboundFromContext(ctx)
 | |
| 		outbounds := session.OutboundsFromContext(ctx)
 | |
| 		var splice = inbound.CanSpliceCopy == 1
 | |
| 		for _, ob := range outbounds {
 | |
| 			if ob.CanSpliceCopy != 1 {
 | |
| 				splice = false
 | |
| 			}
 | |
| 		}
 | |
| 		if splice {
 | |
| 			errors.LogInfo(ctx, "CopyRawConn splice")
 | |
| 			statWriter, _ := writer.(*dispatcher.SizeStatWriter)
 | |
| 			//runtime.Gosched() // necessary
 | |
| 			time.Sleep(time.Millisecond)    // without this, there will be a rare ssl error for freedom splice
 | |
| 			timer.SetTimeout(8 * time.Hour) // prevent leak, just in case
 | |
| 			if inTimer != nil {
 | |
| 				inTimer.SetTimeout(8 * time.Hour)
 | |
| 			}
 | |
| 			w, err := tc.ReadFrom(readerConn)
 | |
| 			if readCounter != nil {
 | |
| 				readCounter.Add(w) // outbound stats
 | |
| 			}
 | |
| 			if writeCounter != nil {
 | |
| 				writeCounter.Add(w) // inbound stats
 | |
| 			}
 | |
| 			if statWriter != nil {
 | |
| 				statWriter.Counter.Add(w) // user stats
 | |
| 			}
 | |
| 			if err != nil && errors.Cause(err) != io.EOF {
 | |
| 				return err
 | |
| 			}
 | |
| 			return nil
 | |
| 		}
 | |
| 		buffer, err := reader.ReadMultiBuffer()
 | |
| 		if !buffer.IsEmpty() {
 | |
| 			if readCounter != nil {
 | |
| 				readCounter.Add(int64(buffer.Len()))
 | |
| 			}
 | |
| 			timer.Update()
 | |
| 			if werr := writer.WriteMultiBuffer(buffer); werr != nil {
 | |
| 				return werr
 | |
| 			}
 | |
| 		}
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func readV(ctx context.Context, reader buf.Reader, writer buf.Writer, timer signal.ActivityUpdater, readCounter stats.Counter) error {
 | |
| 	errors.LogInfo(ctx, "CopyRawConn readv")
 | |
| 	if err := buf.Copy(reader, writer, buf.UpdateActivity(timer), buf.AddToStatCounter(readCounter)); err != nil {
 | |
| 		return errors.New("failed to process response").Base(err)
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | 
