Commit Graph

66 Commits

Author SHA1 Message Date
user
d4e3638143 feat: per-listener latency tracking
Each listener now tracks chain setup latency independently via a
dict[str, LatencyTracker] on Metrics. The global aggregate stays for
summary output. /status embeds per-listener latency on each listener
entry; /metrics includes a listener_latency map keyed by host:port.
2026-02-18 08:14:09 +01:00
user
b8f7217e43 feat: connection rate and chain latency metrics
Add RateTracker (rolling deque, events/sec) and LatencyTracker (circular
buffer, p50/p95/p99 in ms) to the Metrics class.  Both are recorded in
_handle_client and exposed via summary(), to_dict(), /status, and /metrics.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 00:16:46 +01:00
user
e7de479c88 fix: enable cprofile in compose command 2026-02-17 22:37:09 +01:00
user
28c9830f56 docs: reorder listeners -- deepest chain on default port
:1080 = Tor + 2 pool hops, :1081 = Tor + 1, :1082 = Tor only.
2026-02-17 22:06:40 +01:00
user
7dc3926f48 feat: multi-listener with configurable proxy chaining
Each listener binds to its own port with an independent chain.
The "pool" keyword in a chain appends a random alive proxy from
the shared pool; multiple pool entries = multiple hops.

  :1080 -> Tor only (0 pool hops)
  :1081 -> Tor + 1 pool proxy
  :1082 -> Tor + 2 pool proxies

Shared resources (ProxyPool, Tor, metrics, semaphore, API) are
reused across listeners. FirstHopPool is shared per unique first
hop. Backward compatible: old listen/chain format still works.
2026-02-17 22:03:37 +01:00
user
ba60d087c0 fix: mark proxies alive incrementally during health tests
Proxies that pass the TLS handshake are now immediately added to the
alive list instead of waiting for the entire batch to complete. On
cold start with large pools, this means proxies become available
within seconds rather than waiting 30+ minutes.
2026-02-17 19:02:36 +01:00
user
aac69f6a3e fix: always defer health tests to background on startup
Cold start with large pools blocked the server for the entire test
duration. Now start() always defers health testing so the server
listens immediately. Proxies become available as tests complete.
2026-02-17 18:58:06 +01:00
user
6d9a21ac02 docs: update TASKS.md with TLS health check completion 2026-02-17 18:30:02 +01:00
user
e78fc8dc3c feat: replace HTTP health check with TLS handshake
Replace _http_check (HTTP GET to httpbin.org) with _tls_check that
performs a TLS handshake through the proxy chain. Multiple targets
(google, cloudflare, amazon) rotated round-robin eliminate the single
point of failure. Lighter, faster, harder to block than HTTP.

