github.com/yaling888/clash@v1.53.0/transport/socks4/socks4.go (about) 1 package socks4 2 3 import ( 4 "errors" 5 "io" 6 "net" 7 "net/netip" 8 "strconv" 9 10 "github.com/yaling888/clash/common/pool" 11 "github.com/yaling888/clash/component/auth" 12 ) 13 14 const Version = 0x04 15 16 type Command = uint8 17 18 const ( 19 CmdConnect Command = 0x01 20 CmdBind Command = 0x02 21 ) 22 23 type Code = uint8 24 25 const ( 26 RequestGranted Code = 90 27 RequestRejected Code = 91 28 RequestIdentdFailed Code = 92 29 RequestIdentdMismatched Code = 93 30 ) 31 32 var ( 33 errVersionMismatched = errors.New("version code mismatched") 34 errCommandNotSupported = errors.New("command not supported") 35 errIPv6NotSupported = errors.New("IPv6 not supported") 36 37 ErrRequestRejected = errors.New("request rejected or failed") 38 ErrRequestIdentdFailed = errors.New("request rejected because SOCKS server cannot connect to identd on the client") 39 ErrRequestIdentdMismatched = errors.New("request rejected because the client program and identd report different user-ids") 40 ErrRequestUnknownCode = errors.New("request failed with unknown code") 41 ) 42 43 var subnet = netip.PrefixFrom(netip.IPv4Unspecified(), 24) 44 45 func ServerHandshake(rw io.ReadWriter, authenticator auth.Authenticator) (addr string, command Command, err error) { 46 var req [8]byte 47 if _, err = io.ReadFull(rw, req[:]); err != nil { 48 return 49 } 50 51 r := pool.BufferReader(req[:]) 52 if r.ReadUint8() != Version { 53 err = errVersionMismatched 54 return 55 } 56 57 if command = r.ReadUint8(); command != CmdConnect { 58 err = errCommandNotSupported 59 return 60 } 61 62 var ( 63 host string 64 port string 65 code uint8 66 userID []byte 67 ) 68 if userID, err = readUntilNull(rw); err != nil { 69 return 70 } 71 72 dstPort := r.ReadUint16be() 73 dstAddr := r.ReadIPv4() 74 if isReservedIP(dstAddr) { 75 var target []byte 76 if target, err = readUntilNull(rw); err != nil { 77 return 78 } 79 host = string(target) 80 } 81 82 port = strconv.Itoa(int(dstPort)) 83 if host != "" { 84 addr = net.JoinHostPort(host, port) 85 } else { 86 addr = net.JoinHostPort(dstAddr.String(), port) 87 } 88 89 // SOCKS4 only support USERID auth. 90 if authenticator == nil || authenticator.HasUser(userID) { 91 code = RequestGranted 92 } else { 93 code = RequestIdentdMismatched 94 err = ErrRequestIdentdMismatched 95 } 96 97 reply := pool.BufferWriter(make([]byte, 0, 8)) 98 reply.PutUint8(0) // reply code 99 reply.PutUint8(code) // result code 100 reply.PutUint16be(dstPort) 101 reply.PutSlice(dstAddr.AsSlice()) 102 103 _, wErr := rw.Write(reply.Bytes()) 104 if err == nil { 105 err = wErr 106 } 107 return 108 } 109 110 func ClientHandshake(rw io.ReadWriter, addr string, command Command, userID string) (err error) { 111 host, portStr, err := net.SplitHostPort(addr) 112 if err != nil { 113 return err 114 } 115 116 port, err := strconv.ParseUint(portStr, 10, 16) 117 if err != nil { 118 return err 119 } 120 121 dstIP, err := netip.ParseAddr(host) 122 if err != nil /* HOST */ { 123 dstIP = netip.AddrFrom4([4]byte{0, 0, 0, 1}) 124 } else if dstIP.Is6() /* IPv6 */ { 125 return errIPv6NotSupported 126 } 127 128 req := pool.BufferWriter{} 129 req.PutUint8(Version) 130 req.PutUint8(command) 131 req.PutUint16be(uint16(port)) 132 req.PutSlice(dstIP.AsSlice()) 133 req.PutString(userID) 134 req.PutUint8(0) /* NULL */ 135 136 if isReservedIP(dstIP) /* SOCKS4A */ { 137 req.PutString(host) 138 req.PutUint8(0) /* NULL */ 139 } 140 141 if _, err = rw.Write(req.Bytes()); err != nil { 142 return err 143 } 144 145 var resp [8]byte 146 if _, err = io.ReadFull(rw, resp[:]); err != nil { 147 return err 148 } 149 150 if resp[0] != 0x00 { 151 return errVersionMismatched 152 } 153 154 switch resp[1] { 155 case RequestGranted: 156 return nil 157 case RequestRejected: 158 return ErrRequestRejected 159 case RequestIdentdFailed: 160 return ErrRequestIdentdFailed 161 case RequestIdentdMismatched: 162 return ErrRequestIdentdMismatched 163 default: 164 return ErrRequestUnknownCode 165 } 166 } 167 168 // For version 4A, if the client cannot resolve the destination host's 169 // domain name to find its IP address, it should set the first three bytes 170 // of DSTIP to NULL and the last byte to a non-zero value. (This corresponds 171 // to IP address 0.0.0.x, with x nonzero. As decreed by IANA -- The 172 // Internet Assigned Numbers Authority -- such an address is inadmissible 173 // as a destination IP address and thus should never occur if the client 174 // can resolve the domain name.) 175 func isReservedIP(ip netip.Addr) bool { 176 return !ip.IsUnspecified() && subnet.Contains(ip) 177 } 178 179 func readUntilNull(r io.Reader) ([]byte, error) { 180 buf := pool.BufferWriter{} 181 var data [1]byte 182 183 for { 184 if _, err := r.Read(data[:]); err != nil { 185 return nil, err 186 } 187 if data[0] == 0 { 188 return buf.Bytes(), nil 189 } 190 buf.PutUint8(data[0]) 191 } 192 }