mirror of
https://github.com/n0-computer/noq.git
synced 2026-09-21 02:33:23 +00:00
session resumption
This commit is contained in:
+2
-2
@@ -12,7 +12,7 @@ use std::io::{self, Write};
|
||||
use std::str;
|
||||
|
||||
use failure::Error;
|
||||
use quicr::{Endpoint, Config, Io, Timer, Event, Directionality, ReadError};
|
||||
use quicr::{Endpoint, Config, Io, Timer, Event, Directionality, ReadError, ClientConfig};
|
||||
use slog::{Logger, Drain};
|
||||
|
||||
fn main() {
|
||||
@@ -77,7 +77,7 @@ impl Context {
|
||||
|
||||
fn run(&mut self) -> Result<()> {
|
||||
let epoch = Instant::now();
|
||||
let c = self.client.connect(self.remote, Some(self.remote_host.as_bytes()));
|
||||
let c = self.client.connect(self.remote, ClientConfig { server_name: Some(&self.remote_host), ..ClientConfig::default() });
|
||||
let mut time = 0;
|
||||
let mut buf = Vec::new();
|
||||
let mut sent = 0;
|
||||
|
||||
@@ -14,6 +14,7 @@ use std::net::ToSocketAddrs;
|
||||
use std::io::{self, Write};
|
||||
use std::time::{Instant, Duration};
|
||||
use std::path::PathBuf;
|
||||
use std::fs;
|
||||
|
||||
use futures::Future;
|
||||
use tokio::runtime::current_thread::Runtime;
|
||||
@@ -31,10 +32,16 @@ struct Opt {
|
||||
/// file to log TLS keys to for debugging
|
||||
#[structopt(parse(from_os_str), long = "keylog")]
|
||||
keylog: Option<PathBuf>,
|
||||
|
||||
url: Url,
|
||||
|
||||
/// whether to accept invalid (e.g. self-signed) TLS certificates
|
||||
#[structopt(long = "accept-insecure-certs")]
|
||||
accept_insecure_certs: bool,
|
||||
|
||||
/// file to read/write session tickets to
|
||||
#[structopt(long = "session-cache", parse(from_os_str))]
|
||||
session_cache: Option<PathBuf>,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
@@ -50,27 +57,48 @@ fn main() {
|
||||
::std::process::exit(code);
|
||||
}
|
||||
|
||||
fn run(log: Logger, options: Opt) -> Result<()> {
|
||||
fn run(log: Logger, mut options: Opt) -> Result<()> {
|
||||
let url = options.url;
|
||||
let remote = url.with_default_port(|_| Ok(4433))?.to_socket_addrs()?.next().ok_or(format_err!("couldn't resolve to an address"))?;
|
||||
|
||||
let mut runtime = Runtime::new()?;
|
||||
|
||||
let mut config = quicr::Config {
|
||||
protocols: vec![b"hq-11"[..].into()],
|
||||
keylog: options.keylog,
|
||||
..quicr::Config::default()
|
||||
};
|
||||
|
||||
let ticket;
|
||||
if let Some(path) = options.session_cache.take() {
|
||||
ticket = match fs::read(&path) {
|
||||
Ok(x) => Some(x),
|
||||
Err(ref e) if e.kind() == io::ErrorKind::NotFound => None,
|
||||
Err(e) => { return Err(e.into()); }
|
||||
};
|
||||
config.session_cache = Some(Box::new(move |_, _, data| {
|
||||
fs::write(&path, data).unwrap();
|
||||
}));
|
||||
} else {
|
||||
ticket = None;
|
||||
}
|
||||
|
||||
let mut builder = quicr::Endpoint::new();
|
||||
builder.logger(log.clone())
|
||||
.config(quicr::Config {
|
||||
protocols: vec![b"hq-11"[..].into()],
|
||||
keylog: options.keylog,
|
||||
accept_insecure_certs: options.accept_insecure_certs,
|
||||
..quicr::Config::default()
|
||||
});
|
||||
.config(config);
|
||||
let (endpoint, driver, _) = builder.bind("[::]:0")?;
|
||||
runtime.spawn(driver.map_err(|e| eprintln!("IO error: {}", e)));
|
||||
|
||||
let request = format!("GET {}\r\n", url.path());
|
||||
let start = Instant::now();
|
||||
runtime.block_on(
|
||||
endpoint.connect(&remote, url.host_str().map(|x| x.as_bytes()))
|
||||
endpoint.connect(&remote,
|
||||
quicr::ClientConfig {
|
||||
server_name: Some(url.host_str().ok_or(format_err!("URL missing host"))?),
|
||||
accept_insecure_certs: options.accept_insecure_certs,
|
||||
session_ticket: ticket.as_ref().map(|x| &x[..]),
|
||||
..quicr::ClientConfig::default()
|
||||
})
|
||||
.map_err(|e| format_err!("failed to connect: {}", e))
|
||||
.and_then(move |(conn, _)| {
|
||||
eprintln!("connected at {}", duration_secs(&start.elapsed()));
|
||||
|
||||
+3
-5
@@ -89,7 +89,7 @@ use bytes::Bytes;
|
||||
|
||||
use quicr::{Directionality, StreamId, ConnectionHandle, Side, CertConfig};
|
||||
|
||||
pub use quicr::{Config, ConnectionError, ConnectionId, ListenKeys};
|
||||
pub use quicr::{Config, ClientConfig, ConnectionError, ConnectionId, ListenKeys};
|
||||
|
||||
/// Errors that can occur during the construction of an `Endpoint`.
|
||||
#[derive(Debug, Fail)]
|
||||
@@ -307,13 +307,11 @@ impl Endpoint {
|
||||
}}
|
||||
|
||||
/// Connect to a remote endpoint.
|
||||
///
|
||||
/// `hostname` is used by the remote endpoint for disambiguation if `addr` hosts multiple services.
|
||||
pub fn connect(&self, addr: &SocketAddr, hostname: Option<&[u8]>) -> impl Future<Item=(Connection, IncomingStreams), Error=ConnectionError> {
|
||||
pub fn connect(&self, addr: &SocketAddr, config: ClientConfig) -> impl Future<Item=(Connection, IncomingStreams), Error=ConnectionError> {
|
||||
let (send, recv) = oneshot::channel();
|
||||
let conn = {
|
||||
let mut endpoint = self.0.borrow_mut();
|
||||
let conn = endpoint.inner.connect(normalize(*addr), hostname);
|
||||
let conn = endpoint.inner.connect(normalize(*addr), config);
|
||||
endpoint.pending.insert(conn, Pending::new(Some(send)));
|
||||
conn
|
||||
};
|
||||
|
||||
+59
-11
@@ -10,9 +10,10 @@ use rand::distributions::Sample;
|
||||
use slab::Slab;
|
||||
use openssl::{self, ex_data};
|
||||
use openssl::ssl::{self, SslContext, SslMethod, SslOptions, SslVersion, SslMode, Ssl, SslStream, HandshakeError, MidHandshakeSslStream,
|
||||
SslStreamBuilder, SslAlert, SslRef};
|
||||
SslStreamBuilder, SslAlert, SslRef, SslSession};
|
||||
use openssl::pkey::{PKeyRef, Private};
|
||||
use openssl::x509::X509Ref;
|
||||
use openssl::x509::verify::X509CheckFlags;
|
||||
use openssl::hash::MessageDigest;
|
||||
use openssl::symm::{Cipher, encrypt_aead, decrypt_aead};
|
||||
use blake2::Blake2b;
|
||||
@@ -92,17 +93,14 @@ pub struct Config {
|
||||
/// If empty, application-layer protocol negotiation will not be preformed.
|
||||
pub protocols: Vec<Box<[u8]>>,
|
||||
|
||||
/// Whether to accept inauthentic or unverifiable peer certificates.
|
||||
///
|
||||
/// Turning this off exposes clients to man-in-the-middle attacks in the same manner as an unencrypted TCP
|
||||
/// connection, but allows them to connect to servers that are using self-signed certificates.
|
||||
pub accept_insecure_certs: bool,
|
||||
|
||||
/// Path to write NSS SSLKEYLOGFILE-compatible key log.
|
||||
///
|
||||
/// Enabling this compromises security by committing secret information to disk. Useful for debugging communications
|
||||
/// when using tools like Wireshark.
|
||||
pub keylog: Option<PathBuf>,
|
||||
|
||||
/// Function to store session tickets transmitted by the server for fast resumption.
|
||||
pub session_cache: Option<Box<Fn(Option<&str>, &SocketAddrV6, &[u8]) + Send + Sync + 'static>>,
|
||||
}
|
||||
|
||||
pub struct CertConfig<'a> {
|
||||
@@ -141,12 +139,37 @@ impl Default for Config {
|
||||
loss_reduction_factor: 0x8000, // 1/2
|
||||
protocols: Vec::new(),
|
||||
|
||||
accept_insecure_certs: false,
|
||||
keylog: None,
|
||||
session_cache: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ClientConfig<'a> {
|
||||
/// The name of the server the client intends to connect to.
|
||||
///
|
||||
/// Used for both certificate validation, and for disambiguating between multiple domains hosted by the same IP
|
||||
/// address (using SNI).
|
||||
pub server_name: Option<&'a str>,
|
||||
|
||||
/// A ticket to resume a previous session faster than performing a full handshake.
|
||||
pub session_ticket: Option<&'a [u8]>,
|
||||
|
||||
/// Whether to accept inauthentic or unverifiable peer certificates.
|
||||
///
|
||||
/// Turning this off exposes clients to man-in-the-middle attacks in the same manner as an unencrypted TCP
|
||||
/// connection, but allows them to connect to servers that are using self-signed certificates.
|
||||
pub accept_insecure_certs: bool,
|
||||
}
|
||||
|
||||
impl<'a> Default for ClientConfig<'a> {
|
||||
fn default() -> Self { Self {
|
||||
server_name: None,
|
||||
session_ticket: None,
|
||||
accept_insecure_certs: false,
|
||||
}}
|
||||
}
|
||||
|
||||
/// The main entry point to the library
|
||||
///
|
||||
/// This object performs no I/O whatsoever. Instead, it generates a stream of I/O operations for a backend to perform
|
||||
@@ -338,6 +361,16 @@ impl Endpoint {
|
||||
});
|
||||
}
|
||||
|
||||
if config.session_cache.is_some() {
|
||||
let config = config.clone();
|
||||
tls.set_session_cache_mode(ssl::SslSessionCacheMode::CLIENT | ssl::SslSessionCacheMode::NO_INTERNAL_STORE);
|
||||
tls.set_new_session_callback(move |tls, session| {
|
||||
let conn = tls.ex_data(*CONNECTION_INFO_INDEX).expect("connection info unset");
|
||||
let name = tls.servername(ssl::NameType::HOST_NAME);
|
||||
(config.session_cache.as_ref().unwrap())(name, &conn.remote, &session.to_der().expect("failed to serialize session ticket"));
|
||||
});
|
||||
}
|
||||
|
||||
let tls = tls.build();
|
||||
|
||||
Ok(Self {
|
||||
@@ -482,15 +515,30 @@ impl Endpoint {
|
||||
}
|
||||
|
||||
/// Initiate a connection
|
||||
pub fn connect(&mut self, remote: SocketAddrV6, hostname: Option<&[u8]>) -> ConnectionHandle {
|
||||
pub fn connect(&mut self, remote: SocketAddrV6, config: ClientConfig) -> ConnectionHandle {
|
||||
let local_id = ConnectionId::random(&mut self.rng, LOCAL_ID_LEN as u8);
|
||||
let remote_id = ConnectionId::random(&mut self.rng, MAX_CID_SIZE as u8);
|
||||
trace!(self.log, "initial dcid"; "value" => %remote_id);
|
||||
let conn = self.add_connection(remote_id.clone(), local_id, remote_id, remote, Side::Client);
|
||||
let mut tls = Ssl::new(&self.tls).unwrap(); // Is this fallible?
|
||||
if !self.config.accept_insecure_certs { tls.set_verify(ssl::SslVerifyMode::PEER); }
|
||||
if !config.accept_insecure_certs {
|
||||
tls.set_verify(ssl::SslVerifyMode::PEER);
|
||||
let param = tls.param_mut();
|
||||
if let Some(name) = config.server_name {
|
||||
param.set_hostflags(X509CheckFlags::NO_PARTIAL_WILDCARDS);
|
||||
match name.parse() {
|
||||
Ok(ip) => { param.set_ip(ip).expect("failed to inform TLS of remote ip"); }
|
||||
Err(_) => { param.set_host(name).expect("failed to inform TLS of remote hostname"); }
|
||||
}
|
||||
}
|
||||
}
|
||||
tls.set_ex_data(*CONNECTION_INFO_INDEX, ConnectionInfo { id: self.connections[conn.0].local_id.clone(), remote });
|
||||
if let Some(hostname) = hostname { tls.set_hostname(str::from_utf8(hostname).expect("malformed hostname")).unwrap(); }
|
||||
if let Some(name) = config.server_name { tls.set_hostname(name).unwrap(); }
|
||||
if let Some(session) = config.session_ticket {
|
||||
if let Err(e) = SslSession::from_der(session).and_then(|x| unsafe { tls.set_session(&x) }) {
|
||||
error!(self.log, "failed to set TLS session"; "reason" => %e);
|
||||
}
|
||||
}
|
||||
let mut tls = match tls.connect(MemoryStream::new()) {
|
||||
Ok(_) => unreachable!(),
|
||||
Err(HandshakeError::WouldBlock(tls)) => tls,
|
||||
|
||||
+1
-1
@@ -37,7 +37,7 @@ pub use frame::{ApplicationClose, ConnectionClose};
|
||||
|
||||
mod endpoint;
|
||||
pub use endpoint::{Endpoint, Config, CertConfig, ListenKeys, ConnectionHandle, Event, Io, Timer, ConnectionError, ReadError, WriteError,
|
||||
ConnectionId, EndpointError};
|
||||
ConnectionId, EndpointError, ClientConfig};
|
||||
|
||||
mod transport_error;
|
||||
pub use transport_error::Error as TransportError;
|
||||
|
||||
+4
-4
@@ -79,7 +79,7 @@ struct Pair {
|
||||
impl Default for Pair {
|
||||
fn default() -> Self {
|
||||
Pair::new(Config { max_remote_uni_streams: 32, max_remote_bi_streams: 32, ..Config::default() },
|
||||
Config { accept_insecure_certs: true, ..Config::default() })
|
||||
Config::default())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ impl Pair {
|
||||
|
||||
fn connect(&mut self) -> (ConnectionHandle, ConnectionHandle) {
|
||||
info!(self.log, "connecting");
|
||||
let client_conn = self.client.connect(self.server.addr, None);
|
||||
let client_conn = self.client.connect(self.server.addr, ClientConfig { accept_insecure_certs: true, ..ClientConfig::default() });
|
||||
self.drive();
|
||||
let server_conn = if let Some(c) = self.server.accept() { c } else { panic!("server didn't connect"); };
|
||||
assert_matches!(self.client.poll(), Some((conn, Event::Connected { .. })) if conn == client_conn);
|
||||
@@ -367,7 +367,7 @@ fn stop_stream() {
|
||||
fn reject_self_signed_cert() {
|
||||
let mut pair = Pair::new(Config::default(), Config::default());
|
||||
info!(pair.log, "connecting");
|
||||
let client_conn = pair.client.connect(pair.server.addr, None);
|
||||
let client_conn = pair.client.connect(pair.server.addr, ClientConfig::default());
|
||||
pair.drive();
|
||||
assert_matches!(pair.client.poll(),
|
||||
Some((conn, Event::ConnectionLost { reason: ConnectionError::TransportError {
|
||||
@@ -398,7 +398,7 @@ fn congestion() {
|
||||
fn high_latency_handshake() {
|
||||
let mut pair = Pair::default();
|
||||
pair.latency = 200 * 1000;
|
||||
let client_conn = pair.client.connect(pair.server.addr, None);
|
||||
let client_conn = pair.client.connect(pair.server.addr, ClientConfig { accept_insecure_certs: true, ..ClientConfig::default() });
|
||||
pair.drive();
|
||||
let server_conn = if let Some(c) = pair.server.accept() { c } else { panic!("server didn't connect"); };
|
||||
assert_matches!(pair.client.poll(), Some((conn, Event::Connected { .. })) if conn == client_conn);
|
||||
|
||||
Reference in New Issue
Block a user