codekingpro/portable-devtools
114k
1from base64 import b64encode2try:3 from collections.abc import Callable4except ImportError:5 from collections import Callable6from errno import EOPNOTSUPP, EINVAL, EAGAIN7import functools8from io import BytesIO9import logging10import os11from os import SEEK_CUR12import socket13import struct14import sys15 16__version__ = "1.7.1"17 18 19if os.name == "nt" and sys.version_info < (3, 0):20 try:21 import win_inet_pton22 except ImportError:23 raise ImportError(24 "To run PySocks on Windows you must install win_inet_pton")25 26log = logging.getLogger(__name__)27 28PROXY_TYPE_SOCKS4 = SOCKS4 = 129PROXY_TYPE_SOCKS5 = SOCKS5 = 230PROXY_TYPE_HTTP = HTTP = 331 32PROXY_TYPES = {"SOCKS4": SOCKS4, "SOCKS5": SOCKS5, "HTTP": HTTP}33PRINTABLE_PROXY_TYPES = dict(zip(PROXY_TYPES.values(), PROXY_TYPES.keys()))34 35_orgsocket = _orig_socket = socket.socket36 37 38def set_self_blocking(function):39 40 @functools.wraps(function)41 def wrapper(*args, **kwargs):42 self = args[0]43 try:44 _is_blocking = self.gettimeout()45 if _is_blocking == 0:46 self.setblocking(True)47 return function(*args, **kwargs)48 except Exception as e:49 raise50 finally:51 # set orgin blocking52 if _is_blocking == 0:53 self.setblocking(False)54 return wrapper55 56 57class ProxyError(IOError):58 """Socket_err contains original socket.error exception."""59 def __init__(self, msg, socket_err=None):60 self.msg = msg61 self.socket_err = socket_err62 63 if socket_err:64 self.msg += ": {}".format(socket_err)65 66 def __str__(self):67 return self.msg68 69 70class GeneralProxyError(ProxyError):71 pass72 73 74class ProxyConnectionError(ProxyError):75 pass76 77 78class SOCKS5AuthError(ProxyError):79 pass80 81 82class SOCKS5Error(ProxyError):83 pass84 85 86class SOCKS4Error(ProxyError):87 pass88 89 90class HTTPError(ProxyError):91 pass92 93SOCKS4_ERRORS = {94 0x5B: "Request rejected or failed",95 0x5C: ("Request rejected because SOCKS server cannot connect to identd on"96 " the client"),97 0x5D: ("Request rejected because the client program and identd report"98 " different user-ids")99}100 101SOCKS5_ERRORS = {102 0x01: "General SOCKS server failure",103 0x02: "Connection not allowed by ruleset",104 0x03: "Network unreachable",105 0x04: "Host unreachable",106 0x05: "Connection refused",107 0x06: "TTL expired",108 0x07: "Command not supported, or protocol error",109 0x08: "Address type not supported"110}111 112DEFAULT_PORTS = {SOCKS4: 1080, SOCKS5: 1080, HTTP: 8080}113 114 115def set_default_proxy(proxy_type=None, addr=None, port=None, rdns=True,116 username=None, password=None):117 """Sets a default proxy.118 119 All further socksocket objects will use the default unless explicitly120 changed. All parameters are as for socket.set_proxy()."""121 socksocket.default_proxy = (proxy_type, addr, port, rdns,122 username.encode() if username else None,123 password.encode() if password else None)124 125 126def setdefaultproxy(*args, **kwargs):127 if "proxytype" in kwargs:128 kwargs["proxy_type"] = kwargs.pop("proxytype")129 return set_default_proxy(*args, **kwargs)130 131 132def get_default_proxy():133 """Returns the default proxy, set by set_default_proxy."""134 return socksocket.default_proxy135 136getdefaultproxy = get_default_proxy137 138 139def wrap_module(module):140 """Attempts to replace a module's socket library with a SOCKS socket.141 142 Must set a default proxy using set_default_proxy(...) first. This will143 only work on modules that import socket directly into the namespace;144 most of the Python Standard Library falls into this category."""145 if socksocket.default_proxy:146 module.socket.socket = socksocket147 else:148 raise GeneralProxyError("No default proxy specified")149 150wrapmodule = wrap_module151 152 153def create_connection(dest_pair,154 timeout=None, source_address=None,155 proxy_type=None, proxy_addr=None,156 proxy_port=None, proxy_rdns=True,157 proxy_username=None, proxy_password=None,158 socket_options=None):159 """create_connection(dest_pair, *[, timeout], **proxy_args) -> socket object160 161 Like socket.create_connection(), but connects to proxy162 before returning the socket object.163 164 dest_pair - 2-tuple of (IP/hostname, port).165 **proxy_args - Same args passed to socksocket.set_proxy() if present.166 timeout - Optional socket timeout value, in seconds.167 source_address - tuple (host, port) for the socket to bind to as its source168 address before connecting (only for compatibility)169 """170 # Remove IPv6 brackets on the remote address and proxy address.171 remote_host, remote_port = dest_pair172 if remote_host.startswith("["):173 remote_host = remote_host.strip("[]")174 if proxy_addr and proxy_addr.startswith("["):175 proxy_addr = proxy_addr.strip("[]")176 177 err = None178 179 # Allow the SOCKS proxy to be on IPv4 or IPv6 addresses.180 for r in socket.getaddrinfo(proxy_addr, proxy_port, 0, socket.SOCK_STREAM):181 family, socket_type, proto, canonname, sa = r182 sock = None183 try:184 sock = socksocket(family, socket_type, proto)185 186 if socket_options:187 for opt in socket_options:188 sock.setsockopt(*opt)189 190 if isinstance(timeout, (int, float)):191 sock.settimeout(timeout)192 193 if proxy_type:194 sock.set_proxy(proxy_type, proxy_addr, proxy_port, proxy_rdns,195 proxy_username, proxy_password)196 if source_address:197 sock.bind(source_address)198 199 sock.connect((remote_host, remote_port))200 return sock201 202 except (socket.error, ProxyError) as e:203 err = e204 if sock:205 sock.close()206 sock = None207 208 if err:209 raise err210 211 raise socket.error("gai returned empty list.")212 213 214class _BaseSocket(socket.socket):215 """Allows Python 2 delegated methods such as send() to be overridden."""216 def __init__(self, *pos, **kw):217 _orig_socket.__init__(self, *pos, **kw)218 219 self._savedmethods = dict()220 for name in self._savenames:221 self._savedmethods[name] = getattr(self, name)222 delattr(self, name) # Allows normal overriding mechanism to work223 224 _savenames = list()225 226 227def _makemethod(name):228 return lambda self, *pos, **kw: self._savedmethods[name](*pos, **kw)229for name in ("sendto", "send", "recvfrom", "recv"):230 method = getattr(_BaseSocket, name, None)231 232 # Determine if the method is not defined the usual way233 # as a function in the class.234 # Python 2 uses __slots__, so there are descriptors for each method,235 # but they are not functions.236 if not isinstance(method, Callable):237 _BaseSocket._savenames.append(name)238 setattr(_BaseSocket, name, _makemethod(name))239 240 241class socksocket(_BaseSocket):242 """socksocket([family[, type[, proto]]]) -> socket object243 244 Open a SOCKS enabled socket. The parameters are the same as245 those of the standard socket init. In order for SOCKS to work,246 you must specify family=AF_INET and proto=0.247 The "type" argument must be either SOCK_STREAM or SOCK_DGRAM.248 """249 250 default_proxy = None251 252 def __init__(self, family=socket.AF_INET, type=socket.SOCK_STREAM,253 proto=0, *args, **kwargs):254 if type not in (socket.SOCK_STREAM, socket.SOCK_DGRAM):255 msg = "Socket type must be stream or datagram, not {!r}"256 raise ValueError(msg.format(type))257 258 super(socksocket, self).__init__(family, type, proto, *args, **kwargs)259 self._proxyconn = None # TCP connection to keep UDP relay alive260 261 if self.default_proxy:262 self.proxy = self.default_proxy263 else:264 self.proxy = (None, None, None, None, None, None)265 self.proxy_sockname = None266 self.proxy_peername = None267 268 self._timeout = None269 270 def _readall(self, file, count):271 """Receive EXACTLY the number of bytes requested from the file object.272 273 Blocks until the required number of bytes have been received."""274 data = b""275 while len(data) < count:276 d = file.read(count - len(data))277 if not d:278 raise GeneralProxyError("Connection closed unexpectedly")279 data += d280 return data281 282 def settimeout(self, timeout):283 self._timeout = timeout284 try:285 # test if we're connected, if so apply timeout286 peer = self.get_proxy_peername()287 super(socksocket, self).settimeout(self._timeout)288 except socket.error:289 pass290 291 def gettimeout(self):292 return self._timeout293 294 def setblocking(self, v):295 if v:296 self.settimeout(None)297 else:298 self.settimeout(0.0)299 300 def set_proxy(self, proxy_type=None, addr=None, port=None, rdns=True,301 username=None, password=None):302 """ Sets the proxy to be used.303 304 proxy_type - The type of the proxy to be used. Three types305 are supported: PROXY_TYPE_SOCKS4 (including socks4a),306 PROXY_TYPE_SOCKS5 and PROXY_TYPE_HTTP307 addr - The address of the server (IP or DNS).308 port - The port of the server. Defaults to 1080 for SOCKS309 servers and 8080 for HTTP proxy servers.310 rdns - Should DNS queries be performed on the remote side311 (rather than the local side). The default is True.312 Note: This has no effect with SOCKS4 servers.313 username - Username to authenticate with to the server.314 The default is no authentication.315 password - Password to authenticate with to the server.316 Only relevant when username is also provided."""317 self.proxy = (proxy_type, addr, port, rdns,318 username.encode() if username else None,319 password.encode() if password else None)320 321 def setproxy(self, *args, **kwargs):322 if "proxytype" in kwargs:323 kwargs["proxy_type"] = kwargs.pop("proxytype")324 return self.set_proxy(*args, **kwargs)325 326 def bind(self, *pos, **kw):327 """Implements proxy connection for UDP sockets.328 329 Happens during the bind() phase."""330 (proxy_type, proxy_addr, proxy_port, rdns, username,331 password) = self.proxy332 if not proxy_type or self.type != socket.SOCK_DGRAM:333 return _orig_socket.bind(self, *pos, **kw)334 335 if self._proxyconn:336 raise socket.error(EINVAL, "Socket already bound to an address")337 if proxy_type != SOCKS5:338 msg = "UDP only supported by SOCKS5 proxy type"339 raise socket.error(EOPNOTSUPP, msg)340 super(socksocket, self).bind(*pos, **kw)341 342 # Need to specify actual local port because343 # some relays drop packets if a port of zero is specified.344 # Avoid specifying host address in case of NAT though.345 _, port = self.getsockname()346 dst = ("0", port)347 348 self._proxyconn = _orig_socket()349 proxy = self._proxy_addr()350 self._proxyconn.connect(proxy)351 352 UDP_ASSOCIATE = b"\x03"353 _, relay = self._SOCKS5_request(self._proxyconn, UDP_ASSOCIATE, dst)354 355 # The relay is most likely on the same host as the SOCKS proxy,356 # but some proxies return a private IP address (10.x.y.z)357 host, _ = proxy358 _, port = relay359 super(socksocket, self).connect((host, port))360 super(socksocket, self).settimeout(self._timeout)361 self.proxy_sockname = ("0.0.0.0", 0) # Unknown362 363 def sendto(self, bytes, *args, **kwargs):364 if self.type != socket.SOCK_DGRAM:365 return super(socksocket, self).sendto(bytes, *args, **kwargs)366 if not self._proxyconn:367 self.bind(("", 0))368 369 address = args[-1]370 flags = args[:-1]371 372 header = BytesIO()373 RSV = b"\x00\x00"374 header.write(RSV)375 STANDALONE = b"\x00"376 header.write(STANDALONE)377 self._write_SOCKS5_address(address, header)378 379 sent = super(socksocket, self).send(header.getvalue() + bytes, *flags,380 **kwargs)381 return sent - header.tell()382 383 def send(self, bytes, flags=0, **kwargs):384 if self.type == socket.SOCK_DGRAM:385 return self.sendto(bytes, flags, self.proxy_peername, **kwargs)386 else:387 return super(socksocket, self).send(bytes, flags, **kwargs)388 389 def recvfrom(self, bufsize, flags=0):390 if self.type != socket.SOCK_DGRAM:391 return super(socksocket, self).recvfrom(bufsize, flags)392 if not self._proxyconn:393 self.bind(("", 0))394 395 buf = BytesIO(super(socksocket, self).recv(bufsize + 1024, flags))396 buf.seek(2, SEEK_CUR)397 frag = buf.read(1)398 if ord(frag):399 raise NotImplementedError("Received UDP packet fragment")400 fromhost, fromport = self._read_SOCKS5_address(buf)401 402 if self.proxy_peername:403 peerhost, peerport = self.proxy_peername404 if fromhost != peerhost or peerport not in (0, fromport):405 raise socket.error(EAGAIN, "Packet filtered")406 407 return (buf.read(bufsize), (fromhost, fromport))408 409 def recv(self, *pos, **kw):410 bytes, _ = self.recvfrom(*pos, **kw)411 return bytes412 413 def close(self):414 if self._proxyconn:415 self._proxyconn.close()416 return super(socksocket, self).close()417 418 def get_proxy_sockname(self):419 """Returns the bound IP address and port number at the proxy."""420 return self.proxy_sockname421 422 getproxysockname = get_proxy_sockname423 424 def get_proxy_peername(self):425 """426 Returns the IP and port number of the proxy.427 """428 return self.getpeername()429 430 getproxypeername = get_proxy_peername431 432 def get_peername(self):433 """Returns the IP address and port number of the destination machine.434 435 Note: get_proxy_peername returns the proxy."""436 return self.proxy_peername437 438 getpeername = get_peername439 440 def _negotiate_SOCKS5(self, *dest_addr):441 """Negotiates a stream connection through a SOCKS5 server."""442 CONNECT = b"\x01"443 self.proxy_peername, self.proxy_sockname = self._SOCKS5_request(444 self, CONNECT, dest_addr)445 446 def _SOCKS5_request(self, conn, cmd, dst):447 """448 Send SOCKS5 request with given command (CMD field) and449 address (DST field). Returns resolved DST address that was used.450 """451 proxy_type, addr, port, rdns, username, password = self.proxy452 453 writer = conn.makefile("wb")454 reader = conn.makefile("rb", 0) # buffering=0 renamed in Python 3455 try:456 # First we'll send the authentication packages we support.457 if username and password:458 # The username/password details were supplied to the459 # set_proxy method so we support the USERNAME/PASSWORD460 # authentication (in addition to the standard none).461 writer.write(b"\x05\x02\x00\x02")462 else:463 # No username/password were entered, therefore we464 # only support connections with no authentication.465 writer.write(b"\x05\x01\x00")466 467 # We'll receive the server's response to determine which468 # method was selected469 writer.flush()470 chosen_auth = self._readall(reader, 2)471 472 if chosen_auth[0:1] != b"\x05":473 # Note: string[i:i+1] is used because indexing of a bytestring474 # via bytestring[i] yields an integer in Python 3475 raise GeneralProxyError(476 "SOCKS5 proxy server sent invalid data")477 478 # Check the chosen authentication method479 480 if chosen_auth[1:2] == b"\x02":481 # Okay, we need to perform a basic username/password482 # authentication.483 if not (username and password):484 # Although we said we don't support authentication, the485 # server may still request basic username/password486 # authentication487 raise SOCKS5AuthError("No username/password supplied. "488 "Server requested username/password"489 " authentication")490 491 writer.write(b"\x01" + chr(len(username)).encode()492 + username493 + chr(len(password)).encode()494 + password)495 writer.flush()496 auth_status = self._readall(reader, 2)497 if auth_status[0:1] != b"\x01":498 # Bad response499 raise GeneralProxyError(500 "SOCKS5 proxy server sent invalid data")501 if auth_status[1:2] != b"\x00":502 # Authentication failed503 raise SOCKS5AuthError("SOCKS5 authentication failed")504 505 # Otherwise, authentication succeeded506 507 # No authentication is required if 0x00508 elif chosen_auth[1:2] != b"\x00":509 # Reaching here is always bad510 if chosen_auth[1:2] == b"\xFF":511 raise SOCKS5AuthError(512 "All offered SOCKS5 authentication methods were"513 " rejected")514 else:515 raise GeneralProxyError(516 "SOCKS5 proxy server sent invalid data")517 518 # Now we can request the actual connection519 writer.write(b"\x05" + cmd + b"\x00")520 resolved = self._write_SOCKS5_address(dst, writer)521 writer.flush()522 523 # Get the response524 resp = self._readall(reader, 3)525 if resp[0:1] != b"\x05":526 raise GeneralProxyError(527 "SOCKS5 proxy server sent invalid data")528 529 status = ord(resp[1:2])530 if status != 0x00:531 # Connection failed: server returned an error532 error = SOCKS5_ERRORS.get(status, "Unknown error")533 raise SOCKS5Error("{:#04x}: {}".format(status, error))534 535 # Get the bound address/port536 bnd = self._read_SOCKS5_address(reader)537 538 super(socksocket, self).settimeout(self._timeout)539 return (resolved, bnd)540 finally:541 reader.close()542 writer.close()543 544 def _write_SOCKS5_address(self, addr, file):545 """546 Return the host and port packed for the SOCKS5 protocol,547 and the resolved address as a tuple object.548 """549 host, port = addr550 proxy_type, _, _, rdns, username, password = self.proxy551 family_to_byte = {socket.AF_INET: b"\x01", socket.AF_INET6: b"\x04"}552 553 # If the given destination address is an IP address, we'll554 # use the IP address request even if remote resolving was specified.555 # Detect whether the address is IPv4/6 directly.556 for family in (socket.AF_INET, socket.AF_INET6):557 try:558 addr_bytes = socket.inet_pton(family, host)559 file.write(family_to_byte[family] + addr_bytes)560 host = socket.inet_ntop(family, addr_bytes)561 file.write(struct.pack(">H", port))562 return host, port563 except socket.error:564 continue565 566 # Well it's not an IP number, so it's probably a DNS name.567 if rdns:568 # Resolve remotely569 host_bytes = host.encode("idna")570 file.write(b"\x03" + chr(len(host_bytes)).encode() + host_bytes)571 else:572 # Resolve locally573 addresses = socket.getaddrinfo(host, port, socket.AF_UNSPEC,574 socket.SOCK_STREAM,575 socket.IPPROTO_TCP,576 socket.AI_ADDRCONFIG)577 # We can't really work out what IP is reachable, so just pick the578 # first.579 target_addr = addresses[0]580 family = target_addr[0]581 host = target_addr[4][0]582 583 addr_bytes = socket.inet_pton(family, host)584 file.write(family_to_byte[family] + addr_bytes)585 host = socket.inet_ntop(family, addr_bytes)586 file.write(struct.pack(">H", port))587 return host, port588 589 def _read_SOCKS5_address(self, file):590 atyp = self._readall(file, 1)591 if atyp == b"\x01":592 addr = socket.inet_ntoa(self._readall(file, 4))593 elif atyp == b"\x03":594 length = self._readall(file, 1)595 addr = self._readall(file, ord(length))596 elif atyp == b"\x04":597 addr = socket.inet_ntop(socket.AF_INET6, self._readall(file, 16))598 else:599 raise GeneralProxyError("SOCKS5 proxy server sent invalid data")600 601 port = struct.unpack(">H", self._readall(file, 2))[0]602 return addr, port603 604 def _negotiate_SOCKS4(self, dest_addr, dest_port):605 """Negotiates a connection through a SOCKS4 server."""606 proxy_type, addr, port, rdns, username, password = self.proxy607 608 writer = self.makefile("wb")609 reader = self.makefile("rb", 0) # buffering=0 renamed in Python 3610 try:611 # Check if the destination address provided is an IP address612 remote_resolve = False613 try:614 addr_bytes = socket.inet_aton(dest_addr)615 except socket.error:616 # It's a DNS name. Check where it should be resolved.617 if rdns:618 addr_bytes = b"\x00\x00\x00\x01"619 remote_resolve = True620 else:621 addr_bytes = socket.inet_aton(622 socket.gethostbyname(dest_addr))623 624 # Construct the request packet625 writer.write(struct.pack(">BBH", 0x04, 0x01, dest_port))626 writer.write(addr_bytes)627 628 # The username parameter is considered userid for SOCKS4629 if username:630 writer.write(username)631 writer.write(b"\x00")632 633 # DNS name if remote resolving is required634 # NOTE: This is actually an extension to the SOCKS4 protocol635 # called SOCKS4A and may not be supported in all cases.636 if remote_resolve:637 writer.write(dest_addr.encode("idna") + b"\x00")638 writer.flush()639 640 # Get the response from the server641 resp = self._readall(reader, 8)642 if resp[0:1] != b"\x00":643 # Bad data644 raise GeneralProxyError(645 "SOCKS4 proxy server sent invalid data")646 647 status = ord(resp[1:2])648 if status != 0x5A:649 # Connection failed: server returned an error650 error = SOCKS4_ERRORS.get(status, "Unknown error")651 raise SOCKS4Error("{:#04x}: {}".format(status, error))652 653 # Get the bound address/port654 self.proxy_sockname = (socket.inet_ntoa(resp[4:]),655 struct.unpack(">H", resp[2:4])[0])656 if remote_resolve:657 self.proxy_peername = socket.inet_ntoa(addr_bytes), dest_port658 else:659 self.proxy_peername = dest_addr, dest_port660 finally:661 reader.close()662 writer.close()663 664 def _negotiate_HTTP(self, dest_addr, dest_port):665 """Negotiates a connection through an HTTP server.666 667 NOTE: This currently only supports HTTP CONNECT-style proxies."""668 proxy_type, addr, port, rdns, username, password = self.proxy669 670 # If we need to resolve locally, we do this now671 addr = dest_addr if rdns else socket.gethostbyname(dest_addr)672 673 http_headers = [674 (b"CONNECT " + addr.encode("idna") + b":"675 + str(dest_port).encode() + b" HTTP/1.1"),676 b"Host: " + dest_addr.encode("idna")677 ]678 679 if username and password:680 http_headers.append(b"Proxy-Authorization: basic "681 + b64encode(username + b":" + password))682 683 http_headers.append(b"\r\n")684 685 self.sendall(b"\r\n".join(http_headers))686 687 # We just need the first line to check if the connection was successful688 fobj = self.makefile()689 status_line = fobj.readline()690 fobj.close()691 692 if not status_line:693 raise GeneralProxyError("Connection closed unexpectedly")694 695 try:696 proto, status_code, status_msg = status_line.split(" ", 2)697 except ValueError:698 raise GeneralProxyError("HTTP proxy server sent invalid response")699 700 if not proto.startswith("HTTP/"):701 raise GeneralProxyError(702 "Proxy server does not appear to be an HTTP proxy")703 704 try:705 status_code = int(status_code)706 except ValueError:707 raise HTTPError(708 "HTTP proxy server did not return a valid HTTP status")709 710 if status_code != 200:711 error = "{}: {}".format(status_code, status_msg)712 if status_code in (400, 403, 405):713 # It's likely that the HTTP proxy server does not support the714 # CONNECT tunneling method715 error += ("\n[*] Note: The HTTP proxy server may not be"716 " supported by PySocks (must be a CONNECT tunnel"717 " proxy)")718 raise HTTPError(error)719 720 self.proxy_sockname = (b"0.0.0.0", 0)721 self.proxy_peername = addr, dest_port722 723 _proxy_negotiators = {724 SOCKS4: _negotiate_SOCKS4,725 SOCKS5: _negotiate_SOCKS5,726 HTTP: _negotiate_HTTP727 }728 729 @set_self_blocking730 def connect(self, dest_pair, catch_errors=None):731 """732 Connects to the specified destination through a proxy.733 Uses the same API as socket's connect().734 To select the proxy server, use set_proxy().735 736 dest_pair - 2-tuple of (IP/hostname, port).737 """738 if len(dest_pair) != 2 or dest_pair[0].startswith("["):739 # Probably IPv6, not supported -- raise an error, and hope740 # Happy Eyeballs (RFC6555) makes sure at least the IPv4741 # connection works...742 raise socket.error("PySocks doesn't support IPv6: %s"743 % str(dest_pair))744 745 dest_addr, dest_port = dest_pair746 747 if self.type == socket.SOCK_DGRAM:748 if not self._proxyconn:749 self.bind(("", 0))750 dest_addr = socket.gethostbyname(dest_addr)751 752 # If the host address is INADDR_ANY or similar, reset the peer753 # address so that packets are received from any peer754 if dest_addr == "0.0.0.0" and not dest_port:755 self.proxy_peername = None756 else:757 self.proxy_peername = (dest_addr, dest_port)758 return759 760 (proxy_type, proxy_addr, proxy_port, rdns, username,761 password) = self.proxy762 763 # Do a minimal input check first764 if (not isinstance(dest_pair, (list, tuple))765 or len(dest_pair) != 2766 or not dest_addr767 or not isinstance(dest_port, int)):768 # Inputs failed, raise an error769 raise GeneralProxyError(770 "Invalid destination-connection (host, port) pair")771 772 # We set the timeout here so that we don't hang in connection or during773 # negotiation.774 super(socksocket, self).settimeout(self._timeout)775 776 if proxy_type is None:777 # Treat like regular socket object778 self.proxy_peername = dest_pair779 super(socksocket, self).settimeout(self._timeout)780 super(socksocket, self).connect((dest_addr, dest_port))781 return782 783 proxy_addr = self._proxy_addr()784 785 try:786 # Initial connection to proxy server.787 super(socksocket, self).connect(proxy_addr)788 789 except socket.error as error:790 # Error while connecting to proxy791 self.close()792 if not catch_errors:793 proxy_addr, proxy_port = proxy_addr794 proxy_server = "{}:{}".format(proxy_addr, proxy_port)795 printable_type = PRINTABLE_PROXY_TYPES[proxy_type]796 797 msg = "Error connecting to {} proxy {}".format(printable_type,798 proxy_server)799 log.debug("%s due to: %s", msg, error)800 raise ProxyConnectionError(msg, error)801 else:802 raise error803 804 else:805 # Connected to proxy server, now negotiate806 try:807 # Calls negotiate_{SOCKS4, SOCKS5, HTTP}808 negotiate = self._proxy_negotiators[proxy_type]809 negotiate(self, dest_addr, dest_port)810 except socket.error as error:811 if not catch_errors:812 # Wrap socket errors813 self.close()814 raise GeneralProxyError("Socket error", error)815 else:816 raise error817 except ProxyError:818 # Protocol error while negotiating with proxy819 self.close()820 raise821 822 @set_self_blocking823 def connect_ex(self, dest_pair):824 """ https://docs.python.org/3/library/socket.html#socket.socket.connect_ex825 Like connect(address), but return an error indicator instead of raising an exception for errors returned by the C-level connect() call (other problems, such as "host not found" can still raise exceptions).826 """827 try:828 self.connect(dest_pair, catch_errors=True)829 return 0830 except OSError as e:831 # If the error is numeric (socket errors are numeric), then return number as 832 # connect_ex expects. Otherwise raise the error again (socket timeout for example)833 if e.errno:834 return e.errno835 else:836 raise837 838 def _proxy_addr(self):839 """840 Return proxy address to connect to as tuple object841 """842 (proxy_type, proxy_addr, proxy_port, rdns, username,843 password) = self.proxy844 proxy_port = proxy_port or DEFAULT_PORTS.get(proxy_type)845 if not proxy_port:846 raise GeneralProxyError("Invalid proxy type")847 return proxy_addr, proxy_port848 