]>
jfr.im git - yt-dlp.git/blob - youtube_dl/socks.py
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
) or '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 because 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):
107 Proxy
= collections
.namedtuple('Proxy', (
108 'type', 'host', 'port', 'username', 'password', 'remote_dns'))
111 class sockssocket(socket
.socket
):
112 def __init__(self
, *args
, **kwargs
):
114 super(sockssocket
, self
).__init
__(*args
, **kwargs
)
116 def setproxy(self
, proxytype
, addr
, port
, rdns
=True, username
=None, password
=None):
117 assert proxytype
in (ProxyType
.SOCKS4
, ProxyType
.SOCKS4A
, ProxyType
.SOCKS5
)
119 self
._proxy
= Proxy(proxytype
, addr
, port
, username
, password
, rdns
)
121 def recvall(self
, cnt
):
123 while len(data
) < cnt
:
124 cur
= self
.recv(cnt
- len(data
))
126 raise IOError('{0} bytes missing'.format(cnt
- len(data
)))
130 def _recv_bytes(self
, cnt
):
131 data
= self
.recvall(cnt
)
132 return compat_struct_unpack('!{0}B'.format(cnt
), data
)
135 def _len_and_data(data
):
136 return compat_struct_pack('!B', len(data
)) + data
138 def _check_response_version(self
, expected_version
, got_version
):
139 if got_version
!= expected_version
:
141 raise InvalidVersionError(expected_version
, got_version
)
143 def _resolve_address(self
, destaddr
, default
, use_remote_dns
):
145 return socket
.inet_aton(destaddr
)
147 if use_remote_dns
and self
._proxy
.remote_dns
:
150 return socket
.inet_aton(socket
.gethostbyname(destaddr
))
152 def _setup_socks4(self
, address
, is_4a
=False):
153 destaddr
, port
= address
155 ipaddr
= self
._resolve
_address
(destaddr
, SOCKS4_DEFAULT_DSTIP
, use_remote_dns
=is_4a
)
157 packet
= compat_struct_pack('!BBH', SOCKS4_VERSION
, Socks4Command
.CMD_CONNECT
, port
) + ipaddr
159 username
= (self
._proxy
.username
or '').encode('utf-8')
160 packet
+= username
+ b
'\x00'
162 if is_4a
and self
._proxy
.remote_dns
:
163 packet
+= destaddr
.encode('utf-8') + b
'\x00'
167 version
, resp_code
, dstport
, dsthost
= compat_struct_unpack('!BBHI', self
.recvall(8))
169 self
._check
_response
_version
(SOCKS4_REPLY_VERSION
, version
)
171 if resp_code
!= Socks4Error
.ERR_SUCCESS
:
173 raise Socks4Error(resp_code
)
175 return (dsthost
, dstport
)
177 def _setup_socks4a(self
, address
):
178 self
._setup
_socks
4(address
, is_4a
=True)
180 def _socks5_auth(self
):
181 packet
= compat_struct_pack('!B', SOCKS5_VERSION
)
183 auth_methods
= [Socks5Auth
.AUTH_NONE
]
184 if self
._proxy
.username
and self
._proxy
.password
:
185 auth_methods
.append(Socks5Auth
.AUTH_USER_PASS
)
187 packet
+= compat_struct_pack('!B', len(auth_methods
))
188 packet
+= compat_struct_pack('!{0}B'.format(len(auth_methods
)), *auth_methods
)
192 version
, method
= self
._recv
_bytes
(2)
194 self
._check
_response
_version
(SOCKS5_VERSION
, version
)
196 if method
== Socks5Auth
.AUTH_NO_ACCEPTABLE
:
198 raise Socks5Error(method
)
200 if method
== Socks5Auth
.AUTH_USER_PASS
:
201 username
= self
._proxy
.username
.encode('utf-8')
202 password
= self
._proxy
.password
.encode('utf-8')
203 packet
= compat_struct_pack('!B', SOCKS5_USER_AUTH_VERSION
)
204 packet
+= self
._len
_and
_data
(username
) + self
._len
_and
_data
(password
)
207 version
, status
= self
._recv
_bytes
(2)
209 self
._check
_response
_version
(SOCKS5_USER_AUTH_VERSION
, version
)
211 if status
!= SOCKS5_USER_AUTH_SUCCESS
:
213 raise Socks5Error(Socks5Error
.ERR_GENERAL_FAILURE
)
215 def _setup_socks5(self
, address
):
216 destaddr
, port
= address
218 ipaddr
= self
._resolve
_address
(destaddr
, None, use_remote_dns
=True)
223 packet
= compat_struct_pack('!BBB', SOCKS5_VERSION
, Socks5Command
.CMD_CONNECT
, reserved
)
225 destaddr
= destaddr
.encode('utf-8')
226 packet
+= compat_struct_pack('!B', Socks5AddressType
.ATYP_DOMAINNAME
)
227 packet
+= self
._len
_and
_data
(destaddr
)
229 packet
+= compat_struct_pack('!B', Socks5AddressType
.ATYP_IPV4
) + ipaddr
230 packet
+= compat_struct_pack('!H', port
)
234 version
, status
, reserved
, atype
= self
._recv
_bytes
(4)
236 self
._check
_response
_version
(SOCKS5_VERSION
, version
)
238 if status
!= Socks5Error
.ERR_SUCCESS
:
240 raise Socks5Error(status
)
242 if atype
== Socks5AddressType
.ATYP_IPV4
:
243 destaddr
= self
.recvall(4)
244 elif atype
== Socks5AddressType
.ATYP_DOMAINNAME
:
245 alen
= compat_ord(self
.recv(1))
246 destaddr
= self
.recvall(alen
)
247 elif atype
== Socks5AddressType
.ATYP_IPV6
:
248 destaddr
= self
.recvall(16)
249 destport
= compat_struct_unpack('!H', self
.recvall(2))[0]
251 return (destaddr
, destport
)
253 def _make_proxy(self
, connect_func
, address
):
255 return connect_func(self
, address
)
257 result
= connect_func(self
, (self
._proxy
.host
, self
._proxy
.port
))
258 if result
!= 0 and result
is not None:
261 ProxyType
.SOCKS4
: self
._setup
_socks
4,
262 ProxyType
.SOCKS4A
: self
._setup
_socks
4a
,
263 ProxyType
.SOCKS5
: self
._setup
_socks
5,
265 setup_funcs
[self
._proxy
.type](address
)
268 def connect(self
, address
):
269 self
._make
_proxy
(socket
.socket
.connect
, address
)
271 def connect_ex(self
, address
):
272 return self
._make
_proxy
(socket
.socket
.connect_ex
, address
)