mihomo/transport/vless/vision/conn.go
wwqgtxx c2209d68f7
Some checks are pending
Test / test (1.20, macos-15-intel) (push) Waiting to run
Test / test (1.20, macos-latest) (push) Waiting to run
Test / test (1.20, ubuntu-24.04-arm) (push) Waiting to run
Test / test (1.20, ubuntu-latest) (push) Waiting to run
Test / test (1.20, windows-latest) (push) Waiting to run
Test / test (1.21, macos-15-intel) (push) Waiting to run
Test / test (1.21, macos-latest) (push) Waiting to run
Test / test (1.21, ubuntu-24.04-arm) (push) Waiting to run
Test / test (1.21, ubuntu-latest) (push) Waiting to run
Test / test (1.21, windows-latest) (push) Waiting to run
Test / test (1.22, macos-15-intel) (push) Waiting to run
Test / test (1.22, macos-latest) (push) Waiting to run
Test / test (1.22, ubuntu-24.04-arm) (push) Waiting to run
Test / test (1.22, ubuntu-latest) (push) Waiting to run
Test / test (1.22, windows-latest) (push) Waiting to run
Test / test (1.23, macos-15-intel) (push) Waiting to run
Test / test (1.23, macos-latest) (push) Waiting to run
Test / test (1.23, ubuntu-24.04-arm) (push) Waiting to run
Test / test (1.23, ubuntu-latest) (push) Waiting to run
Test / test (1.23, windows-latest) (push) Waiting to run
Test / test (1.24, macos-15-intel) (push) Waiting to run
Test / test (1.24, macos-latest) (push) Waiting to run
Test / test (1.24, ubuntu-24.04-arm) (push) Waiting to run
Test / test (1.24, ubuntu-latest) (push) Waiting to run
Test / test (1.24, windows-latest) (push) Waiting to run
Test / test (1.25, macos-15-intel) (push) Waiting to run
Test / test (1.25, macos-latest) (push) Waiting to run
Test / test (1.25, ubuntu-24.04-arm) (push) Waiting to run
Test / test (1.25, ubuntu-latest) (push) Waiting to run
Test / test (1.25, windows-latest) (push) Waiting to run
Trigger CMFA Update / trigger-CMFA-update (push) Waiting to run
fix: vision panic with dialer-proxy
https://github.com/MetaCubeX/mihomo/issues/2334
2025-11-04 18:54:33 +08:00

312 lines
8.2 KiB
Go

