Skip to content

Commit

Permalink
feat: add support for rust_http_proxy
Browse files Browse the repository at this point in the history
  • Loading branch information
arloor committed Sep 6, 2024
1 parent f3c6541 commit 5d5d345
Show file tree
Hide file tree
Showing 18 changed files with 440 additions and 43 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
/dev
/*.log
/debian/*.log
/.vscode
/*.json
/restart.sh
/udp_echo_server.py
/.idea
/.devcontainer
.DS_Store
.vscode/launch.json
33 changes: 33 additions & 0 deletions .vscode/launch_example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
// 使用 IntelliSense 了解相关属性。
// 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "lldb",
"request": "launch",
"name": "Debug",
"program": "${workspaceFolder}/target/debug/sslocal",
"args": [
"--log-config",
"log4rs.yml",
"--local-addr",
"0.0.0.0:2080",
"-k",
"username:password",
"-v",
"-m",
"aes-256-gcm", // reversed for compatibility
"-s",
"host:port"
],
"cwd": "${workspaceFolder}",
"preLaunchTask": "rust: cargo build",
"env": {
"HOSTNAME": "test",
// "RUST_LOG": "debug"
}
}
]
}
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"rust-analyzer.check.command": "check"
}
24 changes: 24 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"version": "2.0.0",
"tasks": [
{
"type": "cargo",
"command": "build",
"args": [
"--package",
"${workspaceFolderBasename}",
"--bin",
"sslocal",
"--features",
"https-tunnel"
],
"problemMatcher": [
"$rustc"
],
"group": {
"kind": "build"
},
"label": "rust: cargo build"
}
]
}
17 changes: 9 additions & 8 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ default = [
"local-socks4",
"multi-threaded",
"aead-cipher-2022",
# "https-tunnel",
]

# Basic Features
Expand Down Expand Up @@ -151,6 +152,7 @@ local-http-native-tls-vendored = [
"shadowsocks-service/local-http-native-tls-vendored",
]
local-http-rustls = ["local-http", "shadowsocks-service/local-http-rustls"]
https-tunnel = ["shadowsocks-service/https-tunnel"]
# Enable REDIR protocol for sslocal
# (transparent proxy)
local-redir = ["local", "shadowsocks-service/local-redir"]
Expand Down
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PREFIX ?= /usr/local/bin
TARGET ?= debug

.PHONY: all build install uninstall clean
.PHONY: all build install uninstall clean gh
all: build

build:
Expand All @@ -28,3 +28,6 @@ uninstall:

clean:
cargo clean

gh:
cargo install --path . --bin sslocal --features https-tunnel
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## About this fork

Add support for using an HTTP proxy over TLS as the server. Specifically, it aims to support the use of [rust_http_proxy](https://github.com/arloor/rust_http_proxy) as the backend server. Refer to [https-tunnel-readme.md](https-tunnel-readme.md) for more infomation.

# shadowsocks

[![License](https://img.shields.io/github/license/zonyitoo/shadowsocks-rust.svg)](https://github.com/zonyitoo/shadowsocks-rust)
Expand Down
10 changes: 10 additions & 0 deletions crates/shadowsocks-service/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,15 @@ local-http-rustls = [
"webpki-roots",
"rustls-native-certs",
]

https-tunnel = [
"local-http",
"tokio-rustls",
"webpki-roots",
"rustls-native-certs",
"httparse",
"base64",
]
# Enable REDIR protocol for sslocal
# (transparent proxy)
local-redir = ["local"]
Expand Down Expand Up @@ -161,6 +170,7 @@ tokio-rustls = { version = "0.26", optional = true, default-features = false, fe
"tls12",
"ring",
] }
base64 = { version = "0.22", optional = true }
rustls-native-certs = { version = "0.8", optional = true }
async-trait = "0.1"

Expand Down
47 changes: 36 additions & 11 deletions crates/shadowsocks-service/src/local/http/http_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ use pin_project::pin_project;
use shadowsocks::relay::Address;
use tokio::sync::Mutex;

use crate::local::{context::ServiceContext, loadbalancing::PingBalancer, net::AutoProxyClientStream};
use crate::local::{
context::ServiceContext,
loadbalancing::PingBalancer,
net::{tcp::auto_proxy_stream::BasicAuth, AutoProxyClientStream},
};

use super::{
http_stream::ProxyHttpStream,
Expand Down Expand Up @@ -221,8 +225,8 @@ where
}

enum HttpConnection<B> {
Http1(http1::SendRequest<B>),
Http2(http2::SendRequest<B>),
Http1(http1::SendRequest<B>, Option<BasicAuth>),

Check warning on line 228 in crates/shadowsocks-service/src/local/http/http_client.rs

View workflow job for this annotation

GitHub Actions / clippy ubuntu-latest

field `1` is never read

warning: field `1` is never read --> crates/shadowsocks-service/src/local/http/http_client.rs:228:34 | 228 | Http1(http1::SendRequest<B>, Option<BasicAuth>), | ----- field in this variant ^^^^^^^^^^^^^^^^^ | = note: `#[warn(dead_code)]` on by default help: consider changing the field to be of unit type to suppress this warning while preserving the field numbering, or remove the field | 228 | Http1(http1::SendRequest<B>, ()), | ~~

Check warning on line 228 in crates/shadowsocks-service/src/local/http/http_client.rs

View workflow job for this annotation

GitHub Actions / clippy macos-latest

field `1` is never read

warning: field `1` is never read --> crates/shadowsocks-service/src/local/http/http_client.rs:228:34 | 228 | Http1(http1::SendRequest<B>, Option<BasicAuth>), | ----- field in this variant ^^^^^^^^^^^^^^^^^ | = note: `#[warn(dead_code)]` on by default help: consider changing the field to be of unit type to suppress this warning while preserving the field numbering, or remove the field | 228 | Http1(http1::SendRequest<B>, ()), | ~~
Http2(http2::SendRequest<B>, Option<BasicAuth>),

Check warning on line 229 in crates/shadowsocks-service/src/local/http/http_client.rs

View workflow job for this annotation

GitHub Actions / clippy ubuntu-latest

field `1` is never read

warning: field `1` is never read --> crates/shadowsocks-service/src/local/http/http_client.rs:229:34 | 229 | Http2(http2::SendRequest<B>, Option<BasicAuth>), | ----- field in this variant ^^^^^^^^^^^^^^^^^ | help: consider changing the field to be of unit type to suppress this warning while preserving the field numbering, or remove the field | 229 | Http2(http2::SendRequest<B>, ()), | ~~

Check warning on line 229 in crates/shadowsocks-service/src/local/http/http_client.rs

View workflow job for this annotation

GitHub Actions / clippy macos-latest

field `1` is never read

warning: field `1` is never read --> crates/shadowsocks-service/src/local/http/http_client.rs:229:34 | 229 | Http2(http2::SendRequest<B>, Option<BasicAuth>), | ----- field in this variant ^^^^^^^^^^^^^^^^^ | help: consider changing the field to be of unit type to suppress this warning while preserving the field numbering, or remove the field | 229 | Http2(http2::SendRequest<B>, ()), | ~~
}

impl<B> HttpConnection<B>
Expand Down Expand Up @@ -264,6 +268,7 @@ where
scheme
);

let auth = stream.auth();
let stream = ProxyHttpStream::connect_http(stream);

// HTTP/1.x
Expand All @@ -283,7 +288,7 @@ where
}
});

Ok(HttpConnection::Http1(send_request))
Ok(HttpConnection::Http1(send_request, auth))
}

async fn connect_https(
Expand All @@ -293,7 +298,7 @@ where
stream: AutoProxyClientStream,
) -> io::Result<HttpConnection<B>> {
trace!("HTTP making new TLS connection to host: {}, scheme: {}", host, scheme);

let auth = stream.auth();
// TLS handshake, check alpn for h2 support.
let stream = ProxyHttpStream::connect_https(stream, domain).await?;

Expand All @@ -315,7 +320,7 @@ where
}
});

Ok(HttpConnection::Http2(send_request))
Ok(HttpConnection::Http2(send_request, auth))
} else {
// HTTP/1.x TLS
let (send_request, connection) = match http1::Builder::new()
Expand All @@ -334,22 +339,42 @@ where
}
});

Ok(HttpConnection::Http1(send_request))
Ok(HttpConnection::Http1(send_request, auth))
}
}

#[cfg(not(feature = "https-tunnel"))]
#[inline]
pub async fn send_request(&mut self, req: Request<B>) -> hyper::Result<Response<body::Incoming>> {
match self {
HttpConnection::Http1(r) => r.send_request(req).await,
HttpConnection::Http2(r) => r.send_request(req).await,
HttpConnection::Http1(r, _) => r.send_request(req).await,
HttpConnection::Http2(r, _) => r.send_request(req).await,
}
}

#[cfg(feature = "https-tunnel")]
#[inline]
pub async fn send_request(&mut self, mut req: Request<B>) -> hyper::Result<Response<body::Incoming>> {
match self {
HttpConnection::Http1(r, auth) => {
if let Some(auth) = &auth {
req.headers_mut().insert("Proxy-Authorization", auth.0.parse().unwrap());
}
r.send_request(req).await
}
HttpConnection::Http2(r, auth) => {
if let Some(auth) = &auth {
req.headers_mut().insert("Proxy-Authorization", auth.0.parse().unwrap());
}
r.send_request(req).await
}
}
}

pub fn is_closed(&self) -> bool {
match self {
HttpConnection::Http1(r) => r.is_closed(),
HttpConnection::Http2(r) => r.is_closed(),
HttpConnection::Http1(r, _) => r.is_closed(),
HttpConnection::Http2(r, _) => r.is_closed(),
}
}
}
15 changes: 8 additions & 7 deletions crates/shadowsocks-service/src/local/http/http_service.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Shadowsocks HTTP Proxy server dispatcher
use std::{net::SocketAddr, str::FromStr, sync::Arc};
use std::{io, net::SocketAddr, str::FromStr, sync::Arc};

use bytes::Bytes;
use http_body_util::{combinators::BoxBody, BodyExt};
Expand Down Expand Up @@ -51,7 +51,7 @@ impl HttpService {
pub async fn serve_connection(
self,
mut req: Request<body::Incoming>,
) -> hyper::Result<Response<BoxBody<Bytes, hyper::Error>>> {
) -> Result<Response<BoxBody<Bytes, hyper::Error>>, io::Error> {
trace!("request {} {:?}", self.peer_addr, req);

// Parse URI
Expand All @@ -62,14 +62,14 @@ impl HttpService {
if req.uri().authority().is_some() {
// URI has authority but invalid
error!("HTTP {} URI {} doesn't have a valid host", req.method(), req.uri());
return make_bad_request();
return make_bad_request().map_err(|e| io::Error::new(io::ErrorKind::Other, e));
} else {
trace!("HTTP {} URI {} doesn't have a valid host", req.method(), req.uri());
}

match get_addr_from_header(&mut req) {
Ok(h) => h,
Err(()) => return make_bad_request(),
Err(()) => return make_bad_request().map_err(|e| io::Error::new(io::ErrorKind::Other, e)),
}
}
Some(h) => h,
Expand All @@ -88,7 +88,7 @@ impl HttpService {
Ok(s) => s,
Err(err) => {
error!("failed to CONNECT host: {}, error: {}", host, err);
return make_internal_server_error();
return make_internal_server_error().map_err(|e| io::Error::new(io::ErrorKind::Other, e));
}
};

Expand All @@ -98,6 +98,7 @@ impl HttpService {
host,
if stream.is_bypassed() { "bypassed" } else { "proxied" }
);
stream.handshake_tunnel().await?;

let client_addr = self.peer_addr;
tokio::spawn(async move {
Expand Down Expand Up @@ -153,10 +154,10 @@ impl HttpService {
.await
{
Ok(resp) => resp,
Err(HttpClientError::Hyper(e)) => return Err(e),
Err(HttpClientError::Hyper(e)) => return Err(io::Error::new(io::ErrorKind::Other, e)),
Err(HttpClientError::Io(err)) => {
error!("failed to make request to host: {}, error: {}", host, err);
return make_internal_server_error();
return make_internal_server_error().map_err(|e| io::Error::new(io::ErrorKind::Other, e));
}
};

Expand Down
Loading

0 comments on commit 5d5d345

Please sign in to comment.