Skip to content

Commit cb8bd1f

Browse files
committed
Add connect timeout option
1 parent bf6115e commit cb8bd1f

File tree

5 files changed

+136
-54
lines changed

5 files changed

+136
-54
lines changed

CONTRIBUTING.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Contributing
2+
3+
## License
4+
5+
Licensed under either of
6+
7+
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
8+
* MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
9+
10+
at your option.
11+
12+
### Contribution
13+
14+
Unless you explicitly state otherwise, any contribution intentionally
15+
submitted for inclusion in the work by you, as defined in the Apache-2.0
16+
license, shall be dual licensed as above, without any additional terms or
17+
conditions.

README.md

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -77,19 +77,3 @@ let mut connection = Socks5Datagram::bind(PROXY, &TARGET).unwrap();
7777
let buf = [126_u8; 50]
7878
connection.send_to(&buf, &OTHER_ADDR);
7979
```
80-
81-
## License
82-
83-
Licensed under either of
84-
85-
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
86-
* MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
87-
88-
at your option.
89-
90-
### Contribution
91-
92-
Unless you explicitly state otherwise, any contribution intentionally
93-
submitted for inclusion in the work by you, as defined in the Apache-2.0
94-
license, shall be dual licensed as above, without any additional terms or
95-
conditions.

src/error.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::{fmt::Formatter, io, net::SocketAddrV6, string::FromUtf8Error};
33
/// Errors from socks2
44
///
55
/// # Notes
6-
/// `Error` implements PartialEq, but it does not compare fields.
6+
/// `Error` implements `PartialEq`, but it does not compare fields.
77
#[derive(Debug)]
88
#[non_exhaustive]
99
#[allow(missing_docs)]
@@ -15,6 +15,8 @@ pub enum Error {
1515
InvalidPortValue { addr: String, port: String },
1616

1717
// Socks4/Socks5
18+
/// Could not resolve the first socket address.
19+
NoResolveSocketAddr {},
1820
/// Response from server had an invalid version byte.
1921
InvalidResponseVersion { version: u8 },
2022
/// Unknown response code
@@ -101,6 +103,7 @@ impl PartialEq for Error {
101103
peq!(
102104
InvalidSocksAddress,
103105
InvalidPortValue,
106+
NoResolveSocketAddr,
104107
InvalidResponseVersion,
105108
UnknownResponseCode,
106109
ConnectionRefused,
@@ -142,6 +145,7 @@ impl From<Error> for io::Error {
142145
from_error!(
143146
(InvalidSocksAddress, InvalidInput),
144147
(InvalidPortValue, InvalidInput),
148+
(NoResolveSocketAddr, InvalidInput),
145149
(InvalidResponseVersion, InvalidData),
146150
(UnknownResponseCode, Other),
147151
(ConnectionRefused, ConnectionRefused),
@@ -179,6 +183,7 @@ impl std::fmt::Display for Error {
179183
Self::InvalidPortValue { addr, port } => {
180184
write!(f, "invalid port value '{port}' for '{addr}'")
181185
},
186+
Self::NoResolveSocketAddr {} => write!(f, "could not resolve a socket address"),
182187
Self::InvalidResponseVersion { version } => write!(f, "invalid response version '{version}'"),
183188
Self::UnknownResponseCode { code } => write!(f, "unknown response code '{code}'"),
184189
Self::ConnectionRefused { code } => write!(f, "connection refused or the request was rejected or failed '{code}'"),

src/v4.rs

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,10 @@ pub mod client {
4444
io,
4545
io::{Read, Write},
4646
net::{Ipv4Addr, SocketAddr, SocketAddrV4, TcpStream, ToSocketAddrs},
47+
time::Duration,
4748
};
4849

49-
/// A SOCKS4 client.
50+
/// A SOCKS4 and SOCKS4A client.
5051
#[derive(Debug)]
5152
pub struct Socks4Stream {
5253
pub(super) socket: TcpStream,
@@ -65,25 +66,41 @@ pub mod client {
6566
///
6667
/// # Errors
6768
/// - `io::Error(std::io::ErrorKind::*, socks2::Error::*?)`
68-
pub fn connect<T, U>(proxy: T, target: &U, userid: &str) -> io::Result<Self>
69+
pub fn connect<T, U>(
70+
proxy: T,
71+
target: &U,
72+
userid: &str,
73+
timeout: Option<Duration>,
74+
) -> io::Result<Self>
6975
where
7076
T: ToSocketAddrs,
7177
U: ToTargetAddr,
7278
{
73-
Self::connect_raw(1, proxy, target, userid)
79+
Self::connect_raw(1, proxy, target, userid, timeout)
7480
}
7581

7682
pub(super) fn connect_raw<T, U>(
7783
command: u8,
7884
proxy: T,
7985
target: &U,
8086
userid: &str,
87+
timeout: Option<Duration>,
8188
) -> io::Result<Self>
8289
where
8390
T: ToSocketAddrs,
8491
U: ToTargetAddr,
8592
{
86-
let mut socket = TcpStream::connect(proxy)?;
93+
let mut socket = match timeout {
94+
None => TcpStream::connect(proxy)?,
95+
// TODO: Should filter to ipv4 only? Since SOCKS4 only supports that.
96+
Some(t) => TcpStream::connect_timeout(
97+
&proxy
98+
.to_socket_addrs()?
99+
.next()
100+
.ok_or_else(|| Error::NoResolveSocketAddr {}.into_io())?,
101+
t,
102+
)?,
103+
};
87104

88105
let target = target.to_target_addr()?;
89106

@@ -186,9 +203,10 @@ pub mod bind {
186203
use std::{
187204
io,
188205
net::{SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs},
206+
time::Duration,
189207
};
190208

191-
/// A SOCKS4 BIND client.
209+
/// A SOCKS4 and SOCKS4A BIND client.
192210
#[derive(Debug)]
193211
pub struct Socks4Listener(Socks4Stream);
194212

@@ -200,12 +218,17 @@ pub mod bind {
200218
///
201219
/// # Errors
202220
/// - `io::Error(std::io::ErrorKind::*, socks2::Error::*?)`
203-
pub fn bind<T, U>(proxy: T, target: &U, userid: &str) -> io::Result<Self>
221+
pub fn bind<T, U>(
222+
proxy: T,
223+
target: &U,
224+
userid: &str,
225+
timeout: Option<Duration>,
226+
) -> io::Result<Self>
204227
where
205228
T: ToSocketAddrs,
206229
U: ToTargetAddr,
207230
{
208-
Socks4Stream::connect_raw(2, proxy, target, userid).map(Socks4Listener)
231+
Socks4Stream::connect_raw(2, proxy, target, userid, timeout).map(Socks4Listener)
209232
}
210233

211234
/// The address of the proxy-side TCP listener.
@@ -254,6 +277,7 @@ mod test {
254277
use std::{
255278
io::{Read, Write},
256279
net::{SocketAddr, SocketAddrV4, TcpStream, ToSocketAddrs},
280+
time::Duration,
257281
};
258282

259283
const PROXY_ADDR: &str = "127.0.0.1:1084";
@@ -272,7 +296,7 @@ mod test {
272296
#[test]
273297
#[cfg(feature = "client")]
274298
fn google() {
275-
let mut socket = Socks4Stream::connect(PROXY_ADDR, &google_ip(), "").unwrap();
299+
let mut socket = Socks4Stream::connect(PROXY_ADDR, &google_ip(), "", None).unwrap();
276300

277301
socket.write_all(b"GET / HTTP/1.0\r\n\r\n").unwrap();
278302
let mut result = vec![];
@@ -287,7 +311,13 @@ mod test {
287311
#[ignore] // dante doesn't support SOCKS4A
288312
#[cfg(feature = "client")]
289313
fn google_dns() {
290-
let mut socket = Socks4Stream::connect(PROXY_ADDR, &"google.com:80", "").unwrap();
314+
let mut socket = Socks4Stream::connect(
315+
PROXY_ADDR,
316+
&"google.com:80",
317+
"",
318+
Some(Duration::from_secs(10)),
319+
)
320+
.unwrap();
291321

292322
socket.write_all(b"GET / HTTP/1.0\r\n\r\n").unwrap();
293323
let mut result = vec![];
@@ -302,10 +332,10 @@ mod test {
302332
#[cfg(feature = "bind")]
303333
fn bind() {
304334
// First figure out our local address that we'll be connecting from
305-
let socket = Socks4Stream::connect(PROXY_ADDR, &google_ip(), "").unwrap();
335+
let socket = Socks4Stream::connect(PROXY_ADDR, &google_ip(), "", None).unwrap();
306336
let addr = socket.proxy_addr();
307337

308-
let listener = Socks4Listener::bind(PROXY_ADDR, &addr, "").unwrap();
338+
let listener = Socks4Listener::bind(PROXY_ADDR, &addr, "", None).unwrap();
309339
let addr = listener.proxy_addr().unwrap();
310340
let mut end = TcpStream::connect(addr).unwrap();
311341
let mut conn = listener.accept().unwrap();

0 commit comments

Comments
 (0)