github.com/kelleygo/clashcore@v1.0.2/transport/socks4/socks4.go (about) 1 package socks4 2 3 import ( 4 "bytes" 5 "encoding/binary" 6 "errors" 7 "io" 8 "net" 9 "net/netip" 10 "strconv" 11 12 "github.com/kelleygo/clashcore/component/auth" 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 var subnet = netip.PrefixFrom(netip.IPv4Unspecified(), 24) 45 46 func ServerHandshake(rw io.ReadWriter, authenticator auth.Authenticator) (addr string, command Command, err error) { 47 var req [8]byte 48 if _, err = io.ReadFull(rw, req[:]); err != nil { 49 return 50 } 51 52 if req[0] != Version { 53 err = errVersionMismatched 54 return 55 } 56 57 if command = req[1]; command != CmdConnect { 58 err = errCommandNotSupported 59 return 60 } 61 62 var ( 63 dstIP = netip.AddrFrom4(*(*[4]byte)(req[4:8])) // [4]byte 64 dstPort = req[2:4] // [2]byte 65 ) 66 67 var ( 68 host string 69 port string 70 code uint8 71 userID []byte 72 ) 73 if userID, err = readUntilNull(rw); err != nil { 74 return 75 } 76 77 if isReservedIP(dstIP) { 78 var target []byte 79 if target, err = readUntilNull(rw); err != nil { 80 return 81 } 82 host = string(target) 83 } 84 85 port = strconv.Itoa(int(binary.BigEndian.Uint16(dstPort))) 86 if host != "" { 87 addr = net.JoinHostPort(host, port) 88 } else { 89 addr = net.JoinHostPort(dstIP.String(), port) 90 } 91 92 // SOCKS4 only support USERID auth. 93 if authenticator == nil || authenticator.Verify(string(userID), "") { 94 code = RequestGranted 95 } else { 96 code = RequestIdentdMismatched 97 err = ErrRequestIdentdMismatched 98 } 99 100 var reply [8]byte 101 reply[0] = 0x00 // reply code 102 reply[1] = code // result code 103 copy(reply[4:8], dstIP.AsSlice()) 104 copy(reply[2:4], dstPort) 105 106 _, wErr := rw.Write(reply[:]) 107 if err == nil { 108 err = wErr 109 } 110 return 111 } 112 113 func ClientHandshake(rw io.ReadWriter, addr string, command Command, userID string) (err error) { 114 host, portStr, err := net.SplitHostPort(addr) 115 if err != nil { 116 return err 117 } 118 119 port, err := strconv.ParseUint(portStr, 10, 16) 120 if err != nil { 121 return err 122 } 123 124 dstIP, err := netip.ParseAddr(host) 125 if err != nil /* HOST */ { 126 dstIP = netip.AddrFrom4([4]byte{0, 0, 0, 1}) 127 } else if dstIP.Is6() /* IPv6 */ { 128 return errIPv6NotSupported 129 } 130 131 req := &bytes.Buffer{} 132 req.WriteByte(Version) 133 req.WriteByte(command) 134 _ = binary.Write(req, binary.BigEndian, uint16(port)) 135 req.Write(dstIP.AsSlice()) 136 req.WriteString(userID) 137 req.WriteByte(0) /* NULL */ 138 139 if isReservedIP(dstIP) /* SOCKS4A */ { 140 req.WriteString(host) 141 req.WriteByte(0) /* NULL */ 142 } 143 144 if _, err = rw.Write(req.Bytes()); err != nil { 145 return err 146 } 147 148 var resp [8]byte 149 if _, err = io.ReadFull(rw, resp[:]); err != nil { 150 return err 151 } 152 153 if resp[0] != 0x00 { 154 return errVersionMismatched 155 } 156 157 switch resp[1] { 158 case RequestGranted: 159 return nil 160 case RequestRejected: 161 return ErrRequestRejected 162 case RequestIdentdFailed: 163 return ErrRequestIdentdFailed 164 case RequestIdentdMismatched: 165 return ErrRequestIdentdMismatched 166 default: 167 return ErrRequestUnknownCode 168 } 169 } 170 171 // For version 4A, if the client cannot resolve the destination host's 172 // domain name to find its IP address, it should set the first three bytes 173 // of DSTIP to NULL and the last byte to a non-zero value. (This corresponds 174 // to IP address 0.0.0.x, with x nonzero. As decreed by IANA -- The 175 // Internet Assigned Numbers Authority -- such an address is inadmissible 176 // as a destination IP address and thus should never occur if the client 177 // can resolve the domain name.) 178 func isReservedIP(ip netip.Addr) bool { 179 return !ip.IsUnspecified() && subnet.Contains(ip) 180 } 181 182 func readUntilNull(r io.Reader) ([]byte, error) { 183 buf := &bytes.Buffer{} 184 var data [1]byte 185 186 for { 187 if _, err := r.Read(data[:]); err != nil { 188 return nil, err 189 } 190 if data[0] == 0 { 191 return buf.Bytes(), nil 192 } 193 buf.WriteByte(data[0]) 194 } 195 }