Browse Source

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

main
nibrag 7 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 (
SocksConnector, HttpProxyAddr, HttpProxyAuth
)
from yarl import URL
from aiosocks.connector import ProxyConnecotr, ProxyClientRequest




async def load_github_main(): async def load_github_main():
addr = aiosocks.Socks5Addr('127.0.0.1', 1080)
auth = aiosocks.Socks5Auth('proxyuser1', password='pwd')
auth5 = 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:
async with session.get('http://github.com/') as resp:
with aiohttp.ClientSession(connector=conn, request_class=ProxyClientRequest) as session:
# 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
def create_connection(protocol_factory, proxy, proxy_auth, dst, *,
remote_resolve=True, loop=None, ssl=None, family=0,
proto=0, flags=0, sock=None, local_addr=None,
server_hostname=None, reader_limit=DEFAULT_LIMIT):
async def create_connection(protocol_factory, proxy, proxy_auth, dst, *,
remote_resolve=True, loop=None, ssl=None, family=0,
proto=0, flags=0, sock=None, local_addr=None,
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
def open_connection(proxy, proxy_auth, dst, *, remote_resolve=True,
loop=None, limit=DEFAULT_LIMIT, **kwds):
_, protocol = yield from create_connection(
async def open_connection(proxy, proxy_auth, dst, *, remote_resolve=True,
loop=None, limit=DEFAULT_LIMIT, **kwds):
_, protocol = await 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.helpers import BasicAuth as HttpProxyAuth
from aiohttp.connector import sentinel
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
def proxy(self):
"""Proxy info.
Should be Socks4Server/Socks5Server instance.
"""
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
)
async def _create_proxy_connection(self, req):
if req.proxy.scheme == 'http':
return await super()._create_proxy_connection(req)
else:
return await self._create_socks_connection(req)


@asyncio.coroutine
def _create_connection(self, req):
async def _create_socks_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,
self._proxy.port)
proxy_hosts = await self._resolve_host(req.proxy.host, req.proxy.port)
exc = None exc = None


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


