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.
 
 

416 lines
14 KiB

  1. import pytest
  2. import aiosocks
  3. import aiohttp
  4. import asyncio
  5. import os
  6. import ssl
  7. import struct
  8. from aiohttp import web
  9. from aiohttp.test_utils import RawTestServer
  10. from aiohttp.test_utils import make_mocked_coro
  11. from aiosocks.test_utils import FakeSocksSrv, FakeSocks4Srv
  12. from aiosocks.connector import ProxyConnector, ProxyClientRequest
  13. from aiosocks.errors import SocksConnectionError
  14. from aiosocks import constants as c
  15. from async_timeout import timeout
  16. from unittest import mock
  17. async def test_socks4_connect_success(loop):
  18. pld = b'\x00\x5a\x04W\x01\x01\x01\x01test'
  19. async with FakeSocksSrv(loop, pld) as srv:
  20. addr = aiosocks.Socks4Addr('127.0.0.1', srv.port)
  21. auth = aiosocks.Socks4Auth('usr')
  22. dst = ('python.org', 80)
  23. transport, protocol = await aiosocks.create_connection(
  24. None, addr, auth, dst, loop=loop)
  25. assert protocol.proxy_sockname == ('1.1.1.1', 1111)
  26. data = await protocol._stream_reader.read(4)
  27. assert data == b'test'
  28. transport.close()
  29. async def test_socks4_invalid_data(loop):
  30. pld = b'\x01\x5a\x04W\x01\x01\x01\x01'
  31. async with FakeSocksSrv(loop, pld) as srv:
  32. addr = aiosocks.Socks4Addr('127.0.0.1', srv.port)
  33. auth = aiosocks.Socks4Auth('usr')
  34. dst = ('python.org', 80)
  35. with pytest.raises(aiosocks.SocksError) as ct:
  36. await aiosocks.create_connection(
  37. None, addr, auth, dst, loop=loop)
  38. assert 'invalid data' in str(ct)
  39. async def test_socks4_srv_error(loop):
  40. pld = b'\x00\x5b\x04W\x01\x01\x01\x01'
  41. async with FakeSocksSrv(loop, pld) as srv:
  42. addr = aiosocks.Socks4Addr('127.0.0.1', srv.port)
  43. auth = aiosocks.Socks4Auth('usr')
  44. dst = ('python.org', 80)
  45. with pytest.raises(aiosocks.SocksError) as ct:
  46. await aiosocks.create_connection(
  47. None, addr, auth, dst, loop=loop)
  48. assert '0x5b' in str(ct)
  49. # https://stackoverflow.com/a/55693498
  50. def with_timeout(t):
  51. def wrapper(corofunc):
  52. async def run(*args, **kwargs):
  53. with timeout(t):
  54. return await corofunc(*args, **kwargs)
  55. return run
  56. return wrapper
  57. async def test_socks4_datagram_failure():
  58. loop = asyncio.get_event_loop()
  59. async with FakeSocksSrv(loop, b'') as srv:
  60. addr = aiosocks.Socks4Addr('127.0.0.1', srv.port)
  61. with pytest.raises(ValueError):
  62. await aiosocks.open_datagram(addr, None, None, loop=loop)
  63. async def test_socks4_datagram_connect_failure():
  64. loop = asyncio.get_event_loop()
  65. async def raiseconnerr(*args, **kwargs):
  66. raise OSError(1)
  67. async with FakeSocksSrv(loop, b'') as srv:
  68. addr = aiosocks.Socks4Addr('127.0.0.1', srv.port)
  69. with mock.patch.object(loop, 'create_connection',
  70. raiseconnerr), pytest.raises(SocksConnectionError):
  71. await aiosocks.open_datagram(addr, None, None, loop=loop)
  72. @with_timeout(2)
  73. async def test_socks5_datagram_success_anonymous():
  74. #
  75. # This code is testing aiosocks.open_datagram.
  76. #
  77. # The server it is interacting with is srv (FakeSocksSrv).
  78. #
  79. # We mock the UDP Protocol to the SOCKS server w/
  80. # sockservdgram (FakeDGramTransport)
  81. #
  82. # UDP packet flow:
  83. # dgram (DGram) -> sockservdgram (FakeDGramTransport)
  84. # which reflects it back for delivery
  85. #
  86. loop = asyncio.get_event_loop()
  87. pld = b'\x05\x00\x05\x00\x00\x01\x01\x01\x01\x01\x04W'
  88. respdata = b'response data'
  89. async with FakeSocksSrv(loop, pld) as srv:
  90. addr = aiosocks.Socks5Addr('127.0.0.1', srv.port)
  91. auth = aiosocks.Socks5Auth('usr', 'pwd')
  92. dname = 'python.org'
  93. portnum = 53
  94. dst = (dname, portnum)
  95. # Fake SOCKS server UDP relay
  96. class FakeDGramTransport(asyncio.DatagramTransport):
  97. def sendto(self, data, addr=None):
  98. # Verify correct packet was receieved
  99. frag, addr, payload = aiosocks.protocols.Socks5Protocol.parse_udp(data)
  100. assert frag == 0
  101. assert addr == ('python.org', 53)
  102. assert payload == b'some data'
  103. # Send frag reply, make sure it's ignored
  104. ba = bytearray()
  105. ba.extend([ 0, 0, 1, 1, 2, 2, 2, 2, ])
  106. ba += (53).to_bytes(2, 'big')
  107. ba += respdata
  108. dgram.datagram_received(ba, ('3.3.3.3', 0))
  109. # Send reply
  110. # wish I could use build_udp here, but it's async
  111. ba = bytearray()
  112. ba.extend([ 0, 0, 0, 1, 2, 2, 2, 2, ])
  113. ba += (53).to_bytes(2, 'big')
  114. ba += respdata
  115. dgram.datagram_received(ba, ('3.3.3.3', 0))
  116. sockservdgram = FakeDGramTransport()
  117. # Fake the creation of the UDP relay
  118. async def fake_cde(factory, remote_addr):
  119. assert remote_addr == ('1.1.1.1', 1111)
  120. proto = factory()
  121. proto.connection_made(sockservdgram)
  122. return sockservdgram, proto
  123. # Open the UDP connection
  124. with mock.patch.object(loop, 'create_datagram_endpoint',
  125. fake_cde) as m:
  126. dgram = await aiosocks.open_datagram(addr, None, dst, loop=loop)
  127. rdr = srv.get_reader()
  128. # make sure we negotiated the correct command
  129. assert (await rdr.readexactly(5))[4] == c.SOCKS_CMD_UDP_ASSOCIATE
  130. assert dgram.proxy_sockname == ('1.1.1.1', 1111)
  131. dgram.send(b'some data')
  132. # XXX -- assert from fakesockssrv
  133. assert await dgram == (respdata, ('2.2.2.2', 53))
  134. dgram.close()
  135. async def test_socks5_connect_success_anonymous(loop):
  136. pld = b'\x05\x00\x05\x00\x00\x01\x01\x01\x01\x01\x04Wtest'
  137. async with FakeSocksSrv(loop, pld) as srv:
  138. addr = aiosocks.Socks5Addr('127.0.0.1', srv.port)
  139. auth = aiosocks.Socks5Auth('usr', 'pwd')
  140. dst = ('python.org', 80)
  141. transport, protocol = await aiosocks.create_connection(
  142. None, addr, auth, dst, loop=loop)
  143. assert protocol.proxy_sockname == ('1.1.1.1', 1111)
  144. data = await protocol._stream_reader.read(4)
  145. assert data == b'test'
  146. transport.close()
  147. async def test_socks5_connect_success_usr_pwd(loop):
  148. pld = b'\x05\x02\x01\x00\x05\x00\x00\x01\x01\x01\x01\x01\x04Wtest'
  149. async with FakeSocksSrv(loop, pld) as srv:
  150. addr = aiosocks.Socks5Addr('127.0.0.1', srv.port)
  151. auth = aiosocks.Socks5Auth('usr', 'pwd')
  152. dst = ('python.org', 80)
  153. transport, protocol = await aiosocks.create_connection(
  154. None, addr, auth, dst, loop=loop)
  155. assert protocol.proxy_sockname == ('1.1.1.1', 1111)
  156. data = await protocol._stream_reader.read(4)
  157. assert data == b'test'
  158. transport.close()
  159. async def test_socks5_auth_ver_err(loop):
  160. async with FakeSocksSrv(loop, b'\x04\x02') as srv:
  161. addr = aiosocks.Socks5Addr('127.0.0.1', srv.port)
  162. auth = aiosocks.Socks5Auth('usr', 'pwd')
  163. dst = ('python.org', 80)
  164. with pytest.raises(aiosocks.SocksError) as ct:
  165. await aiosocks.create_connection(
  166. None, addr, auth, dst, loop=loop)
  167. assert 'invalid version' in str(ct)
  168. async def test_socks5_auth_method_rejected(loop):
  169. async with FakeSocksSrv(loop, b'\x05\xFF') as srv:
  170. addr = aiosocks.Socks5Addr('127.0.0.1', srv.port)
  171. auth = aiosocks.Socks5Auth('usr', 'pwd')
  172. dst = ('python.org', 80)
  173. with pytest.raises(aiosocks.SocksError) as ct:
  174. await aiosocks.create_connection(
  175. None, addr, auth, dst, loop=loop)
  176. assert 'authentication methods were rejected' in str(ct)
  177. async def test_socks5_auth_status_invalid(loop):
  178. async with FakeSocksSrv(loop, b'\x05\xF0') as srv:
  179. addr = aiosocks.Socks5Addr('127.0.0.1', srv.port)
  180. auth = aiosocks.Socks5Auth('usr', 'pwd')
  181. dst = ('python.org', 80)
  182. with pytest.raises(aiosocks.SocksError) as ct:
  183. await aiosocks.create_connection(
  184. None, addr, auth, dst, loop=loop)
  185. assert 'invalid data' in str(ct)
  186. async def test_socks5_auth_status_invalid2(loop):
  187. async with FakeSocksSrv(loop, b'\x05\x02\x02\x00') as srv:
  188. addr = aiosocks.Socks5Addr('127.0.0.1', srv.port)
  189. auth = aiosocks.Socks5Auth('usr', 'pwd')
  190. dst = ('python.org', 80)
  191. with pytest.raises(aiosocks.SocksError) as ct:
  192. await aiosocks.create_connection(
  193. None, addr, auth, dst, loop=loop)
  194. assert 'invalid data' in str(ct)
  195. async def test_socks5_auth_failed(loop):
  196. async with FakeSocksSrv(loop, b'\x05\x02\x01\x01') as srv:
  197. addr = aiosocks.Socks5Addr('127.0.0.1', srv.port)
  198. auth = aiosocks.Socks5Auth('usr', 'pwd')
  199. dst = ('python.org', 80)
  200. with pytest.raises(aiosocks.SocksError) as ct:
  201. await aiosocks.create_connection(
  202. None, addr, auth, dst, loop=loop)
  203. assert 'authentication failed' in str(ct)
  204. async def test_socks5_cmd_ver_err(loop):
  205. async with FakeSocksSrv(loop, b'\x05\x02\x01\x00\x04\x00\x00') as srv:
  206. addr = aiosocks.Socks5Addr('127.0.0.1', srv.port)
  207. auth = aiosocks.Socks5Auth('usr', 'pwd')
  208. dst = ('python.org', 80)
  209. with pytest.raises(aiosocks.SocksError) as ct:
  210. await aiosocks.create_connection(
  211. None, addr, auth, dst, loop=loop)
  212. assert 'invalid version' in str(ct)
  213. async def test_socks5_cmd_not_granted(loop):
  214. async with FakeSocksSrv(loop, b'\x05\x02\x01\x00\x05\x01\x00') as srv:
  215. addr = aiosocks.Socks5Addr('127.0.0.1', srv.port)
  216. auth = aiosocks.Socks5Auth('usr', 'pwd')
  217. dst = ('python.org', 80)
  218. with pytest.raises(aiosocks.SocksError) as ct:
  219. await aiosocks.create_connection(
  220. None, addr, auth, dst, loop=loop)
  221. assert 'General SOCKS server failure' in str(ct)
  222. async def test_socks5_invalid_address_type(loop):
  223. async with FakeSocksSrv(loop, b'\x05\x02\x01\x00\x05\x00\x00\xFF') as srv:
  224. addr = aiosocks.Socks5Addr('127.0.0.1', srv.port)
  225. auth = aiosocks.Socks5Auth('usr', 'pwd')
  226. dst = ('python.org', 80)
  227. with pytest.raises(aiosocks.SocksError) as ct:
  228. await aiosocks.create_connection(
  229. None, addr, auth, dst, loop=loop)
  230. assert 'invalid data' in str(ct)
  231. async def test_socks5_atype_ipv4(loop):
  232. pld = b'\x05\x02\x01\x00\x05\x00\x00\x01\x01\x01\x01\x01\x04W'
  233. async with FakeSocksSrv(loop, pld) as srv:
  234. addr = aiosocks.Socks5Addr('127.0.0.1', srv.port)
  235. auth = aiosocks.Socks5Auth('usr', 'pwd')
  236. dst = ('python.org', 80)
  237. transport, protocol = await aiosocks.create_connection(
  238. None, addr, auth, dst, loop=loop)
  239. assert protocol.proxy_sockname == ('1.1.1.1', 1111)
  240. transport.close()
  241. async def test_socks5_atype_ipv6(loop):
  242. pld = b'\x05\x02\x01\x00\x05\x00\x00\x04\x00\x00\x00\x00' \
  243. b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x11\x04W'
  244. async with FakeSocksSrv(loop, pld) as srv:
  245. addr = aiosocks.Socks5Addr('127.0.0.1', srv.port)
  246. auth = aiosocks.Socks5Auth('usr', 'pwd')
  247. dst = ('python.org', 80)
  248. transport, protocol = await aiosocks.create_connection(
  249. None, addr, auth, dst, loop=loop)
  250. assert protocol.proxy_sockname == ('::111', 1111) or \
  251. protocol.proxy_sockname == ('::0.0.1.17', 1111)
  252. transport.close()
  253. async def test_socks5_atype_domain(loop):
  254. pld = b'\x05\x02\x01\x00\x05\x00\x00\x03\x0apython.org\x04W'
  255. async with FakeSocksSrv(loop, pld) as srv:
  256. addr = aiosocks.Socks5Addr('127.0.0.1', srv.port)
  257. auth = aiosocks.Socks5Auth('usr', 'pwd')
  258. dst = ('python.org', 80)
  259. transport, protocol = await aiosocks.create_connection(
  260. None, addr, auth, dst, loop=loop)
  261. assert protocol.proxy_sockname == (b'python.org', 1111)
  262. transport.close()
  263. async def test_http_connect(loop):
  264. async def handler(request):
  265. return web.Response(text='Test message')
  266. async with RawTestServer(handler, host='127.0.0.1', loop=loop) as ws:
  267. async with FakeSocks4Srv(loop) as srv:
  268. conn = ProxyConnector(loop=loop, remote_resolve=False)
  269. async with aiohttp.ClientSession(
  270. connector=conn, loop=loop,
  271. request_class=ProxyClientRequest) as ses:
  272. proxy = 'socks4://127.0.0.1:{}'.format(srv.port)
  273. async with ses.get(ws.make_url('/'), proxy=proxy) as resp:
  274. assert resp.status == 200
  275. assert (await resp.text()) == 'Test message'
  276. async def test_https_connect(loop):
  277. async def handler(request):
  278. return web.Response(text='Test message')
  279. here = os.path.join(os.path.dirname(__file__), '..', 'tests')
  280. keyfile = os.path.join(here, 'sample.key')
  281. certfile = os.path.join(here, 'sample.crt')
  282. sslcontext = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
  283. sslcontext.load_cert_chain(certfile, keyfile)
  284. ws = RawTestServer(handler, scheme='https', host='127.0.0.1', loop=loop)
  285. await ws.start_server(loop=loop, ssl=sslcontext)
  286. v_fp = (b'0\x9a\xc9D\x83\xdc\x91\'\x88\x91\x11\xa1d\x97\xfd'
  287. b'\xcb~7U\x14D@L'
  288. b'\x11\xab\x99\xa8\xae\xb7\x14\xee\x8b')
  289. inv_fp = (b'0\x9d\xc9D\x83\xdc\x91\'\x88\x91\x11\xa1d\x97\xfd'
  290. b'\xcb~7U\x14D@L'
  291. b'\x11\xab\x99\xa8\xae\xb7\x14\xee\x9e')
  292. async with FakeSocks4Srv(loop) as srv:
  293. v_conn = ProxyConnector(loop=loop, remote_resolve=False,
  294. verify_ssl=False, fingerprint=v_fp)
  295. inv_conn = ProxyConnector(loop=loop, remote_resolve=False,
  296. verify_ssl=False, fingerprint=inv_fp)
  297. async with aiohttp.ClientSession(
  298. connector=v_conn, loop=loop,
  299. request_class=ProxyClientRequest) as ses:
  300. proxy = 'socks4://127.0.0.1:{}'.format(srv.port)
  301. async with ses.get(ws.make_url('/'), proxy=proxy) as resp:
  302. assert resp.status == 200
  303. assert (await resp.text()) == 'Test message'
  304. async with aiohttp.ClientSession(
  305. connector=inv_conn, loop=loop,
  306. request_class=ProxyClientRequest) as ses:
  307. proxy = 'socks4://127.0.0.1:{}'.format(srv.port)
  308. with pytest.raises(aiohttp.ServerFingerprintMismatch):
  309. async with ses.get(ws.make_url('/'), proxy=proxy) as resp:
  310. assert resp.status == 200