package vision
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"net"
"unsafe"
"github.com/metacubex/mihomo/common/buf"
N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/log"
"github.com/gofrs/uuid/v5"
)
var (
_ N.ExtendedConn = (*Conn)(nil)
)
type Conn struct {
net.Conn // should be *vless.Conn
N.ExtendedReader
N.ExtendedWriter
userUUID uuid.UUID
// [*tls.Conn] or other tls-like [net.Conn]'s internal variables
netConn net.Conn // tlsConn.NetConn()
input *bytes.Reader // &tlsConn.input or nil
rawInput *bytes.Buffer // &tlsConn.rawInput or nil
packetsToFilter int
isTLS bool
isTLS12orAbove bool
enableXTLS bool
cipher uint16
remainingServerHello uint16
readRemainingBuffer *buf.Buffer
readRemainingContent int
readRemainingPadding int
readProcess bool
readFilterUUID bool
readLastCommand byte
writeFilterApplicationData bool
writeDirect bool
writeOnceUserUUID []byte
}
func (vc *Conn) Read(b []byte) (int, error) {
if vc.readProcess {
buffer := buf.With(b)
err := vc.ReadBuffer(buffer)
if unsafe.SliceData(buffer.Bytes()) != unsafe.SliceData(b) { // buffer.Bytes() not at the beginning of b
copy(b, buffer.Bytes())
}
return buffer.Len(), err
}
return vc.ExtendedReader.Read(b)
}
func (vc *Conn) ReadBuffer(buffer *buf.Buffer) error {
if vc.readRemainingBuffer != nil {
_, err := buffer.ReadOnceFrom(vc.readRemainingBuffer)
if vc.readRemainingBuffer.IsEmpty() {
vc.readRemainingBuffer.Release()
vc.readRemainingBuffer = nil
}
return err
}
if vc.readRemainingContent > 0 {
readSize := xrayBufSize // at least read xrayBufSize
if buffer.FreeLen() > readSize { // input buffer larger than xrayBufSize, read as much as possible
readSize = buffer.FreeLen()
}
if readSize > vc.readRemainingContent { // don't read out of bounds
readSize = vc.readRemainingContent
}
readBuffer := buffer
if buffer.FreeLen() < readSize {
readBuffer = buf.NewSize(readSize)
vc.readRemainingBuffer = readBuffer
}
n, err := vc.ExtendedReader.Read(readBuffer.FreeBytes()[:readSize])
readBuffer.Truncate(n)
vc.readRemainingContent -= n
vc.FilterTLS(readBuffer.Bytes())
if vc.readRemainingBuffer != nil {
innerErr := vc.ReadBuffer(buffer) // back to top but not losing err
if err != nil {
err = innerErr
}
}
return err
}
if vc.readRemainingPadding > 0 {
n, err := io.CopyN(io.Discard, vc.ExtendedReader, int64(vc.readRemainingPadding))
if err != nil {
return err
}
vc.readRemainingPadding -= int(n)
}
if vc.readProcess {
switch vc.readLastCommand {
case commandPaddingContinue:
//if vc.isTLS || vc.packetsToFilter > 0 {
need := PaddingHeaderLen
if !vc.readFilterUUID {
need = PaddingHeaderLen - uuid.Size
}
var header []byte
if buffer.FreeLen() < need {
header = make([]byte, need)
} else {
header = buffer.FreeBytes()[:need]
}
_, err := io.ReadFull(vc.ExtendedReader, header)
if err != nil {
return err
}
if vc.readFilterUUID {
vc.readFilterUUID = false
if !bytes.Equal(vc.userUUID.Bytes(), header[:uuid.Size]) {
err = fmt.Errorf("XTLS Vision server responded unknown UUID: %s", uuid.FromBytesOrNil(header[:uuid.Size]))
log.Errorln(err.Error())
return err
}
header = header[uuid.Size:]
}
vc.readRemainingPadding = int(binary.BigEndian.Uint16(header[3:]))
vc.readRemainingContent = int(binary.BigEndian.Uint16(header[1:]))
vc.readLastCommand = header[0]
log.Debugln("XTLS Vision read padding: command=%d, payloadLen=%d, paddingLen=%d",
vc.readLastCommand, vc.readRemainingContent, vc.readRemainingPadding)
return vc.ReadBuffer(buffer)
//}
case commandPaddingEnd:
vc.readProcess = false
return vc.ReadBuffer(buffer)
case commandPaddingDirect:
needReturn := false
if vc.input != nil {
_, err := buffer.ReadOnceFrom(vc.input)
if err != nil {
if !errors.Is(err, io.EOF) {
return err
}
}
if vc.input.Len() == 0 {
needReturn = true
*vc.input = bytes.Reader{} // full reset
vc.input = nil
} else { // buffer is full
return nil
}
}
if vc.rawInput != nil {
_, err := buffer.ReadOnceFrom(vc.rawInput)
if err != nil {
if !errors.Is(err, io.EOF) {
return err
}
}
needReturn = true
if vc.rawInput.Len() == 0 {
*vc.rawInput = bytes.Buffer{} // full reset
vc.rawInput = nil
}
}
if vc.input == nil && vc.rawInput == nil {
vc.readProcess = false
vc.ExtendedReader = N.NewExtendedReader(vc.netConn)
log.Debugln("XTLS Vision direct read start")
}
if needReturn {
return nil
}
default:
err := fmt.Errorf("XTLS Vision read unknown command: %d", vc.readLastCommand)
log.Debugln(err.Error())
return err
}
}
return vc.ExtendedReader.ReadBuffer(buffer)
}
func (vc *Conn) Write(p []byte) (int, error) {
if vc.writeFilterApplicationData {
return N.WriteBuffer(vc, buf.As(p))
}
return vc.ExtendedWriter.Write(p)
}
func (vc *Conn) WriteBuffer(buffer *buf.Buffer) (err error) {
if vc.writeFilterApplicationData {
if buffer.IsEmpty() {
ApplyPadding(buffer, commandPaddingContinue, &vc.writeOnceUserUUID, true) // we do a long padding to hide vless header
return vc.ExtendedWriter.WriteBuffer(buffer)
}
vc.FilterTLS(buffer.Bytes())
buffers := vc.ReshapeBuffer(buffer)
applyPadding := true
for i, buffer := range buffers {
command := commandPaddingContinue
if applyPadding {
if vc.isTLS && buffer.Len() > 6 && bytes.Equal(tlsApplicationDataStart, buffer.To(3)) {
command = commandPaddingEnd
if vc.enableXTLS {
command = commandPaddingDirect
vc.writeDirect = true
}
vc.writeFilterApplicationData = false
applyPadding = false
} else if !vc.isTLS12orAbove && vc.packetsToFilter <= 1 {
command = commandPaddingEnd
vc.writeFilterApplicationData = false
applyPadding = false
}
ApplyPadding(buffer, command, &vc.writeOnceUserUUID, vc.isTLS)
}
err = vc.ExtendedWriter.WriteBuffer(buffer)
if err != nil {
buf.ReleaseMulti(buffers[i:]) // release unwritten buffers
return
}
if command == commandPaddingDirect {
vc.ExtendedWriter = N.NewExtendedWriter(vc.netConn)
log.Debugln("XTLS Vision direct write start")
//time.Sleep(5 * time.Millisecond)
}
}
return err
}
/*if vc.writeDirect {
log.Debugln("XTLS Vision Direct write, payloadLen=%d", buffer.Len())
}*/
return vc.ExtendedWriter.WriteBuffer(buffer)
}
func (vc *Conn) FrontHeadroom() int {
fontHeadroom := PaddingHeaderLen - uuid.Size
if vc.readFilterUUID || vc.writeOnceUserUUID != nil {
fontHeadroom = PaddingHeaderLen
}
if vc.writeFilterApplicationData { // The writer may be replaced, add the required value for vc.netConn
if abs := N.CalculateFrontHeadroom(vc.netConn) - N.CalculateFrontHeadroom(vc.Conn); abs > 0 {
fontHeadroom += abs
}
}
return fontHeadroom
}
func (vc *Conn) RearHeadroom() int {
rearHeadroom := 500 + 900
if vc.writeFilterApplicationData { // The writer may be replaced, add the required value for vc.netConn
if abs := N.CalculateRearHeadroom(vc.netConn) - N.CalculateRearHeadroom(vc.Conn); abs > 0 {
rearHeadroom += abs
}
}
return rearHeadroom
}
func (vc *Conn) NeedHandshake() bool {
return vc.writeOnceUserUUID != nil
}
func (vc *Conn) NeedAdditionalReadDeadline() bool {
return true
}
func (vc *Conn) Upstream() any {
if vc.writeDirect ||
vc.readLastCommand == commandPaddingDirect {
return vc.netConn
}
return vc.Conn
}
func (vc *Conn) ReaderPossiblyReplaceable() bool {
return vc.readProcess
}
func (vc *Conn) ReaderReplaceable() bool {
if !vc.readProcess &&
vc.readLastCommand == commandPaddingDirect {
return true
}
return false
}
func (vc *Conn) WriterPossiblyReplaceable() bool {
return vc.writeFilterApplicationData
}
func (vc *Conn) WriterReplaceable() bool {
if vc.writeDirect {
return true
}
return false
}
func (vc *Conn) Close() error {
if vc.ReaderReplaceable() || vc.WriterReplaceable() { // ignore send closeNotify alert in tls.Conn
return vc.netConn.Close()
}
return vc.Conn.Close()
}