1 import socket
, ssl
, select
, copy
, errno
10 RS_ET_OWN
= 0 # rocksock-specific error
11 RS_ET_SYS
= 1 # system error with errno
12 RS_ET_GAI
= 2 # dns resolution subsystem error
13 RS_ET_SSL
= 3 # ssl subsystem error
18 RS_E_EXCEED_PROXY_LIMIT
= 2
22 RS_E_OUT_OF_BUFFER
= 6
24 RS_E_SOCKS4_NOAUTH
= 8
25 RS_E_SOCKS5_AUTH_EXCEEDSIZE
= 9
26 RS_E_SOCKS4_NO_IP6
= 10
27 RS_E_PROXY_UNEXPECTED_RESPONSE
= 11
28 RS_E_TARGETPROXY_CONNECT_FAILED
= 12
29 RS_E_PROXY_AUTH_FAILED
= 13
30 RS_E_HIT_READTIMEOUT
= 14
31 RS_E_HIT_WRITETIMEOUT
= 15
32 RS_E_HIT_CONNECTTIMEOUT
= 16
33 RS_E_PROXY_GENERAL_FAILURE
= 17
34 RS_E_TARGET_NET_UNREACHABLE
= 18
35 RS_E_TARGETPROXY_NET_UNREACHABLE
= 18
36 RS_E_TARGET_HOST_UNREACHABLE
= 19
37 RS_E_TARGETPROXY_HOST_UNREACHABLE
= 19
38 RS_E_TARGET_CONN_REFUSED
= 20
39 RS_E_TARGETPROXY_CONN_REFUSED
= 20
40 RS_E_TARGET_TTL_EXPIRED
= 21
41 RS_E_TARGETPROXY_TTL_EXPIRED
= 21
42 RS_E_PROXY_COMMAND_NOT_SUPPORTED
= 22
43 RS_E_PROXY_ADDRESSTYPE_NOT_SUPPORTED
= 23
44 RS_E_REMOTE_DISCONNECTED
= 24
45 RS_E_NO_PROXYSTORAGE
= 25
46 RS_E_HOSTNAME_TOO_LONG
= 26
47 RS_E_INVALID_PROXY_URL
= 27
50 class RocksockException(Exception):
51 def __init__(self
, error
, failedproxy
=None, errortype
=RS_ET_OWN
, *args
, **kwargs
):
52 Exception.__init
__(self
,*args
,**kwargs
)
54 self
.errortype
= errortype
55 self
.failedproxy
= failedproxy
57 def get_failedproxy(self
):
58 return self
.failedproxy
63 def get_errortype(self
):
69 raise(ei
[0], ei
[1], ei
[2])
70 # import traceback, sys
71 # traceback.print_exc(file=sys.stderr)
74 def get_errormessage(self
):
76 RS_E_NO_ERROR
: "no error",
77 RS_E_NULL
: "NULL pointer passed",
78 RS_E_EXCEED_PROXY_LIMIT
: "exceeding maximum number of proxies",
79 RS_E_NO_SSL
: "can not establish SSL connection, since library was not compiled with USE_SSL define",
80 RS_E_NO_SOCKET
: "socket is not set up, maybe you should call connect first",
81 RS_E_HIT_TIMEOUT
: "timeout reached on operation",
82 RS_E_OUT_OF_BUFFER
: "supplied buffer is too small",
83 RS_E_SSL_GENERIC
: "generic SSL error", # the C version uses this error when the SSL library does not report any specific error, otherwise errortype SSL will be set and the SSL errorcode be used
84 RS_E_SOCKS4_NOAUTH
:"SOCKS4 authentication not implemented",
85 RS_E_SOCKS5_AUTH_EXCEEDSIZE
: "maximum length for SOCKS5 servername/password/username is 255",
86 RS_E_SOCKS4_NO_IP6
: "SOCKS4 is not compatible with IPv6",
87 RS_E_PROXY_UNEXPECTED_RESPONSE
: "the proxy sent an unexpected response",
88 RS_E_TARGETPROXY_CONNECT_FAILED
: "could not connect to target proxy",
89 RS_E_PROXY_AUTH_FAILED
: "proxy authentication failed or authd not enabled",
90 RS_E_HIT_READTIMEOUT
: "timeout reached on read operation",
91 RS_E_HIT_WRITETIMEOUT
: "timeout reached on write operation",
92 RS_E_HIT_CONNECTTIMEOUT
: "timeout reached on connect operation",
93 RS_E_PROXY_GENERAL_FAILURE
: "proxy general failure",
94 RS_E_TARGETPROXY_NET_UNREACHABLE
: "proxy-target: net unreachable",
95 RS_E_TARGETPROXY_HOST_UNREACHABLE
: "proxy-target: host unreachable",
96 RS_E_TARGETPROXY_CONN_REFUSED
: "proxy-target: connection refused",
97 RS_E_TARGETPROXY_TTL_EXPIRED
: "proxy-target: TTL expired",
98 RS_E_PROXY_COMMAND_NOT_SUPPORTED
: "proxy: command not supported",
99 RS_E_PROXY_ADDRESSTYPE_NOT_SUPPORTED
: "proxy: addresstype not supported",
100 RS_E_REMOTE_DISCONNECTED
: "remote socket closed connection",
101 RS_E_NO_PROXYSTORAGE
: "no proxy storage assigned",
102 RS_E_HOSTNAME_TOO_LONG
: "hostname exceeds 255 chars",
103 RS_E_INVALID_PROXY_URL
: "invalid proxy URL string"
105 if self
.errortype
== RS_ET_SYS
:
106 if self
.error
in errno
.errorcode
:
107 msg
= "ERRNO: " + errno
.errorcode
[self
.error
]
109 msg
= "ERRNO: invalid errno: " + str(self
.error
)
110 elif self
.errortype
== RS_ET_GAI
:
111 msg
= "GAI: " + self
.failedproxy
112 elif self
.errortype
== RS_ET_SSL
:
113 msg
= errordict
[self
.error
]
114 if self
.error
== RS_E_SSL_GENERIC
and self
.failedproxy
!= None:
115 msg
+= ': ' + self
.failedproxy
#failedproxy is repurposed for SSL exceptions
117 msg
= errordict
[self
.error
] + " (proxy %d)"%self
.failedproxy
121 class RocksockHostinfo():
122 def __init__(self
, host
, port
):
123 if port
< 0 or port
> 65535:
124 raise(RocksockException(RS_E_INVALID_PROXY_URL
, failedproxy
=-1))
128 def RocksockHostinfoFromString(s
):
129 host
, port
= s
.split(':')
130 return RocksockHostinfo(host
, port
)
132 def isnumericipv4(ip
):
134 a
,b
,c
,d
= ip
.split('.')
135 if int(a
) < 256 and int(b
) < 256 and int(c
) < 256 and int(d
) < 256:
141 def resolve(hostinfo
, want_v4
=True):
142 if isnumericipv4(hostinfo
.host
):
143 return socket
.AF_INET
, (hostinfo
.host
, hostinfo
.port
)
145 for res
in socket
.getaddrinfo(hostinfo
.host
, hostinfo
.port
, \
146 socket
.AF_UNSPEC
, socket
.SOCK_STREAM
, 0, socket
.AI_PASSIVE
):
147 af
, socktype
, proto
, canonname
, sa
= res
148 if want_v4
and af
!= socket
.AF_INET
: continue
149 if af
!= socket
.AF_INET
and af
!= socket
.AF_INET6
: continue
152 except socket
.gaierror
as e
:
154 raise(RocksockException(eno
, str, errortype
=RS_ET_GAI
))
159 class RocksockProxy():
160 def __init__(self
, host
, port
, type, username
= None, password
=None, **kwargs
):
161 typemap
= { 'none' : RS_PT_NONE
,
162 'socks4' : RS_PT_SOCKS4
,
163 'socks5' : RS_PT_SOCKS5
,
164 'http' : RS_PT_HTTP
}
165 self
.type = typemap
[type] if type in typemap
else type
166 if not self
.type in [RS_PT_NONE
, RS_PT_SOCKS4
, RS_PT_SOCKS5
, RS_PT_HTTP
]:
167 raise(ValueError('Invalid proxy type'))
168 self
.username
= username
169 self
.password
= password
170 self
.hostinfo
= RocksockHostinfo(host
, port
)
172 def RocksockProxyFromURL(url
):
173 # valid URL: socks5://[user:pass@]hostname:port
175 if x
== -1: return None
177 url
= url
[x
+len('://'):]
179 if x
== -1: return None # port is obligatory
180 port
= int(url
[x
+len(':'):]) #TODO: catch exception when port is non-numeric
184 u
, p
= url
[:x
].split(':')
185 url
= url
[x
+len('@'):]
188 return RocksockProxy(host
=url
, port
=port
, type=t
, username
=u
, password
=p
)
192 def __init__(self
, host
=None, port
=0, verifycert
=False, timeout
=0, proxies
=None, **kwargs
):
193 if 'ssl' in kwargs
and kwargs
['ssl'] == True:
194 self
.sslcontext
= ssl
.create_default_context()
195 self
.sslcontext
.check_hostname
= False
196 if not verifycert
: self
.sslcontext
.verify_mode
= ssl
.CERT_NONE
198 self
.sslcontext
= None
200 if proxies
is not None:
202 if isinstance(p
, basestring
):
203 self
.proxychain
.append(RocksockProxyFromURL(p
))
205 self
.proxychain
.append(p
)
206 target
= RocksockProxy(host
, port
, RS_PT_NONE
)
207 self
.proxychain
.append(target
)
209 self
.timeout
= timeout
211 def _translate_socket_error(self
, e
, pnum
):
212 fp
= self
._failed
_proxy
(pnum
)
213 if e
.errno
== errno
.ECONNREFUSED
:
214 return RocksockException(RS_E_TARGET_CONN_REFUSED
, failedproxy
=fp
)
215 return RocksockException(e
.errno
, errortype
=RS_ET_SYS
, failedproxy
=fp
)
217 def _failed_proxy(self
, pnum
):
218 if pnum
< 0: return -1
219 if pnum
>= len(self
.proxychain
)-1: return -1
224 af
, sa
= resolve(self
.proxychain
[0].hostinfo
, True)
228 raise(RocksockException(-3, "unexpected problem resolving DNS, try again", failedproxy
=self
._failed
_proxy
(0), errortype
=RS_ET_GAI
))
229 # print("GOT A WEIRD AF")
231 # raise(RocksockException(-6666, af, errortype=RS_ET_GAI))
233 self
.sock
= socket
.socket(af
, socket
.SOCK_STREAM
)
234 self
.sock
.settimeout(None if self
.timeout
== 0 else self
.timeout
)
236 self
.sock
.connect((sa
[0], sa
[1]))
237 except socket
.timeout
:
238 raise(RocksockException(RS_E_HIT_TIMEOUT
, failedproxy
=self
._failed
_proxy
(0)))
239 except socket
.error
as e
:
240 raise(self
._translate
_socket
_error
(e
, 0))
242 for pnum
in range(1, len(self
.proxychain
)):
243 curr
= self
.proxychain
[pnum
]
244 prev
= self
.proxychain
[pnum
-1]
245 self
._connect
_step
(pnum
)
249 self
.sock
= self
.sslcontext
.wrap_socket(self
.sock
, server_hostname
=self
.proxychain
[len(self
.proxychain
)-1].hostinfo
.host
)
250 except ssl
.SSLError
as e
:
251 reason
= self
._get
_ssl
_exception
_reason
(e
)
252 #if hasattr(e, 'library'): subsystem = e.library
253 raise(RocksockException(RS_E_SSL_GENERIC
, failedproxy
=reason
, errortype
=RS_ET_SSL
))
254 except socket
.error
as e
:
255 raise(self
._translate
_socket
_error
(e
, -1))
256 except Exception as e
:
261 self.sock.do_handshake()
263 except ssl.SSLWantReadError:
264 select.select([self.sock], [], [])
265 except ssl.SSLWantWriteError:
266 select.select([], [self.sock], [])
270 def disconnect(self
):
271 if self
.sock
is None: return
273 self
.sock
.shutdown(socket
.SHUT_RDWR
)
280 return select
.select([self
.sock
], [], [], 0)[0]
282 def send(self
, buf
, pnum
=-1):
283 if self
.sock
is None:
284 raise(RocksockException(RS_E_NO_SOCKET
, failedproxy
=self
._failed
_proxy
(pnum
)))
286 return self
.sock
.sendall(buf
)
287 except socket
.error
as e
:
288 raise(self
._translate
_socket
_error
(e
, pnum
))
290 def _get_ssl_exception_reason(self
, e
):
292 if hasattr(e
, 'reason'): s
= e
.reason
293 elif hasattr(e
, 'message'): s
= e
.message
294 elif hasattr(e
, 'args'): s
= e
.args
[0]
297 def recv(self
, count
=-1, pnum
=-1):
301 n
= count
if count
!= -1 else 4096
302 if n
>= 1024*1024: n
= 1024*1024
303 chunk
= self
.sock
.recv(n
)
304 except socket
.timeout
:
305 raise(RocksockException(RS_E_HIT_TIMEOUT
, failedproxy
=self
._failed
_proxy
(pnum
)))
306 except socket
.error
as e
:
307 raise(self
._translate
_socket
_error
(e
, pnum
))
308 except ssl
.SSLError
as e
:
309 s
= self
._get
_ssl
_exception
_reason
(e
)
310 if s
== 'The read operation timed out':
311 raise(RocksockException(RS_E_HIT_READTIMEOUT
, failedproxy
=self
._failed
_proxy
(pnum
)))
313 raise(RocksockException(RS_E_SSL_GENERIC
, failedproxy
=s
, errortype
=RS_ET_SSL
))
315 raise(RocksockException(RS_E_REMOTE_DISCONNECTED
, failedproxy
=self
._failed
_proxy
(pnum
)))
317 if count
== -1: break
318 else: count
-= len(chunk
)
330 def recvuntil(self
, until
):
331 s
= self
.recv(len(until
))
333 while not (s
[-1:] == endc
and s
.endswith(until
)):
337 def _ip_to_int(self
, ip
):
338 a
,b
,c
,d
= ip
.split('.')
339 h
= "0x%.2X%.2X%.2X%.2X"%(int(a
),int(b
),int(c
),int(d
))
342 def _ip_to_bytes(self
, ip
):
343 ip
= self
._ip
_to
_int
(ip
)
344 a
= (ip
& 0xff000000) >> 24
345 b
= (ip
& 0x00ff0000) >> 16
346 c
= (ip
& 0x0000ff00) >> 8
347 d
= (ip
& 0x000000ff) >> 0
348 return chr(a
) + chr(b
) + chr(c
) + chr(d
)
350 def _setup_socks4_header(self
, v4a
, dest
):
352 buf
+= chr(dest
.hostinfo
.port
/ 256)
353 buf
+= chr(dest
.hostinfo
.port
% 256)
357 af
, sa
= resolve(dest
.hostinfo
, True)
358 if af
!= socket
.AF_INET
: raise(RocksockException(RS_E_SOCKS4_NO_IP6
, failedproxy
=-1))
359 buf
+= self
._ip
_to
_bytes
(sa
[0])
361 if v4a
: buf
+= dest
.hostinfo
.host
+ '\0'
364 def _connect_socks4(self
, header
, pnum
):
366 res
= self
.recv(8, pnum
=pnum
)
367 if len(res
) < 8 or ord(res
[0]) != 0:
368 raise(RocksockException(RS_E_PROXY_UNEXPECTED_RESPONSE
, failedproxy
=self
._failed
_proxy
(pnum
)))
373 raise(RocksockException(RS_E_TARGETPROXY_CONNECT_FAILED
, failedproxy
=self
._failed
_proxy
(pnum
)))
374 elif ch
== 0x5c or ch
== 0x5d:
375 return RocksockException(RS_E_PROXY_AUTH_FAILED
, failedproxy
=self
._failed
_proxy
(pnum
))
377 raise(RocksockException(RS_E_PROXY_UNEXPECTED_RESPONSE
, failedproxy
=self
._failed
_proxy
(pnum
)))
379 def _setup_socks5_header(self
, proxy
):
381 if proxy
.username
and proxy
.password
:
382 buf
+= '\x02\x00\x02'
387 def _connect_socks5(self
, header
, pnum
):
389 res
= self
.recv(2, pnum
=pnum
)
390 if len(res
) != 2 or res
[0] != '\x05':
391 raise(RocksockException(RS_E_PROXY_UNEXPECTED_RESPONSE
, failedproxy
=self
._failed
_proxy
(pnum
)))
393 raise(RocksockException(RS_E_PROXY_AUTH_FAILED
, failedproxy
=self
._failed
_proxy
(pnum
)))
396 px
= self
.proxychain
[pnum
-1]
397 if px
.username
and px
.password
:
398 pkt
= '\x01%c%s%c%s'%(len(px
.username
),px
.username
,len(px
.password
),px
.password
)
400 res
= self
.recv(2, pnum
=pnum
)
401 if len(res
) < 2 or res
[1] != '\0':
402 raise(RocksockException(RS_E_PROXY_AUTH_FAILED
, failedproxy
=self
._failed
_proxy
(pnum
)))
403 else: raise(RocksockException(RS_E_PROXY_AUTH_FAILED
, failedproxy
=self
._failed
_proxy
(pnum
)))
404 dst
= self
.proxychain
[pnum
]
405 numeric
= isnumericipv4(dst
.hostinfo
.host
)
407 dstaddr
= self
._ip
_to
_bytes
(dst
.hostinfo
.host
)
409 dstaddr
= chr(len(dst
.hostinfo
.host
)) + dst
.hostinfo
.host
411 pkt
= '\x05\x01\x00%c%s%c%c'% (1 if numeric
else 3, dstaddr
, dst
.hostinfo
.port
/ 256, dst
.hostinfo
.port
% 256)
413 res
= self
.recv(pnum
=pnum
)
414 if len(res
) < 2 or res
[0] != '\x05':
415 raise(RocksockException(RS_E_PROXY_UNEXPECTED_RESPONSE
, failedproxy
=self
._failed
_proxy
(pnum
)))
418 elif ch
== 1: raise(RocksockException(RS_E_PROXY_GENERAL_FAILURE
, failedproxy
=self
._failed
_proxy
(pnum
)))
419 elif ch
== 2: raise(RocksockException(RS_E_PROXY_AUTH_FAILED
, failedproxy
=self
._failed
_proxy
(pnum
)))
420 elif ch
== 3: raise(RocksockException(RS_E_TARGETPROXY_NET_UNREACHABLE
, failedproxy
=self
._failed
_proxy
(pnum
)))
421 elif ch
== 4: raise(RocksockException(RS_E_TARGETPROXY_HOST_UNREACHABLE
, failedproxy
=self
._failed
_proxy
(pnum
)))
422 elif ch
== 5: raise(RocksockException(RS_E_TARGETPROXY_CONN_REFUSED
, failedproxy
=self
._failed
_proxy
(pnum
)))
423 elif ch
== 6: raise(RocksockException(RS_E_TARGETPROXY_TTL_EXPIRED
, failedproxy
=self
._failed
_proxy
(pnum
)))
424 elif ch
== 7: raise(RocksockException(RS_E_PROXY_COMMAND_NOT_SUPPORTED
, failedproxy
=self
._failed
_proxy
(pnum
)))
425 elif ch
== 8: raise(RocksockException(RS_E_PROXY_ADDRESSTYPE_NOT_SUPPORTED
, failedproxy
=self
._failed
_proxy
(pnum
)))
426 else: raise(RocksockException(RS_E_PROXY_UNEXPECTED_RESPONSE
, failedproxy
=self
._failed
_proxy
(pnum
)))
429 def _connect_step(self
, pnum
):
430 prev
= self
.proxychain
[pnum
-1]
431 curr
= self
.proxychain
[pnum
]
432 if prev
.type == RS_PT_SOCKS4
:
433 s4a
= self
._setup
_socks
4_header
(True, curr
)
435 self
._connect
_socks
4(s4a
, pnum
)
436 except RocksockException
as e
:
437 if e
.get_error() == RS_E_TARGETPROXY_CONNECT_FAILED
:
438 s4
= self
._setup
_socks
4_header
(False, curr
)
439 self
._connect
_socks
4(s4a
, pnum
)
441 elif prev
.type == RS_PT_SOCKS5
:
442 s5
= self
._setup
_socks
5_header
(prev
)
443 self
._connect
_socks
5(s5
, pnum
)
444 elif prev
.type == RS_PT_HTTP
:
445 dest
= self
.proxychain
[pnum
]
446 self
.send("CONNECT %s:%d HTTP/1.1\r\n\r\n"%(dest
.hostinfo
.host
, dest
.hostinfo
.port
))
447 resp
= self
.recv(pnum
=pnum
)
449 raise(RocksockException(RS_E_PROXY_UNEXPECTED_RESPONSE
, failedproxy
=self
._failed
_proxy
(pnum
)))
451 raise(RocksockException(RS_E_TARGETPROXY_CONNECT_FAILED
, failedproxy
=self
._failed
_proxy
(pnum
)))
454 if __name__
== '__main__':
456 # RocksockProxyFromURL("socks5://foo:bar@localhost:1080"),
457 # RocksockProxyFromURL("socks5://10.0.0.3:1080"),
458 RocksockProxyFromURL("socks5://127.0.0.1:31339"),
461 #rs = Rocksock(host='googleff242342423f.com', port=443, ssl=True, proxies=proxies)
462 rs
= Rocksock(host
='google.com', port
=80, ssl
=False, proxies
=proxies
)
465 except RocksockException
as e
:
466 print(e
.get_errormessage())
468 rs
.send('GET / HTTP/1.0\r\n\r\n')
472 rs
.send('GET / HTTP/1.0\r\n\r\n')