2018-03-26 20:05:12 +00:00
""" SocksiPy - Python SOCKS module.
2017-01-27 14:40:12 +00:00
Copyright 2006 Dan - Haim . All rights reserved .
2018-03-26 20:05:12 +00:00
Redistribution and use in source and binary forms , with or without
modification , are permitted provided that the following conditions are met :
2017-01-27 14:40:12 +00:00
1. Redistributions of source code must retain the above copyright notice , this
list of conditions and the following disclaimer .
2. Redistributions in binary form must reproduce the above copyright notice ,
this list of conditions and the following disclaimer in the documentation
and / or other materials provided with the distribution .
3. Neither the name of Dan Haim nor the names of his contributors may be used
to endorse or promote products derived from this software without specific
prior written permission .
THIS SOFTWARE IS PROVIDED BY DAN HAIM " AS IS " AND ANY EXPRESS OR IMPLIED
WARRANTIES , INCLUDING , BUT NOT LIMITED TO , THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED . IN NO
EVENT SHALL DAN HAIM OR HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT , INDIRECT ,
INCIDENTAL , SPECIAL , EXEMPLARY , OR CONSEQUENTIAL DAMAGES ( INCLUDING , BUT NOT
LIMITED TO , PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES ; LOSS OF USE , DATA
OR PROFITS ; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY , OR TORT ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT
2018-03-26 20:05:12 +00:00
OF THE USE OF THIS SOFTWARE , EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE .
2017-01-27 14:40:12 +00:00
This module provides a standard socket - like interface for Python
for tunneling connections through SOCKS proxies .
== == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == =
Minor modifications made by Christopher Gilbert ( http : / / motomastyle . com / )
for use in PyLoris ( http : / / pyloris . sourceforge . net / )
Minor modifications made by Mario Vilas ( http : / / breakingcode . wordpress . com / )
mainly to merge bug fixes found in Sourceforge
Modifications made by Anorov ( https : / / github . com / Anorov )
- Forked and renamed to PySocks
2018-03-26 20:05:12 +00:00
- Fixed issue with HTTP proxy failure checking ( same bug that was in the
old ___recvall ( ) method )
2017-01-27 14:40:12 +00:00
- Included SocksiPyHandler ( sockshandler . py ) , to be used as a urllib2 handler ,
2018-03-26 20:05:12 +00:00
courtesy of e000 ( https : / / github . com / e000 ) :
https : / / gist . github . com / 869791 #file_socksipyhandler.py
2017-01-27 14:40:12 +00:00
- Re - styled code to make it readable
- Aliased PROXY_TYPE_SOCKS5 - > SOCKS5 etc .
- Improved exception handling and output
2018-03-26 20:05:12 +00:00
- Removed irritating use of sequence indexes , replaced with tuple unpacked
variables
2017-01-27 14:40:12 +00:00
- Fixed up Python 3 bytestring handling - chr ( 0x03 ) . encode ( ) - > b " \x03 "
- Other general fixes
2018-03-26 20:05:12 +00:00
- Added clarification that the HTTP proxy connection method only supports
CONNECT - style tunneling HTTP proxies
2017-01-27 14:40:12 +00:00
- Various small bug fixes
"""
2018-03-26 20:05:12 +00:00
from base64 import b64encode
2018-09-09 11:40:03 +00:00
try :
from collections . abc import Callable
except ImportError :
from collections import Callable
2017-01-27 14:40:12 +00:00
from errno import EOPNOTSUPP , EINVAL , EAGAIN
2018-03-26 20:05:12 +00:00
import functools
2017-01-27 14:40:12 +00:00
from io import BytesIO
2018-03-26 20:05:12 +00:00
import logging
2017-01-27 14:40:12 +00:00
import os
2018-03-26 20:05:12 +00:00
from os import SEEK_CUR
import socket
import struct
2017-01-27 14:40:12 +00:00
import sys
2018-03-26 20:05:12 +00:00
__version__ = " 1.6.7 "
2017-01-27 14:40:12 +00:00
if os . name == " nt " and sys . version_info < ( 3 , 0 ) :
try :
import win_inet_pton
except ImportError :
2018-03-26 20:05:12 +00:00
raise ImportError (
" To run PySocks on Windows you must install win_inet_pton " )
log = logging . getLogger ( __name__ )
2017-01-27 14:40:12 +00:00
PROXY_TYPE_SOCKS4 = SOCKS4 = 1
PROXY_TYPE_SOCKS5 = SOCKS5 = 2
PROXY_TYPE_HTTP = HTTP = 3
PROXY_TYPES = { " SOCKS4 " : SOCKS4 , " SOCKS5 " : SOCKS5 , " HTTP " : HTTP }
PRINTABLE_PROXY_TYPES = dict ( zip ( PROXY_TYPES . values ( ) , PROXY_TYPES . keys ( ) ) )
_orgsocket = _orig_socket = socket . socket
2018-03-26 20:05:12 +00:00
def set_self_blocking ( function ) :
@functools.wraps ( function )
def wrapper ( * args , * * kwargs ) :
self = args [ 0 ]
try :
_is_blocking = self . gettimeout ( )
if _is_blocking == 0 :
self . setblocking ( True )
return function ( * args , * * kwargs )
except Exception as e :
raise
finally :
# set orgin blocking
if _is_blocking == 0 :
self . setblocking ( False )
return wrapper
2017-01-27 14:40:12 +00:00
class ProxyError ( IOError ) :
2018-03-26 20:05:12 +00:00
""" Socket_err contains original socket.error exception. """
2017-01-27 14:40:12 +00:00
def __init__ ( self , msg , socket_err = None ) :
self . msg = msg
self . socket_err = socket_err
if socket_err :
2018-09-09 11:40:03 +00:00
self . msg + = " : {} " . format ( socket_err )
2017-01-27 14:40:12 +00:00
def __str__ ( self ) :
return self . msg
2018-03-26 20:05:12 +00:00
class GeneralProxyError ( ProxyError ) :
pass
class ProxyConnectionError ( ProxyError ) :
pass
class SOCKS5AuthError ( ProxyError ) :
pass
class SOCKS5Error ( ProxyError ) :
pass
class SOCKS4Error ( ProxyError ) :
pass
class HTTPError ( ProxyError ) :
pass
SOCKS4_ERRORS = {
0x5B : " Request rejected or failed " ,
0x5C : ( " Request rejected because SOCKS server cannot connect to identd on "
" the client " ) ,
0x5D : ( " Request rejected because the client program and identd report "
" different user-ids " )
}
SOCKS5_ERRORS = {
0x01 : " General SOCKS server failure " ,
0x02 : " Connection not allowed by ruleset " ,
0x03 : " Network unreachable " ,
0x04 : " Host unreachable " ,
0x05 : " Connection refused " ,
0x06 : " TTL expired " ,
0x07 : " Command not supported, or protocol error " ,
0x08 : " Address type not supported "
}
DEFAULT_PORTS = { SOCKS4 : 1080 , SOCKS5 : 1080 , HTTP : 8080 }
def set_default_proxy ( proxy_type = None , addr = None , port = None , rdns = True ,
username = None , password = None ) :
""" Sets a default proxy.
All further socksocket objects will use the default unless explicitly
changed . All parameters are as for socket . set_proxy ( ) . """
2017-01-27 14:40:12 +00:00
socksocket . default_proxy = ( proxy_type , addr , port , rdns ,
username . encode ( ) if username else None ,
password . encode ( ) if password else None )
2018-03-26 20:05:12 +00:00
def setdefaultproxy ( * args , * * kwargs ) :
if " proxytype " in kwargs :
kwargs [ " proxy_type " ] = kwargs . pop ( " proxytype " )
return set_default_proxy ( * args , * * kwargs )
2017-01-27 14:40:12 +00:00
def get_default_proxy ( ) :
2018-03-26 20:05:12 +00:00
""" Returns the default proxy, set by set_default_proxy. """
2017-01-27 14:40:12 +00:00
return socksocket . default_proxy
getdefaultproxy = get_default_proxy
2018-03-26 20:05:12 +00:00
2017-01-27 14:40:12 +00:00
def wrap_module ( module ) :
2018-03-26 20:05:12 +00:00
""" Attempts to replace a module ' s socket library with a SOCKS socket.
Must set a default proxy using set_default_proxy ( . . . ) first . This will
only work on modules that import socket directly into the namespace ;
most of the Python Standard Library falls into this category . """
2017-01-27 14:40:12 +00:00
if socksocket . default_proxy :
module . socket . socket = socksocket
else :
raise GeneralProxyError ( " No default proxy specified " )
wrapmodule = wrap_module
2018-03-26 20:05:12 +00:00
def create_connection ( dest_pair ,
timeout = None , source_address = None ,
proxy_type = None , proxy_addr = None ,
2017-01-27 14:40:12 +00:00
proxy_port = None , proxy_rdns = True ,
proxy_username = None , proxy_password = None ,
socket_options = None ) :
""" create_connection(dest_pair, *[, timeout], **proxy_args) -> socket object
Like socket . create_connection ( ) , but connects to proxy
before returning the socket object .
dest_pair - 2 - tuple of ( IP / hostname , port ) .
* * proxy_args - Same args passed to socksocket . set_proxy ( ) if present .
timeout - Optional socket timeout value , in seconds .
source_address - tuple ( host , port ) for the socket to bind to as its source
address before connecting ( only for compatibility )
"""
# Remove IPv6 brackets on the remote address and proxy address.
remote_host , remote_port = dest_pair
2018-03-26 20:05:12 +00:00
if remote_host . startswith ( " [ " ) :
remote_host = remote_host . strip ( " [] " )
if proxy_addr and proxy_addr . startswith ( " [ " ) :
proxy_addr = proxy_addr . strip ( " [] " )
2017-01-27 14:40:12 +00:00
err = None
# Allow the SOCKS proxy to be on IPv4 or IPv6 addresses.
for r in socket . getaddrinfo ( proxy_addr , proxy_port , 0 , socket . SOCK_STREAM ) :
family , socket_type , proto , canonname , sa = r
sock = None
try :
sock = socksocket ( family , socket_type , proto )
if socket_options :
for opt in socket_options :
sock . setsockopt ( * opt )
if isinstance ( timeout , ( int , float ) ) :
sock . settimeout ( timeout )
if proxy_type :
sock . set_proxy ( proxy_type , proxy_addr , proxy_port , proxy_rdns ,
proxy_username , proxy_password )
if source_address :
sock . bind ( source_address )
sock . connect ( ( remote_host , remote_port ) )
return sock
except ( socket . error , ProxyConnectionError ) as e :
err = e
if sock :
sock . close ( )
sock = None
if err :
raise err
raise socket . error ( " gai returned empty list. " )
2018-03-26 20:05:12 +00:00
2017-01-27 14:40:12 +00:00
class _BaseSocket ( socket . socket ) :
2018-03-26 20:05:12 +00:00
""" Allows Python 2 delegated methods such as send() to be overridden. """
2017-01-27 14:40:12 +00:00
def __init__ ( self , * pos , * * kw ) :
_orig_socket . __init__ ( self , * pos , * * kw )
self . _savedmethods = dict ( )
for name in self . _savenames :
self . _savedmethods [ name ] = getattr ( self , name )
delattr ( self , name ) # Allows normal overriding mechanism to work
_savenames = list ( )
2018-03-26 20:05:12 +00:00
2017-01-27 14:40:12 +00:00
def _makemethod ( name ) :
return lambda self , * pos , * * kw : self . _savedmethods [ name ] ( * pos , * * kw )
for name in ( " sendto " , " send " , " recvfrom " , " recv " ) :
method = getattr ( _BaseSocket , name , None )
# Determine if the method is not defined the usual way
# as a function in the class.
# Python 2 uses __slots__, so there are descriptors for each method,
# but they are not functions.
if not isinstance ( method , Callable ) :
_BaseSocket . _savenames . append ( name )
setattr ( _BaseSocket , name , _makemethod ( name ) )
2018-03-26 20:05:12 +00:00
2017-01-27 14:40:12 +00:00
class socksocket ( _BaseSocket ) :
""" socksocket([family[, type[, proto]]]) -> socket object
Open a SOCKS enabled socket . The parameters are the same as
those of the standard socket init . In order for SOCKS to work ,
you must specify family = AF_INET and proto = 0.
The " type " argument must be either SOCK_STREAM or SOCK_DGRAM .
"""
default_proxy = None
2018-03-26 20:05:12 +00:00
def __init__ ( self , family = socket . AF_INET , type = socket . SOCK_STREAM ,
proto = 0 , * args , * * kwargs ) :
2017-01-27 14:40:12 +00:00
if type not in ( socket . SOCK_STREAM , socket . SOCK_DGRAM ) :
msg = " Socket type must be stream or datagram, not {!r} "
raise ValueError ( msg . format ( type ) )
2018-03-26 20:05:12 +00:00
super ( socksocket , self ) . __init__ ( family , type , proto , * args , * * kwargs )
2017-01-27 14:40:12 +00:00
self . _proxyconn = None # TCP connection to keep UDP relay alive
if self . default_proxy :
self . proxy = self . default_proxy
else :
self . proxy = ( None , None , None , None , None , None )
self . proxy_sockname = None
self . proxy_peername = None
self . _timeout = None
def _readall ( self , file , count ) :
2018-03-26 20:05:12 +00:00
""" Receive EXACTLY the number of bytes requested from the file object.
Blocks until the required number of bytes have been received . """
2017-01-27 14:40:12 +00:00
data = b " "
while len ( data ) < count :
d = file . read ( count - len ( data ) )
if not d :
raise GeneralProxyError ( " Connection closed unexpectedly " )
data + = d
return data
def settimeout ( self , timeout ) :
self . _timeout = timeout
try :
# test if we're connected, if so apply timeout
peer = self . get_proxy_peername ( )
2018-03-26 20:05:12 +00:00
super ( socksocket , self ) . settimeout ( self . _timeout )
2017-01-27 14:40:12 +00:00
except socket . error :
pass
def gettimeout ( self ) :
return self . _timeout
def setblocking ( self , v ) :
if v :
self . settimeout ( None )
else :
self . settimeout ( 0.0 )
2018-03-26 20:05:12 +00:00
def set_proxy ( self , proxy_type = None , addr = None , port = None , rdns = True ,
username = None , password = None ) :
""" Sets the proxy to be used.
2017-01-27 14:40:12 +00:00
2018-03-26 20:05:12 +00:00
proxy_type - The type of the proxy to be used . Three types
2017-01-27 14:40:12 +00:00
are supported : PROXY_TYPE_SOCKS4 ( including socks4a ) ,
PROXY_TYPE_SOCKS5 and PROXY_TYPE_HTTP
addr - The address of the server ( IP or DNS ) .
port - The port of the server . Defaults to 1080 for SOCKS
2018-03-26 20:05:12 +00:00
servers and 8080 for HTTP proxy servers .
2017-01-27 14:40:12 +00:00
rdns - Should DNS queries be performed on the remote side
( rather than the local side ) . The default is True .
Note : This has no effect with SOCKS4 servers .
username - Username to authenticate with to the server .
The default is no authentication .
password - Password to authenticate with to the server .
2018-03-26 20:05:12 +00:00
Only relevant when username is also provided . """
2017-01-27 14:40:12 +00:00
self . proxy = ( proxy_type , addr , port , rdns ,
username . encode ( ) if username else None ,
password . encode ( ) if password else None )
2018-03-26 20:05:12 +00:00
def setproxy ( self , * args , * * kwargs ) :
if " proxytype " in kwargs :
kwargs [ " proxy_type " ] = kwargs . pop ( " proxytype " )
return self . set_proxy ( * args , * * kwargs )
2017-01-27 14:40:12 +00:00
def bind ( self , * pos , * * kw ) :
2018-03-26 20:05:12 +00:00
""" Implements proxy connection for UDP sockets.
Happens during the bind ( ) phase . """
( proxy_type , proxy_addr , proxy_port , rdns , username ,
password ) = self . proxy
2017-01-27 14:40:12 +00:00
if not proxy_type or self . type != socket . SOCK_DGRAM :
return _orig_socket . bind ( self , * pos , * * kw )
if self . _proxyconn :
raise socket . error ( EINVAL , " Socket already bound to an address " )
if proxy_type != SOCKS5 :
msg = " UDP only supported by SOCKS5 proxy type "
raise socket . error ( EOPNOTSUPP , msg )
2018-03-26 20:05:12 +00:00
super ( socksocket , self ) . bind ( * pos , * * kw )
2017-01-27 14:40:12 +00:00
# Need to specify actual local port because
# some relays drop packets if a port of zero is specified.
# Avoid specifying host address in case of NAT though.
_ , port = self . getsockname ( )
dst = ( " 0 " , port )
self . _proxyconn = _orig_socket ( )
proxy = self . _proxy_addr ( )
self . _proxyconn . connect ( proxy )
UDP_ASSOCIATE = b " \x03 "
_ , relay = self . _SOCKS5_request ( self . _proxyconn , UDP_ASSOCIATE , dst )
# The relay is most likely on the same host as the SOCKS proxy,
# but some proxies return a private IP address (10.x.y.z)
host , _ = proxy
_ , port = relay
2018-03-26 20:05:12 +00:00
super ( socksocket , self ) . connect ( ( host , port ) )
super ( socksocket , self ) . settimeout ( self . _timeout )
2017-01-27 14:40:12 +00:00
self . proxy_sockname = ( " 0.0.0.0 " , 0 ) # Unknown
def sendto ( self , bytes , * args , * * kwargs ) :
if self . type != socket . SOCK_DGRAM :
2018-03-26 20:05:12 +00:00
return super ( socksocket , self ) . sendto ( bytes , * args , * * kwargs )
2017-01-27 14:40:12 +00:00
if not self . _proxyconn :
self . bind ( ( " " , 0 ) )
address = args [ - 1 ]
flags = args [ : - 1 ]
header = BytesIO ( )
RSV = b " \x00 \x00 "
header . write ( RSV )
STANDALONE = b " \x00 "
header . write ( STANDALONE )
self . _write_SOCKS5_address ( address , header )
2018-03-26 20:05:12 +00:00
sent = super ( socksocket , self ) . send ( header . getvalue ( ) + bytes , * flags ,
* * kwargs )
2017-01-27 14:40:12 +00:00
return sent - header . tell ( )
def send ( self , bytes , flags = 0 , * * kwargs ) :
if self . type == socket . SOCK_DGRAM :
return self . sendto ( bytes , flags , self . proxy_peername , * * kwargs )
else :
2018-03-26 20:05:12 +00:00
return super ( socksocket , self ) . send ( bytes , flags , * * kwargs )
2017-01-27 14:40:12 +00:00
def recvfrom ( self , bufsize , flags = 0 ) :
if self . type != socket . SOCK_DGRAM :
2018-03-26 20:05:12 +00:00
return super ( socksocket , self ) . recvfrom ( bufsize , flags )
2017-01-27 14:40:12 +00:00
if not self . _proxyconn :
self . bind ( ( " " , 0 ) )
2018-03-26 20:05:12 +00:00
buf = BytesIO ( super ( socksocket , self ) . recv ( bufsize + 1024 , flags ) )
2017-01-27 14:40:12 +00:00
buf . seek ( 2 , SEEK_CUR )
frag = buf . read ( 1 )
if ord ( frag ) :
raise NotImplementedError ( " Received UDP packet fragment " )
fromhost , fromport = self . _read_SOCKS5_address ( buf )
if self . proxy_peername :
peerhost , peerport = self . proxy_peername
if fromhost != peerhost or peerport not in ( 0 , fromport ) :
raise socket . error ( EAGAIN , " Packet filtered " )
return ( buf . read ( bufsize ) , ( fromhost , fromport ) )
def recv ( self , * pos , * * kw ) :
bytes , _ = self . recvfrom ( * pos , * * kw )
return bytes
def close ( self ) :
if self . _proxyconn :
self . _proxyconn . close ( )
2018-03-26 20:05:12 +00:00
return super ( socksocket , self ) . close ( )
2017-01-27 14:40:12 +00:00
def get_proxy_sockname ( self ) :
2018-03-26 20:05:12 +00:00
""" Returns the bound IP address and port number at the proxy. """
2017-01-27 14:40:12 +00:00
return self . proxy_sockname
getproxysockname = get_proxy_sockname
def get_proxy_peername ( self ) :
"""
Returns the IP and port number of the proxy .
"""
2018-03-26 20:05:12 +00:00
return self . getpeername ( )
2017-01-27 14:40:12 +00:00
getproxypeername = get_proxy_peername
def get_peername ( self ) :
2018-03-26 20:05:12 +00:00
""" Returns the IP address and port number of the destination machine.
Note : get_proxy_peername returns the proxy . """
2017-01-27 14:40:12 +00:00
return self . proxy_peername
getpeername = get_peername
def _negotiate_SOCKS5 ( self , * dest_addr ) :
2018-03-26 20:05:12 +00:00
""" Negotiates a stream connection through a SOCKS5 server. """
2017-01-27 14:40:12 +00:00
CONNECT = b " \x01 "
2018-03-26 20:05:12 +00:00
self . proxy_peername , self . proxy_sockname = self . _SOCKS5_request (
self , CONNECT , dest_addr )
2017-01-27 14:40:12 +00:00
def _SOCKS5_request ( self , conn , cmd , dst ) :
"""
Send SOCKS5 request with given command ( CMD field ) and
address ( DST field ) . Returns resolved DST address that was used .
"""
proxy_type , addr , port , rdns , username , password = self . proxy
writer = conn . makefile ( " wb " )
reader = conn . makefile ( " rb " , 0 ) # buffering=0 renamed in Python 3
try :
# First we'll send the authentication packages we support.
if username and password :
# The username/password details were supplied to the
# set_proxy method so we support the USERNAME/PASSWORD
# authentication (in addition to the standard none).
writer . write ( b " \x05 \x02 \x00 \x02 " )
else :
# No username/password were entered, therefore we
# only support connections with no authentication.
writer . write ( b " \x05 \x01 \x00 " )
# We'll receive the server's response to determine which
# method was selected
writer . flush ( )
chosen_auth = self . _readall ( reader , 2 )
if chosen_auth [ 0 : 1 ] != b " \x05 " :
# Note: string[i:i+1] is used because indexing of a bytestring
# via bytestring[i] yields an integer in Python 3
2018-03-26 20:05:12 +00:00
raise GeneralProxyError (
" SOCKS5 proxy server sent invalid data " )
2017-01-27 14:40:12 +00:00
# Check the chosen authentication method
if chosen_auth [ 1 : 2 ] == b " \x02 " :
# Okay, we need to perform a basic username/password
# authentication.
2018-09-09 11:40:03 +00:00
if not ( username and password ) :
# Although we said we don't support authentication, the
# server may still request basic username/password
# authentication
raise SOCKS5AuthError ( " No username/password supplied. "
" Server requested username/password "
" authentication " )
2017-01-27 14:40:12 +00:00
writer . write ( b " \x01 " + chr ( len ( username ) ) . encode ( )
+ username
+ chr ( len ( password ) ) . encode ( )
+ password )
writer . flush ( )
auth_status = self . _readall ( reader , 2 )
if auth_status [ 0 : 1 ] != b " \x01 " :
# Bad response
2018-03-26 20:05:12 +00:00
raise GeneralProxyError (
" SOCKS5 proxy server sent invalid data " )
2017-01-27 14:40:12 +00:00
if auth_status [ 1 : 2 ] != b " \x00 " :
# Authentication failed
raise SOCKS5AuthError ( " SOCKS5 authentication failed " )
# Otherwise, authentication succeeded
# No authentication is required if 0x00
elif chosen_auth [ 1 : 2 ] != b " \x00 " :
# Reaching here is always bad
if chosen_auth [ 1 : 2 ] == b " \xFF " :
2018-03-26 20:05:12 +00:00
raise SOCKS5AuthError (
" All offered SOCKS5 authentication methods were "
" rejected " )
2017-01-27 14:40:12 +00:00
else :
2018-03-26 20:05:12 +00:00
raise GeneralProxyError (
" SOCKS5 proxy server sent invalid data " )
2017-01-27 14:40:12 +00:00
# Now we can request the actual connection
writer . write ( b " \x05 " + cmd + b " \x00 " )
resolved = self . _write_SOCKS5_address ( dst , writer )
writer . flush ( )
# Get the response
resp = self . _readall ( reader , 3 )
if resp [ 0 : 1 ] != b " \x05 " :
2018-03-26 20:05:12 +00:00
raise GeneralProxyError (
" SOCKS5 proxy server sent invalid data " )
2017-01-27 14:40:12 +00:00
status = ord ( resp [ 1 : 2 ] )
if status != 0x00 :
# Connection failed: server returned an error
error = SOCKS5_ERRORS . get ( status , " Unknown error " )
2018-09-09 11:40:03 +00:00
raise SOCKS5Error ( " {:#04x} : {} " . format ( status , error ) )
2017-01-27 14:40:12 +00:00
# Get the bound address/port
bnd = self . _read_SOCKS5_address ( reader )
2018-03-26 20:05:12 +00:00
super ( socksocket , self ) . settimeout ( self . _timeout )
2017-01-27 14:40:12 +00:00
return ( resolved , bnd )
finally :
reader . close ( )
writer . close ( )
def _write_SOCKS5_address ( self , addr , file ) :
"""
Return the host and port packed for the SOCKS5 protocol ,
and the resolved address as a tuple object .
"""
host , port = addr
proxy_type , _ , _ , rdns , username , password = self . proxy
family_to_byte = { socket . AF_INET : b " \x01 " , socket . AF_INET6 : b " \x04 " }
# If the given destination address is an IP address, we'll
# use the IP address request even if remote resolving was specified.
# Detect whether the address is IPv4/6 directly.
for family in ( socket . AF_INET , socket . AF_INET6 ) :
try :
addr_bytes = socket . inet_pton ( family , host )
file . write ( family_to_byte [ family ] + addr_bytes )
host = socket . inet_ntop ( family , addr_bytes )
file . write ( struct . pack ( " >H " , port ) )
return host , port
except socket . error :
continue
# Well it's not an IP number, so it's probably a DNS name.
if rdns :
# Resolve remotely
2018-03-26 20:05:12 +00:00
host_bytes = host . encode ( " idna " )
2017-01-27 14:40:12 +00:00
file . write ( b " \x03 " + chr ( len ( host_bytes ) ) . encode ( ) + host_bytes )
else :
# Resolve locally
2018-03-26 20:05:12 +00:00
addresses = socket . getaddrinfo ( host , port , socket . AF_UNSPEC ,
socket . SOCK_STREAM ,
socket . IPPROTO_TCP ,
socket . AI_ADDRCONFIG )
2017-01-27 14:40:12 +00:00
# We can't really work out what IP is reachable, so just pick the
# first.
target_addr = addresses [ 0 ]
family = target_addr [ 0 ]
host = target_addr [ 4 ] [ 0 ]
addr_bytes = socket . inet_pton ( family , host )
file . write ( family_to_byte [ family ] + addr_bytes )
host = socket . inet_ntop ( family , addr_bytes )
file . write ( struct . pack ( " >H " , port ) )
return host , port
def _read_SOCKS5_address ( self , file ) :
atyp = self . _readall ( file , 1 )
if atyp == b " \x01 " :
addr = socket . inet_ntoa ( self . _readall ( file , 4 ) )
elif atyp == b " \x03 " :
length = self . _readall ( file , 1 )
addr = self . _readall ( file , ord ( length ) )
elif atyp == b " \x04 " :
addr = socket . inet_ntop ( socket . AF_INET6 , self . _readall ( file , 16 ) )
else :
raise GeneralProxyError ( " SOCKS5 proxy server sent invalid data " )
port = struct . unpack ( " >H " , self . _readall ( file , 2 ) ) [ 0 ]
return addr , port
def _negotiate_SOCKS4 ( self , dest_addr , dest_port ) :
2018-03-26 20:05:12 +00:00
""" Negotiates a connection through a SOCKS4 server. """
2017-01-27 14:40:12 +00:00
proxy_type , addr , port , rdns , username , password = self . proxy
writer = self . makefile ( " wb " )
reader = self . makefile ( " rb " , 0 ) # buffering=0 renamed in Python 3
try :
# Check if the destination address provided is an IP address
remote_resolve = False
try :
addr_bytes = socket . inet_aton ( dest_addr )
except socket . error :
# It's a DNS name. Check where it should be resolved.
if rdns :
addr_bytes = b " \x00 \x00 \x00 \x01 "
remote_resolve = True
else :
2018-03-26 20:05:12 +00:00
addr_bytes = socket . inet_aton (
socket . gethostbyname ( dest_addr ) )
2017-01-27 14:40:12 +00:00
# Construct the request packet
writer . write ( struct . pack ( " >BBH " , 0x04 , 0x01 , dest_port ) )
writer . write ( addr_bytes )
# The username parameter is considered userid for SOCKS4
if username :
writer . write ( username )
writer . write ( b " \x00 " )
# DNS name if remote resolving is required
# NOTE: This is actually an extension to the SOCKS4 protocol
# called SOCKS4A and may not be supported in all cases.
if remote_resolve :
2018-03-26 20:05:12 +00:00
writer . write ( dest_addr . encode ( " idna " ) + b " \x00 " )
2017-01-27 14:40:12 +00:00
writer . flush ( )
# Get the response from the server
resp = self . _readall ( reader , 8 )
if resp [ 0 : 1 ] != b " \x00 " :
# Bad data
2018-03-26 20:05:12 +00:00
raise GeneralProxyError (
" SOCKS4 proxy server sent invalid data " )
2017-01-27 14:40:12 +00:00
status = ord ( resp [ 1 : 2 ] )
if status != 0x5A :
# Connection failed: server returned an error
error = SOCKS4_ERRORS . get ( status , " Unknown error " )
2018-09-09 11:40:03 +00:00
raise SOCKS4Error ( " {:#04x} : {} " . format ( status , error ) )
2017-01-27 14:40:12 +00:00
# Get the bound address/port
2018-03-26 20:05:12 +00:00
self . proxy_sockname = ( socket . inet_ntoa ( resp [ 4 : ] ) ,
struct . unpack ( " >H " , resp [ 2 : 4 ] ) [ 0 ] )
2017-01-27 14:40:12 +00:00
if remote_resolve :
self . proxy_peername = socket . inet_ntoa ( addr_bytes ) , dest_port
else :
self . proxy_peername = dest_addr , dest_port
finally :
reader . close ( )
writer . close ( )
def _negotiate_HTTP ( self , dest_addr , dest_port ) :
2018-03-26 20:05:12 +00:00
""" Negotiates a connection through an HTTP server.
NOTE : This currently only supports HTTP CONNECT - style proxies . """
2017-01-27 14:40:12 +00:00
proxy_type , addr , port , rdns , username , password = self . proxy
# If we need to resolve locally, we do this now
addr = dest_addr if rdns else socket . gethostbyname ( dest_addr )
http_headers = [
2018-03-26 20:05:12 +00:00
( b " CONNECT " + addr . encode ( " idna " ) + b " : "
+ str ( dest_port ) . encode ( ) + b " HTTP/1.1 " ) ,
b " Host: " + dest_addr . encode ( " idna " )
2017-01-27 14:40:12 +00:00
]
if username and password :
2018-03-26 20:05:12 +00:00
http_headers . append ( b " Proxy-Authorization: basic "
+ b64encode ( username + b " : " + password ) )
2017-01-27 14:40:12 +00:00
http_headers . append ( b " \r \n " )
self . sendall ( b " \r \n " . join ( http_headers ) )
# We just need the first line to check if the connection was successful
fobj = self . makefile ( )
status_line = fobj . readline ( )
fobj . close ( )
if not status_line :
raise GeneralProxyError ( " Connection closed unexpectedly " )
try :
proto , status_code , status_msg = status_line . split ( " " , 2 )
except ValueError :
raise GeneralProxyError ( " HTTP proxy server sent invalid response " )
if not proto . startswith ( " HTTP/ " ) :
2018-03-26 20:05:12 +00:00
raise GeneralProxyError (
" Proxy server does not appear to be an HTTP proxy " )
2017-01-27 14:40:12 +00:00
try :
status_code = int ( status_code )
except ValueError :
2018-03-26 20:05:12 +00:00
raise HTTPError (
" HTTP proxy server did not return a valid HTTP status " )
2017-01-27 14:40:12 +00:00
if status_code != 200 :
2018-09-09 11:40:03 +00:00
error = " {} : {} " . format ( status_code , status_msg )
2017-01-27 14:40:12 +00:00
if status_code in ( 400 , 403 , 405 ) :
2018-03-26 20:05:12 +00:00
# It's likely that the HTTP proxy server does not support the
# CONNECT tunneling method
error + = ( " \n [*] Note: The HTTP proxy server may not be "
" supported by PySocks (must be a CONNECT tunnel "
" proxy) " )
2017-01-27 14:40:12 +00:00
raise HTTPError ( error )
self . proxy_sockname = ( b " 0.0.0.0 " , 0 )
self . proxy_peername = addr , dest_port
_proxy_negotiators = {
SOCKS4 : _negotiate_SOCKS4 ,
SOCKS5 : _negotiate_SOCKS5 ,
HTTP : _negotiate_HTTP
}
2018-03-26 20:05:12 +00:00
@set_self_blocking
2018-09-09 11:40:03 +00:00
def connect ( self , dest_pair , catch_errors = None ) :
2017-01-27 14:40:12 +00:00
"""
Connects to the specified destination through a proxy .
Uses the same API as socket ' s connect().
To select the proxy server , use set_proxy ( ) .
dest_pair - 2 - tuple of ( IP / hostname , port ) .
"""
if len ( dest_pair ) != 2 or dest_pair [ 0 ] . startswith ( " [ " ) :
# Probably IPv6, not supported -- raise an error, and hope
# Happy Eyeballs (RFC6555) makes sure at least the IPv4
# connection works...
2018-03-26 20:05:12 +00:00
raise socket . error ( " PySocks doesn ' t support IPv6: %s "
% str ( dest_pair ) )
2017-01-27 14:40:12 +00:00
dest_addr , dest_port = dest_pair
if self . type == socket . SOCK_DGRAM :
if not self . _proxyconn :
self . bind ( ( " " , 0 ) )
dest_addr = socket . gethostbyname ( dest_addr )
# If the host address is INADDR_ANY or similar, reset the peer
# address so that packets are received from any peer
if dest_addr == " 0.0.0.0 " and not dest_port :
self . proxy_peername = None
else :
self . proxy_peername = ( dest_addr , dest_port )
return
2018-03-26 20:05:12 +00:00
( proxy_type , proxy_addr , proxy_port , rdns , username ,
password ) = self . proxy
2017-01-27 14:40:12 +00:00
# Do a minimal input check first
if ( not isinstance ( dest_pair , ( list , tuple ) )
or len ( dest_pair ) != 2
or not dest_addr
or not isinstance ( dest_port , int ) ) :
2018-03-26 20:05:12 +00:00
# Inputs failed, raise an error
raise GeneralProxyError (
" Invalid destination-connection (host, port) pair " )
2017-01-27 14:40:12 +00:00
# We set the timeout here so that we don't hang in connection or during
# negotiation.
2018-03-26 20:05:12 +00:00
super ( socksocket , self ) . settimeout ( self . _timeout )
2017-01-27 14:40:12 +00:00
if proxy_type is None :
# Treat like regular socket object
self . proxy_peername = dest_pair
2018-03-26 20:05:12 +00:00
super ( socksocket , self ) . settimeout ( self . _timeout )
super ( socksocket , self ) . connect ( ( dest_addr , dest_port ) )
2017-01-27 14:40:12 +00:00
return
proxy_addr = self . _proxy_addr ( )
try :
# Initial connection to proxy server.
2018-03-26 20:05:12 +00:00
super ( socksocket , self ) . connect ( proxy_addr )
2017-01-27 14:40:12 +00:00
except socket . error as error :
# Error while connecting to proxy
self . close ( )
2018-09-09 11:40:03 +00:00
if not catch_errors :
proxy_addr , proxy_port = proxy_addr
proxy_server = " {} : {} " . format ( proxy_addr , proxy_port )
printable_type = PRINTABLE_PROXY_TYPES [ proxy_type ]
msg = " Error connecting to {} proxy {} " . format ( printable_type ,
proxy_server )
log . debug ( " %s due to: %s " , msg , error )
raise ProxyConnectionError ( msg , error )
else :
raise error
2017-01-27 14:40:12 +00:00
else :
# Connected to proxy server, now negotiate
try :
# Calls negotiate_{SOCKS4, SOCKS5, HTTP}
negotiate = self . _proxy_negotiators [ proxy_type ]
negotiate ( self , dest_addr , dest_port )
except socket . error as error :
2018-09-09 11:40:03 +00:00
if not catch_errors :
# Wrap socket errors
self . close ( )
raise GeneralProxyError ( " Socket error " , error )
else :
raise error
2017-01-27 14:40:12 +00:00
except ProxyError :
# Protocol error while negotiating with proxy
self . close ( )
raise
2018-09-09 11:40:03 +00:00
@set_self_blocking
def connect_ex ( self , dest_pair ) :
""" https://docs.python.org/3/library/socket.html#socket.socket.connect_ex
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 ) .
"""
try :
self . connect ( dest_pair , catch_errors = True )
return 0
except OSError as e :
# If the error is numeric (socket errors are numeric), then return number as
# connect_ex expects. Otherwise raise the error again (socket timeout for example)
if e . errno :
return e . errno
else :
raise
2017-01-27 14:40:12 +00:00
def _proxy_addr ( self ) :
"""
Return proxy address to connect to as tuple object
"""
2018-03-26 20:05:12 +00:00
( proxy_type , proxy_addr , proxy_port , rdns , username ,
password ) = self . proxy
2017-01-27 14:40:12 +00:00
proxy_port = proxy_port or DEFAULT_PORTS . get ( proxy_type )
if not proxy_port :
raise GeneralProxyError ( " Invalid proxy type " )
2018-09-09 11:40:03 +00:00
return proxy_addr , proxy_port