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.
 
 

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