github.com/igoogolx/clash@v1.19.8/transport/v2ray-plugin/mux.go (about)

     1  package obfs
     2  
     3  import (
     4  	"encoding/binary"
     5  	"errors"
     6  	"io"
     7  	"net"
     8  	"net/netip"
     9  
    10  	"github.com/Dreamacro/protobytes"
    11  )
    12  
    13  type SessionStatus = byte
    14  
    15  const (
    16  	SessionStatusNew       SessionStatus = 0x01
    17  	SessionStatusKeep      SessionStatus = 0x02
    18  	SessionStatusEnd       SessionStatus = 0x03
    19  	SessionStatusKeepAlive SessionStatus = 0x04
    20  )
    21  
    22  const (
    23  	OptionNone  = byte(0x00)
    24  	OptionData  = byte(0x01)
    25  	OptionError = byte(0x02)
    26  )
    27  
    28  type MuxOption struct {
    29  	ID   [2]byte
    30  	Port uint16
    31  	Host string
    32  	Type string
    33  }
    34  
    35  // Mux is an mux-compatible client for v2ray-plugin, not a complete implementation
    36  type Mux struct {
    37  	net.Conn
    38  	buf    protobytes.BytesWriter
    39  	id     [2]byte
    40  	length [2]byte
    41  	status [2]byte
    42  	otb    []byte
    43  	remain int
    44  }
    45  
    46  func (m *Mux) Read(b []byte) (int, error) {
    47  	if m.remain != 0 {
    48  		length := m.remain
    49  		if len(b) < m.remain {
    50  			length = len(b)
    51  		}
    52  
    53  		n, err := m.Conn.Read(b[:length])
    54  		if err != nil {
    55  			return 0, err
    56  		}
    57  		m.remain -= n
    58  		return n, nil
    59  	}
    60  
    61  	for {
    62  		_, err := io.ReadFull(m.Conn, m.length[:])
    63  		if err != nil {
    64  			return 0, err
    65  		}
    66  		length := binary.BigEndian.Uint16(m.length[:])
    67  		if length > 512 {
    68  			return 0, errors.New("invalid metalen")
    69  		}
    70  
    71  		_, err = io.ReadFull(m.Conn, m.id[:])
    72  		if err != nil {
    73  			return 0, err
    74  		}
    75  
    76  		_, err = m.Conn.Read(m.status[:])
    77  		if err != nil {
    78  			return 0, err
    79  		}
    80  
    81  		opcode := m.status[0]
    82  		if opcode == SessionStatusKeepAlive {
    83  			continue
    84  		}
    85  
    86  		opts := m.status[1]
    87  
    88  		if opts != OptionData {
    89  			continue
    90  		}
    91  
    92  		_, err = io.ReadFull(m.Conn, m.length[:])
    93  		if err != nil {
    94  			return 0, err
    95  		}
    96  		dataLen := int(binary.BigEndian.Uint16(m.length[:]))
    97  		m.remain = dataLen
    98  		if dataLen > len(b) {
    99  			dataLen = len(b)
   100  		}
   101  
   102  		n, err := m.Conn.Read(b[:dataLen])
   103  		m.remain -= n
   104  		return n, err
   105  	}
   106  }
   107  
   108  func (m *Mux) Write(b []byte) (int, error) {
   109  	if m.otb != nil {
   110  		// create a sub connection
   111  		if _, err := m.Conn.Write(m.otb); err != nil {
   112  			return 0, err
   113  		}
   114  		m.otb = nil
   115  	}
   116  	m.buf.Reset()
   117  	m.buf.PutUint16be(4)
   118  	m.buf.PutSlice(m.id[:])
   119  	m.buf.PutUint8(SessionStatusKeep)
   120  	m.buf.PutUint8(OptionData)
   121  	m.buf.PutUint16be(uint16(len(b)))
   122  	m.buf.Write(b)
   123  
   124  	return m.Conn.Write(m.buf.Bytes())
   125  }
   126  
   127  func (m *Mux) Close() error {
   128  	_, err := m.Conn.Write([]byte{0x0, 0x4, m.id[0], m.id[1], SessionStatusEnd, OptionNone})
   129  	if err != nil {
   130  		return err
   131  	}
   132  	return m.Conn.Close()
   133  }
   134  
   135  func NewMux(conn net.Conn, option MuxOption) *Mux {
   136  	buf := protobytes.BytesWriter{}
   137  
   138  	// fill empty length
   139  	buf.PutSlice([]byte{0x0, 0x0})
   140  	buf.PutSlice(option.ID[:])
   141  	buf.PutUint8(SessionStatusNew)
   142  	buf.PutUint8(OptionNone)
   143  
   144  	// tcp
   145  	netType := byte(0x1)
   146  	if option.Type == "udp" {
   147  		netType = byte(0x2)
   148  	}
   149  	buf.PutUint8(netType)
   150  
   151  	// port
   152  	buf.PutUint16be(option.Port)
   153  
   154  	// address
   155  	ip, err := netip.ParseAddr(option.Host)
   156  	if err != nil {
   157  		buf.PutUint8(0x2)
   158  		buf.PutString(option.Host)
   159  	} else if ip.Is4() {
   160  		buf.PutUint8(0x1)
   161  		buf.PutSlice(ip.AsSlice())
   162  	} else {
   163  		buf.PutUint8(0x3)
   164  		buf.PutSlice(ip.AsSlice())
   165  	}
   166  
   167  	metadata := buf.Bytes()
   168  	binary.BigEndian.PutUint16(metadata[:2], uint16(len(metadata)-2))
   169  
   170  	return &Mux{
   171  		Conn: conn,
   172  		id:   option.ID,
   173  		otb:  metadata,
   174  	}
   175  }