- Add test_targets config field (replaces test_url)
- Backward compat: legacy test_url extracts hostname automatically
- Add ssl.create_default_context() and round-robin index to ProxyPool
- Update docs (example.yaml, USAGE.md, CHEATSHEET.md)
2026-02-17 18:26:21 +01:00
user
3638c607da fix: show exception class name when pool source error message is empty
TimeoutError.__str__() returns '' in Python, causing truncated log
lines like "source ... failed: ". Fall back to the class name.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 18:00:11 +01:00
e6c82ad3c0 Merge pull request 'feat: control API and Tor integration' (#1) from feat/control-api into main
Reviewed-on: #1
2026-02-17 09:56:06 +00:00
user
6c84a144c0 feat: add --tracemalloc flag for memory profiling
Uses Python's built-in tracemalloc module to show top N memory
allocators on exit. Orthogonal to --cprofile; both can run together.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 10:43:47 +01:00
user
d2df32fdab docs: document Tor control port integration
Adds Tor control port section to USAGE.md covering config,
auth modes, rate limiting, and API endpoints. Updates README
feature line and config example, CHEATSHEET with tor snippets,
and marks the feature complete in TASKS.md and ROADMAP.md.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 20:09:51 +01:00
user
f0281c4069 test: Tor controller and API endpoint tests
Covers: password/cookie/bare auth, auth failure, connect failure,
NEWNYM success/rate-limiting/reconnect, GETINFO multi-line parsing,
start/stop lifecycle, GET /tor status, POST /tor/newnym dispatch,
and TorConfig YAML parsing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 20:09:05 +01:00
user
ff217be9c8 feat: wire Tor controller into server and API
Start/stop TorController in serve() lifecycle when tor: config
is present. Adds GET /tor (status) and POST /tor/newnym (signal)
endpoints to the control API. Logs control address at startup.
Adds tor: section and api_listen to example config.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 20:07:18 +01:00
user
b07135ad44 feat: Tor control port client with NEWNYM support
Async TCP client for the Tor control protocol (port 9051).
Supports password, cookie, and bare authentication. Provides
NEWNYM signaling with client-side 10s rate limiting and
optional periodic timer. Auto-reconnects on disconnect.

Adds TorConfig dataclass and YAML parsing to config module.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 20:06:07 +01:00
user
c939101a73 docs: document control API
Add API section to README (features, CLI, config), PROJECT (architecture),
USAGE (full endpoint reference with examples), CHEATSHEET (curl one-liners).
Update TASKS and ROADMAP.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 19:07:10 +01:00
user
4ee2cf5bb0 test: add control API tests
29 tests covering request parsing, JSON response format, all GET/POST
handlers with mock context, 404/405 error routing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 19:05:28 +01:00
user
b72d083f56 feat: wire control API into server and config
Add api_host/api_port to Config dataclass, parse api_listen key in
load_config(), add --api [HOST:]PORT CLI flag. Start/stop API server
in serve() alongside the SOCKS5 listener.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 19:03:44 +01:00
user
ecf9a840e4 feat: add control API module
Lightweight asyncio HTTP handler for runtime inspection and management.
Endpoints: /status, /metrics, /pool, /pool/alive, /config (GET) and
/reload, /pool/test, /pool/refresh (POST). Raw HTTP/1.1 parsing, JSON
responses, no new dependencies.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 19:02:43 +01:00
user
66fc76ceb3 merge: codebase consolidation and startup fixes
11 commits: deduplicate constants/parsing, consolidate health-check
logic, remove legacy ProxySource layer, instant warm start, early
signal handler registration, k8s-file logging driver.

60 tests passing, ruff clean, zero behavior changes.
2026-02-15 22:24:35 +01:00
user
a1fc19fb45 docs: update for codebase consolidation and startup fixes
- Remove source.py from architecture (deleted)
- Add metrics.py to module list
- Update warm start: trusts cached state, instant startup
- Update signal handling: registered before startup
- Add refactoring tasks to TASKS.md
- Remove stale troubleshooting entry

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 22:20:17 +01:00
user
4ae40fe0a1 feat: use k8s-file logging driver with 10MB rotation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 22:14:23 +01:00
user
6881c7d862 fix: register signal handlers before pool startup
Move SIGTERM/SIGINT handler registration to the top of serve()
so signals are never ignored during slow startup phases (cold
start health tests, source fetching). Previously, signals sent
before pool.start() returned had no handler, causing podman to
escalate to SIGKILL after the stop timeout.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 22:09:12 +01:00
user
590126bcf8 fix: defer health tests on warm start for instant startup
On warm start, trust persisted alive state and serve immediately.
Health tests run in background. Cold start behavior unchanged.

Previously warm start blocked on testing all cached-alive proxies
before binding the listen port, causing multi-minute delays when
the chain or proxies were slow to respond.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 22:01:18 +01:00
user
5418b30441 test: add tests for extracted parse_api_proxies
Cover valid entries, invalid proto/port, missing keys, and
mixed valid/invalid input.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 21:51:55 +01:00
user
6d9a4a2503 refactor: remove legacy ProxySource layer
Delete source.py, ProxySourceConfig, and Config.proxy_source.
ProxyPool fully supersedes ProxySource. The YAML backward-compat
conversion in load_config is preserved so old configs still work.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 21:51:21 +01:00
user
a99b318bfb refactor: rename ambiguous variables in config loader
pp -> pool_raw, ps -> src_raw for clarity.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 21:49:44 +01:00
user
2864ee6743 refactor: replace ensure_future with create_task
Use the modern asyncio.create_task() in pool.py and server.py.
Replace redundant list comprehension with list() in evict_keys copy.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 21:49:13 +01:00
user
18789bbc63 refactor: remove threading from metrics
The server is pure asyncio (single-threaded). The threading.Lock
was never contended. Use a float accumulator in _human_bytes to
avoid the int-to-float type: ignore.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 21:48:47 +01:00
user
210d3539f1 refactor: consolidate health-check HTTP logic in pool
Extract _http_check() to deduplicate identical HTTP GET + status
parsing between _test_proxy and _test_chain.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 21:48:22 +01:00
user
4582f54b5b refactor: extract shared proxy parsing and constants
Move VALID_PROTOS to config.py as single source of truth.
Add parse_api_proxies() to eliminate duplicate API response
parsing in pool.py and source.py.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 21:47:46 +01:00
user
40560ef6dd docs: document connection limit, async fetch, and connection pool
Update USAGE, CHEATSHEET, README, PROJECT, and TASKS for the three
performance improvements: max_connections semaphore, async HTTP
source fetching, and first-hop TCP connection pool.
2026-02-15 17:56:08 +01:00
user
248f5c3306 feat: pre-warmed TCP connection pool to first hop
Add FirstHopPool that maintains a deque of pre-established TCP
connections to chain[0]. Connections idle beyond pool_max_idle are
evicted; a background task refills to pool_size. build_chain() tries
the pool first, falls back to open_connection. Enabled with
pool_size > 0 in config. Only pools the TCP handshake -- SOCKS/HTTP
tunnels are consumed, not returned.
2026-02-15 17:56:03 +01:00
user
903cb38b9f feat: async HTTP client and parallel source fetching
Replace blocking urllib with a minimal async HTTP/1.1 client (http.py)
using asyncio streams. Pool source fetches now run in parallel via
asyncio.gather. Dead proxy reporting uses async POST. Handles
Content-Length, chunked transfer-encoding, and connection-close bodies.
No new dependencies.
2026-02-15 17:55:56 +01:00
user
714e8efb3d feat: cap concurrent connections with semaphore
Add max_connections config (default 256) with -m/--max-connections CLI
flag. Server wraps on_client in asyncio.Semaphore to prevent fd
exhaustion under load. Value reloads on SIGHUP; active connections
drain normally. Also adds pool_size/pool_max_idle config fields and
first_hop_pool wiring in server.py (used by next commits), and fixes
asyncio.TimeoutError -> TimeoutError lint warnings.
2026-02-15 17:55:50 +01:00
user
076213a830 docs: document data volume mount and container profiling
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 17:05:51 +01:00
user
11b2bdcb4f chore: disable cProfile in compose by default
Keep the command as a comment for easy re-enable when needed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 17:04:59 +01:00
user
de7a5906ae chore: add data volume and cProfile to compose
Mount ~/.cache/s5p as /data for pool state and profile output.
Enable cProfile by default, dumping to /data/s5p.prof.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 17:02:10 +01:00
user
d6f3850614 fix: set root logger handler level on SIGHUP log_level reload
basicConfig creates a StreamHandler with its own level filter.
Changing only the logger levels left the handler filtering at the
original level, so debug messages were silently dropped after reload.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 16:26:32 +01:00
user
a5e634e406 docs: update project docs for SIGHUP reload and dead proxy reporting
Add hot reload section to USAGE with reloadable settings table.
Add dead proxy reporting section with report_url config and payload
format. Update example.yaml, ROADMAP, TASKS, TODO, CHEATSHEET.
2026-02-15 16:05:39 +01:00
user
650db64d70 feat: add dead proxy reporting to source API
When report_url is configured, POST evicted proxy list as JSON after
each health test cycle. Fire-and-forget: failures are logged at debug
level. Payload format: {"dead": [{"proto": "socks5", "proxy": "host:port"}]}.
2026-02-15 16:04:19 +01:00
user
818169758b feat: add SIGHUP hot config reload
On SIGHUP, re-read the YAML config file and update mutable runtime
settings: timeout, retries, log_level, and pool config (sources,
intervals, thresholds). Pool triggers an immediate source re-fetch.
Listen address and chain require restart.
2026-02-15 16:02:57 +01:00
user
fc1dea70f4 docs: update project docs for warm start and chain health check
Add warm start and chain pre-flight sections to USAGE. Mark both
features complete in ROADMAP and TASKS. Remove implemented items
from TODO. Update README, PROJECT, and CHEATSHEET.
2026-02-15 16:00:23 +01:00
user
0816a7f0cb feat: add static chain health check before pool tests
Test the static chain (without pool proxy) before running pool health
tests. If the chain itself is unreachable, skip proxy testing and log a
clear warning. Prevents false mass-failure when the issue is upstream
(e.g., Tor is down), not the exit proxies.
2026-02-15 15:59:26 +01:00
user
8e2d6a654a feat: add fast warm start with deferred full health test
On warm start (state has alive proxies), only quick-test the
previously-alive subset before serving. Full health test runs in
background. Cold start behavior unchanged (test all before serving).
Reduces startup blocking from minutes to seconds on warm restarts.
2026-02-15 15:58:22 +01:00
user
eddcc5f615 docs: update project docs for backoff, stale expiry, pool metrics
Add failure backoff and stale expiry sections to USAGE. Document pool=
field in metrics output. Update ROADMAP, TASKS, TODO with completed
items and remaining suggestions. Add metrics example to CHEATSHEET.
2026-02-15 15:56:06 +01:00
user
8aa384a80b feat: add pool stats to periodic metrics log
Append pool=alive/total to the 60-second metrics summary and shutdown
log. Pool health is now visible without waiting for health test cycles.
2026-02-15 15:54:52 +01:00
user
e1403a67fc feat: add stale proxy expiry based on last_seen TTL
Evict proxies not returned by sources for >3 refresh cycles and not
currently alive. Cleans up proxies removed upstream faster than waiting
for max_fails consecutive health test failures.
2026-02-15 15:54:17 +01:00
user
4801e70b93 feat: add per-proxy backoff after connection failure
Track last_fail timestamp on ProxyEntry. When a connection attempt fails
in server.py, report_failure() records the time. The selection weight
multiplies by min(fail_age/60, 1.0), ramping back from floor over 60s.
Prevents wasting retries on proxies that just failed.
2026-02-15 15:53:39 +01:00