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.
 
 

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