re-add the "socks" feature (using tokio-socks) (#769)
The "socks" feature has been removed for a while now, the optional dependency on the "socks" crate commented out. The code for actually providing the socks feature was, however, still mostly present, if a bit out of date. This commit re-adds the socks feature using the tokio-socks (instead of socks) crate. Closes #620
This commit is contained in:
9
.github/workflows/ci.yml
vendored
9
.github/workflows/ci.yml
vendored
@@ -62,7 +62,8 @@ jobs:
|
|||||||
- "feat.: gzip"
|
- "feat.: gzip"
|
||||||
- "feat.: json"
|
- "feat.: json"
|
||||||
- "feat.: stream"
|
- "feat.: stream"
|
||||||
# - "feat.: socks"
|
- "feat.: socks/default-tls"
|
||||||
|
- "feat.: socks/rustls-tls"
|
||||||
# - "feat.: trust-dns"
|
# - "feat.: trust-dns"
|
||||||
|
|
||||||
include:
|
include:
|
||||||
@@ -115,8 +116,10 @@ jobs:
|
|||||||
features: "--features json"
|
features: "--features json"
|
||||||
- name: "feat.: stream"
|
- name: "feat.: stream"
|
||||||
features: "--features stream"
|
features: "--features stream"
|
||||||
# - name: "feat.: socks"
|
- name: "feat.: socks/default-tls"
|
||||||
# features: "--features socks"
|
features: "--features socks"
|
||||||
|
- name: "feat.: socks/rustls-tls"
|
||||||
|
features: "--features socks,rustls-tls"
|
||||||
# - name: "feat.: trust-dns"
|
# - name: "feat.: trust-dns"
|
||||||
# features: "--features trust-dns"
|
# features: "--features trust-dns"
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,8 @@ json = ["serde_json"]
|
|||||||
|
|
||||||
stream = []
|
stream = []
|
||||||
|
|
||||||
|
socks = ["tokio-socks"]
|
||||||
|
|
||||||
# Internal (PRIVATE!) features used to aid testing.
|
# Internal (PRIVATE!) features used to aid testing.
|
||||||
# Don't rely on these whatsoever. They may disappear at anytime.
|
# Don't rely on these whatsoever. They may disappear at anytime.
|
||||||
|
|
||||||
@@ -107,7 +109,7 @@ async-compression = { version = "0.2.0", default-features = false, features = ["
|
|||||||
serde_json = { version = "1.0", optional = true }
|
serde_json = { version = "1.0", optional = true }
|
||||||
|
|
||||||
## socks
|
## socks
|
||||||
#socks = { version = "0.3.2", optional = true }
|
tokio-socks = { version = "0.2", optional = true }
|
||||||
|
|
||||||
## trust-dns
|
## trust-dns
|
||||||
#trust-dns-resolver = { version = "0.11", optional = true }
|
#trust-dns-resolver = { version = "0.11", optional = true }
|
||||||
@@ -156,6 +158,11 @@ name = "json_typed"
|
|||||||
path = "examples/json_typed.rs"
|
path = "examples/json_typed.rs"
|
||||||
required-features = ["json"]
|
required-features = ["json"]
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "tor_socks"
|
||||||
|
path = "examples/tor_socks.rs"
|
||||||
|
required-features = ["socks"]
|
||||||
|
|
||||||
[[test]]
|
[[test]]
|
||||||
name = "blocking"
|
name = "blocking"
|
||||||
path = "tests/blocking.rs"
|
path = "tests/blocking.rs"
|
||||||
|
|||||||
24
examples/tor_socks.rs
Normal file
24
examples/tor_socks.rs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#![deny(warnings)]
|
||||||
|
|
||||||
|
// This is using the `tokio` runtime. You'll need the following dependency:
|
||||||
|
//
|
||||||
|
// `tokio = { version = "0.2", features = ["macros"] }`
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<(), reqwest::Error> {
|
||||||
|
// Make sure you are running tor and this is your socks port
|
||||||
|
let proxy = reqwest::Proxy::all("socks5://127.0.0.1:9050").expect("tor proxy should be there");
|
||||||
|
let client = reqwest::Client::builder()
|
||||||
|
.proxy(proxy)
|
||||||
|
.build()
|
||||||
|
.expect("should be able to build reqwest client");
|
||||||
|
|
||||||
|
let res = client.get("https://check.torproject.org").send().await?;
|
||||||
|
println!("Status: {}", res.status());
|
||||||
|
|
||||||
|
let text = res.text().await?;
|
||||||
|
let is_tor = text.contains("Congratulations. This browser is configured to use Tor.");
|
||||||
|
println!("Is Tor: {}", is_tor);
|
||||||
|
assert!(is_tor);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -148,9 +148,9 @@ impl Connector {
|
|||||||
#[cfg(feature = "socks")]
|
#[cfg(feature = "socks")]
|
||||||
async fn connect_socks(
|
async fn connect_socks(
|
||||||
&self,
|
&self,
|
||||||
dst: Destination,
|
dst: Uri,
|
||||||
proxy: ProxyScheme,
|
proxy: ProxyScheme,
|
||||||
) -> Result<(Conn, Connected), io::Error> {
|
) -> Result<Conn, BoxError> {
|
||||||
let dns = match proxy {
|
let dns = match proxy {
|
||||||
ProxyScheme::Socks5 {
|
ProxyScheme::Socks5 {
|
||||||
remote_dns: false, ..
|
remote_dns: false, ..
|
||||||
@@ -158,37 +158,43 @@ impl Connector {
|
|||||||
ProxyScheme::Socks5 {
|
ProxyScheme::Socks5 {
|
||||||
remote_dns: true, ..
|
remote_dns: true, ..
|
||||||
} => socks::DnsResolve::Proxy,
|
} => socks::DnsResolve::Proxy,
|
||||||
ProxyScheme::Http { .. } => {
|
ProxyScheme::Http { .. } | ProxyScheme::Https { .. } => {
|
||||||
unreachable!("connect_socks is only called for socks proxies");
|
unreachable!("connect_socks is only called for socks proxies");
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
match &self.inner {
|
match &self.inner {
|
||||||
#[cfg(feature = "default-tls")]
|
#[cfg(feature = "default-tls")]
|
||||||
Inner::DefaultTls(_http, tls) => {
|
Inner::DefaultTls(_http, tls) => {
|
||||||
if dst.scheme() == "https" {
|
if dst.scheme() == Some(&Scheme::HTTPS) {
|
||||||
use self::native_tls_async::TlsConnectorExt;
|
let host = dst
|
||||||
|
.host()
|
||||||
let host = dst.host().to_owned();
|
.ok_or(io::Error::new(io::ErrorKind::Other, "no host in url"))?
|
||||||
let socks_connecting = socks::connect(proxy, dst, dns);
|
.to_string();
|
||||||
let (conn, connected) = socks::connect(proxy, dst, dns).await?;
|
let conn = socks::connect(proxy, dst, dns).await?;
|
||||||
let tls_connector = tokio_tls::TlsConnector::from(tls.clone());
|
let tls_connector = tokio_tls::TlsConnector::from(tls.clone());
|
||||||
let io = tls_connector
|
let io = tls_connector
|
||||||
.connect(&host, conn)
|
.connect(&host, conn)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
||||||
Ok((Box::new(io) as Conn, connected))
|
return Ok(Conn {
|
||||||
|
inner: Box::new(NativeTlsConn { inner: io }),
|
||||||
|
is_proxy: false,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(feature = "rustls-tls")]
|
#[cfg(feature = "rustls-tls")]
|
||||||
Inner::RustlsTls { tls_proxy, .. } => {
|
Inner::RustlsTls { tls_proxy, .. } => {
|
||||||
if dst.scheme() == "https" {
|
if dst.scheme() == Some(&Scheme::HTTPS) {
|
||||||
use tokio_rustls::webpki::DNSNameRef;
|
use tokio_rustls::webpki::DNSNameRef;
|
||||||
use tokio_rustls::TlsConnector as RustlsConnector;
|
use tokio_rustls::TlsConnector as RustlsConnector;
|
||||||
|
|
||||||
let tls = tls_proxy.clone();
|
let tls = tls_proxy.clone();
|
||||||
let host = dst.host().to_owned();
|
let host = dst
|
||||||
let (conn, connected) = socks::connect(proxy, dst, dns);
|
.host()
|
||||||
|
.ok_or(io::Error::new(io::ErrorKind::Other, "no host in url"))?
|
||||||
|
.to_string();
|
||||||
|
let conn = socks::connect(proxy, dst, dns).await?;
|
||||||
let dnsname = DNSNameRef::try_from_ascii_str(&host)
|
let dnsname = DNSNameRef::try_from_ascii_str(&host)
|
||||||
.map(|dnsname| dnsname.to_owned())
|
.map(|dnsname| dnsname.to_owned())
|
||||||
.map_err(|_| io::Error::new(io::ErrorKind::Other, "Invalid DNS Name"))?;
|
.map_err(|_| io::Error::new(io::ErrorKind::Other, "Invalid DNS Name"))?;
|
||||||
@@ -196,12 +202,17 @@ impl Connector {
|
|||||||
.connect(dnsname.as_ref(), conn)
|
.connect(dnsname.as_ref(), conn)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
||||||
Ok((Box::new(io) as Conn, connected))
|
return Ok(Conn {
|
||||||
|
inner: Box::new(RustlsTlsConn { inner: io }),
|
||||||
|
is_proxy: false,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "__tls"))]
|
#[cfg(not(feature = "__tls"))]
|
||||||
Inner::Http(_) => socks::connect(proxy, dst, dns),
|
Inner::Http(_) => ()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
socks::connect(proxy, dst, dns).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn connect_with_maybe_proxy(
|
async fn connect_with_maybe_proxy(
|
||||||
@@ -277,7 +288,7 @@ impl Connector {
|
|||||||
ProxyScheme::Http { host, auth } => (into_uri(Scheme::HTTP, host), auth),
|
ProxyScheme::Http { host, auth } => (into_uri(Scheme::HTTP, host), auth),
|
||||||
ProxyScheme::Https { host, auth } => (into_uri(Scheme::HTTPS, host), auth),
|
ProxyScheme::Https { host, auth } => (into_uri(Scheme::HTTPS, host), auth),
|
||||||
#[cfg(feature = "socks")]
|
#[cfg(feature = "socks")]
|
||||||
ProxyScheme::Socks5 { .. } => return this.connect_socks(dst, proxy_scheme),
|
ProxyScheme::Socks5 { .. } => return self.connect_socks(dst, proxy_scheme).await,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -326,7 +337,8 @@ impl Connector {
|
|||||||
use tokio_rustls::webpki::DNSNameRef;
|
use tokio_rustls::webpki::DNSNameRef;
|
||||||
use tokio_rustls::TlsConnector as RustlsConnector;
|
use tokio_rustls::TlsConnector as RustlsConnector;
|
||||||
|
|
||||||
let host = dst.host()
|
let host = dst
|
||||||
|
.host()
|
||||||
.ok_or(io::Error::new(io::ErrorKind::Other, "no host in url"))?
|
.ok_or(io::Error::new(io::ErrorKind::Other, "no host in url"))?
|
||||||
.to_string();
|
.to_string();
|
||||||
let port = dst.port().map(|r| r.as_u16()).unwrap_or(443);
|
let port = dst.port().map(|r| r.as_u16()).unwrap_or(443);
|
||||||
@@ -430,6 +442,10 @@ pub(crate) trait AsyncConn: AsyncRead + AsyncWrite + Connection {}
|
|||||||
impl<T: AsyncRead + AsyncWrite + Connection> AsyncConn for T {}
|
impl<T: AsyncRead + AsyncWrite + Connection> AsyncConn for T {}
|
||||||
|
|
||||||
pin_project! {
|
pin_project! {
|
||||||
|
/// Note: the `is_proxy` member means *is plain text HTTP proxy*.
|
||||||
|
/// This tells hyper whether the URI should be written in
|
||||||
|
/// * origin-form (`GET /just/a/path HTTP/1.1`), when `is_proxy == false`, or
|
||||||
|
/// * absolute-form (`GET http://foo.bar/and/a/path HTTP/1.1`), otherwise.
|
||||||
pub(crate) struct Conn {
|
pub(crate) struct Conn {
|
||||||
#[pin]
|
#[pin]
|
||||||
inner: Box<dyn AsyncConn + Send + Sync + Unpin + 'static>,
|
inner: Box<dyn AsyncConn + Send + Sync + Unpin + 'static>,
|
||||||
@@ -779,15 +795,12 @@ mod rustls_tls_conn {
|
|||||||
|
|
||||||
#[cfg(feature = "socks")]
|
#[cfg(feature = "socks")]
|
||||||
mod socks {
|
mod socks {
|
||||||
|
use http::Uri;
|
||||||
|
use tokio_socks::tcp::Socks5Stream;
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
use futures::{future, Future};
|
|
||||||
use hyper::client::connect::{Connected, Destination};
|
|
||||||
use socks::Socks5Stream;
|
|
||||||
use std::net::ToSocketAddrs;
|
use std::net::ToSocketAddrs;
|
||||||
use tokio::{net::TcpStream, reactor};
|
|
||||||
|
|
||||||
use super::Connecting;
|
use super::{BoxError, Scheme};
|
||||||
use crate::proxy::ProxyScheme;
|
use crate::proxy::ProxyScheme;
|
||||||
|
|
||||||
pub(super) enum DnsResolve {
|
pub(super) enum DnsResolve {
|
||||||
@@ -797,13 +810,19 @@ mod socks {
|
|||||||
|
|
||||||
pub(super) async fn connect(
|
pub(super) async fn connect(
|
||||||
proxy: ProxyScheme,
|
proxy: ProxyScheme,
|
||||||
dst: Destination,
|
dst: Uri,
|
||||||
dns: DnsResolve,
|
dns: DnsResolve,
|
||||||
) -> Result<super::Conn, BoxError> {
|
) -> Result<super::Conn, BoxError> {
|
||||||
let https = dst.scheme() == Some(&Scheme::HTTPS);
|
let https = dst.scheme() == Some(&Scheme::HTTPS);
|
||||||
let original_host = dst.host().to_owned();
|
let original_host = dst
|
||||||
let mut host = original_host.clone();
|
.host()
|
||||||
let port = dst.port().unwrap_or_else(|| if https { 443 } else { 80 });
|
.ok_or(io::Error::new(io::ErrorKind::Other, "no host in url"))?;
|
||||||
|
let mut host = original_host.to_owned();
|
||||||
|
let port = match dst.port() {
|
||||||
|
Some(p) => p.as_u16(),
|
||||||
|
None if https => 443u16,
|
||||||
|
_ => 80u16,
|
||||||
|
};
|
||||||
|
|
||||||
if let DnsResolve::Local = dns {
|
if let DnsResolve::Local = dns {
|
||||||
let maybe_new_target = (host.as_str(), port).to_socket_addrs()?.next();
|
let maybe_new_target = (host.as_str(), port).to_socket_addrs()?.next();
|
||||||
@@ -826,13 +845,17 @@ mod socks {
|
|||||||
&password,
|
&password,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
.map_err(|e| format!("socks connect error: {}", e))?
|
||||||
} else {
|
} else {
|
||||||
let s = Socks5Stream::connect(socket_addr, (host.as_str(), port)).await;
|
Socks5Stream::connect(socket_addr, (host.as_str(), port))
|
||||||
TcpStream::from_std(s.into_inner(), &reactor::Handle::default())
|
.await
|
||||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?
|
.map_err(|e| format!("socks connect error: {}", e))?
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok((Box::new(s) as super::Conn, Connected::new()))
|
Ok(super::Conn {
|
||||||
|
inner: Box::new( stream.into_inner() ),
|
||||||
|
is_proxy: false,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -169,6 +169,7 @@
|
|||||||
//! - **gzip**: Provides response body gzip decompression.
|
//! - **gzip**: Provides response body gzip decompression.
|
||||||
//! - **json**: Provides serialization and deserialization for JSON bodies.
|
//! - **json**: Provides serialization and deserialization for JSON bodies.
|
||||||
//! - **stream**: Adds support for `futures::Stream`.
|
//! - **stream**: Adds support for `futures::Stream`.
|
||||||
|
//! - **socks**: Provides SOCKS5 proxy support.
|
||||||
//!
|
//!
|
||||||
//!
|
//!
|
||||||
//! [hyper]: http://hyper.rs
|
//! [hyper]: http://hyper.rs
|
||||||
@@ -180,8 +181,6 @@
|
|||||||
//! [redirect]: crate::redirect
|
//! [redirect]: crate::redirect
|
||||||
//! [Proxy]: ./struct.Proxy.html
|
//! [Proxy]: ./struct.Proxy.html
|
||||||
//! [cargo-features]: https://doc.rust-lang.org/stable/cargo/reference/manifest.html#the-features-section
|
//! [cargo-features]: https://doc.rust-lang.org/stable/cargo/reference/manifest.html#the-features-section
|
||||||
|
|
||||||
////! - **socks**: Provides SOCKS5 proxy support.
|
|
||||||
////! - **trust-dns**: Enables a trust-dns async resolver instead of default
|
////! - **trust-dns**: Enables a trust-dns async resolver instead of default
|
||||||
////! threadpool using `getaddrinfo`.
|
////! threadpool using `getaddrinfo`.
|
||||||
|
|
||||||
|
|||||||
11
src/proxy.rs
11
src/proxy.rs
@@ -383,14 +383,15 @@ impl ProxyScheme {
|
|||||||
// Resolve URL to a host and port
|
// Resolve URL to a host and port
|
||||||
#[cfg(feature = "socks")]
|
#[cfg(feature = "socks")]
|
||||||
let to_addr = || {
|
let to_addr = || {
|
||||||
let addrs = try_!(url.socket_addrs(|| match url.scheme() {
|
let addrs = url.socket_addrs(|| match url.scheme() {
|
||||||
"socks5" | "socks5h" => Some(1080),
|
"socks5" | "socks5h" => Some(1080),
|
||||||
_ => None,
|
_ => None,
|
||||||
}));
|
})
|
||||||
|
.map_err(crate::error::builder)?;
|
||||||
addrs
|
addrs
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.next()
|
.next()
|
||||||
.ok_or_else(crate::error::unknown_proxy_scheme)
|
.ok_or_else(|| crate::error::builder("unknown proxy scheme"))
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut scheme = match url.scheme() {
|
let mut scheme = match url.scheme() {
|
||||||
@@ -418,7 +419,7 @@ impl ProxyScheme {
|
|||||||
ProxyScheme::Http { .. } => "http",
|
ProxyScheme::Http { .. } => "http",
|
||||||
ProxyScheme::Https { .. } => "https",
|
ProxyScheme::Https { .. } => "https",
|
||||||
#[cfg(feature = "socks")]
|
#[cfg(feature = "socks")]
|
||||||
ProxyScheme::Socks5 => "socks5",
|
ProxyScheme::Socks5 { .. } => "socks5",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -428,7 +429,7 @@ impl ProxyScheme {
|
|||||||
ProxyScheme::Http { host, .. } => host.as_str(),
|
ProxyScheme::Http { host, .. } => host.as_str(),
|
||||||
ProxyScheme::Https { host, .. } => host.as_str(),
|
ProxyScheme::Https { host, .. } => host.as_str(),
|
||||||
#[cfg(feature = "socks")]
|
#[cfg(feature = "socks")]
|
||||||
ProxyScheme::Socks5 => panic!("socks5"),
|
ProxyScheme::Socks5 { .. } => panic!("socks5"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user