Add SOCKS5 CONNECT support

This commit is contained in:
2026-10-29 00:26:15 +00:00
parent f191d66ee4
commit 488f25491d
10 changed files with 1145 additions and 65 deletions
+37 -1
View File
@@ -6,6 +6,8 @@ Hermes is a minimal HTTP/1.1 forward proxy written in Rust. It supports streamin
- Absolute-form HTTP forwarding (`GET http://host/path`)
- HTTPS and arbitrary TCP tunneling with `CONNECT host:port`
- Optional SOCKS5 CONNECT listener with no-auth and username/password authentication
- SOCKS5 IPv4, IPv6, and domain destinations with shared ACL enforcement
- Streaming request and response bodies
- Upstream connection pooling and configurable timeouts
- Ordered allow/deny ACLs using Basic authentication, client IP networks, and destination domains
@@ -67,7 +69,9 @@ The Gitea workflow in `.gitea/workflows/build.yaml` builds and publishes `linux/
```yaml
listen_addr: "127.0.0.1:8080"
socks5_listen_addr: "127.0.0.1:1080"
connect_timeout_ms: 10000
socks5_handshake_timeout_ms: 10000
upstream_timeout_ms: 30000
telemetry:
@@ -110,7 +114,9 @@ acl:
| Field | Default | Description |
| --- | --- | --- |
| `listen_addr` | `127.0.0.1:8080` | Downstream listen address |
| `socks5_listen_addr` | `null` | Optional SOCKS5 listen address; disabled when omitted |
| `connect_timeout_ms` | `10000` | TCP connection timeout for `CONNECT` and the HTTP connector |
| `socks5_handshake_timeout_ms` | `10000` | Timeout for SOCKS5 negotiation and CONNECT request parsing |
| `upstream_timeout_ms` | `30000` | Time limit for receiving upstream HTTP response headers |
| `telemetry` | see OpenTelemetry below | Logging, resource, and OTLP configuration |
| `acl.default_action` | `deny` | Action when no rule matches |
@@ -200,6 +206,35 @@ Password verification is synchronous and inexpensive, and digest comparisons are
**Basic authentication is not encrypted in transit.** A client sending Basic credentials over cleartext HTTP exposes them to the network. Use Basic authentication only on localhost or a trusted private network unless downstream TLS is added. Do not expose Hermes publicly without appropriate network controls.
## SOCKS5
Set `socks5_listen_addr` to enable a separate SOCKS5 listener alongside the HTTP proxy:
```yaml
listen_addr: "127.0.0.1:8080"
socks5_listen_addr: "127.0.0.1:1080"
socks5_handshake_timeout_ms: 10000
```
The listener supports SOCKS5 CONNECT with IPv4, IPv6, and domain destinations. Domain destinations are resolved by Hermes; use `--socks5-hostname` with curl to request proxy-side DNS resolution:
```sh
# Client-side DNS with an IP destination
curl --socks5 127.0.0.1:1080 http://198.51.100.10/
# Proxy-side DNS with a domain destination
curl --socks5-hostname 127.0.0.1:1080 http://example.com/
# Username/password authentication using the configured ACL user
curl --socks5-hostname 127.0.0.1:1080 \
--proxy-user 'alice:your-password' \
http://example.com/
```
SOCKS5 reuses the ACL's salted password hashes and applies `users`, `networks`, and `domains` before upstream dialing. Username/password negotiation is selected when required by the ACL; otherwise no-auth is used. Authentication is cleartext on the network, so restrict the listener to localhost or trusted networks. ACL denial returns SOCKS reply `0x02` before any upstream connection.
Only CONNECT is supported. SOCKS4, BIND, and UDP ASSOCIATE are not implemented. Docker users can publish the listener with `-p 127.0.0.1:1080:1080` when it is enabled.
## OpenTelemetry
Hermes exports traces, metrics, and logs independently to an OpenTelemetry Collector using OTLP/HTTP (protobuf). All settings come from `telemetry` in YAML; `OTEL_*` environment variables are not read.
@@ -247,6 +282,7 @@ In another terminal, run Hermes and send a request through it. The collector wil
- `proxy.upstream`: upstream HTTP client operation
- `proxy.connect`: `CONNECT` handshake and tunnel lifetime
- `proxy.upstream_connect`: upstream TCP connection establishment
- `socks5.connect`: SOCKS5 negotiation, authorization, upstream connection, and tunnel lifetime
For plain HTTP, incoming W3C `traceparent` and `tracestate` headers establish the parent context, and a new context is injected upstream. Trace context cannot be propagated inside an encrypted `CONNECT` tunnel. Request spans include the ACL decision, reason, and matched rule name without credentials.
@@ -264,7 +300,7 @@ Hermes uses standard HTTP instrument names where applicable and adds proxy-speci
- `hermes.proxy.tunnel.bytes`
- `hermes.proxy.acl.decisions`
Metric attributes are deliberately bounded. Raw URLs, destination hostnames, usernames, rule names, and client addresses are not metric attributes.
Metric attributes are deliberately bounded. Tunnel and ACL metrics include `proxy.protocol` to distinguish HTTP, HTTP CONNECT, and SOCKS5. Raw URLs, destination hostnames, usernames, rule names, and client addresses are not metric attributes.
### Logs