Skip to main content

Add your description here

Project description

💃 soxy👯‍♀️proxy 🕺

Ruff PyPI PyPI

An asynchronous proxy server written in pure Python, supporting SOCKS4, SOCKS4a, SOCKS5, and SOCKS5h protocols. The project is designed for educational purposes and has no external dependencies.

This project is actively developed on a periodic basis and is maintained as a hobby. Contributions and feedback are welcome. Feel free to reach out via my Telegram account @shpaker.

Features

  • Asynchronous: Built with asyncio for high performance
  • No Dependencies: Pure Python implementation without external libraries
  • Multiple Protocols: Support for SOCKS4, SOCKS4a, SOCKS5, and SOCKS5h
  • Flexible Configuration: Custom authentication and domain resolution
  • Rule System: Access control based on IP addresses and domains
  • CLI and API: Use via command line or programmatic interface
  • Type Safety: Full type hints support

Requirements

  • Python >= 3.14

🛩️ Installation

pip install soxyproxy

🫶🏼 Getting Started

👟 Command Line Tool

  1. Create a configuration file config.toml:
[proxy]
protocol = "socks5"
transport = "tcp"

[transport]
host = "127.0.0.1"
port = 1080

[[ruleset.connecting.allow]]
from = "127.0.0.1"

[[ruleset.proxying.allow]]
from = "127.0.0.1"
to = "0.0.0.0/0"
  1. Start the server:
# Logs to terminal
soxy config.toml

# Logs to file
soxy config.toml --logfile logs.txt
# or
soxy config.toml -l logs.txt
  1. Test the connection:
# SOCKS5 (without authentication)
curl -x "socks5://127.0.0.1:1080" https://google.com -v

# SOCKS5h (with domain resolution on proxy side)
curl -x "socks5h://127.0.0.1:1080" https://google.com -v

# SOCKS5 with authentication (if configured)
curl -x "socks5://alice:password123@127.0.0.1:1080" https://google.com -v

👨‍💻 Running from Code

import asyncio
import logging
from ipaddress import IPv4Address, IPv4Network
from socket import gethostbyname

import soxy

logging.basicConfig(level=logging.INFO)


def auther(username: str, password: str) -> bool:
    """Simple authentication function."""
    return username == "user" and password == "pass"


def resolver(domain_name: str) -> IPv4Address:
    """Domain name resolver."""
    return IPv4Address(gethostbyname(domain_name))


async def main() -> None:
    async with soxy.Proxy(
        protocol=soxy.Socks5(
            auther=auther,
            resolver=resolver,  # Required for SOCKS5h and SOCKS4a
        ),
        transport=soxy.TcpTransport(host="127.0.0.1", port=1080),
        ruleset=soxy.Ruleset(
            allow_connecting_rules=[
                soxy.ConnectingRule(
                    from_addresses=IPv4Address("127.0.0.1"),
                )
            ],
            allow_proxying_rules=[
                soxy.ProxyingRule(
                    from_addresses=IPv4Address("127.0.0.1"),
                    to_addresses=IPv4Network("0.0.0.0/0"),
                ),
            ],
        ),
    ) as app:
        await app.serve_forever()


if __name__ == "__main__":
    asyncio.run(main())

Testing with curl

socks5:

curl -x "socks5://top:secret@127.0.0.1:1080" https://google.com -v

socks5h:

curl -x "socks5h://top:secret@127.0.0.1:1080" https://google.com -v

Configuration

Configuration File Structure

Configuration is specified in TOML format and consists of three main sections:

[proxy]

Proxy server settings:

  • protocol (string): SOCKS protocol ("socks4", "socks4a", "socks5", "socks5h")
  • transport (string): Transport protocol (currently only "tcp")

[proxy.auth] (optional)

Authentication settings (optional - if omitted, proxy works without authentication):

  • For SOCKS5/SOCKS5h: Dictionary mapping username to password
  • For SOCKS4/SOCKS4a: Dictionary with usernames as keys (values are ignored, only presence of username is checked)

Example:

[proxy.auth]
alice = "password123"
bob = "secret456"

[transport]

Transport layer settings:

  • host (string): IP address to listen on (e.g., "127.0.0.1" or "0.0.0.0")
  • port (number): Port to listen on (e.g., 1080)

[ruleset]

Access control rules:

  • connecting.allow: List of rules allowing client connections
  • connecting.block: List of rules blocking client connections
  • proxying.allow: List of rules allowing request proxying
  • proxying.block: List of rules blocking request proxying

Each rule contains:

  • from: Source IP address or network (IPv4/IPv6 address or CIDR)
  • to: Destination IP address, network, or domain name (only for proxying rules)

Full Configuration Example

[proxy]
protocol = "socks5"
transport = "tcp"

# Optional authentication (omit this section for no authentication)
[proxy.auth]
alice = "password123"
bob = "secret456"
charlie = "mypass789"

[transport]
host = "0.0.0.0"
port = 1080

# Connection rules
[[ruleset.connecting.allow]]
from = "127.0.0.1"

[[ruleset.connecting.allow]]
from = "192.168.1.0/24"

[[ruleset.connecting.block]]
from = "10.0.0.1"

