Skip to content

Commit 3f2012c

Browse files
heyjoe127FreeOnlineUser
authored andcommitted
bitreq: add SOCKS5 proxy support
Add SOCKS5 proxy support (RFC 1928) alongside existing HTTP CONNECT. Targets are encoded per RFC 1928: IPv4 literals as ATYP 0x01, IPv6 literals as ATYP 0x04, and anything else as a domain name (ATYP 0x03) with DNS resolution left to the proxy. The latter enables .onion routing through Tor. New public API: - `Proxy::new_socks5(addr)` for unauthenticated SOCKS5. Accepts both `socks5://` and `socks5h://` URL schemes; both behave identically. - `Proxy::new_socks5_with_credentials(addr, user, pass)` for RFC 1929 username/password auth at construction. - `Proxy::set_credentials(user, pass)` to mutate credentials on an existing `Proxy`. Takes `String` so callers can hand over an existing allocation. Lets a caller hold one `Proxy` and rotate credentials for Tor circuit isolation without rebuilding from URL. Length validation is kind-aware: SOCKS5 enforces RFC 1929's 1-255 / 0-255 byte limits; HTTP Basic (RFC 7617) has no protocol-level limit. Internally, both proxy kinds expose a single `handshake_sync` / `handshake_async` entry point. The connection layer treats every proxy uniformly and no longer branches on type. Sync and async handshake bodies share protocol logic via two `macro_rules!` macros that template over `.await` insertion. Motivation: ldk-node needs to route HTTP calls (RGS, scoring) through Tor's SOCKS proxy. See lightningdevkit/ldk-node#834. Tests cover SOCKS5 parsing and credentials (including empty-user and length-limit edge cases per proxy kind), SOCKS5 mock-server handshakes (success, .onion, server rejection, port encoding, domain length, IPv4/IPv6 literal ATYP encoding, credential auth, rejection, no-auth, rotation), and HTTP CONNECT mock-server handshakes through the polymorphic dispatcher.
1 parent 774a655 commit 3f2012c

4 files changed

Lines changed: 1034 additions & 78 deletions

File tree

bitreq/README.md

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,29 @@ This crate is a fork for the very nice
77
rename it because I wanted to totally gut it and provide a crate with
88
different goals. Many thanks to the original author.
99

10-
Simple, minimal-dependency HTTP client. Optional features for http
11-
proxies (`proxy`), async support (`async`, `async-https`), and https
12-
with various TLS implementations (`https-rustls`, `https-rustls-probe`,
13-
and `https` which is an alias for `https-rustls`).
10+
Simple, minimal-dependency HTTP client. Optional features for HTTP
11+
proxies and SOCKS5 proxies (`proxy`), async support (`async`,
12+
`async-https`), and https with various TLS implementations
13+
(`https-rustls`, `https-rustls-probe`, and `https` which is an alias
14+
for `https-rustls`).
15+
16+
### Proxy Support
17+
18+
The `proxy` feature enables both HTTP CONNECT and SOCKS5 proxies:
19+
20+
```rust
21+
// HTTP CONNECT proxy
22+
let proxy = bitreq::Proxy::new_http("http://proxy.example.com:8080").unwrap();
23+
let response = bitreq::get("http://example.com").with_proxy(proxy).send();
24+
25+
// SOCKS5 proxy (e.g., Tor)
26+
let proxy = bitreq::Proxy::new_socks5("127.0.0.1:9050").unwrap();
27+
let response = bitreq::get("http://example.com").with_proxy(proxy).send();
28+
```
29+
30+
SOCKS5 proxies use domain-based addressing (RFC 1928 ATYP 0x03), so
31+
DNS resolution happens at the proxy. This is required for `.onion`
32+
routing through Tor.
1433

1534
Without any optional features, my casual testing indicates about 100
1635
KB additional executable size for stripped release builds using this

bitreq/src/connection.rs

Lines changed: 2 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ use std::sync::{Arc, Mutex};
1313
use std::task::{Context, Poll};
1414
use std::time::Instant;
1515

16-
#[cfg(all(feature = "async", feature = "proxy"))]
17-
use tokio::io::AsyncReadExt;
1816
#[cfg(feature = "async")]
1917
use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt, ReadHalf, WriteHalf};
2018
#[cfg(feature = "async")]
@@ -337,36 +335,8 @@ impl AsyncConnection {
337335
#[cfg(feature = "proxy")]
338336
match &params.proxy {
339337
Some(proxy) => {
340-
// do proxy things
341338
let mut tcp = Self::tcp_connect(&proxy.server, proxy.port).await?;
342-
343-
let proxy_request = proxy.connect(params.host, params.port);
344-
tcp.write_all(proxy_request.as_bytes()).await?;
345-
tcp.flush().await?;
346-
347-
// Max proxy response size to prevent unbounded memory allocation
348-
const MAX_PROXY_RESPONSE_SIZE: usize = 16 * 1024;
349-
let mut proxy_response = Vec::new();
350-
let mut buf = [0; 256];
351-
352-
loop {
353-
let n = tcp.read(&mut buf).await?;
354-
if n == 0 {
355-
// EOF reached
356-
break;
357-
}
358-
proxy_response.extend_from_slice(&buf[..n]);
359-
if proxy_response.len() > MAX_PROXY_RESPONSE_SIZE {
360-
return Err(Error::ProxyConnect);
361-
}
362-
if n < buf.len() {
363-
// Partial read indicates end of response
364-
break;
365-
}
366-
}
367-
368-
crate::Proxy::verify_response(&proxy_response)?;
369-
339+
proxy.handshake_async(&mut tcp, params.host, params.port).await?;
370340
Ok(tcp)
371341
}
372342
None => Self::tcp_connect(params.host, params.port).await,
@@ -710,35 +680,8 @@ impl Connection {
710680
#[cfg(feature = "proxy")]
711681
match &params.proxy {
712682
Some(proxy) => {
713-
// do proxy things
714683
let mut tcp = Self::tcp_connect(&proxy.server, proxy.port, timeout_at)?;
715-
716-
write!(tcp, "{}", proxy.connect(params.host, params.port))?;
717-
tcp.flush()?;
718-
719-
// Max proxy response size to prevent unbounded memory allocation
720-
const MAX_PROXY_RESPONSE_SIZE: usize = 16 * 1024;
721-
let mut proxy_response = Vec::new();
722-
let mut buf = [0; 256];
723-
724-
loop {
725-
let n = tcp.read(&mut buf)?;
726-
if n == 0 {
727-
// EOF reached
728-
break;
729-
}
730-
proxy_response.extend_from_slice(&buf[..n]);
731-
if proxy_response.len() > MAX_PROXY_RESPONSE_SIZE {
732-
return Err(Error::ProxyConnect);
733-
}
734-
if n < buf.len() {
735-
// Partial read indicates end of response
736-
break;
737-
}
738-
}
739-
740-
crate::Proxy::verify_response(&proxy_response)?;
741-
684+
proxy.handshake_sync(&mut tcp, params.host, params.port)?;
742685
Ok(tcp)
743686
}
744687
None => Self::tcp_connect(params.host, params.port, timeout_at),

0 commit comments

Comments
 (0)