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