From cac4b065ca0fae3258c1b89f82f7e278b6c1ddd8 Mon Sep 17 00:00:00 2001 From: Ajit Banerjee Date: Thu, 29 Aug 2024 14:03:46 -0700 Subject: [PATCH] modifiying end to end benches to show tls impact --- Cargo.toml | 8 ++ benches/end_to_end.rs | 285 +++++++++++++++++++++++++++++++++-------- scripts/local_certs.sh | 11 ++ 3 files changed, 251 insertions(+), 53 deletions(-) create mode 100755 scripts/local_certs.sh diff --git a/Cargo.toml b/Cargo.toml index a1cdd9e7..3ff6bda3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,6 +39,8 @@ pin-project-lite = { version = "0.2.4", optional = true } smallvec = { version = "1.12", features = ["const_generics", "const_new"], optional = true } tracing = { version = "0.1", default-features = false, features = ["std"], optional = true } want = { version = "0.3", optional = true } +rustls-pemfile = "2.1.3" +rustls = "0.23.12" [dev-dependencies] form_urlencoded = "1" @@ -63,6 +65,12 @@ tokio = { version = "1", features = [ ] } tokio-test = "0.4" tokio-util = "0.7.10" +tokio-rustls = "0.26.0" +hyper-tls = "0.6.0" +rustls = "0.23.12" +rustls-pemfile = "2.1.3" +hyper-rustls = { version = "0.27.2", features = ["http2"] } +rustls-pki-types = "1" [features] # Nothing by default diff --git a/benches/end_to_end.rs b/benches/end_to_end.rs index a277688d..ec46f1c8 100644 --- a/benches/end_to_end.rs +++ b/benches/end_to_end.rs @@ -12,7 +12,7 @@ use std::net::SocketAddr; use futures_util::future::join_all; use http_body_util::BodyExt; -use hyper::{Method, Request, Response}; + use hyper::{Method, Request, Response}; type BoxedBody = http_body_util::combinators::BoxBody; @@ -31,6 +31,23 @@ fn http1_consecutive_x1_req_10b(b: &mut test::Bencher) { .bench(b) } +#[bench] +fn http1_consecutive_x1_req_100kb(b: &mut test::Bencher) { + opts() + .method(Method::POST) + .request_body(&[b's'; 1024 * 100]) + .bench(b) +} + +#[bench] +fn http1_consecutive_x1_req_100kb_tls(b: &mut test::Bencher) { + opts() + .method(Method::POST) + .request_body(&[b's'; 1024 * 100]) + .tls() + .bench(b) +} + #[bench] fn http1_consecutive_x1_both_100kb(b: &mut test::Bencher) { let body = &[b'x'; 1024 * 100]; @@ -140,8 +157,8 @@ fn http2_parallel_x10_req_10mb(b: &mut test::Bencher) { } #[bench] -fn http2_parallel_x10_req_10kb_100_chunks(b: &mut test::Bencher) { - let body = &[b'x'; 1024 * 10]; +fn http2_parallel_x10_req_100kb_100_chunks(b: &mut test::Bencher) { + let body = &[b'x'; 1024 * 100]; opts() .http2() .parallel(10) @@ -151,8 +168,19 @@ fn http2_parallel_x10_req_10kb_100_chunks(b: &mut test::Bencher) { } #[bench] -fn http2_parallel_x10_req_10kb_100_chunks_adaptive_window(b: &mut test::Bencher) { - let body = &[b'x'; 1024 * 10]; +fn http2_parallel_x10_req_100kb_100_chunks_tls(b: &mut test::Bencher) { + let body = &[b'x'; 1024 * 100]; + opts() + .http2() + .parallel(10) + .method(Method::POST) + .request_chunks(body, 100) + .tls() + .bench(b) +} +#[bench] +fn http2_parallel_x10_req_100kb_100_chunks_adaptive_window(b: &mut test::Bencher) { + let body = &[b'x'; 1024 * 100]; opts() .http2() .parallel(10) @@ -163,8 +191,34 @@ fn http2_parallel_x10_req_10kb_100_chunks_adaptive_window(b: &mut test::Bencher) } #[bench] -fn http2_parallel_x10_req_10kb_100_chunks_max_window(b: &mut test::Bencher) { - let body = &[b'x'; 1024 * 10]; +fn http2_parallel_x10_req_100kb_100_chunks_adaptive_window_tls(b: &mut test::Bencher) { + let body = &[b'x'; 1024 * 100]; + opts() + .http2() + .parallel(10) + .method(Method::POST) + .request_chunks(body, 100) + .http2_adaptive_window() + .tls() + .bench(b) +} + +#[bench] +fn http2_parallel_x10_req_100kb_100_chunks_max_window(b: &mut test::Bencher) { + let body = &[b'x'; 1024 * 100]; + opts() + .http2() + .parallel(10) + .method(Method::POST) + .request_chunks(body, 100) + .http2_stream_window(HTTP2_MAX_WINDOW) + .http2_conn_window(HTTP2_MAX_WINDOW) + .bench(b) +} + +#[bench] +fn http2_parallel_x10_req_100kb_100_chunks_max_window_tls(b: &mut test::Bencher) { + let body = &[b'x'; 1024 * 100]; opts() .http2() .parallel(10) @@ -172,6 +226,7 @@ fn http2_parallel_x10_req_10kb_100_chunks_max_window(b: &mut test::Bencher) { .request_chunks(body, 100) .http2_stream_window(HTTP2_MAX_WINDOW) .http2_conn_window(HTTP2_MAX_WINDOW) + .tls() .bench(b) } @@ -212,6 +267,7 @@ struct Opts { request_body: Option<&'static [u8]>, request_chunks: usize, response_body: &'static [u8], + tls: bool, } fn opts() -> Opts { @@ -225,6 +281,7 @@ fn opts() -> Opts { request_body: None, request_chunks: 0, response_body: b"", + tls: false, } } @@ -281,8 +338,41 @@ impl Opts { self } + fn tls(mut self) -> Self { + self.tls = true; + self + } + fn bench(self, b: &mut test::Bencher) { - use std::sync::Arc; + + use rustls::{pki_types::ServerName, ClientConfig, RootCertStore}; + use rustls_pemfile::{Item, read_one_from_slice}; + + use std::{ + fs::read, + sync::Arc, + }; + + use tokio_rustls::TlsConnector; + const CERT_FILE: &str = "repro-ca-cert.pem"; + + let connector = if self.tls { + let cert_bytes = read(CERT_FILE).unwrap(); + let (item, _) = read_one_from_slice(&cert_bytes).unwrap().unwrap(); + let cert = match item { + Item::X509Certificate(cert) => cert, + _ => todo!("invalid cert format"), + }; + let mut root_store = RootCertStore::empty(); + root_store.add(cert).unwrap(); + let config = ClientConfig::builder() + .with_root_certificates(root_store) + .with_no_client_auth(); + Some(TlsConnector::from(Arc::new(config))) + } else { + None + }; + let _ = pretty_env_logger::try_init(); // Create a runtime of current thread. let rt = Arc::new( @@ -312,27 +402,56 @@ impl Opts { let mut client = rt.block_on(async { if self.http2 { let tcp = tokio::net::TcpStream::connect(&addr).await.unwrap(); - let io = support::TokioIo::new(tcp); - let (tx, conn) = hyper::client::conn::http2::Builder::new(support::TokioExecutor) - .initial_stream_window_size(self.http2_stream_window) - .initial_connection_window_size(self.http2_conn_window) - .adaptive_window(self.http2_adaptive_window) - .handshake(io) - .await - .unwrap(); - tokio::spawn(conn); - Client::Http2(tx) + + if let Some(connector) = connector { + let domain = ServerName::try_from("localhost").unwrap(); + let tls_stream = connector.connect(domain, tcp).await.unwrap(); + let io = support::TokioIo::new(tls_stream); + let (tx, conn) = hyper::client::conn::http2::Builder::new(support::TokioExecutor) + .initial_stream_window_size(self.http2_stream_window) + .initial_connection_window_size(self.http2_conn_window) + .adaptive_window(self.http2_adaptive_window) + .handshake(io) + .await + .unwrap(); + tokio::spawn(conn); + Client::Http2(tx) + } else { + let io = support::TokioIo::new(tcp); + let (tx, conn) = hyper::client::conn::http2::Builder::new(support::TokioExecutor) + .initial_stream_window_size(self.http2_stream_window) + .initial_connection_window_size(self.http2_conn_window) + .adaptive_window(self.http2_adaptive_window) + .handshake(io) + .await + .unwrap(); + tokio::spawn(conn); + Client::Http2(tx) + } + } else if self.parallel_cnt > 1 { todo!("http/1 parallel >1"); } else { let tcp = tokio::net::TcpStream::connect(&addr).await.unwrap(); - let io = support::TokioIo::new(tcp); - let (tx, conn) = hyper::client::conn::http1::Builder::new() - .handshake(io) - .await - .unwrap(); - tokio::spawn(conn); - Client::Http1(tx) + if let Some(connector) = connector { + let domain = ServerName::try_from("localhost").unwrap(); + let tls_stream = connector.connect(domain, tcp).await.unwrap(); + let io = support::TokioIo::new(tls_stream); + let (tx, conn) = hyper::client::conn::http1::Builder::new() + .handshake(io) + .await + .unwrap(); + tokio::spawn(conn); + Client::Http1(tx) + } else { + let io = support::TokioIo::new(tcp); + let (tx, conn) = hyper::client::conn::http1::Builder::new() + .handshake(io) + .await + .unwrap(); + tokio::spawn(conn); + Client::Http1(tx) + } } }); @@ -405,6 +524,31 @@ fn spawn_server(rt: &tokio::runtime::Runtime, opts: &Opts) -> SocketAddr { use http_body_util::Full; use hyper::service::service_fn; use tokio::net::TcpListener; + use std::{io::Cursor, fs::read}; + use std::sync::Arc; + use rustls::ServerConfig; + use rustls_pemfile::private_key; + use tokio_rustls::TlsAcceptor; + const SERVER_CERT_FILE: &str = "server-cert.pem"; + const SERVER_KEY_FILE: &str = "server-key.pem"; + + + let mut cursor = Cursor::new(read(SERVER_CERT_FILE).unwrap()); + let certs = vec![rustls_pemfile::certs(&mut cursor) + .next() + .expect("No certificates found") + .expect("Failed to parse certificate")]; + let mut key_reader = Cursor::new(read(SERVER_KEY_FILE).unwrap()); + let private_key = private_key(&mut key_reader) + .unwrap() + .unwrap(); + + let tls_config = ServerConfig::builder() + .with_no_client_auth() + .with_single_cert(certs, private_key) + .expect("bad certificate/key"); + let tls_config = Arc::new(tls_config); + let addr = "127.0.0.1:0".parse::().unwrap(); let listener = rt.block_on(async { TcpListener::bind(&addr).await.unwrap() }); @@ -415,35 +559,70 @@ fn spawn_server(rt: &tokio::runtime::Runtime, opts: &Opts) -> SocketAddr { rt.spawn(async move { let _ = &opts; while let Ok((sock, _)) = listener.accept().await { - let io = support::TokioIo::new(sock); - if opts.http2 { - tokio::spawn( - hyper::server::conn::http2::Builder::new(support::TokioExecutor) - .initial_stream_window_size(opts.http2_stream_window) - .initial_connection_window_size(opts.http2_conn_window) - .adaptive_window(opts.http2_adaptive_window) - .serve_connection( - io, - service_fn(move |req: Request| async move { - let mut req_body = req.into_body(); - while let Some(_chunk) = req_body.frame().await {} - Ok::<_, std::convert::Infallible>(Response::new( - Full::::from(body), - )) - }), - ), - ); + if opts.tls { + let tls_acceptor = TlsAcceptor::from(tls_config.clone()); + let tls_sock = tls_acceptor.accept(sock).await.unwrap(); + let io = support::TokioIo::new(tls_sock); + if opts.http2 { + tokio::spawn( + hyper::server::conn::http2::Builder::new(support::TokioExecutor) + .initial_stream_window_size(opts.http2_stream_window) + .initial_connection_window_size(opts.http2_conn_window) + .adaptive_window(opts.http2_adaptive_window) + .serve_connection( + io, + service_fn(move |req: Request| async move { + let mut req_body = req.into_body(); + while let Some(_chunk) = req_body.frame().await {} + Ok::<_, std::convert::Infallible>(Response::new( + Full::::from(body), + )) + }), + ), + ); + } else { + tokio::spawn(hyper::server::conn::http1::Builder::new().serve_connection( + io, + service_fn(move |req: Request| async move { + let mut req_body = req.into_body(); + while let Some(_chunk) = req_body.frame().await {} + Ok::<_, std::convert::Infallible>(Response::new( + Full::::from(body), + )) + }), + )); + } } else { - tokio::spawn(hyper::server::conn::http1::Builder::new().serve_connection( - io, - service_fn(move |req: Request| async move { - let mut req_body = req.into_body(); - while let Some(_chunk) = req_body.frame().await {} - Ok::<_, std::convert::Infallible>(Response::new( - Full::::from(body), - )) - }), - )); + let io = support::TokioIo::new(sock); + if opts.http2 { + tokio::spawn( + hyper::server::conn::http2::Builder::new(support::TokioExecutor) + .initial_stream_window_size(opts.http2_stream_window) + .initial_connection_window_size(opts.http2_conn_window) + .adaptive_window(opts.http2_adaptive_window) + .serve_connection( + io, + service_fn(move |req: Request| async move { + let mut req_body = req.into_body(); + while let Some(_chunk) = req_body.frame().await {} + Ok::<_, std::convert::Infallible>(Response::new( + Full::::from(body), + )) + }), + ), + ); + } else { + tokio::spawn(hyper::server::conn::http1::Builder::new().serve_connection( + io, + service_fn(move |req: Request| async move { + let mut req_body = req.into_body(); + while let Some(_chunk) = req_body.frame().await {} + Ok::<_, std::convert::Infallible>(Response::new( + Full::::from(body), + )) + }), + )); + } } } }); diff --git a/scripts/local_certs.sh b/scripts/local_certs.sh new file mode 100755 index 00000000..c17498b9 --- /dev/null +++ b/scripts/local_certs.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +# 1. Generate CA's private key and self-signed certificate +openssl req -x509 -newkey rsa:4096 -days 1825 -nodes -keyout repro-ca-key.pem -out repro-ca-cert.pem -subj "/C=US/CN=repro root CA" + +# 2. Generate web server's private key and certificate signing request (CSR) +openssl req -newkey rsa:4096 -nodes -keyout server-key.pem -out server-req.pem -subj "/C=US/CN=localhost" + +# 3. Use CA's private key to sign web server's CSR and get back the signed certificate +printf "subjectAltName=DNS:%s\n" "localhost" > server-ext.cnf +openssl x509 -req -in server-req.pem -days 60 -CA repro-ca-cert.pem -CAkey repro-ca-key.pem -CAcreateserial -out server-cert.pem -extfile server-ext.cnf -- 2.39.3 (Apple Git-146)