Browse Source

Dropped supporting python 3.4, added supporting aiohttp 2.0, migrate to pytest

main
nibrag 8 years ago
parent
commit
b8c62da7a6
12 changed files with 1297 additions and 1527 deletions
  1. +31
    -34
      README.rst
  2. +9
    -11
      aiosocks/__init__.py
  3. +79
    -74
      aiosocks/connector.py
  4. +27
    -41
      aiosocks/protocols.py
  5. +136
    -0
      aiosocks/test_utils.py
  6. +0
    -0
      tests/__init__.py
  7. +1
    -0
      tests/conftest.py
  8. +0
    -242
      tests/helpers.py
  9. +135
    -111
      tests/test_connector.py
  10. +95
    -132
      tests/test_create_connect.py
  11. +271
    -346
      tests/test_functional.py
  12. +513
    -536
      tests/test_protocols.py

+ 31
- 34
README.rst View File

@@ -11,6 +11,12 @@ SOCKS proxy client for asyncio and aiohttp
.. image:: https://badge.fury.io/py/aiosocks.svg .. image:: https://badge.fury.io/py/aiosocks.svg
:target: https://badge.fury.io/py/aiosocks :target: https://badge.fury.io/py/aiosocks



Dependencies
------------
python 3.5+
aiohttp 2.0+

Features Features
-------- --------
- SOCKS4, SOCKS4a and SOCKS5 version - SOCKS4, SOCKS4a and SOCKS5 version
@@ -136,56 +142,47 @@ aiohttp usage
import asyncio import asyncio
import aiohttp import aiohttp
import aiosocks import aiosocks
from aiosocks.connector import ( from yarl import URL
SocksConnector, HttpProxyAddr, HttpProxyAuth from aiosocks.connector import ProxyConnecotr, ProxyClientRequest
)




async def load_github_main(): async def load_github_main():
addr = aiosocks.Socks5Addr('127.0.0.1', 1080) auth5 = aiosocks.Socks5Auth('proxyuser1', password='pwd')
auth = aiosocks.Socks5Auth('proxyuser1', password='pwd') auth4 = aiosocks.Socks4Auth('proxyuser1')
ba = aiohttp.BasicAuth('login')


# remote resolve # remote resolve
conn = SocksConnector(proxy=addr, proxy_auth=auth, remote_resolve=True) conn = ProxyConnecotr(remote_resolve=True)


# or locale resolve # or locale resolve
conn = SocksConnector(proxy=addr, proxy_auth=auth, remote_resolve=False) conn = SocksConnector(remote_resolve=False)


try: try:
with aiohttp.ClientSession(connector=conn) as session: with aiohttp.ClientSession(connector=conn, request_class=ProxyClientRequest) as session:
async with session.get('http://github.com/') as resp: # socks5 proxy
async with session.get('http://github.com/', proxy=URL('socks5://127.0.0.1:1080'),
proxy_auth=auth5) as resp:
if resp.status == 200:
print(await resp.text())

# socks4 proxy
async with session.get('http://github.com/', proxy=URL('socks4://127.0.0.1:1081'),
proxy_auth=auth4) as resp:
if resp.status == 200:
print(await resp.text())

# http proxy
async with session.get('http://github.com/', proxy=URL('http://127.0.0.1:8080'),
proxy_auth=ba) as resp:
if resp.status == 200: if resp.status == 200:
print(await resp.text()) print(await resp.text())
except aiohttp.ProxyConnectionError: except aiohttp.ProxyConnectionError:
# connection problem # connection problem
except aiosocks.SocksError: except aiosocks.SocksError:
# communication problem # communication problem
if __name__ == '__main__': if __name__ == '__main__':
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
loop.run_until_complete(load_github_main()) loop.run_until_complete(load_github_main())
loop.close() loop.close()

proxy_connector
^^^^^^^^^^^^^^^
A unified method to create `connector`.

.. code-block:: python

import asyncio
import aiohttp
import aiosocks
from aiosocks.connector import (
proxy_connector, HttpProxyAddr, HttpProxyAuth
)

# make SocksConnector
conn = proxy_connector(aiosocks.Socks5Addr(...),
remote_resolve=True, verify_ssl=False)
# return SocksConnector instance

# make aiohttp.ProxyConnector (http proxy)
conn = proxy_connector(HttpProxyAddr('http://proxy'),
HttpProxyAuth('login', 'pwd'), verify_ssl=True)
# return aiohttp.ProxyConnector instance

+ 9
- 11
aiosocks/__init__.py View File

@@ -17,11 +17,10 @@ __all__ = ('Socks4Protocol', 'Socks5Protocol', 'Socks4Auth',
'InvalidServerReply', 'create_connection', 'open_connection') 'InvalidServerReply', 'create_connection', 'open_connection')




@asyncio.coroutine async def create_connection(protocol_factory, proxy, proxy_auth, dst, *,
def create_connection(protocol_factory, proxy, proxy_auth, dst, *, remote_resolve=True, loop=None, ssl=None, family=0,
remote_resolve=True, loop=None, ssl=None, family=0, proto=0, flags=0, sock=None, local_addr=None,
proto=0, flags=0, sock=None, local_addr=None, server_hostname=None, reader_limit=DEFAULT_LIMIT):
server_hostname=None, reader_limit=DEFAULT_LIMIT):
assert isinstance(proxy, SocksAddr), ( assert isinstance(proxy, SocksAddr), (
'proxy must be Socks4Addr() or Socks5Addr() tuple' 'proxy must be Socks4Addr() or Socks5Addr() tuple'
) )
@@ -70,7 +69,7 @@ def create_connection(protocol_factory, proxy, proxy_auth, dst, *,
reader_limit=reader_limit) reader_limit=reader_limit)


try: try:
transport, protocol = yield from loop.create_connection( transport, protocol = await loop.create_connection(
socks_factory, proxy.host, proxy.port, family=family, socks_factory, proxy.host, proxy.port, family=family,
proto=proto, flags=flags, sock=sock, local_addr=local_addr) proto=proto, flags=flags, sock=sock, local_addr=local_addr)
except OSError as exc: except OSError as exc:
@@ -79,7 +78,7 @@ def create_connection(protocol_factory, proxy, proxy_auth, dst, *,
(exc.errno, proxy.host, proxy.port, exc.strerror)) from exc (exc.errno, proxy.host, proxy.port, exc.strerror)) from exc


