Skip to content
This repository was archived by the owner on Sep 4, 2024. It is now read-only.

Commit ce8f318

Browse files
Add SOCKS5 Support for SimpleHttpTransport
This adds a new feature flag 'proxy', which converts the regular `TcpStream` of `SimpleHttpTransport`, in `Socks5Stream`. Which allows Socks5 procy connection in rpc calls. - Builder methods added for proxy params. - New `Client::http_proxy()` to create a proxy transport. - Update and add tests for proxy.
1 parent dca6844 commit ce8f318

File tree

3 files changed

+105
-2
lines changed

3 files changed

+105
-2
lines changed

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,13 @@ simple_http = [ "base64" ]
2323
simple_tcp = []
2424
# Basic transport over a raw UnixStream
2525
simple_uds = []
26+
# Enable Socks5 Proxy in transport
27+
proxy = ["socks"]
2628

2729

2830
[dependencies]
2931
serde = { version = "1", features = ["derive"] }
3032
serde_json = { version = "1", features = [ "raw_value" ] }
3133

3234
base64 = { version = "0.13.0", optional = true }
35+
socks = { version = "0.3.4", optional = true}

contrib/test.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/bin/sh -ex
22

3-
FEATURES="simple_http simple_tcp simple_uds"
3+
FEATURES="simple_http simple_tcp simple_uds proxy"
44

55
cargo --version
66
rustc --version

src/simple_http.rs

Lines changed: 101 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22
//! round-tripper that works with the bitcoind RPC server. This can be used
33
//! if minimal dependencies are a goal and synchronous communication is ok.
44
5+
#[cfg(feature = "proxy")]
6+
use socks::Socks5Stream;
57
use std::io::{BufRead, BufReader, Write};
8+
#[cfg(not(feature = "proxy"))]
69
use std::net::TcpStream;
710
use std::net::{SocketAddr, ToSocketAddrs};
811
use std::time::{Duration, Instant};
@@ -19,6 +22,9 @@ use crate::{Request, Response};
1922
/// Set to 8332, the default RPC port for bitcoind.
2023
pub const DEFAULT_PORT: u16 = 8332;
2124

25+
/// The Default SOCKS5 Port to use for proxy connection.
26+
pub const DEFAULT_PROXY_PORT: u16 = 9050;
27+
2228
/// Simple HTTP transport that implements the necessary subset of HTTP for
2329
/// running a bitcoind RPC client.
2430
#[derive(Clone, Debug)]
@@ -28,6 +34,10 @@ pub struct SimpleHttpTransport {
2834
timeout: Duration,
2935
/// The value of the `Authorization` HTTP header.
3036
basic_auth: Option<String>,
37+
#[cfg(feature = "proxy")]
38+
proxy_addr: net::SocketAddr,
39+
#[cfg(feature = "proxy")]
40+
proxy_auth: Option<(String, String)>,
3141
}
3242

