Add the simplest QUIC connection example

This commit is contained in:
Povilas Balciunas
2019-03-10 23:28:38 +02:00
committed by Dirkjan Ochtman
parent 2dd342f4dc
commit 53e677dac6
2 changed files with 146 additions and 0 deletions
+78
View File
@@ -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<A: ToSocketAddrs>(
bind_addr: A,
server_certs: &[&[u8]],
) -> Result<(Endpoint, EndpointDriver), Box<Error>> {
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<A: ToSocketAddrs>(
bind_addr: A,
) -> Result<(EndpointDriver, Incoming, Vec<u8>), Box<Error>> {
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<ClientConfig, Box<Error>> {
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<u8>), Box<Error>> {
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))
}
+68
View File
@@ -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<std::error::Error>> {
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(())
}