try: try:
yield from waiter await waiter
except: except:
transport.close() transport.close()
raise raise
@@ -87,10 +86,9 @@ def create_connection(protocol_factory, proxy, proxy_auth, dst, *,
return protocol.app_transport, protocol.app_protocol return protocol.app_transport, protocol.app_protocol




@asyncio.coroutine async def open_connection(proxy, proxy_auth, dst, *, remote_resolve=True,
def open_connection(proxy, proxy_auth, dst, *, remote_resolve=True, loop=None, limit=DEFAULT_LIMIT, **kwds):
loop=None, limit=DEFAULT_LIMIT, **kwds): _, protocol = await create_connection(
_, protocol = yield from create_connection(
None, proxy, proxy_auth, dst, reader_limit=limit, None, proxy, proxy_auth, dst, reader_limit=limit,
remote_resolve=remote_resolve, loop=loop, **kwds) remote_resolve=remote_resolve, loop=loop, **kwds)




+ 79
- 74
aiosocks/connector.py View File

@@ -1,105 +1,99 @@
try: try:
import aiohttp import aiohttp
from aiohttp.errors import ProxyConnectionError from aiohttp.connector import sentinel
from aiohttp.helpers import BasicAuth as HttpProxyAuth
except ImportError: except ImportError:
raise ImportError('aiosocks.SocksConnector require aiohttp library') raise ImportError('aiosocks.SocksConnector require aiohttp library')


import asyncio
from collections import namedtuple
from .errors import SocksError, SocksConnectionError from .errors import SocksError, SocksConnectionError
from .helpers import SocksAddr from .helpers import Socks4Auth, Socks5Auth, Socks4Addr, Socks5Addr
from . import create_connection from . import create_connection


__all__ = ('SocksConnector', 'HttpProxyAddr', 'HttpProxyAuth') __all__ = ('ProxyConnector', 'ProxyClientRequest')


class ProxyClientRequest(aiohttp.ClientRequest):
def update_proxy(self, proxy, proxy_auth):
if proxy and proxy.scheme not in ['http', 'socks4', 'socks5']:
raise ValueError(
"Only http, socks4 and socks5 proxies are supported")
if proxy and proxy_auth:
if proxy.scheme == 'http' and \
not isinstance(proxy_auth, aiohttp.BasicAuth):
raise ValueError("proxy_auth must be None or "
"BasicAuth() tuple for http proxy")
if proxy.scheme == 'socks4' and \
not isinstance(proxy_auth, Socks4Auth):
raise ValueError("proxy_auth must be None or Socks4Auth() "
"tuple for socks4 proxy")
if proxy.scheme == 'socks5' and \
not isinstance(proxy_auth, Socks5Auth):
raise ValueError("proxy_auth must be None or Socks5Auth() "
"tuple for socks5 proxy")

self.proxy = proxy
self.proxy_auth = proxy_auth


class ProxyConnector(aiohttp.TCPConnector):
def __init__(self, *, verify_ssl=True, fingerprint=None,
resolve=sentinel, use_dns_cache=True,
family=0, ssl_context=None, local_addr=None,
resolver=None, keepalive_timeout=sentinel,
force_close=False, limit=100, limit_per_host=0,
enable_cleanup_closed=False, loop=None, remote_resolve=True):
super().__init__(
verify_ssl=verify_ssl, fingerprint=fingerprint, resolve=resolve,
family=family, ssl_context=ssl_context, local_addr=local_addr,
resolver=resolver, keepalive_timeout=keepalive_timeout,
force_close=force_close, limit=limit, loop=loop,
limit_per_host=limit_per_host, use_dns_cache=use_dns_cache,
enable_cleanup_closed=enable_cleanup_closed)



class HttpProxyAddr(namedtuple('HttpProxyAddr', ['url'])):
def __new__(cls, url):
if url is None:
raise ValueError('None is not allowed as url value')
return super().__new__(cls, url)


class SocksConnector(aiohttp.TCPConnector):
def __init__(self, proxy, proxy_auth=None, *, remote_resolve=True, **kwgs):
super().__init__(**kwgs)

self._proxy = proxy
self._proxy_auth = proxy_auth
self._remote_resolve = remote_resolve self._remote_resolve = remote_resolve


@property async def _create_proxy_connection(self, req):
def proxy(self): if req.proxy.scheme == 'http':
"""Proxy info. return await super()._create_proxy_connection(req)
Should be Socks4Server/Socks5Server instance. else:
""" return await self._create_socks_connection(req)
return self._proxy

@property
def proxy_auth(self):
"""Proxy auth info.
Should be Socks4Auth/Socks5Auth instance.
"""
return self._proxy_auth

def _validate_ssl_fingerprint(self, transport, host):
has_cert = transport.get_extra_info('sslcontext')
if has_cert and self._fingerprint:
sock = transport.get_extra_info('socket')
if not hasattr(sock, 'getpeercert'):
# Workaround for asyncio 3.5.0
# Starting from 3.5.1 version
# there is 'ssl_object' extra info in transport
sock = transport._ssl_protocol._sslpipe.ssl_object
# gives DER-encoded cert as a sequence of bytes (or None)
cert = sock.getpeercert(binary_form=True)
assert cert
got = self._hashfunc(cert).digest()
expected = self._fingerprint
if got != expected:
transport.close()
raise aiohttp.FingerprintMismatch(
expected, got, host, 80
)


@asyncio.coroutine async def _create_socks_connection(self, req):
def _create_connection(self, req):
if req.ssl: if req.ssl:
sslcontext = self.ssl_context sslcontext = self.ssl_context
else: else:
sslcontext = None sslcontext = None


if not self._remote_resolve: if not self._remote_resolve:
dst_hosts = yield from self._resolve_host(req.host, req.port) dst_hosts = await self._resolve_host(req.host, req.port)
dst = dst_hosts[0]['host'], dst_hosts[0]['port'] dst = dst_hosts[0]['host'], dst_hosts[0]['port']
else: else:
dst = req.host, req.port dst = req.host, req.port


proxy_hosts = yield from self._resolve_host(self._proxy.host, proxy_hosts = await self._resolve_host(req.proxy.host, req.proxy.port)
self._proxy.port)
exc = None exc = None


for hinfo in proxy_hosts: for hinfo in proxy_hosts:
try: if req.proxy.scheme == 'socks4':
proxy = self._proxy.__class__(host=hinfo['host'], proxy = Socks4Addr(hinfo['host'], hinfo['port'])
port=hinfo['port']) else:
proxy = Socks5Addr(hinfo['host'], hinfo['port'])


transp, proto = yield from create_connection( try:
self._factory, proxy, self._proxy_auth, dst, transp, proto = await create_connection(
self._factory, proxy, req.proxy_auth, dst,
loop=self._loop, remote_resolve=self._remote_resolve, loop=self._loop, remote_resolve=self._remote_resolve,
ssl=sslcontext, family=hinfo['family'], ssl=sslcontext, family=hinfo['family'],
proto=hinfo['proto'], flags=hinfo['flags'], proto=hinfo['proto'], flags=hinfo['flags'],
local_addr=self._local_addr, local_addr=self._local_addr,
server_hostname=req.host if sslcontext else None) server_hostname=req.host if sslcontext else None)
self._validate_ssl_fingerprint(transp, req.host)


self._validate_ssl_fingerprint(transp, req.host, req.port)
return transp, proto return transp, proto
except (OSError, SocksError, SocksConnectionError) as e: except (OSError, SocksError, SocksConnectionError) as e:
exc = e exc = e
else: else:
if isinstance(exc, SocksConnectionError): if isinstance(exc, SocksConnectionError):
raise ProxyConnectionError(*exc.args) raise aiohttp.ClientProxyConnectionError(*exc.args)
if isinstance(exc, SocksError): if isinstance(exc, SocksError):
raise exc raise exc
else: else:
@@ -107,12 +101,23 @@ class SocksConnector(aiohttp.TCPConnector):
exc.errno, 'Can not connect to %s:%s [%s]' % exc.errno, 'Can not connect to %s:%s [%s]' %
(req.host, req.port, exc.strerror)) from exc (req.host, req.port, exc.strerror)) from exc



def _validate_ssl_fingerprint(self, transp, host, port):
def proxy_connector(proxy, proxy_auth=None, **kwargs): has_cert = transp.get_extra_info('sslcontext')
if isinstance(proxy, HttpProxyAddr): if has_cert and self._fingerprint:
return aiohttp.ProxyConnector( sock = transp.get_extra_info('socket')
proxy.url, proxy_auth=proxy_auth, **kwargs) if not hasattr(sock, 'getpeercert'):
elif isinstance(proxy, SocksAddr): # Workaround for asyncio 3.5.0
return SocksConnector(proxy, proxy_auth, **kwargs) # Starting from 3.5.1 version
else: # there is 'ssl_object' extra info in transport
raise ValueError('Unsupported `proxy` format') sock = transp._ssl_protocol._sslpipe.ssl_object
# gives DER-encoded cert as a sequence of bytes (or None)
cert = sock.getpeercert(binary_form=True)
assert cert
got = self._hashfunc(cert).digest()
expected = self._fingerprint
if got != expected:
transp.close()
if not self._cleanup_closed_disabled:
self._cleanup_closed_transports.append(transp)
raise aiohttp.ServerFingerprintMismatch(
expected, got, host, port)

+ 27
- 41
aiosocks/protocols.py View File

@@ -10,11 +10,6 @@ from .errors import (
InvalidServerReply, InvalidServerVersion InvalidServerReply, InvalidServerVersion
) )


try:
from asyncio import ensure_future
except ImportError:
ensure_future = asyncio.async



DEFAULT_LIMIT = getattr(asyncio.streams, '_DEFAULT_LIMIT', 2**16) DEFAULT_LIMIT = getattr(asyncio.streams, '_DEFAULT_LIMIT', 2**16)


@@ -54,11 +49,10 @@ class BaseSocksProtocol(asyncio.StreamReaderProtocol):
super().__init__(stream_reader=reader, super().__init__(stream_reader=reader,
client_connected_cb=self.negotiate, loop=self._loop) client_connected_cb=self.negotiate, loop=self._loop)


@asyncio.coroutine async def negotiate(self, reader, writer):
def negotiate(self, reader, writer):
try: try:
req = self.socks_request(c.SOCKS_CMD_CONNECT) req = self.socks_request(c.SOCKS_CMD_CONNECT)
self._proxy_peername, self._proxy_sockname = yield from req self._proxy_peername, self._proxy_sockname = await req
except SocksError as exc: except SocksError as exc:
exc = SocksError('Can not connect to %s:%s. %s' % exc = SocksError('Can not connect to %s:%s. %s' %
(self._dst_host, self._dst_port, exc)) (self._dst_host, self._dst_port, exc))
@@ -134,8 +128,7 @@ class BaseSocksProtocol(asyncio.StreamReaderProtocol):
self._app_protocol.eof_received() self._app_protocol.eof_received()
super().eof_received() super().eof_received()


@asyncio.coroutine async def socks_request(self, cmd):
def socks_request(self, cmd):
raise NotImplementedError raise NotImplementedError


def write_request(self, request): def write_request(self, request):
@@ -150,17 +143,15 @@ class BaseSocksProtocol(asyncio.StreamReaderProtocol):
raise ValueError('Unsupported item') raise ValueError('Unsupported item')
self._stream_writer.write(bdata) self._stream_writer.write(bdata)


@asyncio.coroutine async def read_response(self, n):
def read_response(self, n):
try: try:
return (yield from self._stream_reader.readexactly(n)) return (await self._stream_reader.readexactly(n))
except asyncio.IncompleteReadError as e: except asyncio.IncompleteReadError as e:
raise InvalidServerReply( raise InvalidServerReply(
'Server sent fewer bytes than required (%s)' % str(e)) 'Server sent fewer bytes than required (%s)' % str(e))


@asyncio.coroutine async def _get_dst_addr(self):
def _get_dst_addr(self): infos = await self._loop.getaddrinfo(
infos = yield from self._loop.getaddrinfo(
self._dst_host, self._dst_port, family=socket.AF_UNSPEC, self._dst_host, self._dst_port, family=socket.AF_UNSPEC,
type=socket.SOCK_STREAM, proto=socket.IPPROTO_TCP, type=socket.SOCK_STREAM, proto=socket.IPPROTO_TCP,
flags=socket.AI_ADDRCONFIG) flags=socket.AI_ADDRCONFIG)
@@ -227,8 +218,7 @@ class Socks4Protocol(BaseSocksProtocol):
reader_limit=reader_limit, reader_limit=reader_limit,
negotiate_done_cb=negotiate_done_cb) negotiate_done_cb=negotiate_done_cb)


@asyncio.coroutine async def socks_request(self, cmd):
def socks_request(self, cmd):
# prepare destination addr/port # prepare destination addr/port
host, port = self._dst_host, self._dst_port host, port = self._dst_host, self._dst_port
port_bytes = struct.pack(b'>H', port) port_bytes = struct.pack(b'>H', port)
@@ -242,7 +232,7 @@ class Socks4Protocol(BaseSocksProtocol):
include_hostname = True include_hostname = True
else: else:
# it's not an IP number, so it's probably a DNS name. # it's not an IP number, so it's probably a DNS name.
family, host = yield from self._get_dst_addr() family, host = await self._get_dst_addr()
host_bytes = socket.inet_aton(host) host_bytes = socket.inet_aton(host)


# build and send connect command # build and send connect command
@@ -254,7 +244,7 @@ class Socks4Protocol(BaseSocksProtocol):
self.write_request(req) self.write_request(req)


# read/process result # read/process result
resp = yield from self.read_response(8) resp = await self.read_response(8)


