An attempt at adding UDP support to aiosocks. Untested due to lack of server support.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

397 lines
14 KiB

  1. import asyncio
  2. import aiosocks
  3. import unittest
  4. import socket
  5. from unittest import mock
  6. from asyncio import coroutine as coro
  7. import aiosocks.constants as c
  8. from aiosocks.protocols import BaseSocksProtocol
  9. try:
  10. from asyncio import ensure_future
  11. except ImportError:
  12. ensure_future = asyncio.async
  13. def make_socks4(loop, *, addr=None, auth=None, rr=True, dst=None, r=b''):
  14. addr = addr or aiosocks.Socks4Addr('localhost', 1080)
  15. auth = auth or aiosocks.Socks4Auth('user')
  16. dst = dst or ('python.org', 80)
  17. proto = aiosocks.Socks4Protocol(
  18. proxy=addr, proxy_auth=auth, dst=dst, remote_resolve=rr, loop=loop)
  19. proto._transport = mock.Mock()
  20. proto.read_response = mock.Mock(
  21. side_effect=coro(mock.Mock(return_value=r)))
  22. proto._get_dst_addr = mock.Mock(
  23. side_effect=coro(mock.Mock(return_value=(socket.AF_INET, '127.0.0.1')))
  24. )
  25. return proto
  26. def make_socks5(loop, *, addr=None, auth=None, rr=True, dst=None, r=None):
  27. addr = addr or aiosocks.Socks5Addr('localhost', 1080)
  28. auth = auth or aiosocks.Socks5Auth('user', 'pwd')
  29. dst = dst or ('python.org', 80)
  30. proto = aiosocks.Socks5Protocol(
  31. proxy=addr, proxy_auth=auth, dst=dst, remote_resolve=rr, loop=loop)
  32. proto._transport = mock.Mock()
  33. if not isinstance(r, (list, tuple)):
  34. proto.read_response = mock.Mock(
  35. side_effect=coro(mock.Mock(return_value=r)))
  36. else:
  37. proto.read_response = mock.Mock(
  38. side_effect=coro(mock.Mock(side_effect=r)))
  39. proto._get_dst_addr = mock.Mock(
  40. side_effect=coro(mock.Mock(return_value=(socket.AF_INET, '127.0.0.1')))
  41. )
  42. return proto
  43. class TestBaseSocksProtocol(unittest.TestCase):
  44. def setUp(self):
  45. self.loop = asyncio.new_event_loop()
  46. asyncio.set_event_loop(None)
  47. def tearDown(self):
  48. self.loop.close()
  49. def test_init(self):
  50. with self.assertRaises(ValueError):
  51. BaseSocksProtocol(None, None, None, loop=self.loop)
  52. with self.assertRaises(ValueError):
  53. BaseSocksProtocol(None, None, 123, loop=self.loop)
  54. with self.assertRaises(ValueError):
  55. BaseSocksProtocol(None, None, ('python.org',), loop=self.loop)
  56. def test_write_request(self):
  57. proto = BaseSocksProtocol(None, None, ('python.org', 80),
  58. loop=self.loop)
  59. proto._transport = mock.Mock()
  60. proto.write_request([b'\x00', b'\x01\x02', 0x03])
  61. proto._transport.write.assert_called_with(b'\x00\x01\x02\x03')
  62. with self.assertRaises(ValueError):
  63. proto.write_request(['\x00'])
  64. class TestSocks4Protocol(unittest.TestCase):
  65. def setUp(self):
  66. self.loop = asyncio.new_event_loop()
  67. asyncio.set_event_loop(None)
  68. def tearDown(self):
  69. self.loop.close()
  70. def test_init(self):
  71. addr = aiosocks.Socks4Addr('localhost', 1080)
  72. auth = aiosocks.Socks4Auth('user')
  73. dst = ('python.org', 80)
  74. with self.assertRaises(ValueError):
  75. aiosocks.Socks4Protocol(None, None, dst, loop=self.loop)
  76. with self.assertRaises(ValueError):
  77. aiosocks.Socks4Protocol(None, auth, dst, loop=self.loop)
  78. with self.assertRaises(ValueError):
  79. aiosocks.Socks4Protocol(aiosocks.Socks5Addr('host'), auth, dst,
  80. loop=self.loop)
  81. with self.assertRaises(ValueError):
  82. aiosocks.Socks4Protocol(addr, aiosocks.Socks5Auth('l', 'p'), dst,
  83. loop=self.loop)
  84. aiosocks.Socks4Protocol(addr, None, dst, loop=self.loop)
  85. aiosocks.Socks4Protocol(addr, auth, dst, loop=self.loop)
  86. def test_request_building(self):
  87. resp = b'\x00\x5a\x00P\x7f\x00\x00\x01'
  88. # dst = domain, remote resolve = true
  89. proto = make_socks4(self.loop, dst=('python.org', 80), r=resp)
  90. req = proto.socks_request(c.SOCKS_CMD_CONNECT)
  91. self.loop.run_until_complete(req)
  92. proto._transport.write.assert_called_with(
  93. b'\x04\x01\x00P\x00\x00\x00\x01user\x00python.org\x00'
  94. )
  95. # dst = domain, remote resolve = false
  96. proto = make_socks4(self.loop, dst=('python.org', 80),
  97. rr=False, r=resp)
  98. req = proto.socks_request(c.SOCKS_CMD_CONNECT)
  99. self.loop.run_until_complete(req)
  100. proto._transport.write.assert_called_with(
  101. b'\x04\x01\x00P\x7f\x00\x00\x01user\x00'
  102. )
  103. # dst = ip, remote resolve = true
  104. proto = make_socks4(self.loop, dst=('127.0.0.1', 8800), r=resp)
  105. req = proto.socks_request(c.SOCKS_CMD_CONNECT)
  106. self.loop.run_until_complete(req)
  107. proto._transport.write.assert_called_with(
  108. b'\x04\x01"`\x7f\x00\x00\x01user\x00'
  109. )
  110. # dst = ip, remote resolve = false
  111. proto = make_socks4(self.loop, dst=('127.0.0.1', 8800),
  112. rr=False, r=resp)
  113. req = proto.socks_request(c.SOCKS_CMD_CONNECT)
  114. self.loop.run_until_complete(req)
  115. proto._transport.write.assert_called_with(
  116. b'\x04\x01"`\x7f\x00\x00\x01user\x00'
  117. )
  118. # dst = domain, without user
  119. proto = make_socks4(self.loop, auth=aiosocks.Socks4Auth(''),
  120. dst=('python.org', 80), r=resp)
  121. req = proto.socks_request(c.SOCKS_CMD_CONNECT)
  122. self.loop.run_until_complete(req)
  123. proto._transport.write.assert_called_with(
  124. b'\x04\x01\x00P\x00\x00\x00\x01\x00python.org\x00'
  125. )
  126. # dst = ip, without user
  127. proto = make_socks4(self.loop, auth=aiosocks.Socks4Auth(''),
  128. dst=('127.0.0.1', 8800), r=resp)
  129. req = proto.socks_request(c.SOCKS_CMD_CONNECT)
  130. self.loop.run_until_complete(req)
  131. proto._transport.write.assert_called_with(
  132. b'\x04\x01"`\x7f\x00\x00\x01\x00'
  133. )
  134. def test_response_handling(self):
  135. valid_resp = b'\x00\x5a\x00P\x7f\x00\x00\x01'
  136. invalid_data_resp = b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
  137. socks_err_resp = b'\x00\x5b\x00P\x7f\x00\x00\x01'
  138. socks_err_unk_resp = b'\x00\x5e\x00P\x7f\x00\x00\x01'
  139. # valid result
  140. proto = make_socks4(self.loop, r=valid_resp)
  141. req = ensure_future(
  142. proto.socks_request(c.SOCKS_CMD_CONNECT), loop=self.loop)
  143. self.loop.run_until_complete(req)
  144. self.assertEqual(req.result(), (('python.org', 80), ('127.0.0.1', 80)))
  145. # invalid server reply
  146. proto = make_socks4(self.loop, r=invalid_data_resp)
  147. req = proto.socks_request(c.SOCKS_CMD_CONNECT)
  148. with self.assertRaises(aiosocks.InvalidServerReply):
  149. self.loop.run_until_complete(req)
  150. # socks server sent error
  151. proto = make_socks4(self.loop, r=socks_err_resp)
  152. req = proto.socks_request(c.SOCKS_CMD_CONNECT)
  153. with self.assertRaises(aiosocks.SocksError) as cm:
  154. self.loop.run_until_complete(req)
  155. self.assertTrue('0x5b' in str(cm.exception))
  156. # socks server send unknown error
  157. proto = make_socks4(self.loop, r=socks_err_unk_resp)
  158. req = proto.socks_request(c.SOCKS_CMD_CONNECT)
  159. with self.assertRaises(aiosocks.SocksError) as cm:
  160. self.loop.run_until_complete(req)
  161. self.assertTrue('Unknown error' in str(cm.exception))
  162. class TestSocks5Protocol(unittest.TestCase):
  163. def setUp(self):
  164. self.loop = asyncio.new_event_loop()
  165. asyncio.set_event_loop(None)
  166. def tearDown(self):
  167. self.loop.close()
  168. def test_init(self):
  169. addr = aiosocks.Socks5Addr('localhost', 1080)
  170. auth = aiosocks.Socks5Auth('user', 'pwd')
  171. dst = ('python.org', 80)
  172. with self.assertRaises(ValueError):
  173. aiosocks.Socks5Protocol(None, None, dst, loop=self.loop)
  174. with self.assertRaises(ValueError):
  175. aiosocks.Socks5Protocol(None, auth, dst, loop=self.loop)
  176. with self.assertRaises(ValueError):
  177. aiosocks.Socks5Protocol(aiosocks.Socks4Addr('host'),
  178. auth, dst, loop=self.loop)
  179. with self.assertRaises(ValueError):
  180. aiosocks.Socks5Protocol(addr, aiosocks.Socks4Auth('l'),
  181. dst, loop=self.loop)
  182. aiosocks.Socks5Protocol(addr, None, dst, loop=self.loop)
  183. aiosocks.Socks5Protocol(addr, auth, dst, loop=self.loop)
  184. def test_authenticate(self):
  185. # invalid server version
  186. proto = make_socks5(self.loop, r=b'\x00\x00')
  187. req = proto.authenticate()
  188. with self.assertRaises(aiosocks.InvalidServerVersion):
  189. self.loop.run_until_complete(req)
  190. # anonymous auth granted
  191. proto = make_socks5(self.loop, r=b'\x05\x00')
  192. req = proto.authenticate()
  193. self.loop.run_until_complete(req)
  194. # no acceptable auth methods
  195. proto = make_socks5(self.loop, r=b'\x05\xFF')
  196. req = proto.authenticate()
  197. with self.assertRaises(aiosocks.NoAcceptableAuthMethods):
  198. self.loop.run_until_complete(req)
  199. # unsupported auth method
  200. proto = make_socks5(self.loop, r=b'\x05\xF0')
  201. req = proto.authenticate()
  202. with self.assertRaises(aiosocks.InvalidServerReply):
  203. self.loop.run_until_complete(req)
  204. # auth: username, pwd
  205. # access granted
  206. proto = make_socks5(self.loop, r=(b'\x05\x02', b'\x01\x00',))
  207. req = proto.authenticate()
  208. self.loop.run_until_complete(req)
  209. proto._transport.write.assert_has_calls([
  210. mock.call(b'\x05\x02\x00\x02'),
  211. mock.call(b'\x01\x04user\x03pwd')
  212. ])
  213. # invalid reply
  214. proto = make_socks5(self.loop, r=(b'\x05\x02', b'\x00\x00',))
  215. req = proto.authenticate()
  216. with self.assertRaises(aiosocks.InvalidServerReply):
  217. self.loop.run_until_complete(req)
  218. # access denied
  219. proto = make_socks5(self.loop, r=(b'\x05\x02', b'\x01\x01',))
  220. req = proto.authenticate()
  221. with self.assertRaises(aiosocks.LoginAuthenticationFailed):
  222. self.loop.run_until_complete(req)
  223. def test_write_address(self):
  224. # ipv4
  225. proto = make_socks5(self.loop)
  226. req = proto.write_address('127.0.0.1', 80)
  227. self.loop.run_until_complete(req)
  228. proto._transport.write.assert_called_with(b'\x01\x7f\x00\x00\x01\x00P')
  229. # ipv6
  230. proto = make_socks5(self.loop)
  231. req = proto.write_address(
  232. '2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d', 80)
  233. self.loop.run_until_complete(req)
  234. proto._transport.write.assert_called_with(
  235. b'\x04 \x01\r\xb8\x11\xa3\t\xd7\x1f4\x8a.\x07\xa0v]\x00P')
  236. # domain, remote_resolve = true
  237. proto = make_socks5(self.loop)
  238. req = proto.write_address('python.org', 80)
  239. self.loop.run_until_complete(req)
  240. proto._transport.write.assert_called_with(b'\x03\npython.org\x00P')
  241. # domain, remote resolve = false
  242. proto = make_socks5(self.loop, rr=False)
  243. req = proto.write_address('python.org', 80)
  244. self.loop.run_until_complete(req)
  245. proto._transport.write.assert_called_with(b'\x01\x7f\x00\x00\x01\x00P')
  246. def test_read_address(self):
  247. # ipv4
  248. proto = make_socks5(
  249. self.loop, r=[b'\x01', b'\x7f\x00\x00\x01', b'\x00P'])
  250. req = ensure_future(proto.read_address(), loop=self.loop)
  251. self.loop.run_until_complete(req)
  252. self.assertEqual(req.result(), ('127.0.0.1', 80))
  253. # ipv6
  254. resp = [
  255. b'\x04',
  256. b' \x01\r\xb8\x11\xa3\t\xd7\x1f4\x8a.\x07\xa0v]',
  257. b'\x00P'
  258. ]
  259. proto = make_socks5(self.loop, r=resp)
  260. req = ensure_future(proto.read_address(), loop=self.loop)
  261. self.loop.run_until_complete(req)
  262. self.assertEqual(
  263. req.result(), ('2001:db8:11a3:9d7:1f34:8a2e:7a0:765d', 80))
  264. # domain
  265. proto = make_socks5(
  266. self.loop, r=[b'\x03', b'\n', b'python.org', b'\x00P'])
  267. req = ensure_future(proto.read_address(), loop=self.loop)
  268. self.loop.run_until_complete(req)
  269. self.assertEqual(req.result(), (b'python.org', 80))
  270. def test_socks_request(self):
  271. # invalid version
  272. proto = make_socks5(self.loop, r=[b'\x05\x00', b'\x04\x00\x00'])
  273. req = proto.socks_request(c.SOCKS_CMD_CONNECT)
  274. with self.assertRaises(aiosocks.InvalidServerVersion):
  275. self.loop.run_until_complete(req)
  276. # socks error
  277. proto = make_socks5(self.loop, r=[b'\x05\x00', b'\x05\x02\x00'])
  278. req = proto.socks_request(c.SOCKS_CMD_CONNECT)
  279. with self.assertRaises(aiosocks.SocksError) as ct:
  280. self.loop.run_until_complete(req)
  281. self.assertTrue(
  282. 'Connection not allowed by ruleset' in str(ct.exception))
  283. # socks unknown error
  284. proto = make_socks5(self.loop, r=[b'\x05\x00', b'\x05\xFF\x00'])
  285. req = proto.socks_request(c.SOCKS_CMD_CONNECT)
  286. with self.assertRaises(aiosocks.SocksError) as ct:
  287. self.loop.run_until_complete(req)
  288. self.assertTrue('Unknown error' in str(ct.exception))
  289. # cmd granted
  290. resp = [b'\x05\x00',
  291. b'\x05\x00\x00',
  292. b'\x01', b'\x7f\x00\x00\x01',
  293. b'\x00P']
  294. proto = make_socks5(self.loop, r=resp)
  295. req = ensure_future(proto.socks_request(c.SOCKS_CMD_CONNECT),
  296. loop=self.loop)
  297. self.loop.run_until_complete(req)
  298. self.assertEqual(req.result(), (('python.org', 80), ('127.0.0.1', 80)))
  299. proto._transport.write.assert_has_calls([
  300. mock.call(b'\x05\x02\x00\x02'),
  301. mock.call(b'\x05\x01\x00'),
  302. mock.call(b'\x03\npython.org\x00P')
  303. ])