1from base64 import b64encode 2try: 3 from collections.abc import Callable 4except ImportError: 5 from collections import Callable 6from errno import EOPNOTSUPP, EINVAL, EAGAIN 7import functools 8from io import BytesIO 9import logging 10import os 11from os import SEEK_CUR 12import socket 13import struct 14import sys 15 16__version__ = "1.7.1" 17 18 19if os.name == "nt" and sys.version_info < (3, 0): 20 try: 21 import win_inet_pton 22 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 = 1 29PROXY_TYPE_SOCKS5 = SOCKS5 = 2 30PROXY_TYPE_HTTP = HTTP = 3 31 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.socket 36 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 raise 50 finally: 51 # set orgin blocking 52 if _is_blocking == 0: 53 self.setblocking(False) 54 return wrapper 55 56 57class ProxyError(IOError): 58 """Socket_err contains original socket.error exception.""" 59 def __init__(self, msg, socket_err=None): 60 self.msg = msg 61 self.socket_err = socket_err 62 63 if socket_err: 64 self.msg += ": {}".format(socket_err) 65 66 def __str__(self): 67 return self.msg 68 69 70class GeneralProxyError(ProxyError): 71 pass 72 73 74class ProxyConnectionError(ProxyError): 75 pass 76 77 78class SOCKS5AuthError(ProxyError): 79 pass 80 81 82class SOCKS5Error(ProxyError): 83 pass 84 85 86class SOCKS4Error(ProxyError): 87 pass 88 89 90class HTTPError(ProxyError): 91 pass 92 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 explicitly 120 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_proxy 135 136getdefaultproxy = get_default_proxy 137 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 will 143 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 = socksocket 147 else: 148 raise GeneralProxyError("No default proxy specified") 149 150wrapmodule = wrap_module 151 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 object 160 161 Like socket.create_connection(), but connects to proxy 162 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 source 168 address before connecting (only for compatibility) 169 """ 170 # Remove IPv6 brackets on the remote address and proxy address. 171 remote_host, remote_port = dest_pair 172 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 = None 178 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 = r 182 sock = None 183 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 sock 201 202 except (socket.error, ProxyError) as e: 203 err = e 204 if sock: 205 sock.close() 206 sock = None 207 208 if err: 209 raise err 210 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 work 223 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 way 233 # 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 object 243 244 Open a SOCKS enabled socket. The parameters are the same as 245 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 = None 251 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 alive 260 261 if self.default_proxy: 262 self.proxy = self.default_proxy 263 else: 264 self.proxy = (None, None, None, None, None, None) 265 self.proxy_sockname = None 266 self.proxy_peername = None 267 268 self._timeout = None 269 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 += d 280 return data 281 282 def settimeout(self, timeout): 283 self._timeout = timeout 284 try: 285 # test if we're connected, if so apply timeout 286 peer = self.get_proxy_peername() 287 super(socksocket, self).settimeout(self._timeout) 288 except socket.error: 289 pass 290 291 def gettimeout(self): 292 return self._timeout 293 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 types 305 are supported: PROXY_TYPE_SOCKS4 (including socks4a), 306 PROXY_TYPE_SOCKS5 and PROXY_TYPE_HTTP 307 addr - The address of the server (IP or DNS). 308 port - The port of the server. Defaults to 1080 for SOCKS 309 servers and 8080 for HTTP proxy servers. 310 rdns - Should DNS queries be performed on the remote side 311 (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.proxy 332 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 because 343 # 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, _ = proxy 358 _, port = relay 359 super(socksocket, self).connect((host, port)) 360 super(socksocket, self).settimeout(self._timeout) 361 self.proxy_sockname = ("0.0.0.0", 0) # Unknown 362 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_peername 404 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 bytes 412 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_sockname 421 422 getproxysockname = get_proxy_sockname 423 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_peername 431 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_peername 437 438 getpeername = get_peername 439 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) and 449 address (DST field). Returns resolved DST address that was used. 450 """ 451 proxy_type, addr, port, rdns, username, password = self.proxy 452 453 writer = conn.makefile("wb") 454 reader = conn.makefile("rb", 0) # buffering=0 renamed in Python 3 455 try: 456 # First we'll send the authentication packages we support. 457 if username and password: 458 # The username/password details were supplied to the 459 # set_proxy method so we support the USERNAME/PASSWORD 460 # authentication (in addition to the standard none). 461 writer.write(b"\x05\x02\x00\x02") 462 else: 463 # No username/password were entered, therefore we 464 # only support connections with no authentication. 465 writer.write(b"\x05\x01\x00") 466 467 # We'll receive the server's response to determine which 468 # method was selected 469 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 bytestring 474 # via bytestring[i] yields an integer in Python 3 475 raise GeneralProxyError( 476 "SOCKS5 proxy server sent invalid data") 477 478 # Check the chosen authentication method 479 480 if chosen_auth[1:2] == b"\x02": 481 # Okay, we need to perform a basic username/password 482 # authentication. 483 if not (username and password): 484 # Although we said we don't support authentication, the 485 # server may still request basic username/password 486 # authentication 487 raise SOCKS5AuthError("No username/password supplied. " 488 "Server requested username/password" 489 " authentication") 490 491 writer.write(b"\x01" + chr(len(username)).encode() 492 + username 493 + 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 response 499 raise GeneralProxyError( 500 "SOCKS5 proxy server sent invalid data") 501 if auth_status[1:2] != b"\x00": 502 # Authentication failed 503 raise SOCKS5AuthError("SOCKS5 authentication failed") 504 505 # Otherwise, authentication succeeded 506 507 # No authentication is required if 0x00 508 elif chosen_auth[1:2] != b"\x00": 509 # Reaching here is always bad 510 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 connection 519 writer.write(b"\x05" + cmd + b"\x00") 520 resolved = self._write_SOCKS5_address(dst, writer) 521 writer.flush() 522 523 # Get the response 524 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 error 532 error = SOCKS5_ERRORS.get(status, "Unknown error") 533 raise SOCKS5Error("{:#04x}: {}".format(status, error)) 534 535 # Get the bound address/port 536 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 = addr 550 proxy_type, _, _, rdns, username, password = self.proxy 551 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'll 554 # 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, port 563 except socket.error: 564 continue 565 566 # Well it's not an IP number, so it's probably a DNS name. 567 if rdns: 568 # Resolve remotely 569 host_bytes = host.encode("idna") 570 file.write(b"\x03" + chr(len(host_bytes)).encode() + host_bytes) 571 else: 572 # Resolve locally 573 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 the 578 # 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, port 588 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, port 603 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.proxy 607 608 writer = self.makefile("wb") 609 reader = self.makefile("rb", 0) # buffering=0 renamed in Python 3 610 try: 611 # Check if the destination address provided is an IP address 612 remote_resolve = False 613 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 = True 620 else: 621 addr_bytes = socket.inet_aton( 622 socket.gethostbyname(dest_addr)) 623 624 # Construct the request packet 625 writer.write(struct.pack(">BBH", 0x04, 0x01, dest_port)) 626 writer.write(addr_bytes) 627 628 # The username parameter is considered userid for SOCKS4 629 if username: 630 writer.write(username) 631 writer.write(b"\x00") 632 633 # DNS name if remote resolving is required 634 # NOTE: This is actually an extension to the SOCKS4 protocol 635 # 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 server 641 resp = self._readall(reader, 8) 642 if resp[0:1] != b"\x00": 643 # Bad data 644 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 error 650 error = SOCKS4_ERRORS.get(status, "Unknown error") 651 raise SOCKS4Error("{:#04x}: {}".format(status, error)) 652 653 # Get the bound address/port 654 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_port 658 else: 659 self.proxy_peername = dest_addr, dest_port 660 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.proxy 669 670 # If we need to resolve locally, we do this now 671 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 successful 688 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 the 714 # CONNECT tunneling method 715 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_port 722 723 _proxy_negotiators = { 724 SOCKS4: _negotiate_SOCKS4, 725 SOCKS5: _negotiate_SOCKS5, 726 HTTP: _negotiate_HTTP 727 } 728 729 @set_self_blocking 730 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 hope 740 # Happy Eyeballs (RFC6555) makes sure at least the IPv4 741 # connection works... 742 raise socket.error("PySocks doesn't support IPv6: %s" 743 % str(dest_pair)) 744 745 dest_addr, dest_port = dest_pair 746 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 peer 753 # address so that packets are received from any peer 754 if dest_addr == "0.0.0.0" and not dest_port: 755 self.proxy_peername = None 756 else: 757 self.proxy_peername = (dest_addr, dest_port) 758 return 759 760 (proxy_type, proxy_addr, proxy_port, rdns, username, 761 password) = self.proxy 762 763 # Do a minimal input check first 764 if (not isinstance(dest_pair, (list, tuple)) 765 or len(dest_pair) != 2 766 or not dest_addr 767 or not isinstance(dest_port, int)): 768 # Inputs failed, raise an error 769 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 during 773 # negotiation. 774 super(socksocket, self).settimeout(self._timeout) 775 776 if proxy_type is None: 777 # Treat like regular socket object 778 self.proxy_peername = dest_pair 779 super(socksocket, self).settimeout(self._timeout) 780 super(socksocket, self).connect((dest_addr, dest_port)) 781 return 782 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 proxy 791 self.close() 792 if not catch_errors: 793 proxy_addr, proxy_port = proxy_addr 794 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 error 803 804 else: 805 # Connected to proxy server, now negotiate 806 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 errors 813 self.close() 814 raise GeneralProxyError("Socket error", error) 815 else: 816 raise error 817 except ProxyError: 818 # Protocol error while negotiating with proxy 819 self.close() 820 raise 821 822 @set_self_blocking 823 def connect_ex(self, dest_pair): 824 """ https://docs.python.org/3/library/socket.html#socket.socket.connect_ex 825 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 0 830 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.errno 835 else: 836 raise 837 838 def _proxy_addr(self): 839 """ 840 Return proxy address to connect to as tuple object 841 """ 842 (proxy_type, proxy_addr, proxy_port, rdns, username, 843 password) = self.proxy 844 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_port 848