diff --git a/Cargo.lock b/Cargo.lock index dcec33d..1ce5b35 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -243,6 +243,15 @@ dependencies = [ "subtle", ] +[[package]] +name = "ct-logs" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1a816186fa68d9e426e3cb4ae4dff1fcd8e4a2c34b781bf7a822574a0d0aac8" +dependencies = [ + "sct", +] + [[package]] name = "ctrlc" version = "3.1.8" @@ -635,7 +644,7 @@ dependencies = [ "httpdate", "itoa", "pin-project", - "socket2", + "socket2 0.4.0", "tokio", "tower-service", "tracing", @@ -979,6 +988,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "pem" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd56cbd21fea48d0c440b41cd69c589faacade08c992d9a54e471b79d0fd13eb" +dependencies = [ + "base64", + "once_cell", + "regex", +] + [[package]] name = "percent-encoding" version = "2.1.0" @@ -1326,12 +1346,64 @@ dependencies = [ "unicase", ] +[[package]] +name = "quic_demo" +version = "0.1.0" +dependencies = [ + "anyhow", + "futures-util", + "quinn", + "rcgen", + "tokio", + "tracing", + "tracing-subscriber", +] + [[package]] name = "quick-error" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ac73b1112776fc109b2e61909bc46c7e1bf0d7f690ffb1676553acce16d5cda" +[[package]] +name = "quinn" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c82c0a393b300104f989f3db8b8637c0d11f7a32a9c214560b47849ba8f119aa" +dependencies = [ + "bytes", + "futures", + "lazy_static", + "libc", + "mio", + "quinn-proto", + "rustls", + "socket2 0.3.19", + "thiserror", + "tokio", + "tracing", + "webpki", +] + +[[package]] +name = "quinn-proto" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "047aa96ec7ee6acabad7a1318dff72e9aff8994316bf2166c9b94cbec78ca54c" +dependencies = [ + "bytes", + "ct-logs", + "rand", + "ring", + "rustls", + "rustls-native-certs", + "slab", + "thiserror", + "tinyvec", + "tracing", + "webpki", +] + [[package]] name = "quote" version = "1.0.9" @@ -1390,6 +1462,18 @@ dependencies = [ "libc", ] +[[package]] +name = "rcgen" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48b4fc1b81d685fcd442a86da2e2c829d9e353142633a8159f42bf28e7e94428" +dependencies = [ + "chrono", + "pem", + "ring", + "yasna", +] + [[package]] name = "redox_syscall" version = "0.2.5" @@ -1533,6 +1617,18 @@ dependencies = [ "webpki", ] +[[package]] +name = "rustls-native-certs" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a07b7c1885bd8ed3831c289b7870b13ef46fe0e856d288c30d9cc17d75a2092" +dependencies = [ + "openssl-probe", + "rustls", + "schannel", + "security-framework", +] + [[package]] name = "rusty_ulid" version = "0.10.1" @@ -1685,6 +1781,17 @@ version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" +[[package]] +name = "socket2" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "winapi", +] + [[package]] name = "socket2" version = "0.4.0" @@ -2255,3 +2362,12 @@ checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" dependencies = [ "winapi", ] + +[[package]] +name = "yasna" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e262a29d0e61ccf2b6190d7050d4b237535fc76ce4c1210d9caa316f71dffa75" +dependencies = [ + "chrono", +] diff --git a/Cargo.toml b/Cargo.toml index 054dc26..5f4aa99 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,5 +47,6 @@ chrono = {version = "0.4.19", features = ["serde"]} members = [ "crates/*", + "prototypes/*", "tools/*", ] diff --git a/prototypes/quic_demo/.gitignore b/prototypes/quic_demo/.gitignore new file mode 100644 index 0000000..5c8950b --- /dev/null +++ b/prototypes/quic_demo/.gitignore @@ -0,0 +1,2 @@ +# TLS certs used for QUIC experiments +*.crt diff --git a/prototypes/quic_demo/Cargo.toml b/prototypes/quic_demo/Cargo.toml new file mode 100644 index 0000000..f7d4d10 --- /dev/null +++ b/prototypes/quic_demo/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "quic_demo" +version = "0.1.0" +authors = ["_"] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1.0.38" +futures-util = "0.3.9" +quinn = "0.7.2" +rcgen = "0.8.11" +tokio = { version = "1.4.0", features = ["full"] } +tracing-subscriber = "0.2.16" +tracing = "0.1.25" diff --git a/prototypes/quic_demo/src/bin/quic_demo_server.rs b/prototypes/quic_demo/src/bin/quic_demo_server.rs new file mode 100644 index 0000000..3e9cbd5 --- /dev/null +++ b/prototypes/quic_demo/src/bin/quic_demo_server.rs @@ -0,0 +1,132 @@ +use futures_util::StreamExt; +use tracing::{ + debug, + error, + info, + warn, +}; + +use quinn_utils::*; + +#[tokio::main] +async fn main () -> anyhow::Result <()> { + tracing_subscriber::fmt::init (); + + let server_addr = "0.0.0.0:5000".parse ()?; + let (mut incoming, server_cert) = make_server_endpoint (server_addr)?; + tokio::fs::write ("quic_server.crt", &server_cert).await?; + + debug! ("Waiting for end server to connect"); + + let end_server_conn = incoming.next ().await.ok_or_else (|| anyhow::anyhow! ("No end server connection"))?; + + let end_server_conn = end_server_conn.await?; + + let quinn::NewConnection { + connection: end_server_conn, + .. + } = end_server_conn; + + debug! ("Waiting for client to connect"); + + let client_conn = incoming.next ().await.ok_or_else (|| anyhow::anyhow! ("No client connection"))?; + + let client_conn = client_conn.await?; + + let quinn::NewConnection { + connection: _client_conn, + bi_streams: mut client_incoming_bi_streams, + .. + } = client_conn; + + debug! ("Waiting for client to open bi stream"); + + let (mut client_send, mut client_recv) = client_incoming_bi_streams.next ().await.ok_or_else (|| anyhow::anyhow! ("Client didn't open a bi stream"))??; + + debug! ("Opening bi stream to the end server"); + + let (mut server_send, mut server_recv) = end_server_conn.open_bi ().await?; + + debug! ("Relaying bytes..."); + + // Remember to swap tx and rx for patch cables + + let uplink_task = tokio::spawn (async move { + // Uplink - Client to end server + + let mut buf = vec! [0u8; 65_536]; + while let Some (bytes_read) = client_recv.read (&mut buf).await? { + let buf_slice = &buf [0..bytes_read]; + server_send.write (buf_slice).await?; + } + + debug! ("Uplink closed"); + + Ok::<_, anyhow::Error> (()) + }); + + let downlink_task = tokio::spawn (async move { + // Downlink - End server to client + + let mut buf = vec! [0u8; 65_536]; + while let Some (bytes_read) = server_recv.read (&mut buf).await? { + let buf_slice = &buf [0..bytes_read]; + client_send.write (buf_slice).await?; + } + + debug! ("Downlink closed"); + + Ok::<_, anyhow::Error> (()) + }); + + uplink_task.await??; + downlink_task.await??; + + Ok (()) +} + +// I'm not sure where I got this module from, but it's probably from the +// quinn examples, so the license should be okay. + +mod quinn_utils { + use quinn::{ + Certificate, CertificateChain, ClientConfig, ClientConfigBuilder, Endpoint, Incoming, + PrivateKey, ServerConfig, ServerConfigBuilder, TransportConfig, + }; + use std::{error::Error, net::SocketAddr, sync::Arc}; + + /// Constructs a QUIC endpoint configured to listen for incoming connections + /// on a certain address and port. + /// + /// ## Returns + /// + /// - a stream of incoming QUIC connections + /// - server certificate serialized into DER format + #[allow(unused)] + pub fn make_server_endpoint(bind_addr: SocketAddr) -> anyhow::Result<(Incoming, Vec)> { + let (server_config, server_cert) = configure_server()?; + let mut endpoint_builder = Endpoint::builder(); + endpoint_builder.listen(server_config); + let (_endpoint, incoming) = endpoint_builder.bind(&bind_addr)?; + Ok((incoming, server_cert)) + } + + /// Returns default server configuration along with its certificate. + #[allow(clippy::field_reassign_with_default)] // https://github.com/rust-lang/rust-clippy/issues/6527 + fn configure_server() -> anyhow::Result<(ServerConfig, Vec)> { + 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 = PrivateKey::from_der(&priv_key)?; + + let mut transport_config = TransportConfig::default(); + transport_config.max_concurrent_uni_streams(0).unwrap(); + let mut server_config = ServerConfig::default(); + server_config.transport = Arc::new(transport_config); + let mut cfg_builder = ServerConfigBuilder::new(server_config); + let cert = Certificate::from_der(&cert_der)?; + cfg_builder.certificate(CertificateChain::from_certs(vec![cert]), priv_key)?; + + Ok((cfg_builder.build(), cert_der)) + } +}