Skip to content

Commit a148dfb

Browse files
committed
Merge #76: Implement timeout for socks connection
7493630 Implement timeout for socks connection (Riccardo Casatta) Pull request description: To achieve this we have to include a modified socks library, with supports for timeout Remove the error `BothSocksAndTimeout` which isn't needed anymore ACKs for top commit: afilini: ACK 7493630 Tree-SHA512: ec1b1eba739bd954ba7811eef603cf60f3ba29d7efcf98a9eadc8dcb9b67a49fce364760718faf7b860e78b7f9eebdc4da65783e7af47792573d32cd6c76ff10
2 parents 195d23a + 7493630 commit a148dfb

File tree

10 files changed

+1464
-22
lines changed

10 files changed

+1464
-22
lines changed

Cargo.toml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,23 @@ serde = { version = "^1.0", features = ["derive"] }
2323
serde_json = { version = "^1.0" }
2424

2525
# Optional dependencies
26-
socks = { version = "0.3", optional = true }
2726
openssl = { version = "0.10", optional = true }
2827
rustls = { version = "0.20", optional = true, features = ["dangerous_configuration"] }
2928
webpki = { version = "0.22", optional = true }
3029
webpki-roots = { version = "0.22", optional = true }
3130

31+
byteorder = { version = "1.0", optional = true }
32+
33+
[target.'cfg(unix)'.dependencies]
34+
libc = { version = "0.2", optional = true }
35+
36+
[target.'cfg(windows)'.dependencies]
37+
winapi = { version="0.3.9", features=["winsock2"], optional = true }
38+
3239
[features]
3340
default = ["proxy", "use-rustls"]
3441
minimal = []
3542
debug-calls = []
36-
proxy = ["socks"]
43+
proxy = ["byteorder", "winapi", "libc"]
3744
use-rustls = ["webpki", "webpki-roots", "rustls"]
3845
use-openssl = ["openssl"]

