h3-quinn http3 server with tokio in rust

171 views Asked by At

I'm trying to write a simple http3 server in rust that receives get requests with "commands" and returns JSON object as output. i use tokio with h3-quinn.

these are all the packages that I use:

quinn = "0.10.2"
tokio = { version = "1.34.0", features = ["rt-multi-thread", "full"] }
serde_json = { version = "1.0.108", features = [] }
serde = { version = "1.0.192", features = ["derive"] }
anyhow = "1.0.75"
rcgen = "0.11.3"
rustls = { version = "0.21.8" }
rustls-pemfile = "1.0.4"
h3-quinn = "0.0.4"
log = "0.4.20"
tracing = "0.1.40"
h3 = "0.0.3"
clap = { version = "4.4.8", features = ["derive"] }
env_logger = "0.10.1"

I'm creating a self signed certificate and listening on 0.0.0.0:4443, when i try to test it with curl, it just hangs.

i'm using Gentoo Linux and i compiled curl with http3, and this is how i use it:

curl --http3 -I https://localhost:4443

when i try google and other sites i do get results so it's not a curl related problem.

I added some info messages and i just see the messages starting! and listening on 0.0.0.0:4443, running the curl command doesn't show anymore information.

looks like it's stuck at endpoint.accept().await, i don't think it's a certificate problem because i should have gotten some kind of error instead.

this is the full code:

use std::error::Error;
use std::fs::File;
use std::io::BufReader;
use std::sync::Arc;
use std::time::Duration;
use anyhow::Result;
use rcgen::generate_simple_self_signed;
use h3_quinn::quinn::{self, Endpoint, ServerConfig};
use quinn::{Connection, ConnectionError, TransportConfig};
use rustls::{Certificate, PrivateKey, ServerConfig as RustlsServerConfig};
use log::{error, info};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use clap::Parser;
use env_logger::Env;

// Define a struct for your data
#[derive(serde::Serialize)]
struct MusicWidget {
    name: String,
    short_name: String,
    version: String,
}

// Create instances of the struct
async fn handle_connection(conn: Connection) -> Result<()> {
    let widgets: Vec<MusicWidget> = vec![
        MusicWidget {
            name: "WidgetA".to_string(),
            short_name: "WA".to_string(),
            version: "1.0".to_string(),
        },
        MusicWidget {
            name: "WidgetB".to_string(),
            short_name: "WB".to_string(),
            version: "2.0".to_string(),
        },
        MusicWidget {
            name: "WidgetC".to_string(),
            short_name: "WC".to_string(),
            version: "3.0".to_string(),
        },
    ];



    while let  Ok((mut send, mut recv)) = conn.accept_bi().await {

        let mut buf = vec![0; 1024];
        let _ = recv.read(&mut buf).await?;

        let command = String::from_utf8_lossy(&buf);
        if command.trim() == "get_music_widget_list" {
            let response = serde_json::to_vec(&widgets)?;
            send.write_all(&response).await?;
            send.finish().await?;
        }
    }

    Ok(())
}

fn load_private_key_from_file(path: &str) -> Result<PrivateKey, Box<dyn std::error::Error>> {
    let file = File::open(&path)?;
    let mut reader = BufReader::new(file);
    let keys = rustls_pemfile::pkcs8_private_keys(&mut reader)?;

    if keys.is_empty() {
        Err("No PKCS8-encoded private key found".into())
    } else {
        Ok(PrivateKey(keys[0].clone()))
    }
}

/// Returns default server configuration along with its certificate.
fn configure_server() -> Result<(ServerConfig, Vec<u8>), Box<dyn Error>> {
    let cert = rcgen::generate_simple_self_signed(vec!["localhost".into()]).unwrap();
    let cert_der = cert.serialize_der().unwrap();
    let priv_key = cert.serialize_private_key_der();
    let priv_key = rustls::PrivateKey(priv_key);
    let cert_chain = vec![rustls::Certificate(cert_der.clone())];

    let mut server_config = ServerConfig::with_single_cert(cert_chain, priv_key)?;
    let transport_config = Arc::get_mut(&mut server_config.transport).unwrap();
    transport_config.max_concurrent_uni_streams(0_u8.into());

    Ok((server_config, cert_der))
}


fn generate_self_signed_cert() -> Result<(Vec<u8>, PrivateKey), Box<dyn std::error::Error>> {
    let cert = generate_simple_self_signed(vec!["localhost".into()])?;
    let key = PrivateKey(cert.serialize_private_key_der());
    let cert_der = cert.serialize_der()?;
    Ok((cert_der, key))
}

fn create_server_config() -> Result<ServerConfig, Box<dyn std::error::Error>> {
    let (cert, key) = generate_self_signed_cert()?;

    let cert = Certificate(cert);
    let server_crypto_config = RustlsServerConfig::builder()
        .with_safe_defaults()
        .with_no_client_auth()
        .with_single_cert(vec![cert], key)?;

    let transport_config = Arc::new(TransportConfig::default());
    let mut server_config = ServerConfig::with_crypto(Arc::new(server_crypto_config));
    server_config.transport = transport_config;

    Ok(server_config)
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let env = Env::default()
        .filter_or("MY_LOG_LEVEL", "info");
    env_logger::init_from_env(env);
    info!("starting!");
    // Create a multi-threaded Tokio runtime
    // Create the server configuration
    let (server_config, cf) = configure_server()?;

    //let server_config = create_server_config()?;

    let endpoint = Endpoint::server(server_config, "0.0.0.0:4443".parse()?)?;
    // Start the server and handle incoming connections
    info!("listening on 0.0.0.0:4443");
    while let Some(new_conn) = endpoint.accept().await {
        info!("New connection being attempted");

        tokio::spawn(async move {
            info!("new connection something");
            match new_conn.await {
                Ok(conn) => {
                    info!("new connection something");
                    if let Err(err) = handle_connection(conn).await {
                        tracing::error!("Failed to handle connection: {err:?}");
                    }
                }
                Err(e) => {
                    tracing::error!("Failed to establish connection: {:?}", e);
                }
                _ => {}
            }
        });
    }
    info!("wait idle");
    endpoint.wait_idle().await;

    Ok(())
}

any information regarding this issue would be greatly appreciated.

thanks

0

There are 0 answers