实现proxy逻辑
利用OpenSSL生成自签名证书
1、生成RSA私钥文件
Rust
openssl genrsa -out proxylea_private.key 2048
2、创建证书签名请求文件
Shell
openssl req -new -key proxylea_private.key -out server.csr
3、使用私钥为证书签名(自签名)
Shell
openssl x509 -req -days 3650 -in server.csr -signkey proxylea_private.key -out proxylea_cert.crt
4、将proxylea_cert.crt证书导入电脑受信任根证书目录下
- 自行安装
- 使用Rust安装
动态为相关网站颁发证书
Rust
use std::{fs::File, io::Read, path::Path};
use openssl::asn1::Asn1Integer;
use openssl::bn::BigNum;
use openssl::rand;
use openssl::pkey::{Private, PKey};
use openssl::rsa::Rsa;
use openssl::x509::extension::SubjectAlternativeName;
use openssl::x509::X509;
// 函数:加载根证书和私钥
fn load_root_cert_and_key(
root_cert_path: &str,
root_key_path: &str,
) -> Result<(X509, PKey<Private>), Box<dyn std::error::Error>> {
// 检查根证书文件是否存在
if !Path::new(root_cert_path).exists() {
return Err(format!("根证书文件未找到: {}", root_cert_path).into());
}
// 加载根证书
let mut ca_file = File::open(root_cert_path)?;
let mut ca_bytes = Vec::new();
ca_file.read_to_end(&mut ca_bytes)?;
let ca = X509::from_pem(&ca_bytes)?;
// 检查根密钥文件是否存在
if !Path::new(root_key_path).exists() {
return Err(format!("根密钥文件未找到: {}", root_key_path).into());
}
// 加载根密钥
let mut pkey_file = File::open(root_key_path)?;
let mut pkey_bytes = Vec::new();
pkey_file.read_to_end(&mut pkey_bytes)?;
let pkey = PKey::private_key_from_pem(&pkey_bytes)?;
Ok((ca, pkey))
}
// 函数:客户端动态颁发证书
fn client_cert_signing(
root_cert_path: &str,
root_key_path: &str,
host: &str,
) -> Result<(X509, PKey<Private>), Box<dyn std::error::Error>> {
// 加载根证书和私钥
let (root_cert, root_key) = load_root_cert_and_key(root_cert_path, root_key_path)?;
// 生成密钥对
let rsa = Rsa::generate(2048)?;
let private_key = PKey::from_rsa(rsa)?;
let public_key_pem = private_key.public_key_to_pem()?;
let public_key = PKey::public_key_from_pem(&public_key_pem)?;
// 颁发证书
let mut cert = X509::builder()?;
cert.set_version(2)?;
let mut x509_name = openssl::x509::X509NameBuilder::new()?;
x509_name.append_entry_by_text("C", "ZH")?;
x509_name.append_entry_by_text("ST", "SC")?;
x509_name.append_entry_by_text("L", "YC")?;
x509_name.append_entry_by_text("O", "YC")?;
x509_name.append_entry_by_text("OU", "YC")?;
x509_name.append_entry_by_text("CN", host)?;
let x509_name = x509_name.build();
cert.set_subject_name(&x509_name)?;
cert.set_issuer_name(&root_cert.subject_name())?;
let mut serial_number = [0; 16];
rand::rand_bytes(&mut serial_number)?;
let serial_number = BigNum::from_slice(&serial_number)?;
let serial_number = Asn1Integer::from_bn(&serial_number)?;
cert.set_serial_number(&serial_number)?;
cert.set_not_before(root_cert.not_before())?;
cert.set_not_after(root_cert.not_after())?;
cert.set_pubkey(&public_key)?;
let alternative_name = SubjectAlternativeName::new()
.dns(host)
.build(&cert.x509v3_context(Some(&root_cert), None))?;
cert.append_extension(alternative_name)?;
cert.sign(&root_key, openssl::hash::MessageDigest::sha256())?;
Ok((cert.build(), private_key))
}
// 公共函数:获取证书和密钥
pub fn get_crt_key(host: &str) -> Result<(X509, PKey<Private>), Box<dyn std::error::Error>> {
println!("主机: {:?}", host);
client_cert_signing("src/proxylea_cert.crt", "src/proxylea_private.key", host)
}
// 主函数:示例用法
// fn main() {
// let host = "example.com";
// match get_crt_key(host) {
// Ok((cert, key)) => {
// println!("证书和密钥已成功生成,主机: {}", host);
// }
// Err(e) => {
// eprintln!("生成证书和密钥时出错: {:?}", e);
// }
// }
// }
效果如下,可以看见www.baidu.com网站的证书由自签名证书颁发。
利用hyper构建HTTP代理服务器
构建本地代理服务器
Rust
#[tokio::main]
pub async fn main() -> Result<()> {
// This address is localhost
let addr: SocketAddr = "127.0.0.1:7890".parse().unwrap();
// Bind to the port and listen for incoming TCP connections
let listener = TcpListener::bind(addr).await?;
println!("Listening on http://{}", addr);
loop {
let (tcp_stream, addr) = listener.accept().await?;
let msg = format!("{addr} connected");
dbg!(msg);
tokio::task::spawn(async move {
let io = TokioIo::new(tcp_stream);
let conn = http1::Builder::new()
.serve_connection(io, service_fn(server_upgrade));
// Don't forget to enable upgrades on the connection.
let mut conn = conn.with_upgrades();
let conn = Pin::new(&mut conn);
if let Err(err) = conn.await {
println!("Error serving connection: {:?}", err);
}
});
}
}
将http协议升级到https
1、浏览器在进行https握手时,需要先发送一个CONNECT的http请求给代理服务器,代理服务器返回状态码200的http响应,代表浏览器与代理服务连接建立成功。
2、浏览器与代理服务器开始TLS握手,代理服务器开始连接远程网站服务器。
3、二者都连接成功时就可以传输数据了。
4、对于http请求,代理服务器与远程服务器连接成功后直接转发请求和响应。
Rust
async fn server_upgraded_https(host: &str, upgraded: Upgraded) -> Result<()> {
let upgraded = TokioIo::new(upgraded);
// we have an upgraded connection that we can read and
// write on directly.
//
let tls_acceptor = get_tls_acceptor(host);
let ssl = Ssl::new(tls_acceptor.context())?;
let mut tls_stream = SslStream::new(ssl, upgraded)?;
if let Err(err) = SslStream::accept(Pin::new(&mut tls_stream)).await {
return Err(anyhow!("error during tls handshake connection from : {}", err));
}
let stream = TokioIo::new(tls_stream);
let (sender, conn) = https_remote_connect(host, 443).await?;
tokio::spawn(async move {
if let Err(err) = conn.await {
let err_msg = format!("Connection failed: {:?}", err);
dbg!(err_msg);
}
});
let wrap_sender = Mutex::new(sender);
if let Err(err) = http1::Builder::new()
.serve_connection(stream, service_fn( |req| {
let (req, resp) = intercept_request(req);
async {
match resp {
None => {
let remote_resp = wrap_sender.lock().unwrap().send_request(req);
match remote_resp.await {
Ok(resp) => {
Ok::<_, hyper::Error>(intercept_response(resp))
}
Err(err) => {
let resp = Response::builder()
.status(StatusCode::INTERNAL_SERVER_ERROR)
.header(header::CONTENT_TYPE, "text/plain")
.body(full(err.to_string())).unwrap();
Ok::<_, hyper::Error>(resp)
}
}
}
Some(resp) => {
Ok::<_, hyper::Error>(resp)
}
}
}
})).await {
println!("Error serving connection: {:?}", err);
}
Ok(())
}
拦截本地浏览器请求和远程服务器响应
1、拦截浏览器对baidu网站的请求,并返回一些信息
2、拦截全部http响应,并且打印响应头和添加一些响应头
Rust
/// Intercept local requests
fn intercept_request(mut request: Request<Incoming>) -> (Request<HttpBody>, Option<Response<HttpBody>>) {
dbg!(request.uri().to_string());
request.headers_mut().remove("Accept-Encoding");
let req = request.map(|b| b.boxed());
if let Some(Ok(host)) = req.headers().get(header::HOST).map(|h| h.to_str()) {
if host.contains("127.0.0.1:7890")
|| host.contains("localhost:7890")
|| host.contains("baidu") {
let resp = Response::builder()
.header(header::CONTENT_TYPE, "text/plain")
.body(full("Proxylea Server Power By Rust & Hyper\n"));
return (req, Some(resp.unwrap()));
}
}
(req, None)
}
/// Intercept remote responses
fn intercept_response(mut response: Response<Incoming>) -> Response<HttpBody> {
dbg!({ format!("{:?}", response.headers()) });
response.headers_mut().insert("proxy-server", "Proxylea".parse().unwrap());
//let (parts,incoming)=resp.into_parts();
let resp = response.map(|b| b.map_frame(|frame| {
//You can modify and record the response body
if let Some(bytes) = frame.data_ref() {
//todo
}
frame
}).boxed());
resp
}
结果展示
- 修改响应信息
- 添加请求头信息
- 打印请求信息
全部代码
整体代码写下来问题不大,主要是前面的openssl库的编译有些问题。Rust的hyper库类似于Java的Netty库,都属于底层库,但是hyper功能远不如Netty,hyper只是一个http相关的底层库。还有就是相关文档与资源实在是太少了,只能去看官方的example。从开发效率上来讲还是Netty更快(不如说是Java开发效率更快),但是学习hyper库有助于学习Rust的异步、特征、泛型,如果看见hyper库里面的pin_xx、poll_xx、各种特征与泛型非常自然的话,那么离熟练使用Rust也就不远了。
Rust
use std::error::Error;
use std::net::SocketAddr;
use std::pin::Pin;
use std::sync::Mutex;
use anyhow::{anyhow, Result};
use bytes::Bytes;
use futures::FutureExt;
use http_body_util::{BodyExt, Empty, Full};
use http_body_util::combinators::BoxBody;
use hyper::{header, Request, Response, StatusCode};
use hyper::body::{Body, Incoming};
use hyper::client::conn::http1::{Builder, Connection, SendRequest};
use hyper::rt::{Read, Write};
use hyper::server::conn::http1;
use hyper::service::service_fn;
use hyper::upgrade::Upgraded;
use hyper_util::rt::TokioIo;
use install::install_cert;
use openssl::ssl::{Ssl, SslAcceptor, SslConnector, SslMethod, SslVerifyMode};
use tokio::net::{TcpListener, TcpStream};
use tokio_openssl::SslStream;
pub mod crt;
pub mod install;
type HttpBody = BoxBody<Bytes, hyper::Error>;
#[tokio::main]
pub async fn main() -> Result<()> {
// This address is localhost
let addr: SocketAddr = "127.0.0.1:7999".parse().unwrap();
let _ = install_cert();
// Bind to the port and listen for incoming TCP connections
let listener = TcpListener::bind(addr).await?;
println!("Listening on http://{}", addr);
loop {
println!("{:?}", "loop");
let (tcp_stream, addr) = listener.accept().await?;
let _msg = format!("{addr} connected");
//dbg!(msg);
tokio::task::spawn(async move {
let io = TokioIo::new(tcp_stream);
let conn = http1::Builder::new()
.serve_connection(io, service_fn(handle));
// Don't forget to enable upgrades on the connection.
let mut conn = conn.with_upgrades();
let conn = Pin::new(&mut conn);
if let Err(err) = conn.await {
println!("Error serving connection: {:?}", err);
}
});
}
}
fn get_host_port(host_name: &str) -> (&str, u16) {
match host_name.find(":") {
None => {
(host_name, 80)
}
Some(i) => {
(&host_name[0..i], *&host_name[i + 1..].parse().unwrap_or(80))
}
}
}
fn not_found_host() -> Response<HttpBody> {
Response::builder().status(404).body(full("not found host")).unwrap()
}
/// Our server HTTP handler to initiate HTTP upgrades.
async fn handle(mut req: Request<Incoming>) -> Result<Response<HttpBody>> {
if req.method() != hyper::Method::CONNECT {
let (host, port) = match req.headers().get(header::HOST) {
None => {
return Ok(not_found_host());
}
Some(h) => { get_host_port(h.to_str()?) }
};
let stream = TcpStream::connect((host, port)).await?;
let io = TokioIo::new(stream);
let (mut sender, conn) = Builder::new()
.preserve_header_case(true)
.title_case_headers(true)
.handshake(io)
.await?;
tokio::task::spawn(async move {
if let Err(err) = conn.await {
println!("Connection failed: {:?}", err);
}
});
let (req, resp) = intercept_request(req);
return match resp {
None => {
let resp = sender.send_request(req).await?;
Ok(resp.map(|b| b.boxed()))
}
Some(resp) => {
Ok(resp)
}
};
}
let res = Response::new(empty());
// handle https
tokio::task::spawn(async move {
match hyper::upgrade::on(&mut req).await {
Ok(upgraded) => {
if let Some(host) = req.uri().host() {
if let Err(e) = server_upgraded_https(host, upgraded).await {
let error_msg = format!("server io error: {}", e);
dbg!(error_msg);
};
}
}
Err(e) => eprintln!("upgrade error: {}", e),
}
});
Ok(res)
}
/// https upgraded
async fn server_upgraded_https(host: &str, upgraded: Upgraded) -> Result<()> {
let upgraded = TokioIo::new(upgraded);
// we have an upgraded connection that we can read and
// write on directly.
//
let tls_acceptor = get_tls_acceptor(host);
let ssl = Ssl::new(tls_acceptor.context())?;
let mut tls_stream = SslStream::new(ssl, upgraded)?;
if let Err(err) = SslStream::accept(Pin::new(&mut tls_stream)).await {
return Err(anyhow!("error during tls handshake connection from : {}", err));
}
let stream = TokioIo::new(tls_stream);
let (sender, conn) = https_remote_connect(host, 443).await?;
tokio::spawn(async move {
if let Err(err) = conn.await {
let err_msg = format!("Connection failed: {:?}", err);
dbg!(err_msg);
}
});
let wrap_sender = Mutex::new(sender);
if let Err(err) = http1::Builder::new()
.serve_connection(stream, service_fn(|req| {
let (req, resp) = intercept_request(req);
async {
match resp {
None => {
let remote_resp = wrap_sender.lock().unwrap().send_request(req);
match remote_resp.await {
Ok(resp) => {
Ok::<_, hyper::Error>(intercept_response(resp))
}
Err(err) => {
let resp = Response::builder()
.status(StatusCode::INTERNAL_SERVER_ERROR)
.header(header::CONTENT_TYPE, "text/plain")
.body(full(err.to_string())).unwrap();
Ok::<_, hyper::Error>(resp)
}
}
}
Some(resp) => {
Ok::<_, hyper::Error>(resp)
}
}
}
})).await {
println!("Error serving connection: {:?}", err);
}
Ok(())
}
fn empty() -> HttpBody {
Empty::<Bytes>::new()
.map_err(|never| match never {})
.boxed()
}
fn full<T: Into<Bytes>>(chunk: T) -> HttpBody {
Full::new(chunk.into())
.map_err(|never| match never {})
.boxed()
}
/// Certificate not cached
fn get_tls_acceptor(host: &str) -> SslAcceptor {
let mut tls_builder = SslAcceptor::mozilla_modern_v5(SslMethod::tls()).unwrap();
let (crt, pri_key) = crt::get_crt_key(host).unwrap();
tls_builder.set_certificate(&crt).unwrap();
tls_builder.set_private_key(&pri_key).unwrap();
tls_builder.check_private_key().unwrap();
let tls_acceptor = tls_builder.build();
tls_acceptor
}
async fn https_remote_connect<B>(host: &str, port: u16) -> Result<(SendRequest<B>, Connection<TokioIo<SslStream<TcpStream>>, B>)>
where
B: Body + 'static,
B::Data: Send,
B::Error: Into<Box<dyn Error + Send + Sync>>, {
let addr = format!("{}:{}", host, port);
let tcp_stream = TcpStream::connect(addr).await?;
let mut builder = SslConnector::builder(SslMethod::tls_client())?;
builder.set_verify(SslVerifyMode::NONE);
let connector = builder.build();
let ssl = Ssl::new(connector.context())?;
let mut tls_stream = SslStream::new(ssl, tcp_stream)?;
if let Err(err) = SslStream::connect(Pin::new(&mut tls_stream)).await {
return Err(anyhow!("error during tls handshake connection from : {}", err));
}
let io = TokioIo::new(tls_stream);
Ok(hyper::client::conn::http1::handshake(io).await?)
}
/// Intercept local requests
fn intercept_request(mut request: Request<Incoming>) -> (Request<HttpBody>, Option<Response<HttpBody>>) {
dbg!(request.uri().to_string());
request.headers_mut().remove("Accept-Encoding");
let req = request.map(|b| b.boxed());
if let Some(Ok(host)) = req.headers().get(header::HOST).map(|h| h.to_str()) {
if host.contains("127.0.0.1:7999")
|| host.contains("localhost:7999")
|| host.contains("baidu") {
let resp = Response::builder()
.header(header::CONTENT_TYPE, "text/plain")
.body(full("Proxylea Server Power By Rust & Hyper\n"));
return (req, Some(resp.unwrap()));
}
}
(req, None)
}
/// Intercept remote responses
fn intercept_response(mut response: Response<Incoming>) -> Response<HttpBody> {
dbg!({ format!("{:?}", response.headers()) });
response.headers_mut().insert("proxy-server", "Proxylea".parse().unwrap());
//let (parts,incoming)=resp.into_parts();
let resp = response.map(|b| b.map_frame(|frame| {
if let Some(bytes) = frame.data_ref() {
//
}
frame
}).boxed());
resp
}