transp, proto = yield from create_connection(
self._factory, proxy, self._proxy_auth, dst,
try:
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 proxy_connector(proxy, proxy_auth=None, **kwargs):
if isinstance(proxy, HttpProxyAddr):
return aiohttp.ProxyConnector(
proxy.url, proxy_auth=proxy_auth, **kwargs)
elif isinstance(proxy, SocksAddr):
return SocksConnector(proxy, proxy_auth, **kwargs)
else:
raise ValueError('Unsupported `proxy` format')
def _validate_ssl_fingerprint(self, transp, host, port):
has_cert = transp.get_extra_info('sslcontext')
if has_cert and self._fingerprint:
sock = transp.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 = 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
def negotiate(self, reader, writer):
async 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
def socks_request(self, cmd):
async 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
def read_response(self, n):
async 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
def _get_dst_addr(self):
infos = yield from self._loop.getaddrinfo(
async def _get_dst_addr(self):
infos = await 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
def socks_request(self, cmd):
async 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
def socks_request(self, cmd):
yield from self.authenticate()
async def socks_request(self, cmd):
await 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
def authenticate(self):
async 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
def build_dst_address(self, host, port):
async 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
def read_address(self):
atype = yield from self.read_response(1)
async def read_address(self):
atype = await 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)
addr = yield from self.read_response(ord(length))
length = await self.read_response(1)
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 aiosocks.connector import SocksConnector, proxy_connector, HttpProxyAddr
from .helpers import fake_coroutine
from aiohttp.test_utils import make_mocked_coro
from aiohttp import BasicAuth
from aiosocks.connector import ProxyConnector, ProxyClientRequest
from aiosocks.helpers import Socks4Auth, Socks5Auth




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_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
async def test_connect_proxy_ip():
tr, proto = mock.Mock(name='transport'), mock.Mock(name='protocol')


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)
connector = SocksConnector(aiosocks.Socks5Addr('127.0.0.1'),
None, loop=loop_mock)
req = ProxyClientRequest(
'GET', URL('http://python.org'), 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')
def test_connect_proxy_domain(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()


req = ClientRequest('GET', 'http://python.org', loop=self.loop)
connector = SocksConnector(aiosocks.Socks5Addr('proxy.example'),
None, loop=loop_mock)
req = ProxyClientRequest(
'GET', URL('http://python.org'), 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)
self.assertEqual(connector._resolve_host.call_count, 1)
self.assertIs(conn._transport, tr)
conn.close()


conn.close()


@mock.patch('aiosocks.connector.create_connection')
def test_connect_remote_resolve(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
async def test_connect_remote_resolve(loop):
tr, proto = mock.Mock(name='transport'), mock.Mock(name='protocol')


req = ClientRequest('GET', 'http://python.org', loop=self.loop)
connector = SocksConnector(aiosocks.Socks5Addr('127.0.0.1'),
None, loop=self.loop, remote_resolve=True)
with mock.patch('aiosocks.connector.create_connection',
make_mocked_coro((tr, proto))):
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')
def test_connect_locale_resolve(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
async def test_connect_locale_resolve(loop):
tr, proto = mock.Mock(name='transport'), mock.Mock(name='protocol')


req = ClientRequest('GET', 'http://python.org', loop=self.loop)
connector = SocksConnector(aiosocks.Socks5Addr('proxy.example'),
None, loop=self.loop, remote_resolve=False)
with mock.patch('aiosocks.connector.create_connection',
make_mocked_coro((tr, proto))):
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)
self.assertEqual(connector._resolve_host.call_count, 2)
conn.close()


conn.close()


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


req = ClientRequest('GET', 'http://python.org', loop=self.loop)
connector = SocksConnector(aiosocks.Socks5Addr('127.0.0.1'),
None, loop=loop_mock)
with mock.patch('aiosocks.connector.create_connection', cc_coro):
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(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')
def test_proxy_negotiate_fail(self, cr_conn_mock):
loop_mock = mock.Mock()
cr_conn_mock.side_effect = \
fake_coroutine(aiosocks.SocksError()).side_effect
async def test_proxy_negotiate_fail(loop):
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)
connector = SocksConnector(aiosocks.Socks5Addr('127.0.0.1'),
None, loop=loop_mock)
with mock.patch('aiosocks.connector.create_connection',
make_mocked_coro(raise_exception=aiosocks.SocksError())):
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):
socks4_addr = aiosocks.Socks4Addr('h')
socks5_addr = aiosocks.Socks5Addr('h')
http_addr = HttpProxyAddr('http://proxy')
async def test_proxy_connect_http(loop):
tr, proto = mock.Mock(name='transport'), mock.Mock(name='protocol')
loop_mock = mock.Mock()
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),
SocksConnector)
self.assertIsInstance(proxy_connector(socks5_addr, loop=self.loop),
SocksConnector)
self.assertIsInstance(proxy_connector(http_addr, loop=self.loop),
aiohttp.ProxyConnector)
req = ProxyClientRequest(
'GET', URL('http://python.org'), loop=loop,
proxy=URL('http://127.0.0.1'))
connector = ProxyConnector(loop=loop_mock)


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


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