# Proxying rules
[[ruleset.proxying.allow]]
from = "127.0.0.1"
to = "0.0.0.0/0"

[[ruleset.proxying.allow]]
from = "192.168.1.0/24"
to = "8.8.8.8"

[[ruleset.proxying.block]]
from = "127.0.0.1"
to = "10.0.0.0/8"

Usage Examples

Authentication

Via Configuration File (CLI)

The simplest way to configure authentication is through the configuration file:

[proxy]
protocol = "socks5"
transport = "tcp"

# Authentication dictionary: username -> password
[proxy.auth]
alice = "password123"
bob = "secret456"
charlie = "mypass789"

[transport]
host = "127.0.0.1"
port = 1080

[ruleset]
connecting = { allow = [], block = [] }
proxying = { allow = [], block = [] }

For SOCKS4/SOCKS4a (username only, no password):

[proxy]
protocol = "socks4"

[proxy.auth]
user1 = ""  # Value doesn't matter, only username presence is checked
user2 = ""
admin = ""

Note: Authentication is optional. If [proxy.auth] section is omitted, the proxy works without authentication.

Via Code (Programmatic API)

For SOCKS5 and SOCKS4 protocols, you can configure custom authentication programmatically:

async def async_auther(username: str, password: str) -> bool:
    # Asynchronous database check
    # ...
    return True

# Or synchronous function
def sync_auther(username: str, password: str) -> bool:
    return username in allowed_users and check_password(username, password)

protocol = soxy.Socks5(auther=async_auther)  # or sync_auther

Custom Resolver

For working with domain names (SOCKS5h, SOCKS4a), a resolver is required:

async def custom_resolver(domain_name: str) -> IPv4Address | None:
    # Custom resolution logic
    # For example, using DNS-over-HTTPS
    # ...
    return IPv4Address("1.2.3.4")

protocol = soxy.Socks5(
    resolver=custom_resolver,
)

Access Rules

The rule system allows flexible access control:

ruleset = soxy.Ruleset(
    # Allow connections only from local address
    allow_connecting_rules=[
        soxy.ConnectingRule(
            from_addresses=IPv4Address("127.0.0.1"),
        ),
    ],
    # Allow proxying to any addresses
    allow_proxying_rules=[
        soxy.ProxyingRule(
            from_addresses=IPv4Address("127.0.0.1"),
            to_addresses=IPv4Network("0.0.0.0/0"),
        ),
    ],
    # Block proxying to internal networks
    block_proxying_rules=[
        soxy.ProxyingRule(
            from_addresses=IPv4Network("0.0.0.0/0"),
            to_addresses=IPv4Network("10.0.0.0/8"),
        ),
        soxy.ProxyingRule(
            from_addresses=IPv4Network("0.0.0.0/0"),
            to_addresses=IPv4Network("192.168.0.0/16"),
        ),
    ],
)

Using Different Protocols

# SOCKS4
protocol = soxy.Socks4()

# SOCKS4a (with domain name support)
protocol = soxy.Socks4(resolver=resolver)

# SOCKS5 (without authentication)
protocol = soxy.Socks5()

# SOCKS5 with authentication
protocol = soxy.Socks5(auther=auther)

# SOCKS5h (with domain resolution on proxy side)
protocol = soxy.Socks5(resolver=resolver, auther=auther)

Development

Development Installation

git clone https://github.com/shpaker/soxyproxy.git
cd soxyproxy
pip install -e ".[dev]"

Running Tests

pytest

Code Quality

# Linting
ruff check .

# Type checking
mypy soxy

License

This project is licensed under the GPL-3.0 license.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

soxyproxy-0.10.0.tar.gz (28.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

soxyproxy-0.10.0-py3-none-any.whl (32.0 kB view details)

Uploaded Python 3

File details

Details for the file soxyproxy-0.10.0.tar.gz.

File metadata

  • Download URL: soxyproxy-0.10.0.tar.gz
  • Upload date:
  • Size: 28.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.18 {"installer":{"name":"uv","version":"0.9.18","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for soxyproxy-0.10.0.tar.gz
Algorithm Hash digest
SHA256 f6b8afd63d056ade7a8787891912820130cc002274e3984d3ad602baf69fcc3e
MD5 4af6595df0f14524cb3c714e8e96d32d
BLAKE2b-256 ac52e40c423225bf688badc58f557c8f62f71e93c5bd26af3865e2061b64e142

See more details on using hashes here.

File details

Details for the file soxyproxy-0.10.0-py3-none-any.whl.

File metadata

  • Download URL: soxyproxy-0.10.0-py3-none-any.whl
  • Upload date:
  • Size: 32.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.18 {"installer":{"name":"uv","version":"0.9.18","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for soxyproxy-0.10.0-py3-none-any.whl
Algorithm Hash digest
SHA256 5e9c7e49437777de923ff1891cf54160614d004323159dadb460006ecfde6cd4
MD5 7973cc07dab1aadc29a9050f79b52f40
BLAKE2b-256 8d2effdad534aef3eb44d94ef7c1ab1953e6ad11607caf83350e3ef66f5400d5

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page