@@ -11,6 +11,12 @@ SOCKS proxy client for asyncio and aiohttp | |||
.. image:: https://badge.fury.io/py/aiosocks.svg | |||
:target: https://badge.fury.io/py/aiosocks | |||
Dependencies | |||
------------ | |||
python 3.5+ | |||
aiohttp 2.0+ | |||
Features | |||
-------- | |||
- SOCKS4, SOCKS4a and SOCKS5 version | |||
@@ -136,56 +142,47 @@ aiohttp usage | |||
import asyncio | |||
import aiohttp | |||
import aiosocks | |||
from aiosocks.connector import ( | |||
SocksConnector, HttpProxyAddr, HttpProxyAuth | |||
) | |||
from yarl import URL | |||
from aiosocks.connector import ProxyConnecotr, ProxyClientRequest | |||
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 | |||
conn = SocksConnector(proxy=addr, proxy_auth=auth, remote_resolve=True) | |||
conn = ProxyConnecotr(remote_resolve=True) | |||
# or locale resolve | |||
conn = SocksConnector(proxy=addr, proxy_auth=auth, remote_resolve=False) | |||
conn = SocksConnector(remote_resolve=False) | |||
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: | |||
print(await resp.text()) | |||
except aiohttp.ProxyConnectionError: | |||
# connection problem | |||
except aiosocks.SocksError: | |||
# communication problem | |||
if __name__ == '__main__': | |||
loop = asyncio.get_event_loop() | |||
loop.run_until_complete(load_github_main()) | |||
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 |
@@ -17,11 +17,10 @@ __all__ = ('Socks4Protocol', 'Socks5Protocol', 'Socks4Auth', | |||
'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), ( | |||
'proxy must be Socks4Addr() or Socks5Addr() tuple' | |||
) | |||
@@ -70,7 +69,7 @@ def create_connection(protocol_factory, proxy, proxy_auth, dst, *, | |||
reader_limit=reader_limit) | |||
try: | |||
transport, protocol = yield from loop.create_connection( | |||
transport, protocol = await loop.create_connection( | |||
socks_factory, proxy.host, proxy.port, family=family, | |||
proto=proto, flags=flags, sock=sock, local_addr=local_addr) | |||
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 | |||
try: | |||
yield from waiter | |||
await waiter | |||
except: | |||
transport.close() | |||
raise | |||
@@ -87,10 +86,9 @@ def create_connection(protocol_factory, proxy, proxy_auth, dst, *, | |||
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, | |||
remote_resolve=remote_resolve, loop=loop, **kwds) | |||
@@ -1,105 +1,99 @@ | |||
try: | |||
import aiohttp | |||
from aiohttp.errors import ProxyConnectionError | |||
from aiohttp.helpers import BasicAuth as HttpProxyAuth | |||
from aiohttp.connector import sentinel | |||
except ImportError: | |||
raise ImportError('aiosocks.SocksConnector require aiohttp library') | |||
import asyncio | |||
from collections import namedtuple | |||
from .errors import SocksError, SocksConnectionError | |||
from .helpers import SocksAddr | |||
from .helpers import Socks4Auth, Socks5Auth, Socks4Addr, Socks5Addr | |||
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 | |||
@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: | |||
sslcontext = self.ssl_context | |||
else: | |||
sslcontext = None | |||
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'] | |||
else: | |||
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 | |||
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, | |||
ssl=sslcontext, family=hinfo['family'], | |||
proto=hinfo['proto'], flags=hinfo['flags'], | |||
local_addr=self._local_addr, | |||
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 | |||
except (OSError, SocksError, SocksConnectionError) as e: | |||
exc = e | |||
else: | |||
if isinstance(exc, SocksConnectionError): | |||
raise ProxyConnectionError(*exc.args) | |||
raise aiohttp.ClientProxyConnectionError(*exc.args) | |||
if isinstance(exc, SocksError): | |||
raise exc | |||
else: | |||
@@ -107,12 +101,23 @@ class SocksConnector(aiohttp.TCPConnector): | |||
exc.errno, 'Can not connect to %s:%s [%s]' % | |||
(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) |
@@ -10,11 +10,6 @@ from .errors import ( | |||
InvalidServerReply, InvalidServerVersion | |||
) | |||
try: | |||
from asyncio import ensure_future | |||
except ImportError: | |||
ensure_future = asyncio.async | |||
DEFAULT_LIMIT = getattr(asyncio.streams, '_DEFAULT_LIMIT', 2**16) | |||
@@ -54,11 +49,10 @@ class BaseSocksProtocol(asyncio.StreamReaderProtocol): | |||
super().__init__(stream_reader=reader, | |||
client_connected_cb=self.negotiate, loop=self._loop) | |||
@asyncio.coroutine | |||
def negotiate(self, reader, writer): | |||
async def negotiate(self, reader, writer): | |||
try: | |||
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: | |||
exc = SocksError('Can not connect to %s:%s. %s' % | |||
(self._dst_host, self._dst_port, exc)) | |||
@@ -134,8 +128,7 @@ class BaseSocksProtocol(asyncio.StreamReaderProtocol): | |||
self._app_protocol.eof_received() | |||
super().eof_received() | |||
@asyncio.coroutine | |||
def socks_request(self, cmd): | |||
async def socks_request(self, cmd): | |||
raise NotImplementedError | |||
def write_request(self, request): | |||
@@ -150,17 +143,15 @@ class BaseSocksProtocol(asyncio.StreamReaderProtocol): | |||
raise ValueError('Unsupported item') | |||
self._stream_writer.write(bdata) | |||
@asyncio.coroutine | |||
def read_response(self, n): | |||
async def read_response(self, n): | |||
try: | |||
return (yield from self._stream_reader.readexactly(n)) | |||
return (await self._stream_reader.readexactly(n)) | |||
except asyncio.IncompleteReadError as e: | |||
raise InvalidServerReply( | |||
'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, | |||
type=socket.SOCK_STREAM, proto=socket.IPPROTO_TCP, | |||
flags=socket.AI_ADDRCONFIG) | |||
@@ -227,8 +218,7 @@ class Socks4Protocol(BaseSocksProtocol): | |||
reader_limit=reader_limit, | |||
negotiate_done_cb=negotiate_done_cb) | |||
@asyncio.coroutine | |||
def socks_request(self, cmd): | |||
async def socks_request(self, cmd): | |||
# prepare destination addr/port | |||
host, port = self._dst_host, self._dst_port | |||
port_bytes = struct.pack(b'>H', port) | |||
@@ -242,7 +232,7 @@ class Socks4Protocol(BaseSocksProtocol): | |||
include_hostname = True | |||
else: | |||
# 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) | |||
# build and send connect command | |||
@@ -254,7 +244,7 @@ class Socks4Protocol(BaseSocksProtocol): | |||
self.write_request(req) | |||
# read/process result | |||
resp = yield from self.read_response(8) | |||
resp = await self.read_response(8) | |||
if resp[0] != c.NULL: | |||
raise InvalidServerReply('SOCKS4 proxy server sent invalid data') | |||
@@ -285,17 +275,16 @@ class Socks5Protocol(BaseSocksProtocol): | |||
reader_limit=reader_limit, | |||
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 | |||
dst_addr, resolved = yield from self.build_dst_address( | |||
dst_addr, resolved = await self.build_dst_address( | |||
self._dst_host, self._dst_port) | |||
self.write_request([c.SOCKS_VER5, cmd, c.RSV] + dst_addr) | |||
# read/process command response | |||
resp = yield from self.read_response(3) | |||
resp = await self.read_response(3) | |||
if resp[0] != c.SOCKS_VER5: | |||
raise InvalidServerVersion( | |||
@@ -305,12 +294,11 @@ class Socks5Protocol(BaseSocksProtocol): | |||
error = c.SOCKS5_ERRORS.get(resp[1], 'Unknown 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 | |||
@asyncio.coroutine | |||
def authenticate(self): | |||
async def authenticate(self): | |||
# send available auth methods | |||
if self._auth.login and self._auth.password: | |||
req = [c.SOCKS_VER5, 0x02, | |||
@@ -321,7 +309,7 @@ class Socks5Protocol(BaseSocksProtocol): | |||
self.write_request(req) | |||
# 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: | |||
raise InvalidServerVersion( | |||
@@ -333,7 +321,7 @@ class Socks5Protocol(BaseSocksProtocol): | |||
chr(len(self._auth.password)).encode(), self._auth.password] | |||
self.write_request(req) | |||
auth_status = yield from self.read_response(2) | |||
auth_status = await self.read_response(2) | |||
if auth_status[0] != 0x01: | |||
raise InvalidServerReply( | |||
'SOCKS5 proxy server sent invalid data' | |||
@@ -353,8 +341,7 @@ class Socks5Protocol(BaseSocksProtocol): | |||
'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, | |||
socket.AF_INET6: c.SOCKS5_ATYP_IPv6} | |||
port_bytes = struct.pack('>H', port) | |||
@@ -375,29 +362,28 @@ class Socks5Protocol(BaseSocksProtocol): | |||
req = [c.SOCKS5_ATYP_DOMAIN, chr(len(host_bytes)).encode(), | |||
host_bytes, port_bytes] | |||
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) | |||
req = [family_to_byte[family], host_bytes, port_bytes] | |||
host = socket.inet_ntop(family, host_bytes) | |||
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: | |||
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: | |||
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: | |||
addr = yield from self.read_response(16) | |||
addr = await self.read_response(16) | |||
addr = socket.inet_ntop(socket.AF_INET6, addr) | |||
else: | |||
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] | |||
return addr, port |
@@ -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 +1 @@ | |||
from aiohttp.pytest_plugin import * # noqa |
@@ -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() |
@@ -1,155 +1,179 @@ | |||
import unittest | |||
import asyncio | |||
import aiosocks | |||
import aiohttp | |||
import pytest | |||
from yarl import URL | |||
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.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() | |||
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) |
@@ -1,134 +1,97 @@ | |||
import unittest | |||
import pytest | |||
import aiosocks | |||
import asyncio | |||
from aiohttp.test_utils import make_mocked_coro | |||
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 |
@@ -1,368 +1,293 @@ | |||
import pytest | |||
import aiosocks | |||
import asyncio | |||
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 |