with self.assertRaises(ValueError):
HttpProxyAddr(None)
@pytest.mark.parametrize('proxy', [
(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

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


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

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

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

# proxy argument
with self.assertRaises(AssertionError) as ct:
conn = aiosocks.create_connection(None, None, auth, dst)
self.loop.run_until_complete(conn)
self.assertEqual(str(ct.exception),
'proxy must be Socks4Addr() or Socks5Addr() tuple')

with self.assertRaises(AssertionError) as ct:
conn = aiosocks.create_connection(None, auth, auth, dst)
self.loop.run_until_complete(conn)
self.assertEqual(str(ct.exception),
'proxy must be Socks4Addr() or Socks5Addr() tuple')

# proxy_auth
with self.assertRaises(AssertionError) as ct:
conn = aiosocks.create_connection(None, addr, addr, dst)
self.loop.run_until_complete(conn)
self.assertIn('proxy_auth must be None or Socks4Auth()',
str(ct.exception))

# dst
with self.assertRaises(AssertionError) as ct:
conn = aiosocks.create_connection(None, addr, auth, None)
self.loop.run_until_complete(conn)
self.assertIn('invalid dst format, tuple("dst_host", dst_port))',
str(ct.exception))

# addr and auth compatibility
with self.assertRaises(ValueError) as ct:
conn = aiosocks.create_connection(
None, addr, aiosocks.Socks4Auth(''), dst
)
self.loop.run_until_complete(conn)
self.assertIn('proxy is Socks5Addr but proxy_auth is not Socks5Auth',
str(ct.exception))

with self.assertRaises(ValueError) as ct:
conn = aiosocks.create_connection(
None, aiosocks.Socks4Addr(''), auth, dst
)
self.loop.run_until_complete(conn)
self.assertIn('proxy is Socks4Addr but proxy_auth is not Socks4Auth',
str(ct.exception))

# test ssl, server_hostname
with self.assertRaises(ValueError) as ct:
conn = aiosocks.create_connection(
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)


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

# proxy argument
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)

with pytest.raises(AssertionError) as ct:
await aiosocks.create_connection(None, auth, auth, dst)
assert 'proxy must be Socks4Addr() or Socks5Addr() tuple' in str(ct)

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

# dst
with pytest.raises(AssertionError) as ct:
await aiosocks.create_connection(None, addr, auth, None)
assert 'invalid dst format, tuple("dst_host", dst_port))' in str(ct)

# addr and auth compatibility
with pytest.raises(ValueError) as ct:
await aiosocks.create_connection(
None, addr, aiosocks.Socks4Auth(''), dst)
assert 'proxy is Socks5Addr but proxy_auth is not Socks5Auth' in str(ct)

with pytest.raises(ValueError) as ct:
await aiosocks.create_connection(
None, aiosocks.Socks4Addr(''), auth, dst)
assert 'proxy is Socks4Addr but proxy_auth is not Socks4Auth' in str(ct)

# test ssl, server_hostname
with pytest.raises(ValueError) as ct:
await aiosocks.create_connection(
None, addr, auth, dst, server_hostname='python.org')
assert 'server_hostname is only meaningful with ssl' in str(ct)


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

loop_mock = mock.Mock()
loop_mock.create_connection = make_mocked_coro(raise_exception=OSError())

with pytest.raises(aiosocks.SocksConnectionError):
await aiosocks.create_connection(
None, addr, auth, dst, loop=loop_mock)


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

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

with mock.patch('aiosocks.asyncio.Future') as future_mock:
future_mock.side_effect = make_mocked_coro(
raise_exception=aiosocks.SocksError())

with pytest.raises(aiosocks.SocksError):
await aiosocks.create_connection(
None, addr, auth, dst, loop=loop_mock)


async def test_open_connection():
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 = make_mocked_coro((transp, proto))

with mock.patch('aiosocks.asyncio.Future') as future_mock:
future_mock.side_effect = make_mocked_coro(True)
r, w = await aiosocks.open_connection(addr, auth, dst, loop=loop_mock)

assert reader is r
assert writer is 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
from aiosocks.connector import SocksConnector

try:
from asyncio import ensure_future
except ImportError:
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)
import os
import ssl
from aiohttp import web
from aiohttp.test_utils import RawTestServer
from aiosocks.test_utils import FakeSocksSrv, FakeSocks4Srv
from aiosocks.connector import ProxyConnector, ProxyClientRequest


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):
with fake_socks4_srv(self.loop) as proxy_port:
addr = aiosocks.Socks4Addr('127.0.0.1', proxy_port)
async with FakeSocksSrv(loop, pld) as srv:
addr = aiosocks.Socks4Addr('127.0.0.1', srv.port)
auth = aiosocks.Socks4Auth('usr')
dst = ('python.org', 80)


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


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))
assert protocol.proxy_sockname == ('1.1.1.1', 1111)


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):
with fake_socks4_srv(self.loop) as proxy_port:
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\xf9')
async with FakeSocksSrv(loop, pld) as srv:
addr = aiosocks.Socks4Addr('127.0.0.1', srv.port)
auth = aiosocks.Socks4Auth('usr')
dst = ('python.org', 80)


conn = SocksConnector(proxy=addr, proxy_auth=None, loop=self.loop,
remote_resolve=False, verify_ssl=False,
fingerprint=fp)
with pytest.raises(aiosocks.SocksError) as ct:
await aiosocks.create_connection(
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())
self.assertEqual(content, 'Test message')
with pytest.raises(aiosocks.SocksError) as ct:
await aiosocks.create_connection(
None, addr, auth, dst, loop=loop)
assert '0x5b' in str(ct)


resp.close()


def test_fingerprint_fail(self):
with fake_socks4_srv(self.loop) as proxy_port:
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')
async def test_socks5_connect_success_anonymous(loop):
pld = b'\x05\x00\x05\x00\x00\x01\x01\x01\x01\x01\x04Wtest'


conn = SocksConnector(proxy=addr, proxy_auth=None, loop=self.loop,
remote_resolve=False, verify_ssl=False,
fingerprint=fp)
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)


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))
transport, protocol = await aiosocks.create_connection(
None, addr, auth, dst, loop=loop)


with self.assertRaises(aiohttp.FingerprintMismatch):
self.loop.run_until_complete(make_req())
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_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


Loading…
Cancel
Save