feat: listener retry override, pool protocol filter, conn pool docs

- Per-listener `retries` overrides global default (0 = inherit)
- Pool-level `allowed_protos` filters proxies during merge
- Connection pooling documented in CHEATSHEET.md
- Both features exposed in /config and /status API responses
- 12 new tests (config parsing, API exposure, merge filtering)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
user
2026-02-21 20:35:14 +01:00
parent c1c92ddc39
commit 3593481b30
13 changed files with 674 additions and 120 deletions

View File

@@ -139,6 +139,26 @@ Each pool has independent health testing, state persistence, and source
refresh cycles. The `mitm` source filter adds `?mitm=0` or `?mitm=1` to
API requests.
### Pool protocol filter
Use `allowed_protos` to restrict a pool to specific proxy protocols.
Proxies not matching the list are silently dropped during merge, regardless
of source type (API or file).
```yaml
proxy_pools:
socks_only:
allowed_protos: [socks5] # reject http/socks4 proxies
sources:
- url: http://api:8081/proxies/all
any_proto:
sources:
- url: http://api:8081/proxies/all # no filter, accept all
```
Valid values: `socks5`, `socks4`, `http`. Visible in `/config` API response
when set.
### Backward compatibility
The singular `proxy_pool:` key still works -- it registers as pool `"default"`.
@@ -523,8 +543,8 @@ api_listen: 127.0.0.1:1081
s5p --api 127.0.0.1:1081 -c config/s5p.yaml
```
All responses are `application/json`. Errors return `{"error": "message"}` with
appropriate status code (400, 404, 405, 500).
Responses are `application/json` unless noted otherwise. Errors return
`{"error": "message"}` with appropriate status code (400, 404, 405, 500).
Settings that require a restart: `listen`, `chain`, `pool_size`, `pool_max_idle`, `api_listen`.
@@ -587,43 +607,90 @@ curl -s http://127.0.0.1:1081/status | jq .
#### `GET /metrics`
Full metrics counters with rate, latency percentiles, and per-listener breakdown.
Prometheus/OpenMetrics exposition format. Content-Type:
`application/openmetrics-text; version=1.0.0; charset=utf-8`.
```bash
curl -s http://127.0.0.1:1081/metrics | jq .
curl -s http://127.0.0.1:1081/metrics
```
```json
{
"connections": 1842,
"success": 1790,
"failed": 52,
"retries": 67,
"auth_failures": 0,
"active": 3,
"bytes_in": 52428800,
"bytes_out": 1073741824,
"uptime": 3661.2,
"rate": 4.72,
"latency": {
"count": 1000, "min": 45.2, "max": 2841.7,
"avg": 312.4, "p50": 198.3, "p95": 890.1, "p99": 1523.6
},
"listener_latency": {
"0.0.0.0:1080": {"count": 500, "min": 800.1, "max": 12400.3, "avg": 2100.5, "p50": 1800.2, "p95": 8200.1, "p99": 10500.3},
"0.0.0.0:1081": {"count": 300, "min": 400.5, "max": 5200.1, "avg": 1200.3, "p50": 1000.1, "p95": 3500.2, "p99": 4800.7}
}
}
```
# HELP s5p_connections Total connection attempts.
# TYPE s5p_connections counter
s5p_connections_total 1842
# HELP s5p_connections_success Connections successfully relayed.
# TYPE s5p_connections_success counter
s5p_connections_success_total 1790
# HELP s5p_connections_failed Connection failures.
# TYPE s5p_connections_failed counter
s5p_connections_failed_total 52
# HELP s5p_retries Connection retry attempts.
# TYPE s5p_retries counter
s5p_retries_total 67
# HELP s5p_auth_failures SOCKS5 authentication failures.
# TYPE s5p_auth_failures counter
s5p_auth_failures_total 0
# HELP s5p_bytes_in Bytes received from clients.
# TYPE s5p_bytes_in counter
s5p_bytes_in_total 52428800
# HELP s5p_bytes_out Bytes sent to clients.
# TYPE s5p_bytes_out counter
s5p_bytes_out_total 1073741824
# HELP s5p_active_connections Currently open connections.
# TYPE s5p_active_connections gauge
s5p_active_connections 3
# HELP s5p_uptime_seconds Seconds since server start.
# TYPE s5p_uptime_seconds gauge
s5p_uptime_seconds 3661.2
# HELP s5p_connection_rate Connections per second (rolling window).
# TYPE s5p_connection_rate gauge
s5p_connection_rate 4.72
# HELP s5p_pool_proxies_alive Alive proxies in pool.
# TYPE s5p_pool_proxies_alive gauge
s5p_pool_proxies_alive{pool="clean"} 30
s5p_pool_proxies_alive{pool="mitm"} 12
# HELP s5p_pool_proxies_total Total proxies in pool.
# TYPE s5p_pool_proxies_total gauge
s5p_pool_proxies_total{pool="clean"} 45
s5p_pool_proxies_total{pool="mitm"} 20
# HELP s5p_chain_latency_seconds Chain build latency in seconds.
# TYPE s5p_chain_latency_seconds summary
s5p_chain_latency_seconds{quantile="0.5"} 0.198300
s5p_chain_latency_seconds{quantile="0.95"} 0.890100
s5p_chain_latency_seconds{quantile="0.99"} 1.523600
s5p_chain_latency_seconds_count 1000
s5p_chain_latency_seconds_sum 312.400000
# EOF
```
| Field | Type | Description |
|-------|------|-------------|
| `retries` | int | Total retry attempts |
| `auth_failures` | int | SOCKS5 auth failures |
| `latency` | object/null | Aggregate latency stats (ms), null if no samples |
| `latency.count` | int | Number of samples in buffer (max 1000) |
| `latency.p50/p95/p99` | float | Percentile latency (ms) |
| `listener_latency` | object | Per-listener latency, keyed by `host:port` |
**Metrics exposed:**
| Metric | Type | Labels | Description |
|--------|------|--------|-------------|
| `s5p_connections` | counter | -- | Total connection attempts |
| `s5p_connections_success` | counter | -- | Successfully relayed |
| `s5p_connections_failed` | counter | -- | Connection failures |
| `s5p_retries` | counter | -- | Retry attempts |
| `s5p_auth_failures` | counter | -- | SOCKS5 auth failures |
| `s5p_bytes_in` | counter | -- | Bytes received from clients |
| `s5p_bytes_out` | counter | -- | Bytes sent to clients |
| `s5p_active_connections` | gauge | -- | Currently open connections |
| `s5p_uptime_seconds` | gauge | -- | Seconds since server start |
| `s5p_connection_rate` | gauge | -- | Connections/sec (rolling window) |
| `s5p_pool_proxies_alive` | gauge | `pool` | Alive proxies per pool |
| `s5p_pool_proxies_total` | gauge | `pool` | Total proxies per pool |
| `s5p_chain_latency_seconds` | summary | `quantile` | Chain build latency (p50/p95/p99) |
| `s5p_listener_chain_latency_seconds` | summary | `listener`, `quantile` | Per-listener chain latency |
**Prometheus scrape config:**
```yaml
scrape_configs:
- job_name: s5p
metrics_path: /metrics
static_configs:
- targets: ["127.0.0.1:1081"]
```
#### `GET /pool`
@@ -892,6 +959,31 @@ retries: 5 # try up to 5 different proxies per connection
s5p -r 5 -C socks5://127.0.0.1:9050 -S http://api:8081/proxies
```
### Per-listener retry override
Each listener can override the global `retries` setting. Set `retries` on
a listener to use a different retry count for that port. A value of 0 (or
omitting the key) inherits the global setting.
```yaml
retries: 3 # global default
listeners:
- listen: 0.0.0.0:1080
retries: 5 # deep chain: more retries
chain:
- socks5://127.0.0.1:9050
- pool
- pool
- listen: 0.0.0.0:1082
chain:
- socks5://127.0.0.1:9050 # Tor only: uses global retries=3
```
The effective retry count for a listener is `listener.retries` if set,
otherwise `config.retries`. Visible in `/config` and `/status` API responses
when overridden.
## Hot Reload
Send `SIGHUP` to reload the config file without restarting:
@@ -941,48 +1033,11 @@ metrics: conn=1842 ok=1790 fail=52 retries=67 active=3 in=50.0M out=1.0G rate=4.
| `up` | Server uptime |
| `pool` | Alive/total proxies (only when pool is active) |
### `/metrics` JSON response
### `/metrics` OpenMetrics endpoint
`GET /metrics` returns all counters plus rate, latency percentiles, and
per-listener latency breakdowns:
```json
{
"connections": 1842,
"success": 1790,
"failed": 52,
"retries": 67,
"active": 3,
"bytes_in": 52428800,
"bytes_out": 1073741824,
"uptime": 3661.2,
"rate": 4.72,
"latency": {
"count": 1000,
"min": 45.2,
"max": 2841.7,
"avg": 312.4,
"p50": 198.3,
"p95": 890.1,
"p99": 1523.6
},
"listener_latency": {
"0.0.0.0:1080": {"count": 500, "min": 800.1, "max": 12400.3, "avg": 2100.5, "p50": 1800.2, "p95": 8200.1, "p99": 10500.3},
"0.0.0.0:1081": {"count": 300, "min": 400.5, "max": 5200.1, "avg": 1200.3, "p50": 1000.1, "p95": 3500.2, "p99": 4800.7},
"0.0.0.0:1082": {"count": 200, "min": 150.2, "max": 2000.1, "avg": 500.3, "p50": 400.1, "p95": 1200.5, "p99": 1800.2}
}
}
```
| Field | Type | Description |
|-------|------|-------------|
| `rate` | float | Connections/sec (rolling window of last 256 events) |
| `latency` | object/null | Aggregate chain setup latency in ms (null if no samples) |
| `latency.count` | int | Number of samples in buffer (max 1000) |
| `latency.p50` | float | Median latency (ms) |
| `latency.p95` | float | 95th percentile (ms) |
| `latency.p99` | float | 99th percentile (ms) |
| `listener_latency` | object | Per-listener latency, keyed by `host:port` |
`GET /metrics` returns all counters, gauges, pool stats, and latency summaries
in OpenMetrics format (see [API Reference](#get-metrics) above). Use `/status`
for the JSON equivalent with aggregate data.
### Per-listener latency