]>
Raphaƫl G. Git Repositories - youtubedl/blob - youtube_dl/socks.py
fd49d74352c7d50e15956169f686c3ffbb107b5c
1 # Public Domain SOCKS proxy protocol implementation
2 # Adapted from https://gist.github.com/bluec0re/cafd3764412967417fd3
4 from __future__
import unicode_literals
7 # SOCKS4 protocol http://www.openssh.com/txt/socks4.protocol
8 # SOCKS4A protocol http://www.openssh.com/txt/socks4a.protocol
9 # SOCKS5 protocol https://tools.ietf.org/html/rfc1928
10 # SOCKS5 username/password authentication https://tools.ietf.org/html/rfc1929
21 __author__
= 'Timo Schmid <coding@timoschmid.de>'
24 SOCKS4_REPLY_VERSION
= 0x00
25 # Excerpt from SOCKS4A protocol:
26 # if the client cannot resolve the destination host's domain name to find its
27 # IP address, it should set the first three bytes of DSTIP to NULL and the last
28 # byte to a non-zero value.
29 SOCKS4_DEFAULT_DSTIP
= compat_struct_pack('!BBBB', 0, 0, 0, 0xFF)
32 SOCKS5_USER_AUTH_VERSION
= 0x01
33 SOCKS5_USER_AUTH_SUCCESS
= 0x00
36 class Socks4Command(object):
41 class Socks5Command(Socks4Command
):
42 CMD_UDP_ASSOCIATE
= 0x03
45 class Socks5Auth(object):
49 AUTH_NO_ACCEPTABLE
= 0xFF # For server response
52 class Socks5AddressType(object):
54 ATYP_DOMAINNAME
= 0x03
58 class ProxyError(IOError):
61 def __init__(self
, code
=None, msg
=None):
62 if code
is not None and msg
is None:
63 msg
= self
.CODES
.get(code
) and 'unknown error'
64 super(ProxyError
, self
).__init
__(code
, msg
)
67 class InvalidVersionError(ProxyError
):
68 def __init__(self
, expected_version
, got_version
):
69 msg
= ('Invalid response version from server. Expected {0:02x} got '
70 '{1:02x}'.format(expected_version
, got_version
))
71 super(InvalidVersionError
, self
).__init
__(0, msg
)
74 class Socks4Error(ProxyError
):
78 91: 'request rejected or failed',
79 92: 'request rejected becasue SOCKS server cannot connect to identd on the client',
80 93: 'request rejected because the client program and identd report different user-ids'
84 class Socks5Error(ProxyError
):
85 ERR_GENERAL_FAILURE
= 0x01
88 0x01: 'general SOCKS server failure',
89 0x02: 'connection not allowed by ruleset',
90 0x03: 'Network unreachable',
91 0x04: 'Host unreachable',
92 0x05: 'Connection refused',
94 0x07: 'Command not supported',
95 0x08: 'Address type not supported',
96 0xFE: 'unknown username or invalid password',
97 0xFF: 'all offered authentication methods were rejected'
101 class ProxyType(object):
106 Proxy
= collections
.namedtuple('Proxy', (
107 'type', 'host', 'port', 'username', 'password', 'remote_dns'))
110 class sockssocket(socket
.socket
):
111 def __init__(self
, *args
, **kwargs
):
113 super(sockssocket
, self
).__init
__(*args
, **kwargs
)
115 def setproxy(self
, proxytype
, addr
, port
, rdns
=True, username
=None, password
=None):
116 assert proxytype
in (ProxyType
.SOCKS4
, ProxyType
.SOCKS4A
, ProxyType
.SOCKS5
)
118 self
._proxy
= Proxy(proxytype
, addr
, port
, username
, password
, rdns
)
120 def recvall(self
, cnt
):
122 while len(data
) < cnt
:
123 cur
= self
.recv(cnt
- len(data
))
125 raise IOError('{0} bytes missing'.format(cnt
- len(data
)))
129 def _recv_bytes(self
, cnt
):
130 data
= self
.recvall(cnt
)
131 return compat_struct_unpack('!{0}B'.format(cnt
), data
)
134 def _len_and_data(data
):
135 return compat_struct_pack('!B', len(data
)) + data
137 def _check_response_version(self
, expected_version
, got_version
):
138 if got_version
!= expected_version
:
140 raise InvalidVersionError(expected_version
, got_version
)
142 def _resolve_address(self
, destaddr
, default
, use_remote_dns
):
144 return socket
.inet_aton(destaddr
)
146 if use_remote_dns
and self
._proxy
.remote_dns
:
149 return socket
.inet_aton(socket
.gethostbyname(destaddr
))
151 def _setup_socks4(self
, address
, is_4a
=False):
152 destaddr
, port
= address
154 ipaddr
= self
._resolve
_address
(destaddr
, SOCKS4_DEFAULT_DSTIP
, use_remote_dns
=is_4a
)
156 packet
= compat_struct_pack('!BBH', SOCKS4_VERSION
, Socks4Command
.CMD_CONNECT
, port
) + ipaddr
158 username
= (self
._proxy
.username
or '').encode('utf-8')
159 packet
+= username
+ b
'\x00'
161 if is_4a
and self
._proxy
.remote_dns
:
162 packet
+= destaddr
.encode('utf-8') + b
'\x00'
166 version
, resp_code
, dstport
, dsthost
= compat_struct_unpack('!BBHI', self
.recvall(8))
168 self
._check
_response
_version
(SOCKS4_REPLY_VERSION
, version
)
170 if resp_code
!= Socks4Error
.ERR_SUCCESS
:
172 raise Socks4Error(resp_code
)
174 return (dsthost
, dstport
)
176 def _setup_socks4a(self
, address
):
177 self
._setup
_socks
4(address
, is_4a
=True)
179 def _socks5_auth(self
):
180 packet
= compat_struct_pack('!B', SOCKS5_VERSION
)
182 auth_methods
= [Socks5Auth
.AUTH_NONE
]
183 if self
._proxy
.username
and self
._proxy
.password
:
184 auth_methods
.append(Socks5Auth
.AUTH_USER_PASS
)
186 packet
+= compat_struct_pack('!B', len(auth_methods
))
187 packet
+= compat_struct_pack('!{0}B'.format(len(auth_methods
)), *auth_methods
)
191 version
, method
= self
._recv
_bytes
(2)
193 self
._check
_response
_version
(SOCKS5_VERSION
, version
)
195 if method
== Socks5Auth
.AUTH_NO_ACCEPTABLE
:
197 raise Socks5Error(method
)
199 if method
== Socks5Auth
.AUTH_USER_PASS
:
200 username
= self
._proxy
.username
.encode('utf-8')
201 password
= self
._proxy
.password
.encode('utf-8')
202 packet
= compat_struct_pack('!B', SOCKS5_USER_AUTH_VERSION
)
203 packet
+= self
._len
_and
_data
(username
) + self
._len
_and
_data
(password
)
206 version
, status
= self
._recv
_bytes
(2)
208 self
._check
_response
_version
(SOCKS5_USER_AUTH_VERSION
, version
)
210 if status
!= SOCKS5_USER_AUTH_SUCCESS
:
212 raise Socks5Error(Socks5Error
.ERR_GENERAL_FAILURE
)
214 def _setup_socks5(self
, address
):
215 destaddr
, port
= address
217 ipaddr
= self
._resolve
_address
(destaddr
, None, use_remote_dns
=True)
222 packet
= compat_struct_pack('!BBB', SOCKS5_VERSION
, Socks5Command
.CMD_CONNECT
, reserved
)
224 destaddr
= destaddr
.encode('utf-8')
225 packet
+= compat_struct_pack('!B', Socks5AddressType
.ATYP_DOMAINNAME
)
226 packet
+= self
._len
_and
_data
(destaddr
)
228 packet
+= compat_struct_pack('!B', Socks5AddressType
.ATYP_IPV4
) + ipaddr
229 packet
+= compat_struct_pack('!H', port
)
233 version
, status
, reserved
, atype
= self
._recv
_bytes
(4)
235 self
._check
_response
_version
(SOCKS5_VERSION
, version
)
237 if status
!= Socks5Error
.ERR_SUCCESS
:
239 raise Socks5Error(status
)
241 if atype
== Socks5AddressType
.ATYP_IPV4
:
242 destaddr
= self
.recvall(4)
243 elif atype
== Socks5AddressType
.ATYP_DOMAINNAME
:
244 alen
= compat_ord(self
.recv(1))
245 destaddr
= self
.recvall(alen
)
246 elif atype
== Socks5AddressType
.ATYP_IPV6
:
247 destaddr
= self
.recvall(16)
248 destport
= compat_struct_unpack('!H', self
.recvall(2))[0]
250 return (destaddr
, destport
)
252 def _make_proxy(self
, connect_func
, address
):
254 return connect_func(self
, address
)
256 result
= connect_func(self
, (self
._proxy
.host
, self
._proxy
.port
))
257 if result
!= 0 and result
is not None:
260 ProxyType
.SOCKS4
: self
._setup
_socks
4,
261 ProxyType
.SOCKS4A
: self
._setup
_socks
4a
,
262 ProxyType
.SOCKS5
: self
._setup
_socks
5,
264 setup_funcs
[self
._proxy
.type](address
)
267 def connect(self
, address
):
268 self
._make
_proxy
(socket
.socket
.connect
, address
)
270 def connect_ex(self
, address
):
271 return self
._make
_proxy
(socket
.socket
.connect_ex
, address
)