From f80e0a03b3bea2d99dd3e51247fc436cabcee2ca Mon Sep 17 00:00:00 2001 From: stammw Date: Fri, 3 Jan 2020 19:26:14 +0100 Subject: [PATCH] H3 interop server --- interop/Cargo.toml | 6 +- interop/src/h3_server.rs | 216 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 219 insertions(+), 3 deletions(-) create mode 100644 interop/src/h3_server.rs diff --git a/interop/Cargo.toml b/interop/Cargo.toml index e3618548e..f862eddca 100644 --- a/interop/Cargo.toml +++ b/interop/Cargo.toml @@ -25,6 +25,6 @@ webpki = "0.21" name = "main" path = "src/main.rs" -# [[bin]] -# name = "qif" -# path = "src/qif.rs" +[[bin]] +name = "h3_server" +path = "src/h3_server.rs" diff --git a/interop/src/h3_server.rs b/interop/src/h3_server.rs new file mode 100644 index 000000000..5897faec3 --- /dev/null +++ b/interop/src/h3_server.rs @@ -0,0 +1,216 @@ +use std::{cmp, fs, net::SocketAddr, path::PathBuf}; + +use anyhow::{anyhow, Context, Result}; +use bytes::Bytes; +use futures::{AsyncReadExt, AsyncWriteExt, StreamExt}; +use http::{Response, StatusCode}; +use structopt::{self, StructOpt}; +use tracing::error; + +use quinn_h3::{ + self, + server::{Builder as ServerBuilder, Connecting, RecvRequest}, +}; + +#[derive(StructOpt, Debug, Clone)] +#[structopt(name = "h3_server")] +struct Opt { + /// TLS private key in PEM format + #[structopt( + parse(from_os_str), + short = "k", + long = "key", + requires = "cert", + default_value = "key.der" + )] + key: PathBuf, + /// TLS certificate in PEM format + #[structopt( + parse(from_os_str), + short = "c", + long = "cert", + requires = "key", + default_value = "cert.der" + )] + cert: PathBuf, + /// Enable stateless retries + /// Address to listen on + #[structopt(long = "listen", default_value = "0.0.0.0:4433")] + listen: SocketAddr, +} + +#[tokio::main] +async fn main() -> Result<()> { + tracing::subscriber::set_global_default( + tracing_subscriber::FmtSubscriber::builder() + .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) + .finish(), + ) + .unwrap(); + let opt = Opt::from_args(); + + let key = fs::read(opt.key).context("failed to read private key")?; + let key = quinn::PrivateKey::from_der(&key[..])?; + let cert_chain = fs::read(opt.cert).context("failed to read certificate chain")?; + let cert_chain = + quinn::CertificateChain::from_certs(vec![quinn::Certificate::from_der(&cert_chain)?]); + + let mut server = ServerBuilder::default(); + server + .certificate(cert_chain, key) + .expect("failed to add cert"); + + let (_, mut incoming) = server.build().expect("bind failed"); + + println!("server listening"); + while let Some(connecting) = incoming.next().await { + tokio::spawn(async move { + if let Err(e) = handle_connection(connecting).await { + error!("handling connection failed: {:?}", e) + } + }); + } + + Ok(()) +} + +async fn handle_connection(connecting: Connecting) -> Result<()> { + println!("server received connection"); + let mut incoming = connecting.await.context("accept failed")?; + + tokio::spawn(async move { + while let Some(request) = incoming.next().await { + tokio::spawn(async move { + if let Err(e) = handle_request(request).await { + eprintln!("request error: {}", e) + } + }); + } + }); + + Ok(()) +} + +async fn handle_request(recv_request: RecvRequest) -> Result<()> { + let (request, mut recv_body, sender) = recv_request.await?; + println!("received request: {:?}", request); + + let mut body = Vec::with_capacity(1024); + recv_body + .read_to_end(&mut body) + .await + .map_err(|e| anyhow!("failed to send response headers: {:?}", e))?; + + println!("received body: {}", String::from_utf8_lossy(&body)); + if let Some(trailers) = recv_body.trailers().await { + println!("received trailers: {:?}", trailers); + } + + match request.uri().path() { + "/" => home(sender).await?, + x if !x.is_empty() => match parse_size(&x[1..]) { + Ok(n) => payload(sender, n).await?, + Err(_) => home(sender).await?, + }, + _ => home(sender).await?, + }; + + Ok(()) +} + +async fn home(sender: quinn_h3::server::Sender) -> Result<()> { + let response = Response::builder() + .status(StatusCode::OK) + .body(HOME) + .expect("failed to build response"); + sender + .send_response(response) + .await + .map_err(|e| anyhow!("failed to send response: {:?}", e))?; + Ok(()) +} + +async fn payload(sender: quinn_h3::server::Sender, len: usize) -> Result<()> { + if len > 1_000_000_000 { + let response = Response::builder() + .status(StatusCode::BAD_REQUEST) + .body(Bytes::from(format!("requested {}: too large", len))) + .expect("failed to build response"); + sender.send_response(response).await?; + return Ok(()); + } + + let response = Response::builder() + .status(StatusCode::OK) + .body(()) + .expect("failed to build response"); + + let mut body_writer = sender + .send_response(response) + .await + .map_err(|e| anyhow!("failed to send response: {:?}", e))?; + + let mut remaining = len; + while remaining > 0 { + let size = cmp::min(remaining, TEXT.len()); + body_writer.write_all(&TEXT[..size]).await?; + remaining -= size; + } + body_writer.flush().await?; + body_writer.close().await?; + + Ok(()) +} + +fn parse_size(literal: &str) -> Result { + let pos = literal + .find(|c: char| !c.is_ascii_digit()) + .unwrap_or_else(|| literal.len()); + let num: usize = literal[..pos] + .parse() + .map_err(|_| anyhow!("parse failed"))?; + let scale = match literal[pos..].to_uppercase().as_str() { + "K" => 1000, + "M" => 1_000_000, + "G" => 1_000_000_000, + _ => 1, + }; + Ok(num * scale) +} + +const TEXT: &[u8] = + b"It would be different if we could not step back and reflect on the process,\n\ + but were merely led from impulse to impulse without self- consciousness. But human\n\ + beings do not act solely on impulse. They are prudent, they reflect, they weigh\n\ + consequences, they ask whether what they are doing is worth while. Not only are their\n\ + lives full of particular choices that hang together in larger activities with temporal\n\ + structure: they also decide in the broadest terms what to pursue and what to avoid, what\n\ + the priorities among their various aims should be, and what kind of people they want to\n\ + be or become. Some men are faced with such choices by the large decisions they make from\n\ + time to time; some merely by reflection on the course their lives are taking as the product\n\ + of countless small decisions. They decide whom to marry, what profession to follow, whether\n\ + to join the Country Club, or the Resistance; or they may just wonder why they go on being\n\ + salesmen or academics or taxi drivers, and then stop thinking about it after a certain period\n\ + of inconclusive reflection."; + +const HOME: &str = r##" + + + Quinn H3 interop server + + +

Welcome to the quinn-h3 interop server.

+

+ Draft version: draft-24
+ Available tests: VHDCRZSBU3 +

+

+ Use '/{n}' to get n bytes of deep thoughts.
+ For example /1000000 + to get 1MB. Limit: 1GB +

+

Checkout our project's repository.

+

Say hi on quickdev slack workspace at `quinn`.

+ + +"##;