├── socks5 ├── tests │ ├── __init__.py │ ├── rfc1929 │ │ ├── __init__.py │ │ ├── test_writer.py │ │ ├── test_reader.py │ │ ├── test_events.py │ │ └── test_connection.py │ ├── test_writer.py │ ├── test_events.py │ ├── test_reader.py │ └── test_connection.py ├── auth │ ├── __init__.py │ └── rfc1929 │ │ ├── __init__.py │ │ ├── _data_structure.py │ │ ├── _writer.py │ │ ├── _reader.py │ │ ├── events.py │ │ └── connection.py ├── exception.py ├── __init__.py ├── define.py ├── _writer.py ├── _data_structure.py ├── _reader.py ├── connection.py └── events.py ├── .gitignore ├── setup.cfg ├── .flake8 ├── requirements.txt ├── .coveragerc ├── .travis.yml ├── Makefile ├── LICENSE ├── examples ├── simple_server.py ├── client.py └── curio_server.py ├── setup.py └── README.md /socks5/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /socks5/tests/rfc1929/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .coverage 3 | htmlcov 4 | .*.swp 5 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md 3 | 4 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | ignore = E501 3 | exclude = **/__init__.py 4 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | ipaddress==1.0.16 2 | transitions==0.4.1 3 | construct==2.8.8 4 | -------------------------------------------------------------------------------- /socks5/auth/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function, unicode_literals 2 | -------------------------------------------------------------------------------- /socks5/exception.py: -------------------------------------------------------------------------------- 1 | class ParserError(Exception): 2 | pass 3 | 4 | 5 | class ProtocolError(Exception): 6 | pass 7 | -------------------------------------------------------------------------------- /socks5/auth/rfc1929/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function, unicode_literals 2 | 3 | from .events import * 4 | from .connection import Connection 5 | -------------------------------------------------------------------------------- /socks5/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function, unicode_literals 2 | 3 | from socks5.define import * 4 | from socks5.events import * 5 | from socks5.exception import * 6 | from socks5.connection import Connection 7 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [report] 2 | omit = socks5/tests/* 3 | exclude_lines = 4 | pragma: no cover 5 | def __repr__ 6 | def __str__ 7 | def __ne__ 8 | raise AssertionError 9 | raise NotImplementedError 10 | if __name__ == .__main__.: 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - 2.7 4 | - 3.3 5 | - 3.4 6 | - 3.5 7 | - pypy 8 | install: 9 | - pip install setuptools 10 | - pip install -r requirements.txt 11 | - pip install coverage coveralls 12 | script: 13 | - python setup.py test 14 | - coverage run --source=socks5 -m unittest discover 15 | after_success: 16 | coveralls 17 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | python -B -m unittest discover 3 | 4 | lint: 5 | @flake8 && echo "Static Check Without Error" 6 | 7 | coverage: 8 | @coverage run --source=socks5 -m unittest discover 9 | 10 | build-pkg: 11 | @python setup.py sdist 12 | 13 | test-deploy: 14 | @python setup.py sdist register -r testpypi 15 | @python setup.py sdist upload -r testpypi 16 | 17 | deploy: 18 | @python setup.py sdist register -r pypi 19 | @python setup.py sdist upload -r pypi 20 | 21 | clean: 22 | rm -rf dist 23 | rm -rf socks5.egg-info 24 | -------------------------------------------------------------------------------- /socks5/auth/rfc1929/_data_structure.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function, unicode_literals 2 | 3 | from construct import this, If, Switch, OneOf 4 | from construct import Struct 5 | from construct import Byte, Int8ub 6 | from construct import PascalString 7 | 8 | AuthRequest = Struct( 9 | "version" / OneOf(Int8ub, [1]), 10 | "username" / PascalString(Byte), 11 | "password" / PascalString(Byte) 12 | ) 13 | 14 | AuthResponse = Struct( 15 | "version" / OneOf(Int8ub, [1]), 16 | "status" / Byte 17 | ) 18 | -------------------------------------------------------------------------------- /socks5/auth/rfc1929/_writer.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function, unicode_literals 2 | 3 | from ._data_structure import AuthRequest, AuthResponse 4 | 5 | 6 | def write_auth_request(event): 7 | event_dict = event.__dict__ 8 | 9 | event_dict["version"] = 1 10 | event_dict["username"] = event.username.encode("ascii") 11 | event_dict["password"] = event.password.encode("ascii") 12 | return AuthRequest.build(event_dict) 13 | 14 | 15 | def write_auth_response(event): 16 | event_dict = event.__dict__ 17 | 18 | event_dict["version"] = 1 19 | return AuthResponse.build(event_dict) 20 | -------------------------------------------------------------------------------- /socks5/tests/rfc1929/test_writer.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function, unicode_literals 2 | import unittest 3 | import struct 4 | 5 | 6 | from socks5.define import RESP_STATUS 7 | 8 | from socks5.auth.rfc1929.events import AuthRequest, AuthResponse 9 | from socks5.auth.rfc1929._writer import write_auth_request, write_auth_response 10 | 11 | 12 | class TestWriter(unittest.TestCase): 13 | def test_auth_request(self): 14 | event = AuthRequest("user", "password") 15 | data = write_auth_request(event) 16 | expected_data = struct.pack("!BB4sB8s", 0x1, 0x4, b"user", 0x8, b"password") 17 | self.assertEqual(data, expected_data) 18 | 19 | def test_auth_response(self): 20 | event = AuthResponse(RESP_STATUS["SUCCESS"]) 21 | data = write_auth_response(event) 22 | expected_data = struct.pack("!BB", 0x1, 0x0) 23 | self.assertEqual(data, expected_data) 24 | -------------------------------------------------------------------------------- /socks5/define.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function, unicode_literals 2 | 3 | AUTH_TYPE = { 4 | "NO_AUTH": 0x0, 5 | "GSSAPI": 0x1, 6 | "USERNAME_PASSWD": 0x2, 7 | "NO_SUPPORT_AUTH_METHOD": 0xFF 8 | } 9 | 10 | REQ_COMMAND = { 11 | "CONNECT": 0x1, 12 | "BIND": 0x02, 13 | "UDP_ASSOCIATE": 0x03 14 | } 15 | 16 | RESP_STATUS = { 17 | # socksv5 response status 18 | "SUCCESS": 0x0, 19 | "GENRAL_FAILURE": 0x01, 20 | "CONNECTION_NOT_ALLOWED": 0x02, 21 | "NETWORK_UNREACHABLE": 0x03, 22 | "HOST_UNREACHABLE": 0x04, 23 | "CONNECTION_REFUSED": 0x05, 24 | "TTL_EXPIRED": 0x06, 25 | "COMMAND_NOT_SUPPORTED": 0x07, 26 | "ADDRESS_TYPE_NOT_SUPPORTED": 0x08, 27 | 28 | # socksv4/socksv4a response status 29 | "REQUEST_GRANTED": 0x5a, 30 | "REQUEST_REJECTED": 0x5b, 31 | "REQUEST_FAIELD_NO_IDENTD": 0x5c, 32 | "REQUEST_FAIELD_IDENTD_AUTH_FAIL": 0x5d 33 | 34 | } 35 | 36 | ADDR_TYPE = { 37 | "IPV4": 0x01, 38 | "DOMAINNAME": 0x03, 39 | "IPV6": 0x04 40 | } 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | 4 | Copyright (c) 2016, Rueimin Jiang. All rights reserved. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | -------------------------------------------------------------------------------- /examples/simple_server.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function, unicode_literals 2 | 3 | from socks5 import GreetingResponse, Response 4 | from socks5 import AUTH_TYPE, RESP_STATUS 5 | from socks5 import Connection 6 | import socket 7 | 8 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 9 | sock.bind(("127.0.0.1", 5580)) 10 | 11 | sock.listen(5) 12 | 13 | while True: 14 | clientsock, address = sock.accept() 15 | socks_conn = Connection(our_role="server") 16 | socks_conn.initiate_connection() 17 | 18 | while True: 19 | data = clientsock.recv(1024) 20 | _event = socks_conn.recv(data) 21 | if _event != "NeedMoreData": 22 | break 23 | 24 | print(_event) 25 | 26 | _event = GreetingResponse(AUTH_TYPE["NO_AUTH"]) 27 | 28 | print(_event) 29 | _data = socks_conn.send(_event) 30 | clientsock.send(_data) 31 | 32 | while True: 33 | data = clientsock.recv(1024) 34 | _event = socks_conn.recv(data) 35 | if _event != "NeedMoreData": 36 | break 37 | 38 | print(_event) 39 | _event = Response(RESP_STATUS["SUCCESS"], _event.atyp, _event.addr, _event.port) 40 | print(_event) 41 | _data = socks_conn.send(_event) 42 | clientsock.send(_data) 43 | -------------------------------------------------------------------------------- /socks5/auth/rfc1929/_reader.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function, unicode_literals 2 | 3 | import sys 4 | import construct 5 | 6 | from socks5.exception import ParserError 7 | from socks5.events import NeedMoreData 8 | 9 | from . import _data_structure as data_structure 10 | from .events import AuthRequest, AuthResponse 11 | 12 | if sys.version_info.major <= 2: 13 | string_func = unicode 14 | else: 15 | string_func = str 16 | 17 | 18 | def read_auth_request(data): 19 | try: 20 | parsed_data = dict(data_structure.AuthRequest.parse(data)) 21 | except (construct.FieldError, construct.RangeError): 22 | return NeedMoreData() 23 | except construct.ValidationError: 24 | raise ParserError("read_auth_request: Incorrect version.") 25 | 26 | parsed_data.pop("version") 27 | parsed_data["username"] = string_func(parsed_data["username"], encoding="ascii") 28 | parsed_data["password"] = string_func(parsed_data["password"], encoding="ascii") 29 | 30 | return AuthRequest(**parsed_data) 31 | 32 | 33 | def read_auth_response(data): 34 | try: 35 | parsed_data = dict(data_structure.AuthResponse.parse(data)) 36 | except (construct.FieldError, construct.RangeError): 37 | return NeedMoreData() 38 | except construct.ValidationError: 39 | raise ParserError("read_auth_response: Incorrect version.") 40 | 41 | parsed_data.pop("version") 42 | return AuthResponse(**parsed_data) 43 | -------------------------------------------------------------------------------- /socks5/tests/rfc1929/test_reader.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function, unicode_literals 2 | 3 | import unittest 4 | import struct 5 | 6 | from socks5.exception import ParserError 7 | from socks5.events import NeedMoreData 8 | 9 | from socks5.auth.rfc1929._reader import read_auth_request, read_auth_response 10 | 11 | 12 | class TestReader(unittest.TestCase): 13 | def test_auth_request(self): 14 | auth_request = read_auth_request( 15 | struct.pack("!BB4sB8s", 0x1, 0x4, b"user", 0x8, b"password")) 16 | self.assertEqual(auth_request.username, "user") 17 | self.assertEqual(auth_request.password, "password") 18 | 19 | def test_auth_request_not_enough_data(self): 20 | event = read_auth_request( 21 | struct.pack("!B", 0x1)) 22 | self.assertIsInstance(event, NeedMoreData) 23 | 24 | def test_auth_request_failed_incorrect_version(self): 25 | with self.assertRaises(ParserError): 26 | read_auth_request( 27 | struct.pack("!B", 0x4)) 28 | 29 | def test_auth_response(self): 30 | auth_response = read_auth_response( 31 | struct.pack("!BB", 0x1, 0x0)) 32 | self.assertEqual(auth_response.status, 0) 33 | 34 | def test_auth_response_not_enough_data(self): 35 | event = read_auth_response( 36 | struct.pack("!B", 0x1)) 37 | self.assertIsInstance(event, NeedMoreData) 38 | 39 | def test_auth_response_failed_incorrect_version(self): 40 | with self.assertRaises(ParserError): 41 | read_auth_response( 42 | struct.pack("!B", 0x4)) 43 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | setup( 4 | name="socks5", 5 | version="0.2.1+dev", 6 | description="SOCKSv5 bring your own io library", 7 | long_description=""" 8 | Socks5 bring your own io library, inspired by h11 and hyper-h2. 9 | 10 | Source code: https://github.com/mike820324/socks5 11 | 12 | Documentation: https://github.com/mike820324/socks5/blob/master/README.md 13 | """, 14 | url="https://github.com/mike820324/socks5", 15 | author="MicroMike", 16 | author_email="mike820324@gmail.com", 17 | license="MIT", 18 | classifiers=[ 19 | "License :: OSI Approved :: MIT License", 20 | "Development Status :: 3 - Alpha", 21 | "Intended Audience :: Developers", 22 | "Operating System :: POSIX", 23 | "Operating System :: MacOS", 24 | "Programming Language :: Python :: Implementation :: CPython", 25 | "Programming Language :: Python :: Implementation :: PyPy", 26 | "Programming Language :: Python", 27 | "Programming Language :: Python :: 2", 28 | "Programming Language :: Python :: 2.7", 29 | "Programming Language :: Python :: 3", 30 | "Programming Language :: Python :: 3.3", 31 | "Programming Language :: Python :: 3.4", 32 | "Programming Language :: Python :: 3.5", 33 | "Topic :: Internet", 34 | "Topic :: System :: Networking", 35 | ], 36 | keywords=["socks", "socks5", "protocol"], 37 | packages=find_packages(include=[ 38 | "socks5", "socks5.*", 39 | ]), 40 | include_package_data=True, 41 | install_requires=[ 42 | "ipaddress==1.0.16", 43 | "transitions==0.4.1", 44 | "construct==2.8.8" 45 | ] 46 | ) 47 | -------------------------------------------------------------------------------- /socks5/tests/rfc1929/test_events.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function, unicode_literals 2 | 3 | import unittest 4 | 5 | from socks5.define import RESP_STATUS 6 | from socks5.auth.rfc1929.events import NeedMoreData, AuthRequest, AuthResponse 7 | 8 | 9 | class TestEvents(unittest.TestCase): 10 | def test_need_more_data(self): 11 | event = NeedMoreData() 12 | self.assertEqual(event, "NeedMoreData") 13 | 14 | def test_rfc1929_auth_request(self): 15 | event = AuthRequest("user", "password") 16 | self.assertEqual(event, "AuthRequest") 17 | self.assertEqual(event.username, "user") 18 | self.assertEqual(event.password, "password") 19 | 20 | def test_rfc1929_auth_request_username_too_long(self): 21 | with self.assertRaises(ValueError): 22 | AuthRequest("a" * 256, "password") 23 | 24 | def test_rfc1929_auth_request_password_too_long(self): 25 | with self.assertRaises(ValueError): 26 | AuthRequest("user", "a" * 256) 27 | 28 | def test_rfc1929_auth_request_incorrect_username_type(self): 29 | with self.assertRaises(ValueError): 30 | AuthRequest(b"user", "a") 31 | 32 | def test_rfc1929_auth_request_incorrect_password_type(self): 33 | with self.assertRaises(ValueError): 34 | AuthRequest("user", b"a") 35 | 36 | def test_rfc1929_auth_response(self): 37 | event = AuthResponse(RESP_STATUS["SUCCESS"]) 38 | self.assertEqual(event, "AuthResponse") 39 | 40 | self.assertEqual(event.status, RESP_STATUS["SUCCESS"]) 41 | 42 | def test_rfc1929_auth_response_failed_unsupported_status(self): 43 | with self.assertRaises(ValueError): 44 | AuthResponse(0xff) 45 | -------------------------------------------------------------------------------- /examples/client.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, unicode_literals, print_function 2 | 3 | import argparse 4 | import socket 5 | 6 | from socks5 import GreetingRequest, Request 7 | from socks5 import AUTH_TYPE, REQ_COMMAND, ADDR_TYPE 8 | from socks5 import Connection 9 | 10 | 11 | def connect(host, port): 12 | print("Starting socks server at {host} {port}".format(**options.__dict__)) 13 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 14 | sock.connect((host, port)) 15 | 16 | return sock 17 | 18 | 19 | def do_socks_protocol(sock): 20 | socks_conn = Connection(our_role="client") 21 | socks_conn.initiate_connection() 22 | 23 | _event = GreetingRequest((AUTH_TYPE["NO_AUTH"], )) 24 | print("sending event: {}".format(_event)) 25 | 26 | _data = socks_conn.send(_event) 27 | sock.send(_data) 28 | 29 | while True: 30 | _data = sock.recv(1024) 31 | _event = socks_conn.recv(_data) 32 | 33 | if _event != "NeedMoreData": 34 | break 35 | 36 | print("receiving event: {}".format(_event)) 37 | 38 | _event = Request(REQ_COMMAND["CONNECT"], ADDR_TYPE["DOMAINNAME"], "google.com", 80) 39 | print("sending event: {}".format(_event)) 40 | _data = socks_conn.send(_event) 41 | sock.send(_data) 42 | 43 | while True: 44 | _data = sock.recv(1024) 45 | _event = socks_conn.recv(_data) 46 | 47 | if _event != "NeedMoreData": 48 | break 49 | 50 | print("receiving event: {}".format(_event)) 51 | 52 | 53 | if __name__ == "__main__": 54 | parser = argparse.ArgumentParser(description="Simple socks5 server") 55 | parser.add_argument("--host", dest="host", help="specify the host", default="127.0.0.1") 56 | parser.add_argument("--port", dest="port", type=int, help="specify the proxy port", default=5580) 57 | options = parser.parse_args() 58 | 59 | sock = connect(**options.__dict__) 60 | do_socks_protocol(sock) 61 | -------------------------------------------------------------------------------- /socks5/_writer.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function, unicode_literals 2 | 3 | import ipaddress 4 | from socks5.define import ADDR_TYPE 5 | from socks5._data_structure import GreetingRequest, GreetingResponse 6 | from socks5._data_structure import Request, Response 7 | 8 | 9 | def write_greeting_request(event): 10 | event_dict = event.__dict__ 11 | 12 | if event == "GreetingRequest": 13 | event_dict["version"] = 5 14 | if event == "Socks4Request": 15 | event_dict["version"] = 4 16 | event_dict["addr"] = int(event.addr) 17 | event_dict["name"] = event.name.encode("ascii") 18 | event_dict["domainname"] = event.domainname.encode("idna") 19 | 20 | return GreetingRequest.build(event_dict) 21 | 22 | 23 | def write_greeting_response(event): 24 | event_dict = event.__dict__ 25 | 26 | if event == "GreetingResponse": 27 | event_dict["version"] = 5 28 | if event == "Socks4Response": 29 | # NOTE: socksv4 will have a null byte in front 30 | event_dict["version"] = 0 31 | event_dict["addr"] = int(event.addr) 32 | 33 | return GreetingResponse.build(event_dict) 34 | 35 | 36 | def write_request(event): 37 | event_dict = event.__dict__ 38 | 39 | event_dict["version"] = 5 40 | if event.atyp == ADDR_TYPE["IPV4"]: 41 | event_dict["addr"] = int(ipaddress.IPv4Address(event.addr)) 42 | 43 | elif event.atyp == ADDR_TYPE["IPV6"]: 44 | event_dict["addr"] = int(ipaddress.IPv6Address(event.addr)) 45 | 46 | else: 47 | event_dict["addr"] = event.addr.encode("idna") 48 | 49 | return Request.build(event_dict) 50 | 51 | 52 | def write_response(event): 53 | event_dict = event.__dict__ 54 | 55 | event_dict["version"] = 5 56 | if event.atyp == ADDR_TYPE["IPV4"]: 57 | event_dict["addr"] = int(ipaddress.IPv4Address(event.addr)) 58 | 59 | elif event.atyp == ADDR_TYPE["IPV6"]: 60 | event_dict["addr"] = int(ipaddress.IPv6Address(event.addr)) 61 | 62 | else: 63 | event_dict["addr"] = event.addr.encode("idna") 64 | 65 | return Response.build(event_dict) 66 | -------------------------------------------------------------------------------- /examples/curio_server.py: -------------------------------------------------------------------------------- 1 | """ 2 | This example use the curio module to implement a simple socks5 server. 3 | 4 | Note: This example can only works in python 3.5 and above due to the async/await syntax. 5 | 6 | Author: Mike Jiang 7 | License: MIT 8 | """ 9 | 10 | import argparse 11 | from curio import run, spawn, tcp_server 12 | from socks5 import Connection, Socks4Response, GreetingResponse, Response 13 | from socks5 import AUTH_TYPE, RESP_STATUS, ADDR_TYPE 14 | 15 | 16 | async def socks5_handler(client, addr): 17 | print("client connect from address: {}".format(addr)) 18 | conn = Connection(our_role="server") 19 | conn.initiate_connection() 20 | 21 | # greeting request 22 | data = await client.recv(1024) 23 | _event = conn.recv(data) 24 | print("receiving event: {}".format(_event)) 25 | 26 | # greeting response 27 | if _event == "GreetingRequest": 28 | event = GreetingResponse(AUTH_TYPE["NO_AUTH"]) 29 | print("sending event: {}".format(event)) 30 | data = conn.send(event) 31 | await client.send(data) 32 | 33 | # socks request 34 | data = await client.recv(1024) 35 | _event = conn.recv(data) 36 | print("receiving event: {}".format(_event)) 37 | 38 | # socks response 39 | event = Response(RESP_STATUS["SUCCESS"], _event.atyp, _event.addr, _event.port) 40 | print("sending event: {}".format(event)) 41 | data = conn.send(event) 42 | await client.send(data) 43 | 44 | elif _event == "Socks4Request": 45 | event = Socks4Response(RESP_STATUS["REQUEST_GRANTED"], _event.addr, _event.port) 46 | print("sending event: {}".format(event)) 47 | data = conn.send(event) 48 | await client.send(data) 49 | 50 | print("socks end") 51 | 52 | if __name__ == "__main__": 53 | parser = argparse.ArgumentParser(description="Simple socks5 server") 54 | parser.add_argument("--host", dest="host", help="specify the host", default="127.0.0.1") 55 | parser.add_argument("--port", dest="port", type=int, help="specify the proxy port", default=5580) 56 | options = parser.parse_args() 57 | 58 | try: 59 | print("Starting socks server at {host} {port}".format(**options.__dict__)) 60 | run(tcp_server(options.host, options.port, socks5_handler), with_monitor=False) 61 | except KeyboardInterrupt: 62 | print("bye") 63 | -------------------------------------------------------------------------------- /socks5/_data_structure.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function, unicode_literals 2 | 3 | from construct import this, If, Switch, OneOf 4 | from construct import Struct, Enum, Array, Embedded 5 | from construct import Byte, BytesInteger, Int8ub, Int16ub, Padding 6 | from construct import PascalString, CString 7 | 8 | ReqCommand = Enum( 9 | Byte, 10 | CONNECT=0x1, 11 | BIND=0x02, 12 | UDP_ASSOCIATE=0x03 13 | ) 14 | 15 | RespStatus = Enum( 16 | Byte, 17 | SUCCESS=0x0, 18 | GENRAL_FAILURE=0x01, 19 | CONNECTION_NOT_ALLOWED=0x02, 20 | NETWORK_UNREACHABLE=0x03, 21 | HOST_UNREACHABLE=0x04, 22 | CONNECTION_REFUSED=0x05, 23 | TTL_EXPIRED=0x06, 24 | COMMAND_NOT_SUPPORTED=0x07, 25 | ADDRESS_TYPE_NOT_SUPPORTED=0x08 26 | ) 27 | 28 | AddrType = Enum( 29 | Byte, 30 | IPV4=0x01, 31 | DOMAINNAME=0x03, 32 | IPV6=0x04 33 | ) 34 | 35 | Requestv4 = Struct( 36 | "cmd" / Byte, 37 | "port" / Int16ub, 38 | "addr" / BytesInteger(4), 39 | "name" / CString(), 40 | "domainname" / If(this.addr == 1, CString()) 41 | ) 42 | 43 | Responsev4 = Struct( 44 | "status" / Byte, 45 | "port" / Int16ub, 46 | "addr" / BytesInteger(4), 47 | ) 48 | 49 | GreetingRequest = Struct( 50 | "version" / OneOf(Int8ub, [4, 5]), 51 | Embedded( 52 | Switch( 53 | this.version, 54 | { 55 | 0x4: Requestv4, 56 | 0x5: Struct( 57 | "nmethod" / Int8ub, 58 | "methods" / Array(this.nmethod, Byte) 59 | ) 60 | } 61 | ) 62 | ) 63 | ) 64 | 65 | GreetingResponse = Struct( 66 | "version" / OneOf(Int8ub, [0, 5]), 67 | Embedded( 68 | Switch( 69 | this.version, 70 | { 71 | 0x0: Responsev4, 72 | 0x5: Struct( 73 | "auth_type" / Int8ub 74 | ) 75 | } 76 | ) 77 | ) 78 | ) 79 | 80 | Request = Struct( 81 | "version" / OneOf(Int8ub, [5]), 82 | "cmd" / Byte, 83 | Padding(1), 84 | "atyp" / Byte, 85 | "addr" / Switch( 86 | this.atyp, 87 | { 88 | 0x1: BytesInteger(4), 89 | 0x4: BytesInteger(16), 90 | 0x3: PascalString(Byte) 91 | } 92 | ), 93 | "port" / Int16ub 94 | ) 95 | 96 | Response = Struct( 97 | "version" / OneOf(Int8ub, [5]), 98 | "status" / Byte, 99 | Padding(1), 100 | "atyp" / Byte, 101 | "addr" / Switch( 102 | this.atyp, 103 | { 104 | 0x1: BytesInteger(4), 105 | 0x4: BytesInteger(16), 106 | 0x3: PascalString(Byte) 107 | } 108 | ), 109 | "port" / Int16ub 110 | ) 111 | -------------------------------------------------------------------------------- /socks5/_reader.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function, unicode_literals 2 | 3 | import sys 4 | import construct 5 | 6 | from socks5 import _data_structure as data_structure 7 | from socks5.exception import ParserError 8 | from socks5.define import ADDR_TYPE 9 | from socks5.events import NeedMoreData 10 | from socks5.events import Socks4Request, Socks4Response 11 | from socks5.events import GreetingRequest, GreetingResponse 12 | from socks5.events import Request, Response 13 | 14 | if sys.version_info.major <= 2: 15 | string_func = unicode 16 | else: 17 | string_func = str 18 | 19 | 20 | def read_greeting_request(data): 21 | try: 22 | parsed_data = dict(data_structure.GreetingRequest.parse(data)) 23 | except (construct.FieldError, construct.RangeError): 24 | return NeedMoreData() 25 | except construct.ValidationError: 26 | raise ParserError("read_greeting_request: Incorrect version.") 27 | 28 | if parsed_data["version"] == 5: 29 | parsed_data.pop("version") 30 | parsed_data.pop("nmethod") 31 | return GreetingRequest(**parsed_data) 32 | 33 | elif parsed_data["version"] == 4: 34 | parsed_data.pop("version") 35 | parsed_data["name"] = string_func(parsed_data["name"], encoding="ascii") 36 | if parsed_data["domainname"]: 37 | parsed_data["domainname"] = string_func(parsed_data["domainname"], encoding="ascii") 38 | else: 39 | parsed_data.pop("domainname") 40 | return Socks4Request(**parsed_data) 41 | 42 | 43 | def read_greeting_response(data): 44 | try: 45 | parsed_data = dict(data_structure.GreetingResponse.parse(data)) 46 | except (construct.FieldError, construct.RangeError): 47 | return NeedMoreData() 48 | except construct.ValidationError: 49 | raise ParserError("read_greeting_response: Incorrect version.") 50 | 51 | if parsed_data["version"] == 5: 52 | parsed_data.pop("version") 53 | return GreetingResponse(**parsed_data) 54 | 55 | # NOTE: socksv4 will have a null byte in front 56 | elif parsed_data["version"] == 0x0: 57 | parsed_data.pop("version") 58 | return Socks4Response(**parsed_data) 59 | 60 | 61 | def read_request(data): 62 | try: 63 | parsed_data = dict(data_structure.Request.parse(data)) 64 | except (construct.FieldError, construct.RangeError): 65 | return NeedMoreData() 66 | except construct.ValidationError: 67 | raise ParserError("read_request: Incorrect version.") 68 | 69 | parsed_data.pop("version") 70 | if parsed_data["atyp"] == ADDR_TYPE["DOMAINNAME"]: 71 | parsed_data["addr"] = string_func(parsed_data["addr"], encoding="ascii") 72 | 73 | return Request(**parsed_data) 74 | 75 | 76 | def read_response(data): 77 | try: 78 | parsed_data = dict(data_structure.Response.parse(data)) 79 | except (construct.FieldError, construct.RangeError): 80 | return NeedMoreData() 81 | except construct.ValidationError: 82 | raise ParserError("read_response: Incorrect version.") 83 | 84 | parsed_data.pop("version") 85 | if parsed_data["atyp"] == ADDR_TYPE["DOMAINNAME"]: 86 | parsed_data["addr"] = string_func(parsed_data["addr"], encoding="ascii") 87 | 88 | return Response(**parsed_data) 89 | -------------------------------------------------------------------------------- /socks5/auth/rfc1929/events.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function, unicode_literals 2 | 3 | import sys 4 | from socks5.define import RESP_STATUS 5 | 6 | if sys.version_info.major <= 2: 7 | string_func = unicode 8 | else: 9 | string_func = str 10 | 11 | 12 | class NeedMoreData(object): 13 | event_type = "NeedMoreData" 14 | 15 | def __eq__(self, value): 16 | return self.event_type == value 17 | 18 | def __ne__(self, value): 19 | return not self.__eq__(value) 20 | 21 | def __str__(self): 22 | return "NeedMoreData" 23 | 24 | 25 | class AuthRequest(object): 26 | """ 27 | This event represent the socks5 auth request. 28 | 29 | Args: 30 | username (unicode): specify the username. 31 | password (unicode): specify the password. 32 | 33 | Raise: 34 | ValueError: ValueError will be raised when the following condition occured. 35 | - username type is not unicode. 36 | - password type is not unicode. 37 | 38 | Example: 39 | >>> # python 2 example 40 | >>> import sys 41 | >>> sys.version_info.major 42 | 2 43 | >>> event = AuthRequest(u"user", u"password") 44 | >>> event == "AuthRequest" 45 | True 46 | >>> event.username == u"user" 47 | True 48 | >>> event.password == u"password" 49 | True 50 | >>> # python 3 example 51 | >>> import sys 52 | >>> sys.version_info.major 53 | 3 54 | >>> event = AuthRequest("user", "password") 55 | >>> event == "AuthRequest" 56 | True 57 | >>> event.username == "user" 58 | True 59 | >>> event.password == "password" 60 | True 61 | """ 62 | event_type = "AuthRequest" 63 | 64 | def __init__(self, username, password): 65 | if not isinstance(username, string_func) or not isinstance(password, string_func): 66 | raise ValueError("username or password expect to be unicode string") 67 | 68 | if len(username) >= 256 or len(password) >= 256: 69 | raise ValueError("username or password too long") 70 | 71 | self.username = username 72 | self.password = password 73 | 74 | def __eq__(self, value): 75 | return self.event_type == value 76 | 77 | def __ne__(self, value): 78 | return not self.__eq__(value) 79 | 80 | def __str__(self): 81 | return "SOCKSv5 Auth Request: username: {username}, password: {password}".format(**self.__dict__) 82 | 83 | 84 | class AuthResponse(object): 85 | """ 86 | This event represent the socks5 auth response. 87 | 88 | Args: 89 | status (int): specify the socks server response status code. 90 | The supported socks status code can be found in ::define.py:: 91 | 92 | Raise: 93 | ValueError: ValueError will be raised when the following condition occured. 94 | - version is not supported. Currently, the supported version is 5. 95 | - specify an unsupported status code. 96 | 97 | Example: 98 | >>> event = AuthResponse(0) 99 | >>> event == "AuthResponse" 100 | True 101 | >>> event.version == 5 102 | True 103 | >>> event.status == 0 104 | True 105 | """ 106 | event_type = "AuthResponse" 107 | 108 | def __init__(self, status): 109 | if status not in RESP_STATUS.values(): 110 | raise ValueError("Unsupported status code") 111 | 112 | self.status = status 113 | 114 | def __eq__(self, value): 115 | return self.event_type == value 116 | 117 | def __ne__(self, value): 118 | return not self.__eq__(value) 119 | 120 | def __str__(self): 121 | return "SOCKSv5 Auth Response: status: {}".format(self.status) 122 | -------------------------------------------------------------------------------- /socks5/auth/rfc1929/connection.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function, unicode_literals 2 | 3 | from transitions import Machine 4 | 5 | from socks5.exception import ProtocolError 6 | from ._reader import read_auth_request, read_auth_response 7 | from ._writer import write_auth_request, write_auth_response 8 | 9 | 10 | class _ClientConnection(object): 11 | states = [ 12 | 'init', 13 | 'auth_request', 14 | 'auth_response', 15 | 'end' 16 | ] 17 | 18 | def __init__(self): 19 | self._buffer = b"" 20 | self.machine = Machine( 21 | model=self, states=self.states, initial='init') 22 | 23 | def initiate_connection(self): 24 | self.machine.set_state("auth_request") 25 | 26 | def recv(self, data): 27 | if self.state != "auth_response": 28 | raise ProtocolError("ClientConnection.recv: Incorrect state {}".format(self.state)) 29 | 30 | self._buffer += data 31 | current_event = read_auth_response(self._buffer) 32 | 33 | if current_event == 'NeedMoreData': 34 | return current_event 35 | else: 36 | self._buffer = b"" 37 | 38 | if self.state == 'auth_response': 39 | self.machine.set_state('end') 40 | 41 | return current_event 42 | 43 | def send(self, event): 44 | if self.state != "auth_request": 45 | raise ProtocolError("ClientConnection.send: Incorrect state {}".format(self.state)) 46 | 47 | if self.state == "auth_request" and event != "AuthRequest": 48 | raise ProtocolError("ClientConnection.send: Incorrect event {0} in state: {1}".format(event, self.state)) 49 | 50 | if self.state == "auth_request": 51 | self.machine.set_state("auth_response") 52 | 53 | return write_auth_request(event) 54 | 55 | 56 | class _ServerConnection(object): 57 | states = [ 58 | 'init', 59 | 'auth_request', 60 | 'auth_response', 61 | 'end' 62 | ] 63 | 64 | def __init__(self): 65 | self._buffer = b"" 66 | self.machine = Machine( 67 | model=self, states=self.states, initial='init') 68 | 69 | def initiate_connection(self): 70 | self.machine.set_state("auth_request") 71 | 72 | def recv(self, data): 73 | if self.state != "auth_request": 74 | raise ProtocolError("ServerConnection.recv: Incorrect state {}".format(self.state)) 75 | 76 | self._buffer += data 77 | current_event = read_auth_request(self._buffer) 78 | 79 | if current_event == "NeedMoreData": 80 | return current_event 81 | else: 82 | self._buffer = b"" 83 | 84 | if self.state == 'auth_request': 85 | self.machine.set_state('auth_response') 86 | 87 | return current_event 88 | 89 | def send(self, event): 90 | if self.state != "auth_response": 91 | raise ProtocolError("ServerConnection.recv: Incorrect state {}".format(self.state)) 92 | 93 | if self.state == "auth_response" and event != "AuthResponse": 94 | raise ProtocolError("ServerConnection.send: Incorrect event {0} in state: {1}".format(event, self.state)) 95 | 96 | if self.state == "auth_response": 97 | self.machine.set_state("end") 98 | return write_auth_response(event) 99 | 100 | 101 | class Connection(object): 102 | def __init__(self, our_role): 103 | if our_role == "server": 104 | self._conn = _ServerConnection() 105 | elif our_role == "client": 106 | self._conn = _ClientConnection() 107 | else: 108 | raise ValueError("unknonw role {}".format(our_role)) 109 | 110 | def initiate_connection(self): 111 | self._conn.initiate_connection() 112 | 113 | def recv(self, data): 114 | return self._conn.recv(data) 115 | 116 | def send(self, event): 117 | return self._conn.send(event) 118 | -------------------------------------------------------------------------------- /socks5/tests/test_writer.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function, unicode_literals 2 | import unittest 3 | import struct 4 | 5 | 6 | from socks5.events import ( 7 | Socks4Request, Socks4Response, 8 | GreetingRequest, GreetingResponse, 9 | Request, Response) 10 | 11 | from socks5.define import ( 12 | REQ_COMMAND, AUTH_TYPE, 13 | RESP_STATUS, ADDR_TYPE) 14 | 15 | from socks5._writer import ( 16 | write_greeting_request, write_greeting_response, 17 | write_request, write_response) 18 | 19 | 20 | class TestWriter(unittest.TestCase): 21 | def test_greeting_request_socks5(self): 22 | event = GreetingRequest([AUTH_TYPE["NO_AUTH"]]) 23 | data = write_greeting_request(event) 24 | expected_data = struct.pack("!BBB", 0x5, 0x1, 0x00) 25 | self.assertEqual(data, expected_data) 26 | 27 | event = GreetingRequest([AUTH_TYPE["NO_AUTH"], AUTH_TYPE["GSSAPI"]]) 28 | data = write_greeting_request(event) 29 | expected_data = struct.pack("!BB2B", 0x5, 0x2, 0x00, 0x01) 30 | self.assertEqual(data, expected_data) 31 | 32 | def test_greeting_request_socks4(self): 33 | event = Socks4Request(1, "127.0.0.1", 5580, "Johnny") 34 | data = write_greeting_request(event) 35 | 36 | expected_data = struct.pack("!BBH4B6sB", 0x4, 0x1, 5580, 127, 0, 0, 1, "Johnny".encode("ascii"), 0) 37 | self.assertEqual(data, expected_data) 38 | 39 | event = Socks4Request(1, "0.0.0.1", 5580, "Johnny", "www.google.com") 40 | data = write_greeting_request(event) 41 | 42 | expected_data = struct.pack( 43 | "!BBH4B6sB14sB", 0x4, 0x1, 5580, 0, 0, 0, 1, "Johnny".encode("ascii"), 0, "www.google.com".encode("idna"), 0) 44 | self.assertEqual(data, expected_data) 45 | 46 | def test_greeting_response_socks5(self): 47 | event = GreetingResponse(AUTH_TYPE["NO_AUTH"]) 48 | data = write_greeting_response(event) 49 | expected_data = struct.pack("!BB", 0x5, 0x0) 50 | self.assertEqual(data, expected_data) 51 | 52 | def test_greeting_response_socks4(self): 53 | event = Socks4Response(0x5a, "127.0.0.1", 5580) 54 | data = write_greeting_response(event) 55 | 56 | expected_data = struct.pack("!BBH4B", 0, 0x5a, 5580, 127, 0, 0, 1) 57 | self.assertEqual(data, expected_data) 58 | 59 | def test_write_request_ipv4(self): 60 | event = Request(REQ_COMMAND["CONNECT"], ADDR_TYPE["IPV4"], u"127.0.0.1", 8080) 61 | data = write_request(event) 62 | expected_data = struct.pack("!BBxB4BH", 0x5, 0x1, 0x1, 127, 0, 0, 1, 8080) 63 | self.assertEqual(data, expected_data) 64 | 65 | def test_write_request_ipv6(self): 66 | event = Request(REQ_COMMAND["CONNECT"], ADDR_TYPE["IPV6"], u"::1", 8080) 67 | data = write_request(event) 68 | expected_data = struct.pack("!BBxB8HH", 69 | 0x5, 0x1, 0x4, 70 | 0, 0, 0, 0, 0, 0, 0, 1, 71 | 8080) 72 | self.assertEqual(data, expected_data) 73 | 74 | def test_write_request_hostname(self): 75 | event = Request(REQ_COMMAND["CONNECT"], ADDR_TYPE["DOMAINNAME"], u"google.com", 8080) 76 | data = write_request(event) 77 | expected_data = struct.pack("!BBxBB10sH", 0x5, 0x1, 0x3, 10, b"google.com", 8080) 78 | self.assertEqual(data, expected_data) 79 | 80 | def test_write_response_ipv4(self): 81 | event = Response(RESP_STATUS["SUCCESS"], ADDR_TYPE["IPV4"], u"127.0.0.1", 8080) 82 | data = write_response(event) 83 | expected_data = struct.pack("!BBxB4BH", 0x5, 0x0, 0x1, 127, 0, 0, 1, 8080) 84 | self.assertEqual(data, expected_data) 85 | 86 | def test_write_response_ipv6(self): 87 | event = Response(RESP_STATUS["SUCCESS"], ADDR_TYPE["IPV6"], u"::1", 8080) 88 | data = write_response(event) 89 | expected_data = struct.pack("!BBxB8HH", 90 | 0x5, 0x0, 0x4, 91 | 0, 0, 0, 0, 0, 0, 0, 1, 92 | 8080) 93 | self.assertEqual(data, expected_data) 94 | 95 | def test_write_response_hostname(self): 96 | event = Response(RESP_STATUS["SUCCESS"], ADDR_TYPE["DOMAINNAME"], u"google.com", 8080) 97 | data = write_response(event) 98 | expected_data = struct.pack("!BBxBB10sH", 0x5, 0x0, 0x3, 10, b"google.com", 8080) 99 | self.assertEqual(data, expected_data) 100 | -------------------------------------------------------------------------------- /socks5/tests/rfc1929/test_connection.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function, unicode_literals 2 | 3 | import unittest 4 | import struct 5 | 6 | from socks5.exception import ProtocolError 7 | from socks5.auth.rfc1929 import Connection 8 | 9 | from socks5.auth.rfc1929.events import AuthRequest, AuthResponse 10 | 11 | from socks5.define import RESP_STATUS 12 | 13 | 14 | class TestServerConnection(unittest.TestCase): 15 | def test_initiate_connection(self): 16 | conn = Connection(our_role="server") 17 | self.assertEqual(conn._conn.state, "init") 18 | 19 | conn.initiate_connection() 20 | self.assertEqual(conn._conn.state, "auth_request") 21 | 22 | def test_incorrect_role(self): 23 | with self.assertRaises(ValueError): 24 | Connection(our_role="yoyo") 25 | 26 | def test_recv_need_more_data(self): 27 | conn = Connection(our_role="server") 28 | conn._conn.machine.set_state("auth_request") 29 | 30 | raw_data = b"\x01" 31 | event = conn.recv(raw_data) 32 | 33 | self.assertEqual(conn._conn.state, "auth_request") 34 | self.assertEqual(event, "NeedMoreData") 35 | 36 | def test_send_auth_response(self): 37 | conn = Connection(our_role="server") 38 | conn._conn.machine.set_state("auth_response") 39 | 40 | event = AuthResponse(RESP_STATUS["SUCCESS"]) 41 | data = conn.send(event) 42 | expected_data = struct.pack("!BB", 0x1, 0x0) 43 | self.assertEqual(conn._conn.state, "end") 44 | self.assertEqual(data, expected_data) 45 | 46 | def test_send_auth_response_incorrect_event(self): 47 | conn = Connection(our_role="server") 48 | conn._conn.machine.set_state("auth_response") 49 | 50 | event = AuthRequest("user", "passwd") 51 | with self.assertRaises(ProtocolError): 52 | conn.send(event) 53 | 54 | def test_send_incorrect_state_auth_request(self): 55 | conn = Connection(our_role="server") 56 | conn._conn.machine.set_state("auth_request") 57 | 58 | event = AuthResponse(0) 59 | with self.assertRaises(ProtocolError): 60 | conn.send(event) 61 | 62 | def test_recv_in_auth_request(self): 63 | conn = Connection(our_role="server") 64 | conn._conn.machine.set_state("auth_request") 65 | 66 | raw_data = struct.pack("!BB4sB8s", 0x1, 0x4, b"user", 0x8, b"password") 67 | event = conn.recv(raw_data) 68 | self.assertEqual(conn._conn.state, "auth_response") 69 | self.assertEqual(event, "AuthRequest") 70 | self.assertEqual(event.username, "user") 71 | self.assertEqual(event.password, "password") 72 | 73 | def test_recv_incorrect_state_auth_response(self): 74 | conn = Connection(our_role="server") 75 | conn._conn.machine.set_state("auth_response") 76 | 77 | with self.assertRaises(ProtocolError): 78 | conn.recv(b"") 79 | 80 | 81 | class TestClientConnection(unittest.TestCase): 82 | def test_initiate_connection(self): 83 | conn = Connection(our_role="client") 84 | self.assertEqual(conn._conn.state, "init") 85 | 86 | conn.initiate_connection() 87 | self.assertEqual(conn._conn.state, "auth_request") 88 | 89 | def test_send_in_auth_request(self): 90 | conn = Connection(our_role="client") 91 | conn._conn.machine.set_state("auth_request") 92 | 93 | event = AuthRequest("user", "password") 94 | data = conn.send(event) 95 | expected_data = struct.pack("!BB4sB8s", 0x1, 0x4, b"user", 0x8, b"password") 96 | self.assertEqual(conn._conn.state, "auth_response") 97 | self.assertEqual(data, expected_data) 98 | 99 | def test_send_in_auth_request_incorrect_event(self): 100 | conn = Connection(our_role="client") 101 | conn._conn.machine.set_state("auth_request") 102 | 103 | event = AuthResponse(0) 104 | with self.assertRaises(ProtocolError): 105 | conn.send(event) 106 | 107 | def test_send_incorrect_state_auth_response(self): 108 | conn = Connection(our_role="client") 109 | conn._conn.machine.set_state("auth_response") 110 | 111 | event = AuthRequest("user", "passwd") 112 | with self.assertRaises(ProtocolError): 113 | conn.send(event) 114 | 115 | def test_recv_need_more_data(self): 116 | conn = Connection(our_role="client") 117 | conn._conn.machine.set_state("auth_response") 118 | 119 | raw_data = b"\x01" 120 | event = conn.recv(raw_data) 121 | 122 | self.assertEqual(conn._conn.state, "auth_response") 123 | self.assertEqual(event, "NeedMoreData") 124 | 125 | def test_recv_in_auth_response(self): 126 | conn = Connection(our_role="client") 127 | conn._conn.machine.set_state("auth_response") 128 | 129 | raw_data = struct.pack("!BB", 0x1, 0x0) 130 | event = conn.recv(raw_data) 131 | self.assertEqual(conn._conn.state, "end") 132 | self.assertEqual(event, "AuthResponse") 133 | self.assertEqual(event.status, 0) 134 | 135 | def test_recv_incorrect_state_auth_request(self): 136 | conn = Connection(our_role="client") 137 | conn._conn.machine.set_state("auth_request") 138 | 139 | raw_data = b"" 140 | with self.assertRaises(ProtocolError): 141 | conn.recv(raw_data) 142 | -------------------------------------------------------------------------------- /socks5/tests/test_events.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function, unicode_literals 2 | 3 | import unittest 4 | import ipaddress 5 | 6 | from socks5.events import ( 7 | NeedMoreData, 8 | Socks4Request, Socks4Response, 9 | GreetingRequest, GreetingResponse, 10 | Request, Response) 11 | 12 | from socks5.define import ( 13 | REQ_COMMAND, AUTH_TYPE, 14 | RESP_STATUS, ADDR_TYPE) 15 | 16 | 17 | class TestEvents(unittest.TestCase): 18 | def test_need_more_data(self): 19 | event = NeedMoreData() 20 | self.assertEqual(event, "NeedMoreData") 21 | 22 | def test_socks4_request(self): 23 | event = Socks4Request(1, "127.0.0.1", 5580, "Johnny") 24 | self.assertEqual(event.cmd, 1) 25 | self.assertEqual(event.port, 5580) 26 | self.assertEqual(event.addr, ipaddress.IPv4Address("127.0.0.1")) 27 | self.assertEqual(event.name, "Johnny") 28 | self.assertEqual(event.domainname, "") 29 | 30 | event = Socks4Request(1, "127.0.0.1", 5580, "Johnny", "www.google.com") 31 | self.assertEqual(event.cmd, 1) 32 | self.assertEqual(event.port, 5580) 33 | self.assertEqual(event.addr, ipaddress.IPv4Address("0.0.0.1")) 34 | self.assertEqual(event.name, "Johnny") 35 | self.assertEqual(event.domainname, "www.google.com") 36 | 37 | def test_socks4_request_domainname_not_specified(self): 38 | with self.assertRaises(ValueError): 39 | Socks4Request(1, "0.0.0.1", 5580, "Johnny") 40 | 41 | def test_socks4_request_unsupported_cmd_type(self): 42 | with self.assertRaises(ValueError): 43 | Socks4Request(0, "127.0.0.1", 5580, "Johnny") 44 | 45 | def test_socks4_request_unsupported_addr_format(self): 46 | with self.assertRaises(ValueError): 47 | Socks4Request(1, "0.0.0.1.0", 5580, "Johnny") 48 | 49 | def test_socks4_request_unsupported_name_format(self): 50 | with self.assertRaises(ValueError): 51 | Socks4Request(1, "0.0.0.1", 5580, b"Johnny") 52 | 53 | def test_socks4_request_unsupported_domainname_format(self): 54 | with self.assertRaises(ValueError): 55 | Socks4Request(1, "0.0.0.1", 5580, "Johnny", b"www.google.com") 56 | 57 | def test_socks4_response(self): 58 | event = Socks4Response(0x5a, "127.0.0.1", 5580) 59 | self.assertEqual(event.status, 0x5a) 60 | self.assertEqual(event.port, 5580) 61 | self.assertEqual(event.addr, ipaddress.IPv4Address("127.0.0.1")) 62 | 63 | def test_socks4_response_unsupported_status_code(self): 64 | with self.assertRaises(ValueError): 65 | Socks4Response(0xff, "127.0.0.1", 5580) 66 | 67 | def test_socks4_response_unsupported_addr_format(self): 68 | with self.assertRaises(ValueError): 69 | Socks4Response(0x5a, "127.0.0.1.1", 5580) 70 | 71 | def test_greeting_request(self): 72 | event = GreetingRequest([AUTH_TYPE["NO_AUTH"]]) 73 | self.assertEqual(event, "GreetingRequest") 74 | self.assertEqual(event.nmethod, 1) 75 | self.assertEqual(event.methods, [AUTH_TYPE["NO_AUTH"]]) 76 | 77 | def test_greeting_request_incorrect_methods_type(self): 78 | with self.assertRaises(ValueError): 79 | GreetingRequest(AUTH_TYPE["NO_AUTH"]) 80 | 81 | def test_greeting_response(self): 82 | event = GreetingResponse(AUTH_TYPE["NO_AUTH"]) 83 | self.assertEqual(event, "GreetingResponse") 84 | self.assertEqual(event.auth_type, AUTH_TYPE["NO_AUTH"]) 85 | 86 | def test_request(self): 87 | event = Request(REQ_COMMAND["CONNECT"], ADDR_TYPE["IPV4"], "127.0.0.1", 8080) 88 | self.assertEqual(event, "Request") 89 | 90 | self.assertEqual(event.cmd, REQ_COMMAND["CONNECT"]) 91 | self.assertEqual(event.atyp, ADDR_TYPE["IPV4"]) 92 | self.assertEqual(event.addr, ipaddress.IPv4Address("127.0.0.1")) 93 | self.assertEqual(event.port, 8080) 94 | 95 | def test_request_unsupported_cmd_type(self): 96 | with self.assertRaises(ValueError): 97 | Request( 98 | 0xff, ADDR_TYPE["IPV4"], "127.0.0.1", 8080) 99 | 100 | def test_request_unsupported_addr_type(self): 101 | with self.assertRaises(ValueError): 102 | Request( 103 | REQ_COMMAND["CONNECT"], 0xff, "127.0.0.1", 8080) 104 | 105 | def test_request_incorrect_domainname_type(self): 106 | with self.assertRaises(ValueError): 107 | Request(REQ_COMMAND["CONNECT"], ADDR_TYPE["DOMAINNAME"], b"www.google.com", 8080) 108 | 109 | def test_request_incorrect_ipv4_format(self): 110 | with self.assertRaises(ValueError): 111 | Request( 112 | REQ_COMMAND["CONNECT"], ADDR_TYPE["IPV4"], "127.0.0.1.1", 8080) 113 | 114 | def test_request_incorrect_ipv6_format(self): 115 | with self.assertRaises(ValueError): 116 | Request( 117 | REQ_COMMAND["CONNECT"], ADDR_TYPE["IPV6"], ":::::::1", 8080) 118 | 119 | def test_response(self): 120 | event = Response(RESP_STATUS["SUCCESS"], ADDR_TYPE["IPV4"], "127.0.0.1", 8080) 121 | self.assertEqual(event, "Response") 122 | 123 | self.assertEqual(event.status, RESP_STATUS["SUCCESS"]) 124 | self.assertEqual(event.atyp, ADDR_TYPE["IPV4"]) 125 | self.assertEqual(event.addr, ipaddress.IPv4Address("127.0.0.1")) 126 | self.assertEqual(event.port, 8080) 127 | 128 | def test_response_incorrect_status_code(self): 129 | with self.assertRaises(ValueError): 130 | Response( 131 | 0xff, ADDR_TYPE["IPV4"], "127.0.0.1", 8080) 132 | 133 | def test_response_unsupported_addr_type(self): 134 | with self.assertRaises(ValueError): 135 | Response( 136 | RESP_STATUS["SUCCESS"], 0xff, "127.0.0.1", 8080) 137 | 138 | def test_response_incorrect_domainname_type(self): 139 | with self.assertRaises(ValueError): 140 | Response(RESP_STATUS["SUCCESS"], ADDR_TYPE["DOMAINNAME"], b"www.google.com", 8080) 141 | 142 | def test_response_incorrect_ipv4_format(self): 143 | with self.assertRaises(ValueError): 144 | Response( 145 | REQ_COMMAND["CONNECT"], ADDR_TYPE["IPV4"], "127.0.0.1.1", 8080) 146 | 147 | def test_response_incorrect_ipv6_format(self): 148 | with self.assertRaises(ValueError): 149 | Response( 150 | REQ_COMMAND["CONNECT"], ADDR_TYPE["IPV6"], ":::::::1", 8080) 151 | -------------------------------------------------------------------------------- /socks5/tests/test_reader.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function, unicode_literals 2 | 3 | import unittest 4 | import struct 5 | import ipaddress 6 | 7 | from socks5.exception import ParserError 8 | from socks5.events import NeedMoreData 9 | from socks5.events import GreetingRequest, GreetingResponse 10 | from socks5.events import Socks4Request, Socks4Response 11 | 12 | from socks5._reader import ( 13 | read_greeting_request, read_greeting_response, 14 | read_request, read_response) 15 | 16 | 17 | class TestReader(unittest.TestCase): 18 | def test_greeting_request_socks5(self): 19 | request = read_greeting_request( 20 | struct.pack("!BB2B", 0x5, 0x2, 0x00, 0x01)) 21 | self.assertIsInstance(request, GreetingRequest) 22 | self.assertEqual(request.nmethod, 2) 23 | self.assertIn(0, request.methods) 24 | self.assertIn(1, request.methods) 25 | 26 | request = read_greeting_request( 27 | struct.pack("!BB3B", 0x5, 0x3, 0x00, 0x01, 0x02)) 28 | self.assertIsInstance(request, GreetingRequest) 29 | self.assertEqual(request.nmethod, 3) 30 | self.assertIn(0, request.methods) 31 | self.assertIn(1, request.methods) 32 | self.assertIn(2, request.methods) 33 | 34 | def test_greeting_request_socks4(self): 35 | raw_data = struct.pack("!BBH4B6sB", 0x4, 0x1, 5580, 127, 0, 0, 1, "Johnny".encode("ascii"), 0) 36 | 37 | request = read_greeting_request(raw_data) 38 | self.assertIsInstance(request, Socks4Request) 39 | self.assertEqual(request.cmd, 1) 40 | self.assertEqual(request.port, 5580) 41 | self.assertEqual(request.addr, ipaddress.IPv4Address("127.0.0.1")) 42 | self.assertEqual(request.name, "Johnny") 43 | 44 | raw_data = struct.pack( 45 | "!BBH4B6sB14sB", 0x4, 0x1, 5580, 0, 0, 0, 1, "Johnny".encode("ascii"), 0, "www.google.com".encode("idna"), 0) 46 | 47 | request = read_greeting_request(raw_data) 48 | self.assertIsInstance(request, Socks4Request) 49 | self.assertEqual(request.cmd, 1) 50 | self.assertEqual(request.port, 5580) 51 | self.assertEqual(request.addr, ipaddress.IPv4Address("0.0.0.1")) 52 | self.assertEqual(request.name, "Johnny") 53 | self.assertEqual(request.domainname, "www.google.com") 54 | 55 | def test_greeting_request_not_enough_data(self): 56 | request = read_greeting_request( 57 | struct.pack("!BB2B", 0x5, 0x3, 0x00, 0x01)) 58 | self.assertIsInstance(request, NeedMoreData) 59 | 60 | def test_greeting_request_failed_invalid_version(self): 61 | with self.assertRaises(ParserError): 62 | read_greeting_request( 63 | struct.pack("!BB2B", 0x3, 0x3, 0x00, 0x01)) 64 | 65 | def test_greeting_response_socks5(self): 66 | response = read_greeting_response( 67 | struct.pack("!BB", 0x5, 0x0)) 68 | self.assertIsInstance(response, GreetingResponse) 69 | self.assertEqual(response.auth_type, 0) 70 | 71 | def test_greeting_response_socks4(self): 72 | raw_data = struct.pack("!BBH4B", 0, 0x5a, 5580, 127, 0, 0, 1) 73 | response = read_greeting_response(raw_data) 74 | self.assertIsInstance(response, Socks4Response) 75 | self.assertEqual(response.status, 0x5a) 76 | self.assertEqual(response.port, 5580) 77 | self.assertEqual(response.addr, ipaddress.IPv4Address("127.0.0.1")) 78 | 79 | def test_greeting_response_not_enouch_data(self): 80 | event = read_greeting_response( 81 | struct.pack("!B", 0x5)) 82 | self.assertIsInstance(event, NeedMoreData) 83 | 84 | def test_greeting_response_failed_incorrect_version(self): 85 | with self.assertRaises(ParserError): 86 | read_greeting_response( 87 | struct.pack("!B", 0x1)) 88 | 89 | def test_read_request_ipv4(self): 90 | request = read_request( 91 | struct.pack("!BBxB4BH", 0x5, 0x1, 0x1, 127, 0, 0, 1, 8080)) 92 | self.assertEqual(request.cmd, 1) 93 | self.assertEqual(request.atyp, 1) 94 | self.assertEqual(request.addr, ipaddress.IPv4Address("127.0.0.1")) 95 | self.assertEqual(request.port, 8080) 96 | 97 | def test_read_request_ipv6(self): 98 | request = read_request( 99 | struct.pack("!BBxB8HH", 100 | 0x5, 0x1, 0x4, 101 | 0, 0, 0, 0, 0, 0, 0, 1, 102 | 8080)) 103 | self.assertEqual(request.cmd, 1) 104 | self.assertEqual(request.atyp, 4) 105 | self.assertEqual(request.addr, ipaddress.IPv6Address("::1")) 106 | self.assertEqual(request.port, 8080) 107 | 108 | def test_read_request_hostname(self): 109 | request = read_request( 110 | struct.pack("!BBxBB10sH", 0x5, 0x1, 0x3, 10, b"google.com", 8080)) 111 | self.assertEqual(request.cmd, 1) 112 | self.assertEqual(request.atyp, 3) 113 | self.assertEqual(request.addr, "google.com") 114 | self.assertEqual(request.port, 8080) 115 | 116 | def test_read_request_not_enough_data(self): 117 | event = read_request( 118 | struct.pack("!B", 0x5)) 119 | self.assertIsInstance(event, NeedMoreData) 120 | 121 | def test_read_request_failed_incorrect_version(self): 122 | with self.assertRaises(ParserError): 123 | read_request( 124 | struct.pack("!B", 0x4)) 125 | 126 | def test_read_response_ipv4(self): 127 | response = read_response( 128 | struct.pack("!BBxB4BH", 0x5, 0x0, 0x1, 127, 0, 0, 1, 8080)) 129 | self.assertEqual(response.status, 0) 130 | self.assertEqual(response.atyp, 1) 131 | self.assertEqual(response.addr, ipaddress.IPv4Address("127.0.0.1")) 132 | self.assertEqual(response.port, 8080) 133 | 134 | def test_read_response_ipv6(self): 135 | response = read_response( 136 | struct.pack("!BBxB8HH", 137 | 0x5, 0x0, 0x4, 138 | 0, 0, 0, 0, 0, 0, 0, 1, 139 | 8080)) 140 | self.assertEqual(response.status, 0) 141 | self.assertEqual(response.atyp, 4) 142 | self.assertEqual(response.addr, ipaddress.IPv6Address("::1")) 143 | self.assertEqual(response.port, 8080) 144 | 145 | def test_read_response_hostname(self): 146 | response = read_response( 147 | struct.pack("!BBxBB10sH", 0x5, 0x0, 0x3, 10, b"google.com", 8080)) 148 | self.assertEqual(response.status, 0) 149 | self.assertEqual(response.atyp, 3) 150 | self.assertEqual(response.addr, "google.com") 151 | self.assertEqual(response.port, 8080) 152 | 153 | def test_read_response_not_enough_data(self): 154 | event = read_response( 155 | struct.pack("!B", 0x5)) 156 | self.assertIsInstance(event, NeedMoreData) 157 | 158 | def test_read_response_failed_incorrect_version(self): 159 | with self.assertRaises(ParserError): 160 | read_response( 161 | struct.pack("!B", 0x4)) 162 | -------------------------------------------------------------------------------- /socks5/connection.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function, unicode_literals 2 | 3 | from transitions import Machine 4 | 5 | from socks5.exception import ProtocolError 6 | from socks5.define import AUTH_TYPE 7 | from socks5 import _reader as reader 8 | from socks5 import _writer as writer 9 | 10 | 11 | class _ClientConnection(object): 12 | states = [ 13 | 'init', 14 | 'greeting_request', 15 | 'greeting_response', 16 | 'auth_inprogress', 17 | 'request', 18 | 'response', 19 | 'end' 20 | ] 21 | 22 | def __init__(self): 23 | self._buffer = b"" 24 | self.machine = Machine( 25 | model=self, states=self.states, initial='init') 26 | self._version = 0xff 27 | self._auth_methods = [AUTH_TYPE["NO_SUPPORT_AUTH_METHOD"]] 28 | self._addr_type = 0xff 29 | self._addr = 0 30 | self._port = 0 31 | 32 | def initiate_connection(self): 33 | self.machine.set_state("greeting_request") 34 | 35 | def auth_end(self): 36 | if self.state != "auth_inprogress": 37 | raise ProtocolError("ClientConnection.auth_end: Incorrect state {}".format(self.state)) 38 | self.machine.set_state("request") 39 | 40 | def recv(self, data): 41 | if self.state not in ("greeting_response", "response"): 42 | raise ProtocolError("ClientConnection.recv: Incorrect state {}".format(self.state)) 43 | 44 | self._buffer += data 45 | _reader = getattr(reader, "read_" + self.state) 46 | current_event = _reader(self._buffer) 47 | 48 | if current_event == 'NeedMoreData': 49 | return current_event 50 | else: 51 | self._buffer = b"" 52 | 53 | if self.state == 'greeting_response': 54 | if current_event == "GreetingResponse": 55 | if self._version != 5 or current_event.auth_type not in self._auth_methods: 56 | raise ProtocolError("ClientConnection:recv: receive incorrect data from server") 57 | 58 | if current_event.auth_type == AUTH_TYPE["NO_AUTH"]: 59 | self.machine.set_state('request') 60 | elif current_event.auth_type == AUTH_TYPE["NO_SUPPORT_AUTH_METHOD"]: 61 | self.machine.set_state("end") 62 | else: 63 | self.machine.set_state('auth_inprogress') 64 | 65 | elif current_event == "Socks4Response": 66 | if self._version != 4 or self._port != current_event.port: 67 | raise ProtocolError("ClientConnection:recv: receive incorrect data from server") 68 | 69 | self.machine.set_state("end") 70 | 71 | elif self.state == 'response': 72 | if (self._version != 5 or 73 | self._addr_type != current_event.atyp or 74 | self._addr != current_event.addr or 75 | self._port != current_event.port): 76 | raise ProtocolError("ClientConnection:recv: receive incorrect data from server") 77 | self.machine.set_state('end') 78 | 79 | return current_event 80 | 81 | def send(self, event): 82 | if self.state not in ("greeting_request", "request"): 83 | raise ProtocolError("ClientConnection.send: Incorrect state {}".format(self.state)) 84 | 85 | if self.state == "greeting_request" and (event != "GreetingRequest" and event != "Socks4Request"): 86 | raise ProtocolError("ClientConnection.send: Incorrect event {0} in state: {1}".format(event, self.state)) 87 | 88 | if self.state == "request" and event != "Request": 89 | raise ProtocolError("ClientConnection.send: Incorrect event {0} in state: {1}".format(event, self.state)) 90 | 91 | _writer = getattr(writer, "write_" + self.state) 92 | if self.state == "greeting_request": 93 | if event == "GreetingRequest": 94 | self._version = 5 95 | self._auth_methods.extend(event.methods) 96 | elif event == "Socks4Request": 97 | self._version = 4 98 | self._port = event.port 99 | self.machine.set_state("greeting_response") 100 | 101 | if self.state == "request": 102 | self._addr_type = event.atyp 103 | self._addr = event.addr 104 | self._port = event.port 105 | self.machine.set_state("response") 106 | 107 | return _writer(event) 108 | 109 | 110 | class _ServerConnection(object): 111 | states = [ 112 | 'init', 113 | 'greeting_request', 114 | 'greeting_response', 115 | 'auth_inprogress', 116 | 'request', 117 | 'response', 118 | 'end' 119 | ] 120 | 121 | def __init__(self): 122 | self._buffer = b"" 123 | self.machine = Machine( 124 | model=self, states=self.states, initial='init') 125 | 126 | self._version = 0xff 127 | self._auth_methods = [AUTH_TYPE["NO_SUPPORT_AUTH_METHOD"]] 128 | self._addr_type = 0xff 129 | self._addr = 0 130 | self._port = 0 131 | 132 | def initiate_connection(self): 133 | self.machine.set_state("greeting_request") 134 | 135 | def auth_end(self): 136 | if self.state != "auth_inprogress": 137 | raise ProtocolError("ServerConnection.auth_end: Incorrect state {}".format(self.state)) 138 | self.machine.set_state("request") 139 | 140 | def recv(self, data): 141 | if self.state not in ("greeting_request", "request"): 142 | raise ProtocolError("ServerConnection.recv: Incorrect state {}".format(self.state)) 143 | 144 | self._buffer += data 145 | _reader = getattr(reader, "read_" + self.state) 146 | current_event = _reader(self._buffer) 147 | 148 | if current_event == "NeedMoreData": 149 | return current_event 150 | else: 151 | self._buffer = b"" 152 | 153 | if self.state == 'greeting_request': 154 | if current_event == "GreetingRequest": 155 | self._version = 5 156 | self._auth_methods.extend(current_event.methods) 157 | elif current_event == "Socks4Request": 158 | self._version = 4 159 | self._port = current_event.port 160 | 161 | self.machine.set_state('greeting_response') 162 | 163 | elif self.state == 'request': 164 | if current_event == "Request": 165 | self._addr_type = current_event.atyp 166 | self._addr = current_event.addr 167 | self._port = current_event.port 168 | 169 | self.machine.set_state('response') 170 | 171 | self._buffer = b"" 172 | return current_event 173 | 174 | def send(self, event): 175 | if self.state not in ("greeting_response", "response"): 176 | raise ProtocolError("ServerConnection.recv: Incorrect state {}".format(self.state)) 177 | 178 | if self.state == "greeting_response" and (event != "GreetingResponse" and event != "Socks4Response"): 179 | raise ProtocolError("ServerConnection.send: Incorrect event {0} in state: {1}".format(event, self.state)) 180 | 181 | if self.state == "response" and event != "Response": 182 | raise ProtocolError("ServerConnection.send: Incorrect event {0} in state: {1}".format(event, self.state)) 183 | 184 | _writer = getattr(writer, "write_" + self.state) 185 | if self.state == "greeting_response": 186 | if event == "GreetingResponse": 187 | if (self._version != 5 or 188 | event.auth_type not in self._auth_methods): 189 | raise ProtocolError("ServerConnection.send: incorrect event from user.") 190 | 191 | if event.auth_type == AUTH_TYPE["NO_AUTH"]: 192 | self.machine.set_state("request") 193 | elif event.auth_type == AUTH_TYPE["NO_SUPPORT_AUTH_METHOD"]: 194 | self.machine.set_state("end") 195 | else: 196 | self.machine.set_state("auth_inprogress") 197 | 198 | elif event == "Socks4Response": 199 | if self._version != 4 or self._port != event.port: 200 | raise ProtocolError("ServerConnection.send: incorrect event from user") 201 | 202 | self.machine.set_state("end") 203 | 204 | if self.state == "response": 205 | if (self._version != 5 or 206 | self._addr_type != event.atyp or 207 | self._addr != event.addr or 208 | self._port != event.port): 209 | raise ProtocolError("ServerConnection.send: receive incorrect data from server") 210 | self.machine.set_state("end") 211 | return _writer(event) 212 | 213 | 214 | class Connection(object): 215 | def __init__(self, our_role): 216 | if our_role == "server": 217 | self._conn = _ServerConnection() 218 | elif our_role == "client": 219 | self._conn = _ClientConnection() 220 | else: 221 | raise ValueError("unknonw role {}".format(our_role)) 222 | 223 | def initiate_connection(self): 224 | self._conn.initiate_connection() 225 | 226 | def auth_end(self): 227 | self._conn.auth_end() 228 | 229 | def recv(self, data): 230 | return self._conn.recv(data) 231 | 232 | def send(self, event): 233 | return self._conn.send(event) 234 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # socks5: 2 | [![Build Status](https://travis-ci.org/mike820324/socks5.svg?branch=master)](https://travis-ci.org/mike820324/socks5) 3 | [![Coverage Status](https://coveralls.io/repos/github/mike820324/socks5/badge.svg?branch=master)](https://coveralls.io/github/mike820324/socks5?branch=master) 4 | 5 | **socks5** is a small module SOCKS version 5 library written fom scratch in Python, 6 | which is highly inspired by [h11](https://github.com/njsmith/h11) and [hyper-h2](https://hyper-h2.readthedocs.io/en/stable/). 7 | 8 | 9 | It's a "bring-your-own-IO" library; that is socks5 module does not contain any network related code. 10 | socks5 only deal with the parsing and state management of the socks5 connection, 11 | the underlying IO part is not cover in the internal code. 12 | 13 | Currently, socks5 module support the following protocol and rfc. 14 | 15 | - socks4 16 | - socks4a 17 | - socks5 : rfc 1928 18 | - socks5 username/password authentication: rfc 1929 19 | 20 | ## Installation: 21 | 22 | ```bash 23 | pip install socks5 24 | 25 | # follow the latest version 26 | git clone https://github.com/mike820324/socks5 27 | cd socks5 && pip install -U . 28 | ``` 29 | 30 | ## Quick Guide: 31 | 32 | Here I will walk through a simple socks5 client/server communication. 33 | 34 | First thing first, we have to initiate a connection. 35 | You can initiate a connection by using the following code snippets. 36 | 37 | ```python 38 | from socks5 import Connection 39 | client_conn = Connection(our_role="client") 40 | client_conn.initiate_connection() 41 | 42 | server_conn = Connection(our_role="server") 43 | server_conn.initiate_connection() 44 | ``` 45 | 46 | The **conn.initiate_connection()** method will initialize the internal state of each connection object. 47 | We must call this method before the connection receive or send data. 48 | 49 | 50 | Next, it's time to send some data to the server. 51 | There are two important methods in Connection object which are, 52 | 53 | - **send** 54 | - **recv** 55 | 56 | This is the only two methods that you need to deal with the connection object. 57 | The following snippets shows how client send a greeting request to the server. 58 | 59 | 60 | ```python 61 | from socks5 import GreetingRequest, GreetingResponse 62 | from socks5 import AUTH_TYPE 63 | 64 | client_event = GreetingRequest([AUTH_TYPE["NO_AUTH"]]) 65 | raw_data = client_conn.send(event) 66 | 67 | _event = server_conn.recv(raw_data) 68 | if AUTH_TYPE["NO_AUTH"] in server_event.auth_type: 69 | server_event = GreetingResponse(AUTH_TYPE["NO_AUTH"]) 70 | else: 71 | server_event = GreetingResponse(AUTH_TYPE["NO_SUPPORT_AUTH_METHOD"]) 72 | 73 | raw_data = server_conn.send(server_event) 74 | 75 | _event = client_conn.recv(raw_data) 76 | ``` 77 | 78 | A simple walk through of the above example, 79 | 80 | 0. client first send a greeting request with auth type no auth. 81 | 1. server receive the raw data and check if client supported the no auth auth type. 82 | - if client support no auth, the greeting response will use no auth. 83 | - if client does not support no auth, a no supported auth method will be returned. 84 | 2. server send a greeting response to the client. 85 | 3. client receive the greeting response 86 | 87 | On the above example, we have also introducecd the GreetingRequest/GreetingResponse event. 88 | 89 | Event is a very important concept in socks5. An Event object abstract the socks5 raw data away. 90 | There are seven types of event in socks5 which are, 91 | 92 | - Socks4Request 93 | - Socks4Response 94 | - GreetingRequest 95 | - GreetingResponse 96 | - Request 97 | - Response 98 | - NeedMoreData 99 | 100 | We will discussed these events in more detailed in later section. 101 | 102 | From the example, the client side first use GreetingRequest to construct a greeting request event and 103 | pass the event to the **client_conn.send** method. After that the method will return a raw data to you. 104 | 105 | The server side then calling **server_conn.recv(raw_data)** to retrieve an event object. 106 | And use the auty_type field of the GreetingRequest event. 107 | 108 | When the check is complete, the server side create a new GreetingResponse event and pass to the **server_conn.send** method. 109 | Just like the **client_conn.send**, it **send** method will return raw data to you. 110 | 111 | In reality, the **raw_data** are the underlying IO layer such as the following code snippets. 112 | 113 | ```python 114 | import socket 115 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 116 | sock.connect(host, port) 117 | 118 | client_conn = ClientConnection() 119 | client_conn.initiate_connection() 120 | client_event = GreetingRequest([AUTH_TYPE["NO_AUTH"]]) 121 | raw_data = client_conn.send(event) 122 | sock.send(raw_data) 123 | 124 | raw_data = sock.recv(1024) 125 | event = client_conn.recv(raw_data) 126 | ``` 127 | 128 | There are more realistic examples in the examples folder. 129 | 130 | ## API Reference: 131 | 132 | This section will introduce the avaliable data structures and api. 133 | 134 | ### Connection: 135 | 136 | In socks5 module, a connection object represent a connected connection. 137 | The connection object helped the user handle the connection state and internal buffer data. 138 | There are two kinds of connection class defined in socks5 module: **socks connection** and **rfc1929 connection**. 139 | 140 | #### Socks Connection: 141 | 142 | A socks connection class. Can import via **socks5** module. 143 | 144 | 145 | The following are methods of this class. 146 | 147 | - **Conncection(our_role: str)**: the our_role parameter can be either "client" or "server". 148 | - **initiate_connection()**: initiate the internal state for the current connection. 149 | - **auth_end()**: indicate the authentication progress has ended and can deal with rest of the protocol. 150 | - **recv(data: bytes) -> Event**: feed the raw data to the connection and return the corresponding events. 151 | - **send(event: Event) -> bytes**: feed the event and return the corresponding raw data. 152 | 153 | #### RFC1929 Auth Connection: 154 | 155 | A RFC1929 Username/Password Auth connection class. Can import via **socks5.auth.rfc1929**. 156 | 157 | The following are the methods within this class: 158 | 159 | - **Conncection(our_role: str)**: the our_role parameter can be either "client" or "server" 160 | - **initiate_connection()**: initiate the internal state for the current connection. 161 | - **recv(data: bytes) -> Event**: feed the raw data to the connection and return the corresponding events. 162 | - **send(event: Event) -> bytes**: feed the event and return the corresponding raw data. 163 | 164 | ### Events: 165 | 166 | An event abstract the socks protocol related data away from the user. 167 | Every socks 5 communication data is an event object in socks5. 168 | Currently, there are three events categories which are, **socks4**, **socks5** and **rfc 1929**. 169 | 170 | #### Socks4 Events: 171 | 172 | There are only two events in socks4 protocol implementation. 173 | 174 | - **Socks4Request(cmd: int, addr: (unicode or int), port: int, username: unicode, domainname: unicode)**: 175 | 176 | Socks4Request represent the socks4 request sent from the client. 177 | The type of the addr should be a IPv4Address compatible type. 178 | 179 | 180 | Example Usage: 181 | 182 | ```python 183 | event = Socks4Request(REQ_COMMAND["CONNECT"], "192.168.0.1", 5580, "Johnny") 184 | 185 | # Socks4a domainname support 186 | event = Socks4Request(REQ_COMMAND["CONNECT"], "0.0.0.1", 5580, "Johnny", "www.google.com") 187 | ``` 188 | 189 | - **Socks4Response(status: int, addr: (unicode or int), port: int)**: 190 | 191 | Socks4Response represent the socks4 resposne sent from the server. 192 | The type of the addr should be a IPv4Address compatible type. 193 | 194 | 195 | Example Usage: 196 | 197 | ```python 198 | event = Socks4Response(RESP_STATUS["REQUEST_GRANTED"], "192.168.0.1", 5580) 199 | ``` 200 | 201 | #### Socks5 Events: 202 | 203 | There are four events in socks5 protocol implementation. 204 | 205 | - **GreetingRequest(methods: list[int])**: 206 | 207 | An initial socks5 greeting request sent from the client. 208 | The user only need to supply the supported methods. 209 | 210 | 211 | Example usage: 212 | 213 | ```python 214 | event = GreetingRequest([AUTH_TYPE["NO_AUTH"]]) 215 | 216 | # Support rfc1929 217 | event = GreetingRequest([AUTH_TYPE["NO_AUTH"], AUTH_TYPE["USERNAME_PASSWD"]]) 218 | ``` 219 | 220 | - **GreetingResponse(auth_type: int)**: 221 | 222 | An initial socks5 greeting response sent from the server. 223 | The auth_type is the authentication type the server choose. 224 | 225 | 226 | Example usage: 227 | 228 | ```python 229 | event = GreetingResponse(AUTH_TYPE["NO_AUTH"]) 230 | 231 | # Support rfc1929 232 | event = GreetingResponse(AUTH_TYPE["USERNAME_PASSWD"]) 233 | 234 | # No Support method 235 | event = GreetingResponse(AUTH_TYPE["NO_SUPPORT_AUTH_METHOD"]) 236 | ``` 237 | 238 | - **Request(cmd: int, atyp: int, addr: (unicode or int), port: int)**: 239 | 240 | The socks5 request sent from the client. 241 | One things to notice is that the addr type should be an ipaddress compatible type. 242 | 243 | 244 | Example usage: 245 | 246 | ```python 247 | event = Request(REQ_COMMAND["CONNECT"], ADDR_TYPE["IPV4"], "192.168.0.1", 5580) 248 | 249 | # ipv6 250 | event = Request(REQ_COMMAND["CONNECT"], ADDR_TYPE["IPV6"], "::1", 5580) 251 | 252 | # domain name 253 | event = Request(REQ_COMMAND["CONNECT"], ADDR_TYPE["DOMAINNAME"], "www.google.com", 5580) 254 | ``` 255 | 256 | - **Response(status: int, atyp: int, addr: (unicode or int), port: int)**: 257 | 258 | The socks5 response sent from the server. 259 | One things to notice is that the addr type should be an ipaddress compatible type. 260 | 261 | 262 | Example Usage: 263 | 264 | ```python 265 | event = Response(RESP_STATUS["SUCCESS"], ADDR_TYPE["IPV4"], "192.168.0.1", 5580) 266 | 267 | # ipv6 268 | event = Response(RESP_STATUS["SUCCESS"], ADDR_TYPE["IPV6"], "::1", 5580) 269 | 270 | # domain name 271 | event = Response(RESP_STATUS["SUCCESS"], ADDR_TYPE["DOMAINNAME"], "www.google.com", 5580) 272 | ``` 273 | 274 | #### RFC 1929 Username/Password Auth Events: 275 | 276 | The rfc 1929 events can imported from **socks5.auth.rfc1929** modules. 277 | 278 | - **AuthRequest(username: unicode, password: unicode)**: 279 | 280 | The username/password authentication request defined in RFC1929. 281 | 282 | 283 | Example Usage: 284 | 285 | ```python 286 | event = AuthRequest("username", "password") 287 | ``` 288 | - **AuthResponse(status: int)**: 289 | 290 | The username/password authentication response defined in RFC1929. 291 | 292 | 293 | Example Usage: 294 | 295 | ```python 296 | event = AuthResponse(RESP_STATUS["SUCCESS"]) 297 | ``` 298 | 299 | ### Misc: 300 | - **NeedMoreData()**: 301 | 302 | This event indicate the raw data is not enough to parsed the current event. 303 | This event **should not** be used directly, only the connection object will return the event to you. 304 | 305 | ## Future Works: 306 | 307 | - socks5 gssapi authentication: rfc 1961 308 | 309 | ## LICENSE: 310 | MIT 311 | -------------------------------------------------------------------------------- /socks5/events.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function, unicode_literals 2 | import sys 3 | import ipaddress 4 | from socks5.define import ADDR_TYPE, REQ_COMMAND, RESP_STATUS 5 | 6 | if sys.version_info.major <= 2: 7 | string_func = unicode 8 | else: 9 | string_func = str 10 | 11 | 12 | class NeedMoreData(object): 13 | event_type = "NeedMoreData" 14 | 15 | def __eq__(self, value): 16 | return self.event_type == value 17 | 18 | def __ne__(self, value): 19 | return not self.__eq__(value) 20 | 21 | def __str__(self): 22 | return "NeedMoreData" 23 | 24 | 25 | class Socks4Request(object): 26 | """ 27 | This event represent the socksv4 request. 28 | 29 | Args: 30 | cmd (int): specify the request socks4 command type. 31 | The supported value can be found in ::define.py:: 32 | addr (unicode/int): specify the address. 33 | port (int): specify the port. 34 | name (unicode): specify the name identd name. 35 | domainname (unicode): specify the domain name. 36 | 37 | Raise: 38 | ValueError: ValueError will be raised when the following condition occured. 39 | - ipaddress.IPv4Address incompatible address format type. 40 | - name is not unicode. 41 | - domainname is not unicode. 42 | - addr field is 1 but domainname not specified 43 | 44 | """ 45 | 46 | event_type = "Socks4Request" 47 | 48 | def __init__(self, cmd, addr, port, name, domainname=""): 49 | if cmd not in [0x1, 0x2]: 50 | raise ValueError("cmd should be either command or bind") 51 | 52 | try: 53 | addr = ipaddress.IPv4Address(addr) 54 | except ipaddress.AddressValueError: 55 | raise ValueError("Invalid ipaddress format for IPv4") 56 | 57 | if int(addr) == 1 and len(domainname) == 0: 58 | raise ValueError("Domain name should be specified when addr is 1") 59 | 60 | if not isinstance(name, string_func) or not isinstance(domainname, string_func): 61 | raise ValueError("name or domainname must be a unicode string") 62 | 63 | self.cmd = cmd 64 | self.port = port 65 | self.name = name 66 | 67 | if domainname: 68 | self.addr = ipaddress.IPv4Address(1) 69 | else: 70 | self.addr = addr 71 | 72 | self.domainname = domainname 73 | 74 | def __eq__(self, value): 75 | return self.event_type == value 76 | 77 | def __ne__(self, value): 78 | return not self.__eq__(value) 79 | 80 | def __str__(self): 81 | return "Sockv4 request" 82 | 83 | 84 | class Socks4Response(object): 85 | """ 86 | This event represent the socksv4 response. 87 | 88 | Args: 89 | status (int): sockv4 compatible status code. 90 | addr (unicode or int): ipv4 address. 91 | port (int): port number 92 | 93 | Raise: 94 | ValueError: ValueError will be raised when the following condition occured. 95 | - unsupported status type. 96 | - ipaddress.IPv4Address incompatible address format type. 97 | """ 98 | 99 | event_type = "Socks4Response" 100 | 101 | def __init__(self, status, addr, port): 102 | if status not in {0x5a, 0x5b, 0x5c, 0x5d}: 103 | raise ValueError("Incorrect status code") 104 | 105 | self.status = status 106 | try: 107 | self.addr = ipaddress.IPv4Address(addr) 108 | except ipaddress.AddressValueError: 109 | raise ValueError("Invalid ipaddress format for IPv4") 110 | 111 | self.port = port 112 | 113 | def __eq__(self, value): 114 | return self.event_type == value 115 | 116 | def __ne__(self, value): 117 | return not self.__eq__(value) 118 | 119 | def __str__(self): 120 | return "Sockv4 Response" 121 | 122 | 123 | class GreetingRequest(object): 124 | """ 125 | This event represent the socks5 greeting request. 126 | 127 | Args: 128 | methods (list/tuple): a list of query methods. 129 | The supported methods can be found in ::define.py:: 130 | 131 | Raise: 132 | ValueError: ValueError will be raised when the following condition occured. 133 | - methods type is not list or tuple. 134 | 135 | Example: 136 | >>> event = GreetingRequest([0, 1]) 137 | >>> event == "GreetingRequest" 138 | True 139 | >>> event.nmethod == 2 140 | True 141 | >>> event.methods == [0, 1] 142 | True 143 | """ 144 | event_type = "GreetingRequest" 145 | 146 | def __init__(self, methods): 147 | if not isinstance(methods, list) and not isinstance(methods, tuple): 148 | raise ValueError("methods should be a list or tuple") 149 | 150 | self.nmethod = len(methods) 151 | self.methods = list(methods) 152 | 153 | def __eq__(self, value): 154 | return self.event_type == value 155 | 156 | def __ne__(self, value): 157 | return not self.__eq__(value) 158 | 159 | def __str__(self): 160 | return "SOCKSv5 Greeting Request: number of method: {nmethod}, Auth Types : {methods}".format(**self.__dict__) 161 | 162 | 163 | class GreetingResponse(object): 164 | """ 165 | This event represent the socks5 greeting response. 166 | 167 | Args: 168 | auth_type (int): specify the auth type server selected. 169 | The supported auth_type can be found in ::define.py:: 170 | 171 | Raise: 172 | ValueError: ValueError will be raised when the following condition occured. 173 | - version is not supported. Currently, the supported version is 5. 174 | 175 | Example: 176 | >>> event = GreetingResponse(0) 177 | >>> event == "GreetingResponse" 178 | True 179 | >>> event.auth_type == 0 180 | True 181 | """ 182 | event_type = "GreetingResponse" 183 | 184 | def __init__(self, auth_type): 185 | self.auth_type = auth_type 186 | 187 | def __eq__(self, value): 188 | return self.event_type == value 189 | 190 | def __ne__(self, value): 191 | return not self.__eq__(value) 192 | 193 | def __str__(self): 194 | return "SOCKSv5 Greeting Response: Auth Type : {0}".format(self.auth_type) 195 | 196 | 197 | class Request(object): 198 | """ 199 | This event represent the socks5 request. 200 | 201 | Args: 202 | cmd (int): specify the request command type. 203 | The supported value can be found in ::define.py:: 204 | atyp (int): specify the request address type. 205 | The supported value can be found in ::define.py:: 206 | addr (unicode/int): specify the address. 207 | port (int): specify the port. 208 | 209 | Note: 210 | The ::addr:: field can accept any ipaddress.ip_address compatible value. 211 | If the ::atyp:: type is domain name, the value **MUST** be a unicode type. 212 | 213 | Raise: 214 | ValueError: ValueError will be raised when the following condition occured. 215 | - specify an unsupported cmd type or atyp type. 216 | - addr field type incorrect. 217 | - addr field mismatched with atyp type. 218 | 219 | Example: 220 | >>> # python 2 example 221 | >>> import sys 222 | >>> sys.version_info.major_version 223 | 2 224 | >>> event = Request(1, 1, u"127.0.0.1", 5580) 225 | >>> event == "Request" 226 | True 227 | >>> event.cmd 228 | 1 229 | >>> event.atyp 230 | 1 231 | >>> event.addr 232 | IPv4Address('127.0.0.1') 233 | >>> event.port 234 | 5580 235 | >>> # addr type is integer 236 | >>> event = Request(1, 1, 1, 5580) 237 | >>> event == "Request" 238 | True 239 | >>> event.cmd 240 | 1 241 | >>> event.atyp 242 | 1 243 | >>> event.addr 244 | IPv4Address('0.0.0.1') 245 | >>> event.port 246 | 5580 247 | >>> event = Request(1, 3, u"google.com", 5580) 248 | >>> event == "Request" 249 | True 250 | >>> event.cmd 251 | 1 252 | >>> event.atyp 253 | 1 254 | >>> event.addr 255 | u"google.com" 256 | >>> event.port 257 | 5580 258 | >>> # python 3 example 259 | >>> import sys 260 | >>> sys.version_info.major_version 261 | 3 262 | >>> event = Request(1, 1, "127.0.0.1", 5580) 263 | >>> event == "Request" 264 | True 265 | >>> event.cmd 266 | 1 267 | >>> event.atyp 268 | 1 269 | >>> event.addr 270 | IPv4Address('127.0.0.1') 271 | >>> event.port 272 | 5580 273 | >>> event = Request(1, 3, "google.com", 5580) 274 | >>> event == "Request" 275 | True 276 | >>> event.cmd 277 | 1 278 | >>> event.atyp 279 | 1 280 | >>> event.addr 281 | "google.com" 282 | >>> event.port 283 | 5580 284 | """ 285 | event_type = "Request" 286 | 287 | def __init__(self, cmd, atyp, addr, port): 288 | if cmd not in REQ_COMMAND.values(): 289 | raise ValueError("Unsupported request command {}".format(cmd)) 290 | 291 | if atyp not in ADDR_TYPE.values(): 292 | raise ValueError("Unsupported address type {}".format(atyp)) 293 | 294 | if atyp == ADDR_TYPE["IPV4"]: 295 | try: 296 | addr = ipaddress.IPv4Address(addr) 297 | except ipaddress.AddressValueError: 298 | raise ValueError("Invalid ipaddress format for IPv4") 299 | elif atyp == ADDR_TYPE["IPV6"]: 300 | try: 301 | addr = ipaddress.IPv6Address(addr) 302 | except ipaddress.AddressValueError: 303 | raise ValueError("Invalid ipaddress format for IPv6") 304 | elif atyp == ADDR_TYPE["DOMAINNAME"] and not isinstance(addr, string_func): 305 | raise ValueError("Domain name expect to be unicode string") 306 | 307 | self.cmd = cmd 308 | self.atyp = atyp 309 | self.addr = addr 310 | self.port = port 311 | 312 | def __eq__(self, value): 313 | return self.event_type == value 314 | 315 | def __ne__(self, value): 316 | return not self.__eq__(value) 317 | 318 | def __str__(self): 319 | return "SOCKSv5 Response: Command {cmd} : Address Type {atyp}, Addr : {addr} Port : {port}".format(**self.__dict__) 320 | 321 | 322 | class Response(object): 323 | """ 324 | This event represent the socks5 response. 325 | 326 | Args: 327 | status (int): specify the socks server response status code. 328 | The supported value can be found in ::define.py:: 329 | atyp (int): specify the request address type. 330 | The supported value can be found in ::define.py:: 331 | addr (unicode/int): specify the address. 332 | port (int): specify the port. 333 | 334 | Note: 335 | The ::addr:: field can accept any ipaddress.ip_address compatible value. 336 | If the ::atyp:: type is domain name, the value **MUST** be a unicode type. 337 | 338 | Raise: 339 | ValueError: ValueError will be raised when the following condition occured. 340 | - specify an unsupported status type or atyp type. 341 | - addr field type incorrect. 342 | - addr field mismatched with atyp type. 343 | 344 | Example: 345 | >>> # python 2 example 346 | >>> import sys 347 | >>> sys.version_info.major_version 348 | 2 349 | >>> event = Response(0, 1, u"127.0.0.1", 5580) 350 | >>> event == "Response" 351 | True 352 | >>> event.status 353 | 0 354 | >>> event.atyp 355 | 1 356 | >>> event.addr 357 | IPv4Address('127.0.0.1') 358 | >>> int(event.addr) 359 | 2130706433 360 | >>> event.port 361 | 5580 362 | >>> event = Response(0, 3, u"google.com", 5580) 363 | >>> event == "Response" 364 | True 365 | >>> event.status 366 | 0 367 | >>> event.atyp 368 | 1 369 | >>> event.addr 370 | u"google.com" 371 | >>> event.port 372 | 5580 373 | >>> # python 3 example 374 | >>> import sys 375 | >>> sys.version_info.major_version 376 | 3 377 | >>> event = Respopnse(0, 1, "127.0.0.1", 5580) 378 | >>> event == "Respopnse" 379 | True 380 | >>> event.status 381 | 0 382 | >>> event.atyp 383 | 1 384 | >>> event.addr 385 | IPv4Address('127.0.0.1') 386 | >>> event.port 387 | 5580 388 | >>> event = Response(0, 3, "google.com", 5580) 389 | >>> event == "Response" 390 | True 391 | >>> event.status 392 | 0 393 | >>> event.atyp 394 | 1 395 | >>> event.addr 396 | "google.com" 397 | >>> event.port 398 | 5580 399 | """ 400 | event_type = "Response" 401 | 402 | def __init__(self, status, atyp, addr, port): 403 | if status not in RESP_STATUS.values(): 404 | raise ValueError("Unsupported status code {}".format(status)) 405 | 406 | if atyp not in ADDR_TYPE.values(): 407 | raise ValueError("Unsupported address type {}".format(atyp)) 408 | 409 | if atyp == ADDR_TYPE["IPV4"]: 410 | try: 411 | addr = ipaddress.IPv4Address(addr) 412 | except ipaddress.AddressValueError: 413 | raise ValueError("Invalid ipaddress format for IPv4") 414 | elif atyp == ADDR_TYPE["IPV6"]: 415 | try: 416 | addr = ipaddress.IPv6Address(addr) 417 | except ipaddress.AddressValueError: 418 | raise ValueError("Invalid ipaddress format for IPv6") 419 | elif atyp == ADDR_TYPE["DOMAINNAME"] and not isinstance(addr, string_func): 420 | raise ValueError("Domain name expect to be unicode string") 421 | 422 | self.status = status 423 | self.atyp = atyp 424 | self.addr = addr 425 | self.port = port 426 | 427 | def __eq__(self, value): 428 | return self.event_type == value 429 | 430 | def __ne__(self, value): 431 | return not self.__eq__(value) 432 | 433 | def __str__(self): 434 | return "SOCKSv5 Response: Status : {status}, Addr : {addr} Port : {port}".format(**self.__dict__) 435 | -------------------------------------------------------------------------------- /socks5/tests/test_connection.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function, unicode_literals 2 | 3 | import unittest 4 | import struct 5 | import ipaddress 6 | 7 | from socks5.exception import ProtocolError 8 | from socks5.connection import Connection 9 | 10 | from socks5.events import ( 11 | Socks4Request, Socks4Response, 12 | GreetingRequest, GreetingResponse, 13 | Request, Response) 14 | 15 | from socks5.define import ( 16 | REQ_COMMAND, AUTH_TYPE, 17 | RESP_STATUS, ADDR_TYPE) 18 | 19 | 20 | class TestServerConnection(unittest.TestCase): 21 | def test_initiate_connection(self): 22 | conn = Connection(our_role="server") 23 | self.assertEqual(conn._conn.state, "init") 24 | 25 | conn.initiate_connection() 26 | self.assertEqual(conn._conn.state, "greeting_request") 27 | 28 | def test_incorrect_role(self): 29 | with self.assertRaises(ValueError): 30 | Connection(our_role="yoyo") 31 | 32 | def test_auth_end(self): 33 | conn = Connection(our_role="server") 34 | conn._conn.machine.set_state("auth_inprogress") 35 | 36 | conn.auth_end() 37 | self.assertEqual(conn._conn.state, "request") 38 | 39 | def test_auth_end_in_incorrect_state(self): 40 | conn = Connection(our_role="server") 41 | conn._conn.machine.set_state("greeting_request") 42 | with self.assertRaises(ProtocolError): 43 | conn.auth_end() 44 | 45 | def test_send_greeting_response_socks4(self): 46 | conn = Connection(our_role="server") 47 | conn._conn.machine.set_state("greeting_response") 48 | conn._conn._version = 4 49 | conn._conn._port = 5580 50 | 51 | event = Socks4Response(0x5a, "127.0.0.1", 5580) 52 | data = conn.send(event) 53 | expected_data = struct.pack("!BBH4B", 0, 0x5a, 5580, 127, 0, 0, 1) 54 | 55 | self.assertEqual(conn._conn.state, "end") 56 | self.assertEqual(data, expected_data) 57 | 58 | def test_send_greeting_response_socks4_with_incorrect_event_detail_verion(self): 59 | conn = Connection(our_role="server") 60 | conn._conn.machine.set_state("greeting_response") 61 | conn._conn._version = 4 62 | conn._conn._port = 5580 63 | 64 | event = GreetingResponse(AUTH_TYPE["NO_AUTH"]) 65 | with self.assertRaises(ProtocolError): 66 | conn.send(event) 67 | 68 | def test_send_greeting_response_socks4_with_incorrect_event_detail_port(self): 69 | conn = Connection(our_role="server") 70 | conn._conn.machine.set_state("greeting_response") 71 | conn._conn._version = 4 72 | conn._conn._port = 5580 73 | 74 | event = Socks4Response(0x5a, "127.0.0.1", 5581) 75 | with self.assertRaises(ProtocolError): 76 | conn.send(event) 77 | 78 | def test_send_greeting_response_no_auth(self): 79 | conn = Connection(our_role="server") 80 | conn._conn.machine.set_state("greeting_response") 81 | conn._conn._version = 5 82 | conn._conn._auth_methods = [AUTH_TYPE["NO_AUTH"]] 83 | 84 | event = GreetingResponse(AUTH_TYPE["NO_AUTH"]) 85 | data = conn.send(event) 86 | expected_data = struct.pack("!BB", 0x5, 0x0) 87 | 88 | self.assertEqual(conn._conn.state, "request") 89 | self.assertEqual(data, expected_data) 90 | 91 | def test_send_greeting_response_with_valid_auth(self): 92 | conn = Connection(our_role="server") 93 | conn._conn.machine.set_state("greeting_response") 94 | conn._conn._version = 5 95 | conn._conn._auth_methods = [AUTH_TYPE["USERNAME_PASSWD"]] 96 | 97 | event = GreetingResponse(AUTH_TYPE["USERNAME_PASSWD"]) 98 | conn.send(event) 99 | self.assertEqual(conn._conn.state, "auth_inprogress") 100 | 101 | def test_send_greeting_response_with_unsupported_auth(self): 102 | conn = Connection(our_role="server") 103 | conn._conn.machine.set_state("greeting_response") 104 | conn._conn._version = 5 105 | conn._conn._auth_methods = [AUTH_TYPE["NO_SUPPORT_AUTH_METHOD"]] 106 | 107 | event = GreetingResponse(AUTH_TYPE["NO_SUPPORT_AUTH_METHOD"]) 108 | conn.send(event) 109 | self.assertEqual(conn._conn.state, "end") 110 | 111 | def test_send_greeting_response_with_incorrect_event_detail_version(self): 112 | conn = Connection(our_role="server") 113 | conn._conn.machine.set_state("greeting_response") 114 | conn._conn._version = 5 115 | conn._conn._auth_methods = [AUTH_TYPE["NO_SUPPORT_AUTH_METHOD"]] 116 | 117 | event = Socks4Response(0x5a, "127.0.0.1", 5580) 118 | with self.assertRaises(ProtocolError): 119 | conn.send(event) 120 | 121 | def test_send_greeting_response_with_incorrect_event_detail_auth_type(self): 122 | conn = Connection(our_role="server") 123 | conn._conn.machine.set_state("greeting_response") 124 | conn._conn._version = 5 125 | conn._conn._auth_methods = [AUTH_TYPE["NO_SUPPORT_AUTH_METHOD"]] 126 | 127 | event = GreetingResponse(AUTH_TYPE["NO_AUTH"]) 128 | with self.assertRaises(ProtocolError): 129 | conn.send(event) 130 | 131 | def test_send_greeting_response_incorrect_event(self): 132 | conn = Connection(our_role="server") 133 | conn._conn.machine.set_state("greeting_response") 134 | 135 | event = GreetingRequest((AUTH_TYPE["NO_AUTH"], )) 136 | with self.assertRaises(ProtocolError): 137 | conn.send(event) 138 | 139 | def test_send_response(self): 140 | conn = Connection(our_role="server") 141 | conn._conn.machine.set_state("response") 142 | conn._conn._version = 5 143 | conn._conn._addr_type = ADDR_TYPE["IPV4"] 144 | conn._conn._addr = ipaddress.IPv4Address("127.0.0.1") 145 | conn._conn._port = 8080 146 | 147 | event = Response(RESP_STATUS["SUCCESS"], ADDR_TYPE["IPV4"], "127.0.0.1", 8080) 148 | data = conn.send(event) 149 | expected_data = struct.pack("!BBxB4BH", 0x5, 0x0, 0x1, 127, 0, 0, 1, 8080) 150 | self.assertEqual(conn._conn.state, "end") 151 | self.assertEqual(data, expected_data) 152 | 153 | def test_send_response_incorrect_event(self): 154 | conn = Connection(our_role="server") 155 | conn._conn.machine.set_state("response") 156 | 157 | event = GreetingRequest((AUTH_TYPE["NO_AUTH"], )) 158 | with self.assertRaises(ProtocolError): 159 | conn.send(event) 160 | 161 | def test_send_response_with_incorrect_event_detail_addr_type(self): 162 | conn = Connection(our_role="server") 163 | conn._conn.machine.set_state("response") 164 | conn._conn._version = 5 165 | conn._conn._addr_type = ADDR_TYPE["IPV4"] 166 | conn._conn._addr = ipaddress.IPv4Address("127.0.0.1") 167 | conn._conn._port = 8080 168 | 169 | event = Response(RESP_STATUS["SUCCESS"], ADDR_TYPE["DOMAINNAME"], "www.google.com", 8080) 170 | with self.assertRaises(ProtocolError): 171 | conn.send(event) 172 | 173 | def test_send_response_with_incorrect_event_detail_addr(self): 174 | conn = Connection(our_role="server") 175 | conn._conn.machine.set_state("response") 176 | conn._conn._version = 5 177 | conn._conn._addr_type = ADDR_TYPE["IPV4"] 178 | conn._conn._addr = ipaddress.IPv4Address("127.0.0.1") 179 | conn._conn._port = 8080 180 | 181 | event = Response(RESP_STATUS["SUCCESS"], ADDR_TYPE["IPV4"], "192.168.0.1", 8080) 182 | with self.assertRaises(ProtocolError): 183 | conn.send(event) 184 | 185 | def test_send_response_with_incorrect_event_detail_port(self): 186 | conn = Connection(our_role="server") 187 | conn._conn.machine.set_state("response") 188 | conn._conn._version = 5 189 | conn._conn._addr_type = ADDR_TYPE["IPV4"] 190 | conn._conn._addr = ipaddress.IPv4Address("127.0.0.1") 191 | conn._conn._port = 8080 192 | 193 | event = Response(RESP_STATUS["SUCCESS"], ADDR_TYPE["IPV4"], "127.0.0.1", 5580) 194 | with self.assertRaises(ProtocolError): 195 | conn.send(event) 196 | 197 | def test_send_incorrect_state_greeting_request(self): 198 | conn = Connection(our_role="server") 199 | conn._conn.machine.set_state("greeting_request") 200 | 201 | event = GreetingRequest((AUTH_TYPE["NO_AUTH"], )) 202 | with self.assertRaises(ProtocolError): 203 | conn.send(event) 204 | 205 | def test_send_incorrect_state_request(self): 206 | conn = Connection(our_role="server") 207 | conn._conn.machine.set_state("request") 208 | 209 | event = GreetingRequest((AUTH_TYPE["NO_AUTH"], )) 210 | with self.assertRaises(ProtocolError): 211 | conn.send(event) 212 | 213 | def test_recv_need_more_data(self): 214 | conn = Connection(our_role="server") 215 | conn._conn.machine.set_state("greeting_request") 216 | 217 | raw_data = b"\x05" 218 | event = conn.recv(raw_data) 219 | self.assertEqual(conn._conn.state, "greeting_request") 220 | self.assertEqual(event, "NeedMoreData") 221 | 222 | def test_recv_in_greeting_request(self): 223 | conn = Connection(our_role="server") 224 | conn._conn.machine.set_state("greeting_request") 225 | 226 | raw_data = struct.pack("!BB2B", 0x5, 0x2, 0x00, 0x01) 227 | event = conn.recv(raw_data) 228 | self.assertEqual(conn._conn.state, "greeting_response") 229 | self.assertEqual(event, "GreetingRequest") 230 | self.assertEqual(event.nmethod, 2) 231 | self.assertIn(0, event.methods) 232 | self.assertIn(1, event.methods) 233 | 234 | def test_recv_in_greeting_request_socks4(self): 235 | conn = Connection(our_role="server") 236 | conn._conn.machine.set_state("greeting_request") 237 | raw_data = struct.pack("!BBH4B6sB", 0x4, 0x1, 5580, 127, 0, 0, 1, "Johnny".encode("ascii"), 0) 238 | event = conn.recv(raw_data) 239 | self.assertEqual(event, "Socks4Request") 240 | self.assertEqual(event.cmd, 1) 241 | self.assertEqual(event.port, 5580) 242 | self.assertEqual(event.addr, ipaddress.IPv4Address("127.0.0.1")) 243 | self.assertEqual(event.name, "Johnny") 244 | 245 | def test_recv_in_request(self): 246 | conn = Connection(our_role="server") 247 | conn._conn.machine.set_state("request") 248 | 249 | raw_data = struct.pack("!BBxB4BH", 0x5, 0x1, 0x1, 127, 0, 0, 1, 8080) 250 | event = conn.recv(raw_data) 251 | self.assertEqual(conn._conn.state, "response") 252 | self.assertEqual(event, "Request") 253 | self.assertEqual(event.cmd, 1) 254 | self.assertEqual(event.atyp, 1) 255 | self.assertEqual(event.addr, ipaddress.IPv4Address("127.0.0.1")) 256 | self.assertEqual(event.port, 8080) 257 | 258 | def test_recv_incorrect_state_greeting_response(self): 259 | conn = Connection(our_role="server") 260 | conn._conn.machine.set_state("greeting_response") 261 | 262 | with self.assertRaises(ProtocolError): 263 | conn.recv(b"") 264 | 265 | def test_recv_incorrect_state_response(self): 266 | conn = Connection(our_role="server") 267 | conn._conn.machine.set_state("response") 268 | 269 | with self.assertRaises(ProtocolError): 270 | conn.recv(b"") 271 | 272 | 273 | class TestClientConnection(unittest.TestCase): 274 | def test_initiate_connection(self): 275 | conn = Connection(our_role="client") 276 | self.assertEqual(conn._conn.state, "init") 277 | 278 | conn.initiate_connection() 279 | self.assertEqual(conn._conn.state, "greeting_request") 280 | 281 | def test_auth_end(self): 282 | conn = Connection(our_role="client") 283 | conn._conn.machine.set_state("auth_inprogress") 284 | 285 | conn.auth_end() 286 | self.assertEqual(conn._conn.state, "request") 287 | 288 | def test_auth_end_in_incorrect_state(self): 289 | conn = Connection(our_role="client") 290 | conn._conn.machine.set_state("greeting_request") 291 | 292 | with self.assertRaises(ProtocolError): 293 | conn.auth_end() 294 | 295 | def test_send_in_greeting_request(self): 296 | conn = Connection(our_role="client") 297 | conn._conn.machine.set_state("greeting_request") 298 | 299 | event = GreetingRequest((AUTH_TYPE["NO_AUTH"], )) 300 | data = conn.send(event) 301 | expected_data = struct.pack("!BBB", 0x5, 0x1, 0x00) 302 | self.assertEqual(conn._conn.state, "greeting_response") 303 | self.assertEqual(data, expected_data) 304 | 305 | def test_send_in_greeting_request_socks4(self): 306 | conn = Connection(our_role="client") 307 | conn._conn.machine.set_state("greeting_request") 308 | 309 | event = Socks4Request(1, "127.0.0.1", 5580, "Johnny") 310 | data = conn.send(event) 311 | expected_data = struct.pack("!BBH4B6sB", 0x4, 0x1, 5580, 127, 0, 0, 1, "Johnny".encode("ascii"), 0) 312 | self.assertEqual(conn._conn.state, "greeting_response") 313 | self.assertEqual(data, expected_data) 314 | 315 | def test_send_in_greeting_request_incorrect_event(self): 316 | conn = Connection(our_role="client") 317 | conn._conn.machine.set_state("greeting_request") 318 | 319 | event = GreetingResponse(AUTH_TYPE["NO_AUTH"]) 320 | with self.assertRaises(ProtocolError): 321 | conn.send(event) 322 | 323 | def test_send_in_request_request(self): 324 | conn = Connection(our_role="client") 325 | conn._conn.machine.set_state("request") 326 | 327 | event = Request(REQ_COMMAND["CONNECT"], ADDR_TYPE["IPV4"], u"127.0.0.1", 8080) 328 | data = conn.send(event) 329 | expected_data = struct.pack("!BBxB4BH", 0x5, 0x1, 0x1, 127, 0, 0, 1, 8080) 330 | self.assertEqual(conn._conn.state, "response") 331 | self.assertEqual(data, expected_data) 332 | 333 | def test_send_in_request_request_incorrect_event(self): 334 | conn = Connection(our_role="client") 335 | conn._conn.machine.set_state("request") 336 | 337 | event = GreetingResponse(AUTH_TYPE["NO_AUTH"]) 338 | with self.assertRaises(ProtocolError): 339 | conn.send(event) 340 | 341 | def test_send_incorrect_state_greeting_response(self): 342 | conn = Connection(our_role="client") 343 | conn._conn.machine.set_state("greeting_response") 344 | 345 | event = GreetingResponse(AUTH_TYPE["NO_AUTH"]) 346 | with self.assertRaises(ProtocolError): 347 | conn.send(event) 348 | 349 | def test_send_incorrect_state_response(self): 350 | conn = Connection(our_role="client") 351 | conn._conn.machine.set_state("response") 352 | 353 | event = GreetingResponse(AUTH_TYPE["NO_AUTH"]) 354 | with self.assertRaises(ProtocolError): 355 | conn.send(event) 356 | 357 | def test_recv_need_more_data(self): 358 | conn = Connection(our_role="client") 359 | conn._conn.machine.set_state("greeting_response") 360 | 361 | raw_data = b"\x05" 362 | event = conn.recv(raw_data) 363 | 364 | self.assertEqual(conn._conn.state, "greeting_response") 365 | self.assertEqual(event, "NeedMoreData") 366 | 367 | def test_recv_in_greeting_response_no_auth(self): 368 | conn = Connection(our_role="client") 369 | conn._conn.machine.set_state("greeting_response") 370 | conn._conn._version = 5 371 | conn._conn._auth_methods = [AUTH_TYPE["NO_AUTH"]] 372 | 373 | raw_data = struct.pack("!BB", 0x5, 0x0) 374 | event = conn.recv(raw_data) 375 | 376 | self.assertEqual(conn._conn.state, "request") 377 | self.assertEqual(event, "GreetingResponse") 378 | self.assertEqual(event.auth_type, 0) 379 | 380 | def test_recv_in_greeting_response_socks4(self): 381 | conn = Connection(our_role="client") 382 | conn._conn.machine.set_state("greeting_response") 383 | conn._conn._version = 4 384 | conn._conn._port = 5580 385 | 386 | raw_data = struct.pack("!BBH4B", 0, 0x5a, 5580, 127, 0, 0, 1) 387 | event = conn.recv(raw_data) 388 | 389 | self.assertEqual(conn._conn.state, "end") 390 | self.assertEqual(event, "Socks4Response") 391 | self.assertEqual(event.status, 0x5a) 392 | self.assertEqual(event.port, 5580) 393 | self.assertEqual(event.addr, ipaddress.IPv4Address("127.0.0.1")) 394 | 395 | def test_recv_in_greeting_response_socks4_incorrect_version(self): 396 | conn = Connection(our_role="client") 397 | conn._conn.machine.set_state("greeting_response") 398 | conn._conn._version = 4 399 | conn._conn._port = 5580 400 | 401 | raw_data = struct.pack("!BB", 0x5, 0xff) 402 | with self.assertRaises(ProtocolError): 403 | conn.recv(raw_data) 404 | 405 | def test_recv_in_greeting_response_with_unsupported_auth(self): 406 | conn = Connection(our_role="client") 407 | conn._conn.machine.set_state("greeting_response") 408 | conn._conn._version = 5 409 | conn._conn._auth_methods = [AUTH_TYPE["NO_SUPPORT_AUTH_METHOD"]] 410 | 411 | raw_data = struct.pack("!BB", 0x5, 0xff) 412 | conn.recv(raw_data) 413 | self.assertEqual(conn._conn.state, "end") 414 | 415 | def test_recv_in_greeting_response_with_valid_auth(self): 416 | conn = Connection(our_role="client") 417 | conn._conn.machine.set_state("greeting_response") 418 | conn._conn._version = 5 419 | conn._conn._auth_methods = [AUTH_TYPE["USERNAME_PASSWD"]] 420 | 421 | raw_data = struct.pack("!BB", 0x5, 0x2) 422 | conn.recv(raw_data) 423 | self.assertEqual(conn._conn.state, "auth_inprogress") 424 | 425 | def test_recv_in_greeting_response_with_incorrect_detail_version(self): 426 | conn = Connection(our_role="client") 427 | conn._conn.machine.set_state("greeting_response") 428 | conn._conn._version = 5 429 | conn._conn._auth_methods = [AUTH_TYPE["NO_SUPPORT_AUTH_METHOD"]] 430 | 431 | raw_data = struct.pack("!BBH4B", 0, 0x5a, 5580, 127, 0, 0, 1) 432 | with self.assertRaises(ProtocolError): 433 | conn.recv(raw_data) 434 | 435 | def test_recv_in_greeting_response_with_incorrect_auth_method(self): 436 | conn = Connection(our_role="client") 437 | conn._conn.machine.set_state("greeting_response") 438 | conn._conn._version = 5 439 | conn._conn._auth_methods = [AUTH_TYPE["NO_SUPPORT_AUTH_METHOD"]] 440 | 441 | raw_data = struct.pack("!BB", 0x5, 0x2) 442 | with self.assertRaises(ProtocolError): 443 | conn.recv(raw_data) 444 | 445 | def test_recv_in_response(self): 446 | conn = Connection(our_role="client") 447 | conn._conn.machine.set_state("response") 448 | conn._conn._version = 5 449 | conn._conn._addr_type = ADDR_TYPE["IPV4"] 450 | conn._conn._addr = ipaddress.IPv4Address("127.0.0.1") 451 | conn._conn._port = 8080 452 | 453 | raw_data = struct.pack("!BBxB4BH", 0x5, 0x0, 0x1, 127, 0, 0, 1, 8080) 454 | event = conn.recv(raw_data) 455 | self.assertEqual(conn._conn.state, "end") 456 | self.assertEqual(event, "Response") 457 | self.assertEqual(event.status, 0) 458 | self.assertEqual(event.atyp, 1) 459 | self.assertEqual(event.addr, ipaddress.IPv4Address("127.0.0.1")) 460 | self.assertEqual(event.port, 8080) 461 | 462 | def test_recv_in_response_with_incorrect_addr(self): 463 | conn = Connection(our_role="client") 464 | conn._conn.machine.set_state("response") 465 | conn._conn._version = 5 466 | conn._conn._addr_type = ADDR_TYPE["IPV4"] 467 | conn._conn._addr = ipaddress.IPv4Address("127.0.0.1") 468 | conn._conn._port = 8080 469 | 470 | raw_data = struct.pack("!BBxB4BH", 0x5, 0x0, 0x1, 127, 0, 0, 2, 8080) 471 | with self.assertRaises(ProtocolError): 472 | conn.recv(raw_data) 473 | 474 | def test_recv_incorrect_state_greeting_request(self): 475 | conn = Connection(our_role="client") 476 | conn._conn.machine.set_state("greeting_request") 477 | 478 | raw_data = b"" 479 | with self.assertRaises(ProtocolError): 480 | conn.recv(raw_data) 481 | 482 | def test_recv_incorrect_state_request(self): 483 | conn = Connection(our_role="client") 484 | conn._conn.machine.set_state("request") 485 | 486 | raw_data = b"" 487 | with self.assertRaises(ProtocolError): 488 | conn.recv(raw_data) 489 | --------------------------------------------------------------------------------