Support HTTPS proxy targets with both native and rustls

This commit is contained in:
zonyitoo
2020-05-04 15:26:16 +08:00
parent 45a3469840
commit 8b7dbc8a6b
3 changed files with 152 additions and 10 deletions

View File

@@ -35,7 +35,7 @@ incremental = false
panic = "abort"
[features]
default = ["sodium", "rc4", "aes-cfb", "aes-ctr", "trust-dns", "local-http"]
default = ["sodium", "rc4", "aes-cfb", "aes-ctr", "trust-dns", "local-http", "local-http-rustls"]
sodium = ["libsodium-sys"]
rc4 = ["openssl"]
aes-cfb = ["openssl"]
@@ -46,10 +46,12 @@ single-threaded = []
trust-dns = ["trust-dns-resolver"]
dns-over-tls = ["trust-dns-resolver/dns-over-rustls"]
dns-over-https = ["trust-dns-resolver/dns-over-https-rustls"]
openssl-vendored = ["openssl/vendored"]
openssl-vendored = ["openssl/vendored", "native-tls/vendored"]
local-dns-relay = []
local-flow-stat = []
local-http = ["hyper", "http", "tower"]
local-http-native-tls = ["tokio-tls", "native-tls"]
local-http-rustls = ["tokio-rustls", "webpki-roots"]
local-redir = []
[dependencies]
@@ -61,6 +63,10 @@ chrono = "0.4"
openssl = { version = "0.10", optional = true }
libc = "^0.2.68"
tokio = { version = "^0.2.7", features = ["net", "signal", "time", "sync", "process", "rt-threaded", "rt-core", "stream", "io-util"] }
tokio-tls = { version = "0.3", optional = true }
native-tls = { version = "0.2", optional = true }
tokio-rustls = { version = "0.13", optional = true }
webpki-roots = { version = "0.19", optional = true }
futures = "0.3"
json5 = "0.2"
base64 = "0.12"

View File

