mirror of
https://github.com/MetaCubeX/mihomo.git
synced 2025-12-19 08:20:05 +08:00
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
https://github.com/MetaCubeX/mihomo/issues/2334
312 lines
8.2 KiB
Go
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()
|
|
}
|