github.com/metacubex/mihomo@v1.18.5/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/metacubex/mihomo/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, user string, 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 user = string(userID) 77 78 if isReservedIP(dstIP) { 79 var target []byte 80 if target, err = readUntilNull(rw); err != nil { 81 return 82 } 83 host = string(target) 84 } 85 86 port = strconv.Itoa(int(binary.BigEndian.Uint16(dstPort))) 87 if host != "" { 88 addr = net.JoinHostPort(host, port) 89 } else { 90 addr = net.JoinHostPort(dstIP.String(), port) 91 } 92 93 // SOCKS4 only support USERID auth. 94 if authenticator == nil || authenticator.Verify(user, "") { 95 code = RequestGranted 96 } else { 97 code = RequestIdentdMismatched 98 err = ErrRequestIdentdMismatched 99 } 100 101 var reply [8]byte 102 reply[0] = 0x00 // reply code 103 reply[1] = code // result code 104 copy(reply[4:8], dstIP.AsSlice()) 105 copy(reply[2:4], dstPort) 106 107 _, wErr := rw.Write(reply[:]) 108 if err == nil { 109 err = wErr 110 } 111 return 112 } 113 114 func ClientHandshake(rw io.ReadWriter, addr string, command Command, userID string) (err error) { 115 host, portStr, err := net.SplitHostPort(addr) 116 if err != nil { 117 return err 118 } 119 120 port, err := strconv.ParseUint(portStr, 10, 16) 121 if err != nil { 122 return err 123 } 124 125 dstIP, err := netip.ParseAddr(host) 126 if err != nil /* HOST */ { 127 dstIP = netip.AddrFrom4([4]byte{0, 0, 0, 1}) 128 } else if dstIP.Is6() /* IPv6 */ { 129 return errIPv6NotSupported 130 } 131 132 req := &bytes.Buffer{} 133 req.WriteByte(Version) 134 req.WriteByte(command) 135 _ = binary.Write(req, binary.BigEndian, uint16(port)) 136 req.Write(dstIP.AsSlice()) 137 req.WriteString(userID) 138 req.WriteByte(0) /* NULL */ 139 140 if isReservedIP(dstIP) /* SOCKS4A */ { 141 req.WriteString(host) 142 req.WriteByte(0) /* NULL */ 143 } 144 145 if _, err = rw.Write(req.Bytes()); err != nil { 146 return err 147 } 148 149 var resp [8]byte 150 if _, err = io.ReadFull(rw, resp[:]); err != nil { 151 return err 152 } 153 154 if resp[0] != 0x00 { 155 return errVersionMismatched 156 } 157 158 switch resp[1] { 159 case RequestGranted: 160 return nil 161 case RequestRejected: 162 return ErrRequestRejected 163 case RequestIdentdFailed: 164 return ErrRequestIdentdFailed 165 case RequestIdentdMismatched: 166 return ErrRequestIdentdMismatched 167 default: 168 return ErrRequestUnknownCode 169 } 170 } 171 172 // For version 4A, if the client cannot resolve the destination host's 173 // domain name to find its IP address, it should set the first three bytes 174 // of DSTIP to NULL and the last byte to a non-zero value. (This corresponds 175 // to IP address 0.0.0.x, with x nonzero. As decreed by IANA -- The 176 // Internet Assigned Numbers Authority -- such an address is inadmissible 177 // as a destination IP address and thus should never occur if the client 178 // can resolve the domain name.) 179 func isReservedIP(ip netip.Addr) bool { 180 return !ip.IsUnspecified() && subnet.Contains(ip) 181 } 182 183 func readUntilNull(r io.Reader) ([]byte, error) { 184 buf := &bytes.Buffer{} 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.WriteByte(data[0]) 195 } 196 }