@@ -317,6 +317,14 @@ impl Address {
Address::DomainNameAddress(.., port) => port,
}
}
/// Get host address string
pub fn host(&self) -> String {
match *self {
Address::SocketAddress(ref addr) => addr.ip().to_string(),
Address::DomainNameAddress(ref domain, ..) => domain.to_owned(),
}
}
}
impl Debug for Address {

View File

@@ -36,6 +36,7 @@ use hyper::{
};
use log::{debug, error, info, trace};
use pin_project::pin_project;
use tokio::io::{AsyncRead, AsyncWrite};
use crate::{
context::SharedContext,
@@ -53,6 +54,108 @@ use crate::{
use super::ProxyStream;
#[pin_project]
enum ProxyHttpStream {
Http(#[pin] ProxyStream),
#[cfg(feature = "local-http-native-tls")]
Https(#[pin] tokio_tls::TlsStream<ProxyStream>),
#[cfg(feature = "local-http-rustls")]
Https(#[pin] tokio_rustls::client::TlsStream<ProxyStream>),
}
impl ProxyHttpStream {
fn connect_http(stream: ProxyStream) -> ProxyHttpStream {
ProxyHttpStream::Http(stream)
}
#[cfg(feature = "local-http-native-tls")]
async fn connect_https(stream: ProxyStream, domain: &str) -> io::Result<ProxyHttpStream> {
use native_tls::TlsConnector;
let cx = match TlsConnector::builder().build() {
Ok(c) => c,
Err(err) => {
return Err(io::Error::new(ErrorKind::Other, format!("tls build: {}", err)));
}
};
let cx = tokio_tls::TlsConnector::from(cx);
cx.connect(domain, stream)
.await
.map(ProxyHttpStream::Https)
.map_err(|err| io::Error::new(ErrorKind::Other, format!("tls connect: {}", err)))
}
#[cfg(feature = "local-http-rustls")]
async fn connect_https(stream: ProxyStream, domain: &str) -> io::Result<ProxyHttpStream> {
use lazy_static::lazy_static;
use tokio_rustls::{rustls::ClientConfig, webpki::DNSNameRef, TlsConnector};
lazy_static! {
static ref TLS_CONFIG: Arc<ClientConfig> = {
let mut config = ClientConfig::new();
config
.root_store
.add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS);
Arc::new(config)
};
}
let connector = TlsConnector::from(TLS_CONFIG.clone());
let host = match DNSNameRef::try_from_ascii_str(domain) {
Ok(n) => n,
Err(_) => {
return Err(io::Error::new(ErrorKind::InvalidInput, "invalid dnsname"));
}
};
connector.connect(host, stream).await.map(ProxyHttpStream::Https)
}
#[cfg(not(any(feature = "local-http-native-tls", feature = "local-http-rustls")))]
async fn connect_https(stream: ProxyStream, domain: &str) -> io::Result<ProxyHttpStream> {
let err = io::Error::new(
ErrorKind::Other,
"https is not supported, consider enable it by feature \"local-http-native-tls\" or \"local-http-rustls\"",
);
Err(err)
}
}
macro_rules! forward_call {
($self:expr, $method:ident $(, $param:expr)*) => {
// #[project]
match $self.as_mut().project() {
// ProxyHttpStream::Http(stream) => stream.$method($($param),*),
__ProxyHttpStreamProjection::Http(stream) => stream.$method($($param),*),
#[cfg(any(feature = "local-http-native-tls", feature = "local-http-rustls"))]
// ProxyHttpStream::Https(stream) => stream.$method($($param),*),
__ProxyHttpStreamProjection::Https(stream) => stream.$method($($param),*),
}
};
}
impl AsyncRead for ProxyHttpStream {
fn poll_read(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>, buf: &mut [u8]) -> Poll<io::Result<usize>> {
forward_call!(self, poll_read, cx, buf)
}
}
impl AsyncWrite for ProxyHttpStream {
fn poll_write(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>, buf: &[u8]) -> Poll<io::Result<usize>> {
forward_call!(self, poll_write, cx, buf)
}
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<io::Result<()>> {
forward_call!(self, poll_flush, cx)
}
fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<io::Result<()>> {
forward_call!(self, poll_shutdown, cx)
}
}
#[derive(Clone)]
struct ShadowSocksConnector {
context: SharedContext,
@@ -69,7 +172,7 @@ impl ShadowSocksConnector {
impl tower::Service<Uri> for ShadowSocksConnector {
type Error = io::Error;
type Future = ShadowSocksConnecting;
type Response = ProxyStream;
type Response = ProxyHttpStream;
fn poll_ready(&mut self, _cx: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
@@ -83,6 +186,7 @@ impl tower::Service<Uri> for ShadowSocksConnector {
ShadowSocksConnecting {
fut: async move {
let svr_cfg = context.server_config(svr_idx);
let is_https = dst.scheme_str() == Some("https");
match host_addr(&dst) {
None => {
@@ -95,7 +199,14 @@ impl tower::Service<Uri> for ShadowSocksConnector {
}
Some(addr) => {
match ProxyStream::connect_proxied(context.clone(), svr_cfg, &addr).await {
Ok(s) => Ok(s),
Ok(s) => {
if is_https {
let host = dst.host().unwrap().trim_start_matches('[').trim_start_matches(']');
ProxyHttpStream::connect_https(s, host).await
} else {
Ok(ProxyHttpStream::connect_http(s))
}
}
Err(err) => {
// Report failure to global statistic
stat.report_failure().await;
@@ -113,11 +224,11 @@ impl tower::Service<Uri> for ShadowSocksConnector {
#[pin_project]
struct ShadowSocksConnecting {
#[pin]
fut: BoxFuture<'static, io::Result<ProxyStream>>,
fut: BoxFuture<'static, io::Result<ProxyHttpStream>>,
}
impl Future for ShadowSocksConnecting {
type Output = io::Result<ProxyStream>;
type Output = io::Result<ProxyHttpStream>;
fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
self.project().fut.poll(cx)
@@ -138,7 +249,7 @@ impl DirectConnector {
impl tower::Service<Uri> for DirectConnector {
type Error = io::Error;
type Future = DirectConnecting;
type Response = ProxyStream;
type Response = ProxyHttpStream;
fn poll_ready(&mut self, _cx: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
@@ -149,6 +260,8 @@ impl tower::Service<Uri> for DirectConnector {
DirectConnecting {
fut: async move {
let is_https = dst.scheme_str() == Some("https");
match host_addr(&dst) {
None => {
use std::io::Error;
@@ -158,7 +271,16 @@ impl tower::Service<Uri> for DirectConnector {
let err = Error::new(ErrorKind::Other, "URI must be a valid Address");
Err(err)
}
Some(addr) => ProxyStream::connect_direct(context, &addr).await,
Some(addr) => {
let s = ProxyStream::connect_direct(context, &addr).await?;
if is_https {
let host = dst.host().unwrap().trim_start_matches('[').trim_start_matches(']');
ProxyHttpStream::connect_https(s, host).await
} else {
Ok(ProxyHttpStream::connect_http(s))
}
}
}
}
.boxed(),
@@ -169,11 +291,11 @@ impl tower::Service<Uri> for DirectConnector {
#[pin_project]
struct DirectConnecting {
#[pin]
fut: BoxFuture<'static, io::Result<ProxyStream>>,
fut: BoxFuture<'static, io::Result<ProxyHttpStream>>,
}
impl Future for DirectConnecting {
type Output = io::Result<ProxyStream>;
type Output = io::Result<ProxyHttpStream>;
fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
self.project().fut.poll(cx)
@@ -359,6 +481,12 @@ impl Connection for ProxyStream {
}
}
impl Connection for ProxyHttpStream {
fn connected(&self) -> Connected {
Connected::new()
}
}
type ShadowSocksHttpClient = Client<ShadowSocksConnector, Body>;
type DirectHttpClient = Client<DirectConnector, Body>;