if resp[0] != c.NULL: if resp[0] != c.NULL:
raise InvalidServerReply('SOCKS4 proxy server sent invalid data') raise InvalidServerReply('SOCKS4 proxy server sent invalid data')
@@ -285,17 +275,16 @@ class Socks5Protocol(BaseSocksProtocol):
reader_limit=reader_limit, reader_limit=reader_limit,
negotiate_done_cb=negotiate_done_cb) negotiate_done_cb=negotiate_done_cb)


@asyncio.coroutine async def socks_request(self, cmd):
def socks_request(self, cmd): await self.authenticate()
yield from self.authenticate()


# build and send command # build and send command
dst_addr, resolved = yield from self.build_dst_address( dst_addr, resolved = await self.build_dst_address(
self._dst_host, self._dst_port) self._dst_host, self._dst_port)
self.write_request([c.SOCKS_VER5, cmd, c.RSV] + dst_addr) self.write_request([c.SOCKS_VER5, cmd, c.RSV] + dst_addr)


# read/process command response # read/process command response
resp = yield from self.read_response(3) resp = await self.read_response(3)


if resp[0] != c.SOCKS_VER5: if resp[0] != c.SOCKS_VER5:
raise InvalidServerVersion( raise InvalidServerVersion(
@@ -305,12 +294,11 @@ class Socks5Protocol(BaseSocksProtocol):
error = c.SOCKS5_ERRORS.get(resp[1], 'Unknown error') error = c.SOCKS5_ERRORS.get(resp[1], 'Unknown error')
raise SocksError('[Errno {0:#04x}]: {1}'.format(resp[1], error)) raise SocksError('[Errno {0:#04x}]: {1}'.format(resp[1], error))


binded = yield from self.read_address() binded = await self.read_address()


return resolved, binded return resolved, binded


@asyncio.coroutine async def authenticate(self):
def authenticate(self):
# send available auth methods # send available auth methods
if self._auth.login and self._auth.password: if self._auth.login and self._auth.password:
req = [c.SOCKS_VER5, 0x02, req = [c.SOCKS_VER5, 0x02,
@@ -321,7 +309,7 @@ class Socks5Protocol(BaseSocksProtocol):
self.write_request(req) self.write_request(req)


# read/process response and send auth data if necessary # read/process response and send auth data if necessary
chosen_auth = yield from self.read_response(2) chosen_auth = await self.read_response(2)


if chosen_auth[0] != c.SOCKS_VER5: if chosen_auth[0] != c.SOCKS_VER5:
raise InvalidServerVersion( raise InvalidServerVersion(
@@ -333,7 +321,7 @@ class Socks5Protocol(BaseSocksProtocol):
chr(len(self._auth.password)).encode(), self._auth.password] chr(len(self._auth.password)).encode(), self._auth.password]
self.write_request(req) self.write_request(req)


auth_status = yield from self.read_response(2) auth_status = await self.read_response(2)
if auth_status[0] != 0x01: if auth_status[0] != 0x01:
raise InvalidServerReply( raise InvalidServerReply(
'SOCKS5 proxy server sent invalid data' 'SOCKS5 proxy server sent invalid data'
@@ -353,8 +341,7 @@ class Socks5Protocol(BaseSocksProtocol):
'SOCKS5 proxy server sent invalid data' 'SOCKS5 proxy server sent invalid data'
) )


@asyncio.coroutine async def build_dst_address(self, host, port):
def build_dst_address(self, host, port):
family_to_byte = {socket.AF_INET: c.SOCKS5_ATYP_IPv4, family_to_byte = {socket.AF_INET: c.SOCKS5_ATYP_IPv4,
socket.AF_INET6: c.SOCKS5_ATYP_IPv6} socket.AF_INET6: c.SOCKS5_ATYP_IPv6}
port_bytes = struct.pack('>H', port) port_bytes = struct.pack('>H', port)
@@ -375,29 +362,28 @@ class Socks5Protocol(BaseSocksProtocol):
req = [c.SOCKS5_ATYP_DOMAIN, chr(len(host_bytes)).encode(), req = [c.SOCKS5_ATYP_DOMAIN, chr(len(host_bytes)).encode(),
host_bytes, port_bytes] host_bytes, port_bytes]
else: else:
family, host_bytes = yield from self._get_dst_addr() family, host_bytes = await self._get_dst_addr()
host_bytes = socket.inet_pton(family, host_bytes) host_bytes = socket.inet_pton(family, host_bytes)
req = [family_to_byte[family], host_bytes, port_bytes] req = [family_to_byte[family], host_bytes, port_bytes]
host = socket.inet_ntop(family, host_bytes) host = socket.inet_ntop(family, host_bytes)


return req, (host, port) return req, (host, port)


@asyncio.coroutine async def read_address(self):
def read_address(self): atype = await self.read_response(1)
atype = yield from self.read_response(1)


if atype[0] == c.SOCKS5_ATYP_IPv4: if atype[0] == c.SOCKS5_ATYP_IPv4:
addr = socket.inet_ntoa((yield from self.read_response(4))) addr = socket.inet_ntoa((await self.read_response(4)))
elif atype[0] == c.SOCKS5_ATYP_DOMAIN: elif atype[0] == c.SOCKS5_ATYP_DOMAIN:
length = yield from self.read_response(1) length = await self.read_response(1)
addr = yield from self.read_response(ord(length)) addr = await self.read_response(ord(length))
elif atype[0] == c.SOCKS5_ATYP_IPv6: elif atype[0] == c.SOCKS5_ATYP_IPv6:
addr = yield from self.read_response(16) addr = await self.read_response(16)
addr = socket.inet_ntop(socket.AF_INET6, addr) addr = socket.inet_ntop(socket.AF_INET6, addr)
else: else:
raise InvalidServerReply('SOCKS5 proxy server sent invalid data') raise InvalidServerReply('SOCKS5 proxy server sent invalid data')


port = yield from self.read_response(2) port = await self.read_response(2)
port = struct.unpack('>H', port)[0] port = struct.unpack('>H', port)[0]


return addr, port return addr, port

+ 136
- 0
aiosocks/test_utils.py View File

@@ -0,0 +1,136 @@
import asyncio
import struct
import socket
from aiohttp.test_utils import unused_port


class FakeSocksSrv:
def __init__(self, loop, write_buff):
self._loop = loop
self._write_buff = write_buff
self._transports = []
self._srv = None
self.port = unused_port()

async def __aenter__(self):
transports = self._transports
write_buff = self._write_buff

class SocksPrimitiveProtocol(asyncio.Protocol):
_transport = None

def connection_made(self, transport):
self._transport = transport
transports.append(transport)

def data_received(self, data):
self._transport.write(write_buff)

def factory():
return SocksPrimitiveProtocol()

self._srv = await self._loop.create_server(
factory, '127.0.0.1', self.port)

return self

async def __aexit__(self, exc_type, exc_val, exc_tb):
for tr in self._transports:
tr.close()

self._srv.close()
await self._srv.wait_closed()


class FakeSocks4Srv:
def __init__(self, loop):
self._loop = loop
self._transports = []
self._futures = []
self._srv = None
self.port = unused_port()

async def __aenter__(self):
transports = self._transports
futures = self._futures

class Socks4Protocol(asyncio.StreamReaderProtocol):
def __init__(self, _loop):
self._loop = _loop
reader = asyncio.StreamReader(loop=self._loop)
super().__init__(reader, client_connected_cb=self.negotiate,
loop=self._loop)

def connection_made(self, transport):
transports.append(transport)
super().connection_made(transport)

async def negotiate(self, reader, writer):
writer.write(b'\x00\x5a\x04W\x01\x01\x01\x01')

data = await reader.read(9)

dst_port = struct.unpack('>H', data[2:4])[0]
dst_addr = data[4:8]

if data[-1] != 0x00:
while True:
byte = await reader.read(1)
if byte == 0x00:
break

if dst_addr == b'\x00\x00\x00\x01':
dst_addr = bytearray()

while True:
byte = await reader.read(1)
if byte == 0x00:
break
dst_addr.append(byte)
else:
dst_addr = socket.inet_ntoa(dst_addr)

cl_reader, cl_writer = await asyncio.open_connection(
host=dst_addr, port=dst_port, loop=self._loop
)
transports.append(cl_writer)

cl_fut = asyncio.ensure_future(
self.retranslator(reader, cl_writer), loop=self._loop)
dst_fut = asyncio.ensure_future(
self.retranslator(cl_reader, writer), loop=self._loop)

futures.append(cl_fut)
futures.append(dst_fut)

async def retranslator(self, reader, writer):
data = bytearray()
while True:
try:
byte = await reader.read(10)
if not byte:
break
data.append(byte[0])
writer.write(byte)
await writer.drain()
except:
break

def factory():
return Socks4Protocol(_loop=self._loop)

self._srv = await self._loop.create_server(
factory, '127.0.0.1', self.port)

return self

async def __aexit__(self, exc_type, exc_val, exc_tb):
for tr in self._transports:
tr.close()

self._srv.close()
await self._srv.wait_closed()

for f in self._futures:
if not f.cancelled() or not f.done():
f.cancel()

+ 0
- 0
tests/__init__.py View File


+ 1
- 0
tests/conftest.py View File

@@ -0,0 +1 @@
from aiohttp.pytest_plugin import * # noqa

+ 0
- 242
tests/helpers.py View File

@@ -1,242 +0,0 @@
import asyncio
import aiohttp
import contextlib
import gc
import os
import socket
import ssl
import struct
import threading
from unittest import mock
from aiohttp.server import ServerHttpProtocol
try:
from asyncio import ensure_future
except ImportError:
ensure_future = asyncio.async


def fake_coroutine(return_value):
def coro(*args, **kwargs):
if isinstance(return_value, Exception):
raise return_value
return return_value

return mock.Mock(side_effect=asyncio.coroutine(coro))


def find_unused_port():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('127.0.0.1', 0))
port = s.getsockname()[1]
s.close()
return port


@contextlib.contextmanager
def fake_socks_srv(loop, write_buff):
transports = []

class SocksPrimitiveProtocol(asyncio.Protocol):
_transport = None

def connection_made(self, transport):
self._transport = transport
transports.append(transport)

def data_received(self, data):
self._transport.write(write_buff)

port = find_unused_port()

def factory():
return SocksPrimitiveProtocol()

srv = loop.run_until_complete(
loop.create_server(factory, '127.0.0.1', port))

yield port

for tr in transports:
tr.close()

srv.close()
loop.run_until_complete(srv.wait_closed())
gc.collect()


@contextlib.contextmanager
def fake_socks4_srv(loop):
port = find_unused_port()
transports = []
futures = []

class Socks4Protocol(asyncio.StreamReaderProtocol):
def __init__(self, _loop):
self._loop = _loop
reader = asyncio.StreamReader(loop=self._loop)
super().__init__(reader, client_connected_cb=self.negotiate,
loop=self._loop)

def connection_made(self, transport):
transports.append(transport)
super().connection_made(transport)

@asyncio.coroutine
def negotiate(self, reader, writer):
writer.write(b'\x00\x5a\x04W\x01\x01\x01\x01')

data = yield from reader.read(9)

dst_port = struct.unpack('>H', data[2:4])[0]
dst_addr = data[4:8]

if data[-1] != 0x00:
while True:
byte = yield from reader.read(1)
if byte == 0x00:
break

if dst_addr == b'\x00\x00\x00\x01':
dst_addr = bytearray()

while True:
byte = yield from reader.read(1)
if byte == 0x00:
break
dst_addr.append(byte)
else:
dst_addr = socket.inet_ntoa(dst_addr)

cl_reader, cl_writer = yield from asyncio.open_connection(
host=dst_addr, port=dst_port, loop=self._loop
)
transports.append(cl_writer)

cl_fut = ensure_future(
self.retranslator(reader, cl_writer), loop=self._loop)
dst_fut = ensure_future(
self.retranslator(cl_reader, writer), loop=self._loop)
futures.append(cl_fut)
futures.append(dst_fut)

@asyncio.coroutine
def retranslator(self, reader, writer):
data = bytearray()
while True:
try:
byte = yield from reader.read(1)
if not byte:
break
data.append(byte[0])
writer.write(byte)
yield from writer.drain()
except:
break

def run(_fut):
thread_loop = asyncio.new_event_loop()
asyncio.set_event_loop(thread_loop)

srv_coroutine = thread_loop.create_server(
lambda: Socks4Protocol(thread_loop), '127.0.0.1', port)
srv = thread_loop.run_until_complete(srv_coroutine)

waiter = asyncio.Future(loop=thread_loop)
loop.call_soon_threadsafe(
_fut.set_result, (thread_loop, waiter))

try:
thread_loop.run_until_complete(waiter)
finally:
# close opened transports
for tr in transports:
tr.close()
for ft in futures:
if not ft.done():
ft.set_result(1)

srv.close()
thread_loop.stop()
thread_loop.close()
gc.collect()

fut = asyncio.Future(loop=loop)
srv_thread = threading.Thread(target=run, args=(fut,))
srv_thread.start()

_thread_loop, _waiter = loop.run_until_complete(fut)

yield port
_thread_loop.call_soon_threadsafe(_waiter.set_result, None)
srv_thread.join()


@contextlib.contextmanager
def http_srv(loop, *, listen_addr=('127.0.0.1', 0), use_ssl=False):
transports = []

class TestHttpServer(ServerHttpProtocol):

def connection_made(self, transport):
transports.append(transport)
super().connection_made(transport)

@asyncio.coroutine
def handle_request(self, message, payload):
response = aiohttp.Response(self.writer, 200, message.version)

text = b'Test message'
response.add_header('Content-type', 'text/plain')
response.add_header('Content-length', str(len(text)))
response.send_headers()
response.write(text)
response.write_eof()

if use_ssl:
here = os.path.join(os.path.dirname(__file__), '..', 'tests')
keyfile = os.path.join(here, 'sample.key')
certfile = os.path.join(here, 'sample.crt')
sslcontext = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
sslcontext.load_cert_chain(certfile, keyfile)
else:
sslcontext = None

def run(_fut):
thread_loop = asyncio.new_event_loop()
asyncio.set_event_loop(thread_loop)

host, port = listen_addr

srv_coroutine = thread_loop.create_server(
lambda: TestHttpServer(), host, port, ssl=sslcontext)
srv = thread_loop.run_until_complete(srv_coroutine)

waiter = asyncio.Future(loop=thread_loop)
loop.call_soon_threadsafe(
_fut.set_result, (thread_loop, waiter,
srv.sockets[0].getsockname()))

try:
thread_loop.run_until_complete(waiter)
finally:
# close opened transports
for tr in transports:
tr.close()

srv.close()
thread_loop.stop()
thread_loop.close()
gc.collect()

fut = asyncio.Future(loop=loop)
srv_thread = threading.Thread(target=run, args=(fut,))
srv_thread.start()

_thread_loop, _waiter, _addr = loop.run_until_complete(fut)

url = '{}://{}:{}'.format(
'https' if use_ssl else 'http', *_addr)

yield url
_thread_loop.call_soon_threadsafe(_waiter.set_result, None)
srv_thread.join()

+ 135
- 111
tests/test_connector.py View File

@@ -1,155 +1,179 @@
import unittest
import asyncio
import aiosocks import aiosocks
import aiohttp import aiohttp
import pytest
from yarl import URL
from unittest import mock from unittest import mock
from aiohttp.client_reqrep import ClientRequest from aiohttp.test_utils import make_mocked_coro
from aiosocks.connector import SocksConnector, proxy_connector, HttpProxyAddr from aiohttp import BasicAuth
from .helpers import fake_coroutine from aiosocks.connector import ProxyConnector, ProxyClientRequest
from aiosocks.helpers import Socks4Auth, Socks5Auth




class TestSocksConnector(unittest.TestCase): async def test_connect_proxy_ip():
def setUp(self): tr, proto = mock.Mock(name='transport'), mock.Mock(name='protocol')
self.loop = asyncio.new_event_loop()
asyncio.set_event_loop(None)

def tearDown(self):
self.loop.close()

def test_properties(self):
addr = aiosocks.Socks4Addr('localhost')
auth = aiosocks.Socks4Auth('login')
conn = SocksConnector(addr, auth, loop=self.loop)
self.assertIs(conn.proxy, addr)
self.assertIs(conn.proxy_auth, auth)

@mock.patch('aiosocks.connector.create_connection')
def test_connect_proxy_ip(self, cr_conn_mock):
tr, proto = mock.Mock(name='transport'), mock.Mock(name='protocol')
cr_conn_mock.side_effect = \
fake_coroutine((tr, proto)).side_effect


with mock.patch('aiosocks.connector.create_connection',
make_mocked_coro((tr, proto))):
loop_mock = mock.Mock() loop_mock = mock.Mock()
loop_mock.getaddrinfo = make_mocked_coro(
[[0, 0, 0, 0, ['127.0.0.1', 1080]]])


req = ClientRequest('GET', 'http://python.org', loop=self.loop) req = ProxyClientRequest(
connector = SocksConnector(aiosocks.Socks5Addr('127.0.0.1'), 'GET', URL('http://python.org'), loop=loop_mock,
None, loop=loop_mock) proxy=URL('socks5://proxy.org'))
connector = ProxyConnector(loop=loop_mock)
conn = await connector.connect(req)


loop_mock.getaddrinfo = fake_coroutine([mock.MagicMock()]) assert loop_mock.getaddrinfo.called
assert conn.protocol is proto


conn = self.loop.run_until_complete(connector.connect(req)) conn.close()


self.assertTrue(loop_mock.getaddrinfo.is_called)
self.assertIs(conn._transport, tr)


conn.close() async def test_connect_proxy_domain():
tr, proto = mock.Mock(name='transport'), mock.Mock(name='protocol')


@mock.patch('aiosocks.connector.create_connection') with mock.patch('aiosocks.connector.create_connection',
def test_connect_proxy_domain(self, cr_conn_mock): make_mocked_coro((tr, proto))):
tr, proto = mock.Mock(name='transport'), mock.Mock(name='protocol')
cr_conn_mock.side_effect = \
fake_coroutine((tr, proto)).side_effect
loop_mock = mock.Mock() loop_mock = mock.Mock()


req = ClientRequest('GET', 'http://python.org', loop=self.loop) req = ProxyClientRequest(
connector = SocksConnector(aiosocks.Socks5Addr('proxy.example'), 'GET', URL('http://python.org'), loop=loop_mock,
None, loop=loop_mock) proxy=URL('socks5://proxy.example'))
connector = ProxyConnector(loop=loop_mock)


connector._resolve_host = fake_coroutine([mock.MagicMock()]) connector._resolve_host = make_mocked_coro([mock.MagicMock()])
conn = await connector.connect(req)


conn = self.loop.run_until_complete(connector.connect(req)) assert connector._resolve_host.call_count == 1
assert conn.protocol is proto


self.assertTrue(connector._resolve_host.is_called) conn.close()
self.assertEqual(connector._resolve_host.call_count, 1)
self.assertIs(conn._transport, tr)


conn.close()


@mock.patch('aiosocks.connector.create_connection') async def test_connect_remote_resolve(loop):
def test_connect_remote_resolve(self, cr_conn_mock): tr, proto = mock.Mock(name='transport'), mock.Mock(name='protocol')
tr, proto = mock.Mock(name='transport'), mock.Mock(name='protocol')
cr_conn_mock.side_effect = \
fake_coroutine((tr, proto)).side_effect


req = ClientRequest('GET', 'http://python.org', loop=self.loop) with mock.patch('aiosocks.connector.create_connection',
connector = SocksConnector(aiosocks.Socks5Addr('127.0.0.1'), make_mocked_coro((tr, proto))):
None, loop=self.loop, remote_resolve=True) req = ProxyClientRequest(
'GET', URL('http://python.org'), loop=loop,
proxy=URL('socks5://127.0.0.1'))
connector = ProxyConnector(loop=loop, remote_resolve=True)
connector._resolve_host = make_mocked_coro([mock.MagicMock()])


connector._resolve_host = fake_coroutine([mock.MagicMock()]) conn = await connector.connect(req)


conn = self.loop.run_until_complete(connector.connect(req)) assert connector._resolve_host.call_count == 1
assert conn.protocol is proto


self.assertEqual(connector._resolve_host.call_count, 1) conn.close()


conn.close()


@mock.patch('aiosocks.connector.create_connection') async def test_connect_locale_resolve(loop):
def test_connect_locale_resolve(self, cr_conn_mock): tr, proto = mock.Mock(name='transport'), mock.Mock(name='protocol')
tr, proto = mock.Mock(name='transport'), mock.Mock(name='protocol')
cr_conn_mock.side_effect = \
fake_coroutine((tr, proto)).side_effect


req = ClientRequest('GET', 'http://python.org', loop=self.loop) with mock.patch('aiosocks.connector.create_connection',
connector = SocksConnector(aiosocks.Socks5Addr('proxy.example'), make_mocked_coro((tr, proto))):
None, loop=self.loop, remote_resolve=False) req = ProxyClientRequest(
'GET', URL('http://python.org'), loop=loop,
proxy=URL('socks5://proxy.example'))
connector = ProxyConnector(loop=loop, remote_resolve=False)
connector._resolve_host = make_mocked_coro([mock.MagicMock()])


connector._resolve_host = fake_coroutine([mock.MagicMock()]) conn = await connector.connect(req)


conn = self.loop.run_until_complete(connector.connect(req)) assert connector._resolve_host.call_count == 2
assert conn.protocol is proto


self.assertTrue(connector._resolve_host.is_called) conn.close()
self.assertEqual(connector._resolve_host.call_count, 2)


conn.close()


@mock.patch('aiosocks.connector.create_connection') async def test_proxy_connect_fail(loop):
def test_proxy_connect_fail(self, cr_conn_mock): loop_mock = mock.Mock()
loop_mock = mock.Mock() loop_mock.getaddrinfo = make_mocked_coro(
cr_conn_mock.side_effect = \ [[0, 0, 0, 0, ['127.0.0.1', 1080]]])
fake_coroutine(aiosocks.SocksConnectionError()).side_effect cc_coro = make_mocked_coro(
raise_exception=aiosocks.SocksConnectionError())


req = ClientRequest('GET', 'http://python.org', loop=self.loop) with mock.patch('aiosocks.connector.create_connection', cc_coro):
connector = SocksConnector(aiosocks.Socks5Addr('127.0.0.1'), req = ProxyClientRequest(
None, loop=loop_mock) 'GET', URL('http://python.org'), loop=loop,
proxy=URL('socks5://127.0.0.1'))
connector = ProxyConnector(loop=loop_mock)


loop_mock.getaddrinfo = fake_coroutine([mock.MagicMock()]) with pytest.raises(aiohttp.ClientConnectionError):
await connector.connect(req)


with self.assertRaises(aiohttp.ProxyConnectionError):
self.loop.run_until_complete(connector.connect(req))


@mock.patch('aiosocks.connector.create_connection') async def test_proxy_negotiate_fail(loop):
def test_proxy_negotiate_fail(self, cr_conn_mock): loop_mock = mock.Mock()
loop_mock = mock.Mock() loop_mock.getaddrinfo = make_mocked_coro(
cr_conn_mock.side_effect = \ [[0, 0, 0, 0, ['127.0.0.1', 1080]]])
fake_coroutine(aiosocks.SocksError()).side_effect


req = ClientRequest('GET', 'http://python.org', loop=self.loop) with mock.patch('aiosocks.connector.create_connection',
connector = SocksConnector(aiosocks.Socks5Addr('127.0.0.1'), make_mocked_coro(raise_exception=aiosocks.SocksError())):
None, loop=loop_mock) req = ProxyClientRequest(
'GET', URL('http://python.org'), loop=loop,
proxy=URL('socks5://127.0.0.1'))
connector = ProxyConnector(loop=loop_mock)


loop_mock.getaddrinfo = fake_coroutine([mock.MagicMock()]) with pytest.raises(aiosocks.SocksError):
await connector.connect(req)


with self.assertRaises(aiosocks.SocksError):
self.loop.run_until_complete(connector.connect(req))


def test_proxy_connector(self): async def test_proxy_connect_http(loop):
socks4_addr = aiosocks.Socks4Addr('h') tr, proto = mock.Mock(name='transport'), mock.Mock(name='protocol')
socks5_addr = aiosocks.Socks5Addr('h') loop_mock = mock.Mock()
http_addr = HttpProxyAddr('http://proxy') loop_mock.getaddrinfo = make_mocked_coro([
[0, 0, 0, 0, ['127.0.0.1', 1080]]])
loop_mock.create_connection = make_mocked_coro((tr, proto))


self.assertIsInstance(proxy_connector(socks4_addr, loop=self.loop), req = ProxyClientRequest(
SocksConnector) 'GET', URL('http://python.org'), loop=loop,
self.assertIsInstance(proxy_connector(socks5_addr, loop=self.loop), proxy=URL('http://127.0.0.1'))
SocksConnector) connector = ProxyConnector(loop=loop_mock)
self.assertIsInstance(proxy_connector(http_addr, loop=self.loop),
aiohttp.ProxyConnector)


with self.assertRaises(ValueError): await connector.connect(req)
proxy_connector(None)


def test_http_proxy_addr(self):
addr = HttpProxyAddr('http://proxy')
self.assertEqual(addr.url, 'http://proxy')


with self.assertRaises(ValueError): @pytest.mark.parametrize('proxy', [
HttpProxyAddr(None) (URL('socks4://proxy.org'), Socks4Auth('login')),
(URL('socks5://proxy.org'), Socks5Auth('login', 'password')),
(URL('http://proxy.org'), BasicAuth('login')), (None, BasicAuth('login')),
(URL('socks4://proxy.org'), None), (None, None)])
def test_proxy_client_request_valid(proxy, loop):
proxy, proxy_auth = proxy
p = ProxyClientRequest('GET', URL('http://python.org'),
proxy=proxy, proxy_auth=proxy_auth, loop=loop)
assert p.proxy is proxy
assert p.proxy_auth is proxy_auth


def test_proxy_client_request_invalid(loop):
with pytest.raises(ValueError) as cm:
ProxyClientRequest(
'GET', URL('http://python.org'),
proxy=URL('socks6://proxy.org'), proxy_auth=None, loop=loop)
assert 'Only http, socks4 and socks5 proxies are supported' in str(cm)

with pytest.raises(ValueError) as cm:
ProxyClientRequest(
'GET', URL('http://python.org'), loop=loop,
proxy=URL('http://proxy.org'), proxy_auth=Socks4Auth('l'))
assert 'proxy_auth must be None or BasicAuth() ' \
'tuple for http proxy' in str(cm)

with pytest.raises(ValueError) as cm:
ProxyClientRequest(
'GET', URL('http://python.org'), loop=loop,
proxy=URL('socks4://proxy.org'), proxy_auth=BasicAuth('l'))
assert 'proxy_auth must be None or Socks4Auth() ' \
'tuple for socks4 proxy' in str(cm)

with pytest.raises(ValueError) as cm:
ProxyClientRequest(
'GET', URL('http://python.org'), loop=loop,
proxy=URL('socks5://proxy.org'), proxy_auth=Socks4Auth('l'))
assert 'proxy_auth must be None or Socks5Auth() ' \
'tuple for socks5 proxy' in str(cm)

+ 95
- 132
tests/test_create_connect.py View File

@@ -1,134 +1,97 @@
import unittest import pytest
import aiosocks import aiosocks
import asyncio from aiohttp.test_utils import make_mocked_coro
from unittest import mock from unittest import mock
from .helpers import fake_coroutine async def test_create_connection_init():

addr = aiosocks.Socks5Addr('localhost')
try: auth = aiosocks.Socks5Auth('usr', 'pwd')
from asyncio import ensure_future dst = ('python.org', 80)
except ImportError: # proxy argument
ensure_future = asyncio.async with pytest.raises(AssertionError) as ct:

await aiosocks.create_connection(None, None, auth, dst)

assert 'proxy must be Socks4Addr() or Socks5Addr() tuple' in str(ct)
class TestCreateConnection(unittest.TestCase): with pytest.raises(AssertionError) as ct:
def setUp(self): await aiosocks.create_connection(None, auth, auth, dst)
self.loop = asyncio.new_event_loop() assert 'proxy must be Socks4Addr() or Socks5Addr() tuple' in str(ct)
asyncio.set_event_loop(None) # proxy_auth

with pytest.raises(AssertionError) as ct:
def tearDown(self): await aiosocks.create_connection(None, addr, addr, dst)
self.loop.close() assert 'proxy_auth must be None or Socks4Auth()' in str(ct)

# dst
def test_init(self): with pytest.raises(AssertionError) as ct:
addr = aiosocks.Socks5Addr('localhost') await aiosocks.create_connection(None, addr, auth, None)
auth = aiosocks.Socks5Auth('usr', 'pwd') assert 'invalid dst format, tuple("dst_host", dst_port))' in str(ct)
dst = ('python.org', 80) # addr and auth compatibility

with pytest.raises(ValueError) as ct:
# proxy argument await aiosocks.create_connection(
with self.assertRaises(AssertionError) as ct: None, addr, aiosocks.Socks4Auth(''), dst)
conn = aiosocks.create_connection(None, None, auth, dst) assert 'proxy is Socks5Addr but proxy_auth is not Socks5Auth' in str(ct)
self.loop.run_until_complete(conn) with pytest.raises(ValueError) as ct:
self.assertEqual(str(ct.exception), await aiosocks.create_connection(
'proxy must be Socks4Addr() or Socks5Addr() tuple') None, aiosocks.Socks4Addr(''), auth, dst)

assert 'proxy is Socks4Addr but proxy_auth is not Socks4Auth' in str(ct)
with self.assertRaises(AssertionError) as ct: # test ssl, server_hostname
conn = aiosocks.create_connection(None, auth, auth, dst) with pytest.raises(ValueError) as ct:
self.loop.run_until_complete(conn) await aiosocks.create_connection(
self.assertEqual(str(ct.exception), None, addr, auth, dst, server_hostname='python.org')
'proxy must be Socks4Addr() or Socks5Addr() tuple') assert 'server_hostname is only meaningful with ssl' in str(ct)

async def test_connection_fail():
# proxy_auth addr = aiosocks.Socks5Addr('localhost')
with self.assertRaises(AssertionError) as ct: auth = aiosocks.Socks5Auth('usr', 'pwd')
conn = aiosocks.create_connection(None, addr, addr, dst) dst = ('python.org', 80)
self.loop.run_until_complete(conn) loop_mock = mock.Mock()
self.assertIn('proxy_auth must be None or Socks4Auth()', loop_mock.create_connection = make_mocked_coro(raise_exception=OSError())
str(ct.exception)) with pytest.raises(aiosocks.SocksConnectionError):

await aiosocks.create_connection(
# dst None, addr, auth, dst, loop=loop_mock)
with self.assertRaises(AssertionError) as ct: async def test_negotiate_fail():
conn = aiosocks.create_connection(None, addr, auth, None) addr = aiosocks.Socks5Addr('localhost')
self.loop.run_until_complete(conn) auth = aiosocks.Socks5Auth('usr', 'pwd')
self.assertIn('invalid dst format, tuple("dst_host", dst_port))', dst = ('python.org', 80)
str(ct.exception)) loop_mock = mock.Mock()

loop_mock.create_connection = make_mocked_coro((mock.Mock(), mock.Mock()))
# addr and auth compatibility with mock.patch('aiosocks.asyncio.Future') as future_mock:
with self.assertRaises(ValueError) as ct: future_mock.side_effect = make_mocked_coro(
conn = aiosocks.create_connection( raise_exception=aiosocks.SocksError())
None, addr, aiosocks.Socks4Auth(''), dst with pytest.raises(aiosocks.SocksError):
) await aiosocks.create_connection(
self.loop.run_until_complete(conn) None, addr, auth, dst, loop=loop_mock)
self.assertIn('proxy is Socks5Addr but proxy_auth is not Socks5Auth', async def test_open_connection():
str(ct.exception)) addr = aiosocks.Socks5Addr('localhost')

auth = aiosocks.Socks5Auth('usr', 'pwd')
with self.assertRaises(ValueError) as ct: dst = ('python.org', 80)
conn = aiosocks.create_connection( transp, proto = mock.Mock(), mock.Mock()
None, aiosocks.Socks4Addr(''), auth, dst reader, writer = mock.Mock(), mock.Mock()
) proto.app_protocol.reader, proto.app_protocol.writer = reader, writer
self.loop.run_until_complete(conn) loop_mock = mock.Mock()
self.assertIn('proxy is Socks4Addr but proxy_auth is not Socks4Auth', loop_mock.create_connection = make_mocked_coro((transp, proto))
str(ct.exception)) with mock.patch('aiosocks.asyncio.Future') as future_mock:

future_mock.side_effect = make_mocked_coro(True)
# test ssl, server_hostname r, w = await aiosocks.open_connection(addr, auth, dst, loop=loop_mock)
with self.assertRaises(ValueError) as ct: assert reader is r
conn = aiosocks.create_connection( assert writer is w
None, addr, auth, dst, server_hostname='python.org'
)
self.loop.run_until_complete(conn)
self.assertIn('server_hostname is only meaningful with ssl',
str(ct.exception))

def test_connection_fail(self):
addr = aiosocks.Socks5Addr('localhost')
auth = aiosocks.Socks5Auth('usr', 'pwd')
dst = ('python.org', 80)

loop_mock = mock.Mock()
loop_mock.create_connection = fake_coroutine(OSError())

with self.assertRaises(aiosocks.SocksConnectionError):
conn = aiosocks.create_connection(
None, addr, auth, dst, loop=loop_mock
)
self.loop.run_until_complete(conn)

@mock.patch('aiosocks.asyncio.Future')
def test_negotiate_fail(self, future_mock):
addr = aiosocks.Socks5Addr('localhost')
auth = aiosocks.Socks5Auth('usr', 'pwd')
dst = ('python.org', 80)

loop_mock = mock.Mock()
loop_mock.create_connection = fake_coroutine(
(mock.Mock(), mock.Mock())
)

fut = fake_coroutine(aiosocks.SocksError())
future_mock.side_effect = fut.side_effect

with self.assertRaises(aiosocks.SocksError):
conn = aiosocks.create_connection(
None, addr, auth, dst, loop=loop_mock
)
self.loop.run_until_complete(conn)

@mock.patch('aiosocks.asyncio.Future')
def test_open_connection(self, future_mock):
addr = aiosocks.Socks5Addr('localhost')
auth = aiosocks.Socks5Auth('usr', 'pwd')
dst = ('python.org', 80)

transp, proto = mock.Mock(), mock.Mock()
reader, writer = mock.Mock(), mock.Mock()

proto.app_protocol.reader, proto.app_protocol.writer = reader, writer

loop_mock = mock.Mock()
loop_mock.create_connection = fake_coroutine((transp, proto))

fut = fake_coroutine(True)
future_mock.side_effect = fut.side_effect

conn = aiosocks.open_connection(addr, auth, dst, loop=loop_mock)
r, w = self.loop.run_until_complete(conn)

self.assertIs(reader, r)
self.assertIs(writer, w)

+ 271
- 346
tests/test_functional.py View File

@@ -1,368 +1,293 @@
import pytest
import aiosocks import aiosocks
import asyncio
import aiohttp import aiohttp
import unittest import os
from aiosocks.connector import SocksConnector import ssl

from aiohttp import web
try: from aiohttp.test_utils import RawTestServer
from asyncio import ensure_future from aiosocks.test_utils import FakeSocksSrv, FakeSocks4Srv
except ImportError: from aiosocks.connector import ProxyConnector, ProxyClientRequest
ensure_future = asyncio.async

from .helpers import fake_socks_srv, fake_socks4_srv, http_srv


class TestCreateSocks4Connection(unittest.TestCase):
def setUp(self):
self.loop = asyncio.new_event_loop()
asyncio.set_event_loop(None)

def tearDown(self):
self.loop.close()

def test_connect_success(self):
with fake_socks_srv(self.loop,
b'\x00\x5a\x04W\x01\x01\x01\x01test') as port:
addr = aiosocks.Socks4Addr('127.0.0.1', port)
auth = aiosocks.Socks4Auth('usr')
dst = ('python.org', 80)

coro = aiosocks.create_connection(
None, addr, auth, dst, loop=self.loop)
transport, protocol = self.loop.run_until_complete(coro)

self.assertEqual(protocol.proxy_sockname, ('1.1.1.1', 1111))

data = self.loop.run_until_complete(
protocol._stream_reader.read(4))
self.assertEqual(data, b'test')

transport.close()

def test_invalid_data(self):
with fake_socks_srv(self.loop,
b'\x01\x5a\x04W\x01\x01\x01\x01') as port:
addr = aiosocks.Socks4Addr('127.0.0.1', port)
auth = aiosocks.Socks4Auth('usr')
dst = ('python.org', 80)

with self.assertRaises(aiosocks.SocksError) as ct:
coro = aiosocks.create_connection(
None, addr, auth, dst, loop=self.loop)
self.loop.run_until_complete(coro)
self.assertIn('invalid data', str(ct.exception))

def test_socks_srv_error(self):
with fake_socks_srv(self.loop,
b'\x00\x5b\x04W\x01\x01\x01\x01') as port:
addr = aiosocks.Socks4Addr('127.0.0.1', port)
auth = aiosocks.Socks4Auth('usr')
dst = ('python.org', 80)

with self.assertRaises(aiosocks.SocksError) as ct:
coro = aiosocks.create_connection(
None, addr, auth, dst, loop=self.loop)
self.loop.run_until_complete(coro)
self.assertIn('0x5b', str(ct.exception))


class TestCreateSocks5Connect(unittest.TestCase):
def setUp(self):
self.loop = asyncio.new_event_loop()
asyncio.set_event_loop(None)

def tearDown(self):
self.loop.close()

def test_connect_success_anonymous(self):
with fake_socks_srv(
self.loop,
b'\x05\x00\x05\x00\x00\x01\x01\x01\x01\x01\x04Wtest') as port:
addr = aiosocks.Socks5Addr('127.0.0.1', port)
auth = aiosocks.Socks5Auth('usr', 'pwd')
dst = ('python.org', 80)

coro = aiosocks.create_connection(
None, addr, auth, dst, loop=self.loop)
transport, protocol = self.loop.run_until_complete(coro)

self.assertEqual(protocol.proxy_sockname, ('1.1.1.1', 1111))

data = self.loop.run_until_complete(
protocol._stream_reader.read(4))
self.assertEqual(data, b'test')

transport.close()

def test_connect_success_usr_pwd(self):
with fake_socks_srv(
self.loop,
b'\x05\x02\x01\x00\x05\x00\x00\x01\x01\x01\x01\x01\x04Wtest'
) as port:
addr = aiosocks.Socks5Addr('127.0.0.1', port)
auth = aiosocks.Socks5Auth('usr', 'pwd')
dst = ('python.org', 80)

coro = aiosocks.create_connection(
None, addr, auth, dst, loop=self.loop)
transport, protocol = self.loop.run_until_complete(coro)

self.assertEqual(protocol.proxy_sockname, ('1.1.1.1', 1111))

data = self.loop.run_until_complete(
protocol._stream_reader.read(4))
self.assertEqual(data, b'test')
transport.close()

def test_auth_ver_err(self):
with fake_socks_srv(self.loop, b'\x04\x02') as port:
addr = aiosocks.Socks5Addr('127.0.0.1', port)
auth = aiosocks.Socks5Auth('usr', 'pwd')
dst = ('python.org', 80)

with self.assertRaises(aiosocks.SocksError) as ct:
coro = aiosocks.create_connection(
None, addr, auth, dst, loop=self.loop)
self.loop.run_until_complete(coro)
self.assertIn('invalid version', str(ct.exception))

def test_auth_method_rejected(self):
with fake_socks_srv(self.loop, b'\x05\xFF') as port:
addr = aiosocks.Socks5Addr('127.0.0.1', port)
auth = aiosocks.Socks5Auth('usr', 'pwd')
dst = ('python.org', 80)

with self.assertRaises(aiosocks.SocksError) as ct:
coro = aiosocks.create_connection(
None, addr, auth, dst, loop=self.loop)
self.loop.run_until_complete(coro)
self.assertIn('authentication methods were rejected',
str(ct.exception))

def test_auth_status_invalid(self):
with fake_socks_srv(self.loop, b'\x05\xF0') as port:
addr = aiosocks.Socks5Addr('127.0.0.1', port)
auth = aiosocks.Socks5Auth('usr', 'pwd')
dst = ('python.org', 80)

with self.assertRaises(aiosocks.SocksError) as ct:
coro = aiosocks.create_connection(
None, addr, auth, dst, loop=self.loop)
self.loop.run_until_complete(coro)
self.assertIn('invalid data', str(ct.exception))

def test_auth_status_invalid2(self):
with fake_socks_srv(self.loop, b'\x05\x02\x02\x00') as port:
addr = aiosocks.Socks5Addr('127.0.0.1', port)
auth = aiosocks.Socks5Auth('usr', 'pwd')
dst = ('python.org', 80)

with self.assertRaises(aiosocks.SocksError) as ct:
coro = aiosocks.create_connection(
None, addr, auth, dst, loop=self.loop)
self.loop.run_until_complete(coro)
self.assertIn('invalid data', str(ct.exception))

def test_auth_failed(self):
with fake_socks_srv(self.loop, b'\x05\x02\x01\x01') as port:
addr = aiosocks.Socks5Addr('127.0.0.1', port)
auth = aiosocks.Socks5Auth('usr', 'pwd')
dst = ('python.org', 80)

with self.assertRaises(aiosocks.SocksError) as ct:
coro = aiosocks.create_connection(
None, addr, auth, dst, loop=self.loop)
self.loop.run_until_complete(coro)
self.assertIn('authentication failed', str(ct.exception))

def test_cmd_ver_err(self):
with fake_socks_srv(self.loop,
b'\x05\x02\x01\x00\x04\x00\x00') as port:
addr = aiosocks.Socks5Addr('127.0.0.1', port)
auth = aiosocks.Socks5Auth('usr', 'pwd')
dst = ('python.org', 80)

with self.assertRaises(aiosocks.SocksError) as ct:
coro = aiosocks.create_connection(
None, addr, auth, dst, loop=self.loop)
self.loop.run_until_complete(coro)
self.assertIn('invalid version', str(ct.exception))

def test_cmd_not_granted(self):
with fake_socks_srv(self.loop,
b'\x05\x02\x01\x00\x05\x01\x00') as port:
addr = aiosocks.Socks5Addr('127.0.0.1', port)
auth = aiosocks.Socks5Auth('usr', 'pwd')
dst = ('python.org', 80)

with self.assertRaises(aiosocks.SocksError) as ct:
coro = aiosocks.create_connection(
None, addr, auth, dst, loop=self.loop)
self.loop.run_until_complete(coro)
self.assertIn('General SOCKS server failure', str(ct.exception))

def test_invalid_address_type(self):
with fake_socks_srv(self.loop,
b'\x05\x02\x01\x00\x05\x00\x00\xFF') as port:
addr = aiosocks.Socks5Addr('127.0.0.1', port)
auth = aiosocks.Socks5Auth('usr', 'pwd')
dst = ('python.org', 80)

with self.assertRaises(aiosocks.SocksError) as ct:
coro = aiosocks.create_connection(
None, addr, auth, dst, loop=self.loop)
self.loop.run_until_complete(coro)
self.assertIn('invalid data', str(ct.exception))

def test_atype_ipv4(self):
with fake_socks_srv(
self.loop,
b'\x05\x02\x01\x00\x05\x00\x00\x01\x01\x01\x01\x01\x04W'
) as port:
addr = aiosocks.Socks5Addr('127.0.0.1', port)
auth = aiosocks.Socks5Auth('usr', 'pwd')
dst = ('python.org', 80)

coro = aiosocks.create_connection(
None, addr, auth, dst, loop=self.loop)
transport, protocol = self.loop.run_until_complete(coro)

self.assertEqual(protocol.proxy_sockname, ('1.1.1.1', 1111))

transport.close()

def test_atype_ipv6(self):
with fake_socks_srv(
self.loop,
b'\x05\x02\x01\x00\x05\x00\x00\x04\x00\x00\x00\x00'
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x11\x04W'
) as port:
addr = aiosocks.Socks5Addr('127.0.0.1', port)
auth = aiosocks.Socks5Auth('usr', 'pwd')
dst = ('python.org', 80)

coro = aiosocks.create_connection(
None, addr, auth, dst, loop=self.loop)
transport, protocol = self.loop.run_until_complete(coro)

self.assertEqual(protocol.proxy_sockname, ('::111', 1111))

transport.close()

def test_atype_domain(self):
with fake_socks_srv(
self.loop,
b'\x05\x02\x01\x00\x05\x00\x00\x03\x0apython.org\x04W'
) as port:
addr = aiosocks.Socks5Addr('127.0.0.1', port)
auth = aiosocks.Socks5Auth('usr', 'pwd')
dst = ('python.org', 80)

coro = aiosocks.create_connection(
None, addr, auth, dst, loop=self.loop)
transport, protocol = self.loop.run_until_complete(coro)

self.assertEqual(protocol.proxy_sockname, (b'python.org', 1111))

transport.close()


class TestSocksConnector(unittest.TestCase):
def setUp(self):
self.loop = asyncio.new_event_loop()
asyncio.set_event_loop(None)

def tearDown(self):
self.loop.close()

def test_http_connect(self):
with fake_socks4_srv(self.loop) as proxy_port:
addr = aiosocks.Socks4Addr('127.0.0.1', proxy_port)

conn = SocksConnector(proxy=addr, proxy_auth=None, loop=self.loop,
remote_resolve=False)

with http_srv(self.loop) as url:
with aiohttp.ClientSession(connector=conn,
loop=self.loop) as ses:
@asyncio.coroutine
def make_req():
return (yield from ses.request('get', url=url))

resp = self.loop.run_until_complete(make_req())

self.assertEqual(resp.status, 200)


content = self.loop.run_until_complete(resp.text())
self.assertEqual(content, 'Test message')


resp.close() async def test_socks4_connect_success(loop):
pld = b'\x00\x5a\x04W\x01\x01\x01\x01test'


def test_https_connect(self): async with FakeSocksSrv(loop, pld) as srv:
with fake_socks4_srv(self.loop) as proxy_port: addr = aiosocks.Socks4Addr('127.0.0.1', srv.port)
addr = aiosocks.Socks4Addr('127.0.0.1', proxy_port) auth = aiosocks.Socks4Auth('usr')
dst = ('python.org', 80)


conn = SocksConnector(proxy=addr, proxy_auth=None, loop=self.loop, transport, protocol = await aiosocks.create_connection(
remote_resolve=False, verify_ssl=False) None, addr, auth, dst, loop=loop)


with http_srv(self.loop, use_ssl=True) as url: assert protocol.proxy_sockname == ('1.1.1.1', 1111)
with aiohttp.ClientSession(connector=conn,
loop=self.loop) as ses:
@asyncio.coroutine
def make_req():
return (yield from ses.request('get', url=url))


resp = self.loop.run_until_complete(make_req()) data = await protocol._stream_reader.read(4)
assert data == b'test'


self.assertEqual(resp.status, 200) transport.close()


content = self.loop.run_until_complete(resp.text())
self.assertEqual(content, 'Test message')


resp.close() async def test_socks4_invalid_data(loop):
pld = b'\x01\x5a\x04W\x01\x01\x01\x01'


def test_fingerprint_success(self): async with FakeSocksSrv(loop, pld) as srv:
with fake_socks4_srv(self.loop) as proxy_port: addr = aiosocks.Socks4Addr('127.0.0.1', srv.port)
addr = aiosocks.Socks4Addr('127.0.0.1', proxy_port) auth = aiosocks.Socks4Auth('usr')
fp = (b's\x93\xfd:\xed\x08\x1do\xa9\xaeq9' dst = ('python.org', 80)
b'\x1a\xe3\xc5\x7f\x89\xe7l\xf9')


conn = SocksConnector(proxy=addr, proxy_auth=None, loop=self.loop, with pytest.raises(aiosocks.SocksError) as ct:
remote_resolve=False, verify_ssl=False, await aiosocks.create_connection(
fingerprint=fp) None, addr, auth, dst, loop=loop)
assert 'invalid data' in str(ct)


with http_srv(self.loop, use_ssl=True) as url:
with aiohttp.ClientSession(connector=conn,
loop=self.loop) as ses:
@asyncio.coroutine
def make_req():
return (yield from ses.request('get', url=url))


resp = self.loop.run_until_complete(make_req()) async def test_socks4_srv_error(loop):
pld = b'\x00\x5b\x04W\x01\x01\x01\x01'


self.assertEqual(resp.status, 200) async with FakeSocksSrv(loop, pld) as srv:
addr = aiosocks.Socks4Addr('127.0.0.1', srv.port)
auth = aiosocks.Socks4Auth('usr')
dst = ('python.org', 80)


content = self.loop.run_until_complete(resp.text()) with pytest.raises(aiosocks.SocksError) as ct:
self.assertEqual(content, 'Test message') await aiosocks.create_connection(
None, addr, auth, dst, loop=loop)
assert '0x5b' in str(ct)


resp.close()


def test_fingerprint_fail(self): async def test_socks5_connect_success_anonymous(loop):
with fake_socks4_srv(self.loop) as proxy_port: pld = b'\x05\x00\x05\x00\x00\x01\x01\x01\x01\x01\x04Wtest'
addr = aiosocks.Socks4Addr('127.0.0.1', proxy_port)
fp = (b's\x93\xfd:\xed\x08\x1do\xa9\xaeq9'
b'\x1a\xe3\xc5\x7f\x89\xe7l\x10')


conn = SocksConnector(proxy=addr, proxy_auth=None, loop=self.loop, async with FakeSocksSrv(loop, pld) as srv:
remote_resolve=False, verify_ssl=False, addr = aiosocks.Socks5Addr('127.0.0.1', srv.port)
fingerprint=fp) auth = aiosocks.Socks5Auth('usr', 'pwd')
dst = ('python.org', 80)


with http_srv(self.loop, use_ssl=True) as url: transport, protocol = await aiosocks.create_connection(
with aiohttp.ClientSession(connector=conn, None, addr, auth, dst, loop=loop)
loop=self.loop) as ses:
@asyncio.coroutine
def make_req():
return (yield from ses.request('get', url=url))


with self.assertRaises(aiohttp.FingerprintMismatch): assert protocol.proxy_sockname == ('1.1.1.1', 1111)
self.loop.run_until_complete(make_req()) data = await protocol._stream_reader.read(4)
assert data == b'test'

transport.close()


async def test_socks5_connect_success_usr_pwd(loop):
pld = b'\x05\x02\x01\x00\x05\x00\x00\x01\x01\x01\x01\x01\x04Wtest'

async with FakeSocksSrv(loop, pld) as srv:
addr = aiosocks.Socks5Addr('127.0.0.1', srv.port)
auth = aiosocks.Socks5Auth('usr', 'pwd')
dst = ('python.org', 80)

transport, protocol = await aiosocks.create_connection(
None, addr, auth, dst, loop=loop)
assert protocol.proxy_sockname == ('1.1.1.1', 1111)

data = await protocol._stream_reader.read(4)
assert data == b'test'
transport.close()


async def test_socks5_auth_ver_err(loop):
async with FakeSocksSrv(loop, b'\x04\x02') as srv:
addr = aiosocks.Socks5Addr('127.0.0.1', srv.port)
auth = aiosocks.Socks5Auth('usr', 'pwd')
dst = ('python.org', 80)

with pytest.raises(aiosocks.SocksError) as ct:
await aiosocks.create_connection(
None, addr, auth, dst, loop=loop)
assert 'invalid version' in str(ct)


async def test_socks5_auth_method_rejected(loop):
async with FakeSocksSrv(loop, b'\x05\xFF') as srv:
addr = aiosocks.Socks5Addr('127.0.0.1', srv.port)
auth = aiosocks.Socks5Auth('usr', 'pwd')
dst = ('python.org', 80)

with pytest.raises(aiosocks.SocksError) as ct:
await aiosocks.create_connection(
None, addr, auth, dst, loop=loop)
assert 'authentication methods were rejected' in str(ct)


async def test_socks5_auth_status_invalid(loop):
async with FakeSocksSrv(loop, b'\x05\xF0') as srv:
addr = aiosocks.Socks5Addr('127.0.0.1', srv.port)
auth = aiosocks.Socks5Auth('usr', 'pwd')
dst = ('python.org', 80)

with pytest.raises(aiosocks.SocksError) as ct:
await aiosocks.create_connection(
None, addr, auth, dst, loop=loop)
assert 'invalid data' in str(ct)


async def test_socks5_auth_status_invalid2(loop):
async with FakeSocksSrv(loop, b'\x05\x02\x02\x00') as srv:
addr = aiosocks.Socks5Addr('127.0.0.1', srv.port)
auth = aiosocks.Socks5Auth('usr', 'pwd')
dst = ('python.org', 80)

with pytest.raises(aiosocks.SocksError) as ct:
await aiosocks.create_connection(
None, addr, auth, dst, loop=loop)
assert 'invalid data' in str(ct)


async def test_socks5_auth_failed(loop):
async with FakeSocksSrv(loop, b'\x05\x02\x01\x01') as srv:
addr = aiosocks.Socks5Addr('127.0.0.1', srv.port)
auth = aiosocks.Socks5Auth('usr', 'pwd')
dst = ('python.org', 80)

with pytest.raises(aiosocks.SocksError) as ct:
await aiosocks.create_connection(
None, addr, auth, dst, loop=loop)
assert 'authentication failed' in str(ct)


async def test_socks5_cmd_ver_err(loop):
async with FakeSocksSrv(loop, b'\x05\x02\x01\x00\x04\x00\x00') as srv:
addr = aiosocks.Socks5Addr('127.0.0.1', srv.port)
auth = aiosocks.Socks5Auth('usr', 'pwd')
dst = ('python.org', 80)

with pytest.raises(aiosocks.SocksError) as ct:
await aiosocks.create_connection(
None, addr, auth, dst, loop=loop)
assert 'invalid version' in str(ct)


async def test_socks5_cmd_not_granted(loop):
async with FakeSocksSrv(loop, b'\x05\x02\x01\x00\x05\x01\x00') as srv:
addr = aiosocks.Socks5Addr('127.0.0.1', srv.port)
auth = aiosocks.Socks5Auth('usr', 'pwd')
dst = ('python.org', 80)

with pytest.raises(aiosocks.SocksError) as ct:
await aiosocks.create_connection(
None, addr, auth, dst, loop=loop)
assert 'General SOCKS server failure' in str(ct)


async def test_socks5_invalid_address_type(loop):
async with FakeSocksSrv(loop, b'\x05\x02\x01\x00\x05\x00\x00\xFF') as srv:
addr = aiosocks.Socks5Addr('127.0.0.1', srv.port)
auth = aiosocks.Socks5Auth('usr', 'pwd')
dst = ('python.org', 80)

with pytest.raises(aiosocks.SocksError) as ct:
await aiosocks.create_connection(
None, addr, auth, dst, loop=loop)
assert 'invalid data' in str(ct)


async def test_socks5_atype_ipv4(loop):
pld = b'\x05\x02\x01\x00\x05\x00\x00\x01\x01\x01\x01\x01\x04W'

async with FakeSocksSrv(loop, pld) as srv:
addr = aiosocks.Socks5Addr('127.0.0.1', srv.port)
auth = aiosocks.Socks5Auth('usr', 'pwd')
dst = ('python.org', 80)

transport, protocol = await aiosocks.create_connection(
None, addr, auth, dst, loop=loop)
assert protocol.proxy_sockname == ('1.1.1.1', 1111)

transport.close()


async def test_socks5_atype_ipv6(loop):
pld = b'\x05\x02\x01\x00\x05\x00\x00\x04\x00\x00\x00\x00' \
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x11\x04W'

async with FakeSocksSrv(loop, pld) as srv:
addr = aiosocks.Socks5Addr('127.0.0.1', srv.port)
auth = aiosocks.Socks5Auth('usr', 'pwd')
dst = ('python.org', 80)

transport, protocol = await aiosocks.create_connection(
None, addr, auth, dst, loop=loop)
assert protocol.proxy_sockname == ('::111', 1111)

transport.close()


async def test_socks5_atype_domain(loop):
pld = b'\x05\x02\x01\x00\x05\x00\x00\x03\x0apython.org\x04W'

async with FakeSocksSrv(loop, pld) as srv:
addr = aiosocks.Socks5Addr('127.0.0.1', srv.port)
auth = aiosocks.Socks5Auth('usr', 'pwd')
dst = ('python.org', 80)

transport, protocol = await aiosocks.create_connection(
None, addr, auth, dst, loop=loop)
assert protocol.proxy_sockname == (b'python.org', 1111)

transport.close()


async def test_http_connect(loop):
async def handler(request):
return web.Response(text='Test message')

async with RawTestServer(handler, host='127.0.0.1', loop=loop) as ws:
async with FakeSocks4Srv(loop) as srv:
conn = ProxyConnector(loop=loop, remote_resolve=False)

async with aiohttp.ClientSession(
connector=conn, loop=loop,
request_class=ProxyClientRequest) as ses:
proxy = 'socks4://127.0.0.1:{}'.format(srv.port)

async with ses.get(ws.make_url('/'), proxy=proxy) as resp:
assert resp.status == 200
assert (await resp.text()) == 'Test message'


async def test_https_connect(loop):
async def handler(request):
return web.Response(text='Test message')

here = os.path.join(os.path.dirname(__file__), '..', 'tests')
keyfile = os.path.join(here, 'sample.key')
certfile = os.path.join(here, 'sample.crt')
sslcontext = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
sslcontext.load_cert_chain(certfile, keyfile)

ws = RawTestServer(handler, scheme='https', host='127.0.0.1', loop=loop)
await ws.start_server(loop=loop, ssl=sslcontext)

v_fp = b's\x93\xfd:\xed\x08\x1do\xa9\xaeq9\x1a\xe3\xc5\x7f\x89\xe7l\xf9'
inv_fp = b's\x93\xfd:\xed\x08\x1do\xa9\xaeq9\x1a\xe3\xc5\x7f\x89\xe7l\x10'

async with FakeSocks4Srv(loop) as srv:
v_conn = ProxyConnector(loop=loop, remote_resolve=False,
verify_ssl=False, fingerprint=v_fp)
inv_conn = ProxyConnector(loop=loop, remote_resolve=False,
verify_ssl=False, fingerprint=inv_fp)

async with aiohttp.ClientSession(
connector=v_conn, loop=loop,
request_class=ProxyClientRequest) as ses:
proxy = 'socks4://127.0.0.1:{}'.format(srv.port)

async with ses.get(ws.make_url('/'), proxy=proxy) as resp:
assert resp.status == 200
assert (await resp.text()) == 'Test message'

async with aiohttp.ClientSession(
connector=inv_conn, loop=loop,
request_class=ProxyClientRequest) as ses:
proxy = 'socks4://127.0.0.1:{}'.format(srv.port)

with pytest.raises(aiohttp.ServerFingerprintMismatch):
async with ses.get(ws.make_url('/'), proxy=proxy) as resp:
assert resp.status == 200

+ 513
- 536
tests/test_protocols.py
File diff suppressed because it is too large
View File


||||||
x
 
000:0
Loading…
Cancel
Save