src/client.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -111,9 +111,12 @@ impl ClientType {
111111
if url.starts_with("ssl://") {
112112
let url = url.replacen("ssl://", "", 1);
113113
let client = match config.socks5() {
114-
Some(socks5) => {
115-
RawClient::new_proxy_ssl(url.as_str(), config.validate_domain(), socks5)?
116-
}
114+
Some(socks5) => RawClient::new_proxy_ssl(
115+
url.as_str(),
116+
config.validate_domain(),
117+
socks5,
118+
config.timeout(),
119+
)?,
117120
None => {
118121
RawClient::new_ssl(url.as_str(), config.validate_domain(), config.timeout())?
119122
}
@@ -125,7 +128,11 @@ impl ClientType {
125128

126129
Ok(match config.socks5().as_ref() {
127130
None => ClientType::TCP(RawClient::new(url.as_str(), config.timeout())?),
128-
Some(socks5) => ClientType::Socks5(RawClient::new_proxy(url.as_str(), socks5)?),
131+
Some(socks5) => ClientType::Socks5(RawClient::new_proxy(
132+
url.as_str(),
133+
socks5,
134+
config.timeout(),
135+
)?),
129136
})
130137
}
131138
}

src/config.rs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,18 +45,12 @@ impl ConfigBuilder {
4545
/// Set the socks5 config if Some, it accept an `Option` because it's easier for the caller to use
4646
/// in a method chain
4747
pub fn socks5(mut self, socks5_config: Option<Socks5Config>) -> Result<Self, Error> {
48-
if socks5_config.is_some() && self.config.timeout.is_some() {
49-
return Err(Error::BothSocksAndTimeout);
50-
}
5148
self.config.socks5 = socks5_config;
5249
Ok(self)
5350
}
5451

5552
/// Sets the timeout
5653
pub fn timeout(mut self, timeout: Option<u8>) -> Result<Self, Error> {
57-
if timeout.is_some() && self.config.socks5.is_some() {
58-
return Err(Error::BothSocksAndTimeout);
59-
}
6054
self.config.timeout = timeout.map(|t| Duration::from_secs(t as u64));
6155
Ok(self)
6256
}

src/lib.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,23 @@ extern crate openssl;
3131
extern crate rustls;
3232
extern crate serde;
3333
extern crate serde_json;
34-
#[cfg(any(feature = "default", feature = "proxy"))]
35-
extern crate socks;
34+
3635
#[cfg(any(feature = "use-rustls", feature = "default"))]
3736
extern crate webpki;
3837
#[cfg(any(feature = "use-rustls", feature = "default"))]
3938
extern crate webpki_roots;
4039

40+
#[cfg(any(feature = "default", feature = "proxy"))]
41+
extern crate byteorder;
42+
43+
#[cfg(all(unix, any(feature = "default", feature = "proxy")))]
44+
extern crate libc;
45+
#[cfg(all(windows, any(feature = "default", feature = "proxy")))]
46+
extern crate winapi;
47+
48+
#[cfg(any(feature = "default", feature = "proxy"))]
49+
mod socks;
50+
4151
mod api;
4252
mod batch;
4353

src/raw_client.rs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ use rustls::{
3030
};
3131

3232
#[cfg(any(feature = "default", feature = "proxy"))]
33-
use socks::{Socks5Stream, TargetAddr, ToTargetAddr};
33+
use crate::socks::{Socks5Stream, TargetAddr, ToTargetAddr};
3434

3535
use stream::ClonableStream;
3636

@@ -396,16 +396,20 @@ impl RawClient<ElectrumProxyStream> {
396396
pub fn new_proxy<T: ToTargetAddr>(
397397
target_addr: T,
398398
proxy: &crate::Socks5Config,
399+
timeout: Option<Duration>,
399400
) -> Result<Self, Error> {
400-
let stream = match proxy.credentials.as_ref() {
401+
let mut stream = match proxy.credentials.as_ref() {
401402
Some(cred) => Socks5Stream::connect_with_password(
402403
&proxy.addr,
403404
target_addr,
404405
&cred.username,
405406
&cred.password,
407+
timeout,
406408
)?,
407-
None => Socks5Stream::connect(&proxy.addr, target_addr)?,
409+
None => Socks5Stream::connect(&proxy.addr, target_addr, timeout)?,
408410
};
411+
stream.get_mut().set_read_timeout(timeout)?;
412+
stream.get_mut().set_write_timeout(timeout)?;
409413

410414
Ok(stream.into())
411415
}
@@ -418,18 +422,22 @@ impl RawClient<ElectrumProxyStream> {
418422
target_addr: T,
419423
validate_domain: bool,
420424
proxy: &crate::Socks5Config,
425+
timeout: Option<Duration>,
421426
) -> Result<RawClient<ElectrumSslStream>, Error> {
422427
let target = target_addr.to_target_addr()?;
423428

424-
let stream = match proxy.credentials.as_ref() {
429+
let mut stream = match proxy.credentials.as_ref() {
425430
Some(cred) => Socks5Stream::connect_with_password(
426431
&proxy.addr,
427432
target_addr,
428433
&cred.username,
429434
&cred.password,
435+
timeout,
430436
)?,
431-
None => Socks5Stream::connect(&proxy.addr, target.clone())?,
437+
None => Socks5Stream::connect(&proxy.addr, target.clone(), timeout)?,
432438
};
439+
stream.get_mut().set_read_timeout(timeout)?;
440+
stream.get_mut().set_write_timeout(timeout)?;
433441

434442
RawClient::new_ssl_from_stream(target, validate_domain, stream.into_inner())
435443
}

src/socks/mod.rs

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
//! SOCKS proxy clients
2+
3+
use std::io;
4+
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs};
5+
use std::vec;
6+
7+
pub use self::v4::{Socks4Listener, Socks4Stream};
8+
pub use self::v5::{Socks5Datagram, Socks5Listener, Socks5Stream};
9+
10+
mod v4;
11+
mod v5;
12+
mod writev;
13+
14+
/// A description of a connection target.
15+
#[derive(Debug, Clone)]
16+
pub enum TargetAddr {
17+
/// Connect to an IP address.
18+
Ip(SocketAddr),
19+
/// Connect to a fully qualified domain name.
20+
///
21+
/// The domain name will be passed along to the proxy server and DNS lookup
22+
/// will happen there.
23+
Domain(String, u16),
24+
}
25+
26+
impl ToSocketAddrs for TargetAddr {
27+
type Iter = Iter;
28+
29+
fn to_socket_addrs(&self) -> io::Result<Iter> {
30+
let inner = match *self {
31+
TargetAddr::Ip(addr) => IterInner::Ip(Some(addr)),
32+
TargetAddr::Domain(ref domain, port) => {
33+
let it = (&**domain, port).to_socket_addrs()?;
34+
IterInner::Domain(it)
35+
}
36+
};
37+
Ok(Iter(inner))
38+
}
39+
}
40+
41+
enum IterInner {
42+
Ip(Option<SocketAddr>),
43+
Domain(vec::IntoIter<SocketAddr>),
44+
}
45+
46+
/// An iterator over `SocketAddr`s associated with a `TargetAddr`.
47+
pub struct Iter(IterInner);
48+
49+
impl Iterator for Iter {
50+
type Item = SocketAddr;
51+
52+
fn next(&mut self) -> Option<SocketAddr> {
53+
match self.0 {
54+
IterInner::Ip(ref mut addr) => addr.take(),
55+
IterInner::Domain(ref mut it) => it.next(),
56+
}
57+
}
58+
}
59+
60+
/// A trait for objects that can be converted to `TargetAddr`.
61+
pub trait ToTargetAddr {
62+
/// Converts the value of `self` to a `TargetAddr`.
63+
fn to_target_addr(&self) -> io::Result<TargetAddr>;
64+
}
65+
66+
impl ToTargetAddr for TargetAddr {
67+
fn to_target_addr(&self) -> io::Result<TargetAddr> {
68+
Ok(self.clone())
69+
}
70+
}
71+
72+
impl ToTargetAddr for SocketAddr {
73+
fn to_target_addr(&self) -> io::Result<TargetAddr> {
74+
Ok(TargetAddr::Ip(*self))
75+
}
76+
}
77+
78+
impl ToTargetAddr for SocketAddrV4 {
79+
fn to_target_addr(&self) -> io::Result<TargetAddr> {
80+
SocketAddr::V4(*self).to_target_addr()
81+
}
82+
}
83+
84+
impl ToTargetAddr for SocketAddrV6 {
85+
fn to_target_addr(&self) -> io::Result<TargetAddr> {
86+
SocketAddr::V6(*self).to_target_addr()
87+
}
88+
}
89+
90+
impl ToTargetAddr for (Ipv4Addr, u16) {
91+
fn to_target_addr(&self) -> io::Result<TargetAddr> {
92+
SocketAddrV4::new(self.0, self.1).to_target_addr()
93+
}
94+
}
95+
96+
impl ToTargetAddr for (Ipv6Addr, u16) {
97+
fn to_target_addr(&self) -> io::Result<TargetAddr> {
98+
SocketAddrV6::new(self.0, self.1, 0, 0).to_target_addr()
99+
}
100+
}
101+
102+
impl<'a> ToTargetAddr for (&'a str, u16) {
103+
fn to_target_addr(&self) -> io::Result<TargetAddr> {
104+
// try to parse as an IP first
105+
if let Ok(addr) = self.0.parse::<Ipv4Addr>() {
106+
return (addr, self.1).to_target_addr();
107+
}
108+
109+
if let Ok(addr) = self.0.parse::<Ipv6Addr>() {
110+
return (addr, self.1).to_target_addr();
111+
}
112+
113+
Ok(TargetAddr::Domain(self.0.to_owned(), self.1))
114+
}
115+
}
116+
117+
impl<'a> ToTargetAddr for &'a str {
118+
fn to_target_addr(&self) -> io::Result<TargetAddr> {
119+
// try to parse as an IP first
120+
if let Ok(addr) = self.parse::<SocketAddrV4>() {
121+
return addr.to_target_addr();
122+
}
123+
124+
if let Ok(addr) = self.parse::<SocketAddrV6>() {
125+
return addr.to_target_addr();
126+
}
127+
128+
// split the string by ':' and convert the second part to u16
129+
let mut parts_iter = self.rsplitn(2, ':');
130+
let port_str = match parts_iter.next() {
131+
Some(s) => s,
132+
None => {
133+
return Err(io::Error::new(
134+
io::ErrorKind::InvalidInput,
135+
"invalid socket address",
136+
))
137+
}
138+
};
139+
140+
let host = match parts_iter.next() {
141+
Some(s) => s,
142+
None => {
143+
return Err(io::Error::new(
144+
io::ErrorKind::InvalidInput,
145+
"invalid socket address",
146+
))
147+
}
148+
};
149+
150+
let port: u16 = match port_str.parse() {
151+
Ok(p) => p,
152+
Err(_) => {
153+
return Err(io::Error::new(
154+
io::ErrorKind::InvalidInput,
155+
"invalid port value",
156+
))
157+
}
158+
};
159+
160+
(host, port).to_target_addr()
161+
}
162+
}

0 commit comments

Comments
 (0)