From 53e677dac67d408b294d87efb93fbe6ccf5c6ca7 Mon Sep 17 00:00:00 2001 From: Povilas Balciunas Date: Sun, 10 Mar 2019 23:28:38 +0200 Subject: [PATCH] Add the simplest QUIC connection example --- quinn/examples/common/mod.rs | 78 ++++++++++++++++++++++++++++++++++++ quinn/examples/connection.rs | 68 +++++++++++++++++++++++++++++++ 2 files changed, 146 insertions(+) create mode 100644 quinn/examples/common/mod.rs create mode 100644 quinn/examples/connection.rs diff --git a/quinn/examples/common/mod.rs b/quinn/examples/common/mod.rs new file mode 100644 index 000000000..8521889be --- /dev/null +++ b/quinn/examples/common/mod.rs @@ -0,0 +1,78 @@ +//! Commonly used code in most examples. + +use quinn::{ + Certificate, CertificateChain, ClientConfig, ClientConfigBuilder, Endpoint, EndpointDriver, + Incoming, PrivateKey, ServerConfig, ServerConfigBuilder, TransportConfig, +}; +use std::error::Error; +use std::net::ToSocketAddrs; +use std::sync::Arc; + +/// Constructs a QUIC endpoint configured for use a client only. +/// +/// ## Args +/// +/// - server_certs: list of trusted certificates. +#[allow(unused)] +pub fn make_client_endpoint( + bind_addr: A, + server_certs: &[&[u8]], +) -> Result<(Endpoint, EndpointDriver), Box> { + let client_cfg = configure_client(server_certs)?; + let mut endpoint_builder = Endpoint::new(); + endpoint_builder.default_client_config(client_cfg); + let (driver, endpoint, _incoming) = endpoint_builder.bind(bind_addr)?; + Ok((endpoint, driver)) +} + +/// Constructs a QUIC endpoint configured to listen for incoming connections on a certain address +/// and port. +/// +/// ## Returns +/// +/// - UDP socket driver +/// - a sream of incoming QUIC connections +/// - server certificate serialized into DER format +pub fn make_server_endpoint( + bind_addr: A, +) -> Result<(EndpointDriver, Incoming, Vec), Box> { + let (server_config, server_cert) = configure_server()?; + let mut endpoint_builder = Endpoint::new(); + endpoint_builder.listen(server_config); + let (driver, _endpoint, incoming) = endpoint_builder.bind(bind_addr)?; + Ok((driver, incoming, server_cert)) +} + +/// Builds default quinn client config and trusts given certificates. +/// +/// ## Args +/// +/// - server_certs: a list of trusted certificates in DER format. +fn configure_client(server_certs: &[&[u8]]) -> Result> { + let mut cfg_builder = ClientConfigBuilder::default(); + for cert in server_certs { + cfg_builder.add_certificate_authority(Certificate::from_der(&cert)?)?; + } + Ok(cfg_builder.build()) +} + +/// Returns default server configuration along with its certificate. +fn configure_server() -> Result<(ServerConfig, Vec), Box> { + let cert = rcgen::generate_simple_self_signed(vec!["localhost".into()]); + let cert_der = cert.serialize_der(); + let priv_key = cert.serialize_private_key_der(); + let priv_key = PrivateKey::from_der(&priv_key)?; + + let server_config = ServerConfig { + transport: Arc::new(TransportConfig { + stream_window_uni: 0, + ..Default::default() + }), + ..Default::default() + }; + 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)) +} diff --git a/quinn/examples/connection.rs b/quinn/examples/connection.rs new file mode 100644 index 000000000..d57e629b4 --- /dev/null +++ b/quinn/examples/connection.rs @@ -0,0 +1,68 @@ +//! This example intends to use the smallest amout of code to make a simple QUIC connection. +//! +//! The server issues it's own certificate and passes it to the client to trust. +//! +//! Run: +//! ```text +//! $ cargo run --example connection +//! ``` +//! +//! This example will make a QUIC connection on localhost, and you should see output like: +//! ```text +//! [server] incoming connection: id=3680c7d3b3ebd250 addr=127.0.0.1:50469 +//! [client] connected: id=61a2df1548935aeb, addr=127.0.0.1:5000 +//! ``` + +use futures::{Future, Stream}; +use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; +use tokio::runtime::current_thread::{self, Runtime}; + +mod common; +use common::{make_client_endpoint, make_server_endpoint}; + +const SERVER_PORT: u16 = 5000; + +fn main() -> Result<(), Box> { + let mut runtime = Runtime::new()?; + + let (driver, incoming, server_cert) = make_server_endpoint(("0.0.0.0", SERVER_PORT))?; + // drive UDP socket + runtime.spawn(driver.map_err(|e| panic!("IO error: {}", e))); + let handle_incoming_conns = incoming + .take(1) + .for_each(move |(conn_driver, conn, _incoming)| { + current_thread::spawn(conn_driver.map_err(|_| ())); + println!( + "[server] incoming connection: id={} addr={}", + conn.remote_id(), + conn.remote_address() + ); + Ok(()) + }); + runtime.spawn(handle_incoming_conns); + + let (endpoint, driver) = make_client_endpoint("0.0.0.0:0", &[&server_cert])?; + // drive UDP socket + runtime.spawn(driver.map_err(|e| panic!("IO error: {}", e))); + + let server_addr = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), SERVER_PORT)); + let connect = endpoint + .connect(&server_addr, "localhost")? + .map_err(|e| panic!("Failed to connect: {}", e)) + .and_then(|(conn_driver, conn, _)| { + current_thread::spawn(conn_driver.map_err(|_| ())); + println!( + "[client] connected: id={}, addr={}", + conn.remote_id(), + conn.remote_address() + ); + Ok(()) + }); + runtime.spawn(connect); + + // We don't need it anymore and dropping the endpoint will make it's driver finish eventually. + drop(endpoint); + + runtime.run()?; + Ok(()) +}