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