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 }