4.0 KiB
4.0 KiB
s5p -- Project
Purpose
A lightweight SOCKS5 proxy server that chains connections through Tor and/or arbitrary proxy hops (SOCKS4, SOCKS5, HTTP CONNECT).
Motivation
Existing solutions (proxychains-ng) rely on LD_PRELOAD hacks, only work
on Linux, and intercept at the library level. s5p is a proper SOCKS5 server
that any application can use natively -- no injection required.
Architecture
TCP tunnel tunnel
Client -------> s5p -------> Hop 1 -------> Hop 2 -------> Target
SOCKS5 proto1 proto2 protoN
- server.py -- asyncio SOCKS5 server, bidirectional relay, signal handling, multi-pool orchestration
- proto.py -- protocol handshakes (SOCKS5, SOCKS4/4a, HTTP CONNECT), chain builder
- config.py -- YAML config loading, proxy URL parsing, API response parsing, pool/listener config
- pool.py -- named proxy pool (multi-source, health-tested, persistent, MITM filtering)
- http.py -- minimal async HTTP/1.1 client (GET/POST JSON, no external deps)
- connpool.py -- pre-warmed TCP connection pool to first chain hop
- api.py -- built-in HTTP control API (runtime metrics, multi-pool state, config reload)
- tor.py -- Tor control port integration (NEWNYM signaling, periodic circuit rotation)
- cli.py -- argparse CLI, logging setup, cProfile support
- metrics.py -- connection counters, per-listener latency, rate tracking (lock-free, asyncio-only)
Deployment
| Method | Command |
|---|---|
| Local venv | pip install -e . then s5p -c config/s5p.yaml |
| Container | make build && make up (Alpine, ~59MB) |
Container mounts ./src and ./config/s5p.yaml read-only, plus
~/.cache/s5p as /data for pool state and profile output.
No application code is baked into the image.
Dependencies
| Package | Purpose |
|---|---|
| pyyaml | Config file parsing |
All other functionality uses Python stdlib (asyncio, socket, struct).
Design Decisions
- No LD_PRELOAD -- clean SOCKS5 server, works with any client
- asyncio -- single-threaded event loop, efficient for I/O-bound proxying
- Domain passthrough -- never resolve DNS locally to prevent leaks
- Tor as a hop -- no special Tor handling; it's just
socks5://127.0.0.1:9050 - Graceful shutdown -- SIGTERM/SIGINT registered before startup for clean container stops
- Config split -- tracked example template, gitignored live config with real addresses
- Proxy pool -- multi-source (API + file), health-tested, persistent, auto-cleaned
- Weighted selection -- recently-tested proxies preferred via recency decay weight
- Failure backoff -- connection failures penalize proxy weight for 60s, avoids retry waste
- Stale expiry -- proxies dropped from sources evicted after 3 refresh cycles if not alive
- Chain pre-flight -- static chain tested before pool health tests; skip on failure
- Warm start -- trust cached alive state on restart, defer all health tests to background
- SIGHUP reload -- re-read config, update pool settings, re-fetch sources
- Dead reporting -- POST evicted proxies to upstream API for list quality feedback
- Connection semaphore -- cap concurrent connections to prevent fd exhaustion
- Async HTTP -- native asyncio HTTP client replaces blocking urllib, parallel fetches
- First-hop pool -- pre-warmed TCP connections to chain[0], stale-evicted, auto-refilled
- Control API -- built-in asyncio HTTP server, no Flask/external deps, disabled by default
- Tor integration -- control port NEWNYM signaling, periodic circuit rotation
- Multi-Tor -- round-robin traffic across multiple Tor nodes (
tor_nodes) - Multi-listener -- per-port chain depth and pool assignment
- Named pools -- independent proxy pools with per-listener binding (
proxy_pools:) - MITM filtering --
mitm: true/falsesource filter,?mitm=0/1API query param - Per-listener latency -- independent latency tracking per listener in
/status