3343
impl Default for SimpleHttpTransport {
@@ -40,6 +50,13 @@ impl Default for SimpleHttpTransport {
4050
path: "/".to_owned(),
4151
timeout: Duration::from_secs(15),
4252
basic_auth: None,
53+
#[cfg(feature = "proxy")]
54+
proxy_addr: net::SocketAddr::new(
55+
net::IpAddr::V4(net::Ipv4Addr::new(127, 0, 0, 1)),
56+
DEFAULT_PROXY_PORT,
57+
),
58+
#[cfg(feature = "proxy")]
59+
proxy_auth: None,
4360
}
4461
}
4562
}
@@ -61,6 +78,20 @@ impl SimpleHttpTransport {
6178
{
6279
// Open connection
6380
let request_deadline = Instant::now() + self.timeout;
81+
#[cfg(feature = "proxy")]
82+
let mut sock = if let Some((username, password)) = &self.proxy_auth {
83+
Socks5Stream::connect_with_password(
84+
self.proxy_addr,
85+
self.addr,
86+
username.as_str(),
87+
password.as_str(),
88+
)?
89+
.into_inner()
90+
} else {
91+
Socks5Stream::connect(self.proxy_addr, self.addr)?.into_inner()
92+
};
93+
94+
#[cfg(not(feature = "proxy"))]
6495
let mut sock = TcpStream::connect_timeout(&self.addr, self.timeout)?;
6596

6697
sock.set_read_timeout(Some(self.timeout))?;
@@ -365,6 +396,22 @@ impl Builder {
365396
self
366397
}
367398

399+
#[cfg(feature = "proxy")]
400+
/// Add proxy address to the transport for SOCKS5 proxy
401+
pub fn proxy_addr<S: AsRef<str>>(mut self, proxy_addr: S) -> Result<Self, Error> {
402+
// We don't expect path in proxy address.
403+
self.tp.proxy_addr = check_url(proxy_addr.as_ref())?.0;
404+
Ok(self)
405+
}
406+
407+
#[cfg(feature = "proxy")]
408+
/// Add optional proxy authentication as ('username', 'password')
409+
pub fn proxy_auth<S: AsRef<str>>(mut self, user: S, pass: S) -> Self {
410+
self.tp.proxy_auth =
411+
Some((user, pass)).map(|(u, p)| (u.as_ref().to_string(), p.as_ref().to_string()));
412+
self
413+
}
414+
368415
/// Builds the final `SimpleHttpTransport`
369416
pub fn build(self) -> SimpleHttpTransport {
370417
self.tp
@@ -390,11 +437,34 @@ impl crate::Client {
390437
}
391438
Ok(crate::Client::with_transport(builder.build()))
392439
}
440+
441+
#[cfg(feature = "proxy")]
442+
/// Create a new JSON_RPC client using a HTTP-Socks5 proxy transport.
443+
pub fn http_proxy(
444+
url: &str,
445+
user: Option<String>,
446+
pass: Option<String>,
447+
proxy_addr: &str,
448+
proxy_auth: Option<(&str, &str)>,
449+
) -> Result<crate::Client, Error> {
450+
let mut builder = Builder::new().url(url)?;
451+
if let Some(user) = user {
452+
builder = builder.auth(user, pass);
453+
}
454+
builder = builder.proxy_addr(proxy_addr)?;
455+
if let Some((user, pass)) = proxy_auth {
456+
builder = builder.proxy_auth(user, pass);
457+
}
458+
let tp = builder.build();
459+
Ok(crate::Client::with_transport(tp))
460+
}
393461
}
394462

395463
#[cfg(test)]
396464
mod tests {
397465
use std::net;
466+
#[cfg(feature = "proxy")]
467+
use std::str::FromStr;
398468

399469
use super::*;
400470
use crate::Client;
@@ -435,7 +505,14 @@ mod tests {
435505
"http://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]",
436506
];
437507
for u in &valid_urls {
438-
Builder::new().url(*u).unwrap_or_else(|_| panic!("error for: {}", u));
508+
let (addr, path) = check_url(u).unwrap();
509+
let builder = Builder::new().url(*u).unwrap_or_else(|_| panic!("error for: {}", u));
510+
assert_eq!(builder.tp.addr, addr);
511+
assert_eq!(builder.tp.path, path);
512+
assert_eq!(builder.tp.timeout, Duration::from_secs(15));
513+
assert_eq!(builder.tp.basic_auth, None);
514+
#[cfg(feature = "proxy")]
515+
assert_eq!(builder.tp.proxy_addr, SocketAddr::from_str("127.0.0.1:9050").unwrap());
439516
}
440517

441518
let invalid_urls = [
@@ -465,4 +542,27 @@ mod tests {
465542

466543
let _ = Client::simple_http("localhost:22", None, None).unwrap();
467544
}
545+
546+
#[cfg(feature = "proxy")]
547+
#[test]
548+
fn construct_with_proxy() {
549+
let tp = Builder::new()
550+
.timeout(Duration::from_millis(100))
551+
.url("localhost:22")
552+
.unwrap()
553+
.auth("user", None)
554+
.proxy_addr("127.0.0.1:9050")
555+
.unwrap()
556+
.build();
557+
let _ = Client::with_transport(tp);
558+
559+
let _ = Client::http_proxy(
560+
"localhost:22",
561+
None,
562+
None,
563+
"127.0.0.1:9050",
564+
Some(("user", "password")),
565+
)
566+
.unwrap();
567+
}
468568
}

0 commit comments

Comments
 (0)