Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 42 additions & 2 deletions h3i/src/client/sync_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@ fn create_config(args: &Config, should_log_keys: bool) -> quiche::Config {
config.set_max_stream_window(args.max_stream_window);
config.grease(false);

if args.enable_early_data {
config.enable_early_data();
}

if args.enable_dgram {
config.enable_dgram(
true,
Expand Down Expand Up @@ -132,6 +136,16 @@ fn create_config(args: &Config, should_log_keys: bool) -> quiche::Config {
pub fn connect(
args: Config, actions: Vec<Action>,
close_trigger_frames: Option<CloseTriggerFrames>,
) -> std::result::Result<ConnectionSummary, ClientError> {
connect_with_early_data(args, None, actions, close_trigger_frames)
}

/// Connect to a server and execute provided early_action and actions.
///
/// See `connect` for additional documentation.
pub fn connect_with_early_data(
args: Config, early_actions: Option<Vec<Action>>, actions: Vec<Action>,
close_trigger_frames: Option<CloseTriggerFrames>,
) -> std::result::Result<ConnectionSummary, ClientError> {
let mut buf = [0; 65535];
let mut out = [0; MAX_DATAGRAM_SIZE];
Expand Down Expand Up @@ -178,11 +192,16 @@ pub fn connect(
return Err(ClientError::Other("invalid socket".to_string()));
};

// Create a QUIC connection and initiate handshake.
// Create a new client-side QUIC connection.
let mut conn =
quiche::connect(connect_url, &scid, local_addr, peer_addr, &mut config)
.map_err(|e| ClientError::Other(e.to_string()))?;

if let Some(session) = &args.session {
conn.set_session(session)
.map_err(|error| ClientError::Other(error.to_string()))?;
}

if let Some(keylog) = &mut keylog {
if let Ok(keylog) = keylog.try_clone() {
conn.set_keylog(Box::new(keylog));
Expand All @@ -195,8 +214,30 @@ pub fn connect(

let mut app_proto_selected = false;

// Send ClientHello and initiate the handshake.
let (write, send_info) = conn.send(&mut out).expect("initial send failed");

let mut client = SyncClient::new(close_trigger_frames);
// Send early data if connection is_in_early_data (resumption with 0-RTT was
// successful) and if we have early_actions.
if conn.is_in_early_data() {
if let Some(early_actions) = early_actions {
let mut early_action_iter = early_actions.iter();
let mut wait_duration = None;
let mut wait_instant = None;
let mut waiting_for = WaitingFor::default();

check_duration_and_do_actions(
&mut wait_duration,
&mut wait_instant,
&mut early_action_iter,
&mut conn,
&mut waiting_for,
client.stream_parsers_mut(),
);
}
}

while let Err(e) = socket.send_to(&out[..write], send_info.to) {
if e.kind() == std::io::ErrorKind::WouldBlock {
log::debug!(
Expand All @@ -216,7 +257,6 @@ pub fn connect(
let mut wait_duration = None;
let mut wait_instant = None;

let mut client = SyncClient::new(close_trigger_frames);
let mut waiting_for = WaitingFor::default();

loop {
Expand Down
4 changes: 4 additions & 0 deletions h3i/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ pub struct Config {
pub max_stream_window: u64,
/// Set the session to attempt resumption.
pub session: Option<Vec<u8>>,
/// Enables sending or receiving early data.
pub enable_early_data: bool,
/// Whether to enable datagram sending.
pub enable_dgram: bool,
/// Datagram receive queue length.
Expand Down Expand Up @@ -194,6 +196,7 @@ impl Config {
max_window: self.max_window,
max_stream_window: self.max_stream_window,
session: None,
enable_early_data: self.enable_early_data,
enable_dgram: self.enable_dgram,
dgram_recv_queue_len: self.dgram_recv_queue_len,
dgram_send_queue_len: self.dgram_send_queue_len,
Expand All @@ -220,6 +223,7 @@ impl Default for Config {
max_window: 25165824,
max_stream_window: 16777216,
session: None,
enable_early_data: false,
enable_dgram: true,
dgram_recv_queue_len: 65536,
dgram_send_queue_len: 65536,
Expand Down
1 change: 1 addition & 0 deletions h3i/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,7 @@ fn config_from_clap() -> std::result::Result<Config, String> {
max_window,
max_stream_window,
session: None,
enable_early_data: false,
enable_dgram,
dgram_recv_queue_len,
dgram_send_queue_len,
Expand Down
11 changes: 11 additions & 0 deletions quiche/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7072,6 +7072,17 @@ impl<F: BufFactory> Connection<F> {
self.handshake.is_in_early_data()
}

/// Returns the early data reason for the connection.
///
/// This status can be useful for logging and debugging. See [BoringSSL]
/// documentation for a definition of the reasons.
///
/// [BoringSSL]: https://commondatastorage.googleapis.com/chromium-boringssl-docs/ssl.h.html#ssl_early_data_reason_t
#[inline]
pub fn early_data_reason(&self) -> u32 {
self.handshake.early_data_reason()
}

/// Returns whether there is stream or DATAGRAM data available to read.
#[inline]
pub fn is_readable(&self) -> bool {
Expand Down
12 changes: 12 additions & 0 deletions quiche/src/tls/boringssl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,12 @@ impl Handshake {
pub fn is_in_early_data(&self) -> bool {
unsafe { SSL_in_early_data(self.as_ptr()) == 1 }
}

pub fn early_data_reason(&self) -> u32 {
let reuse_reason_status =
unsafe { SSL_get_early_data_reason(self.as_ptr()) };
reuse_reason_status.0
}
}

pub(super) fn get_session_bytes(session: *mut SSL_SESSION) -> Result<Vec<u8>> {
Expand All @@ -293,6 +299,10 @@ pub(super) fn get_session_bytes(session: *mut SSL_SESSION) -> Result<Vec<u8>> {
}
pub(super) const TLS_ERROR: c_int = 3;

#[allow(non_camel_case_types)]
#[repr(transparent)]
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub struct ssl_early_data_reason_t(pub ::std::os::raw::c_uint);
extern "C" {
// SSL_METHOD specific for boringssl.
pub(super) fn SSL_CTX_set_tlsext_ticket_keys(
Expand Down Expand Up @@ -341,6 +351,8 @@ extern "C" {

fn SSL_in_early_data(ssl: *const SSL) -> c_int;

fn SSL_get_early_data_reason(ssl: *const SSL) -> ssl_early_data_reason_t;

fn SSL_SESSION_to_bytes(
session: *const SSL_SESSION, out: *mut *mut u8, out_len: *mut usize,
) -> c_int;
Expand Down
4 changes: 4 additions & 0 deletions quiche/src/tls/openssl_quictls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,10 @@ impl Handshake {
false
}

pub fn early_data_reason(&self) -> u32 {
0
}

pub fn set_session(&mut self, session: &[u8]) -> Result<()> {
unsafe {
let ctx = SSL_get_SSL_CTX(self.as_ptr());
Expand Down
1 change: 1 addition & 0 deletions tokio-quiche/examples/async_http3_server/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ where
ServerH3Event::Headers {
incoming_headers,
priority,
is_in_early_data: _,
} => {
// Received headers for a new stream from the H3Driver.
self.handle_incoming_headers(incoming_headers, priority)
Expand Down
1 change: 1 addition & 0 deletions tokio-quiche/src/http3/driver/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ pub use self::client::ClientH3Driver;
pub use self::client::ClientH3Event;
pub use self::client::ClientRequestSender;
pub use self::client::NewClientRequest;
pub use self::server::IsInEarlyData;
pub use self::server::RawPriorityValue;
pub use self::server::ServerEventStream;
pub use self::server::ServerH3Command;
Expand Down
34 changes: 31 additions & 3 deletions tokio-quiche/src/http3/driver/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,24 @@ impl Deref for RawPriorityValue {
}
}

/// The request was received during early data (0-RTT).
#[derive(Clone, Debug)]
pub struct IsInEarlyData(bool);

impl IsInEarlyData {
fn new(is_in_early_data: bool) -> Self {
IsInEarlyData(is_in_early_data)
}
}

impl Deref for IsInEarlyData {
type Target = bool;

fn deref(&self) -> &Self::Target {
&self.0
}
}

/// Events produced by [ServerH3Driver].
#[derive(Debug)]
pub enum ServerH3Event {
Expand All @@ -87,15 +105,24 @@ pub enum ServerH3Event {
incoming_headers: IncomingH3Headers,
/// The latest PRIORITY_UPDATE frame value, if any.
priority: Option<RawPriorityValue>,
is_in_early_data: IsInEarlyData,
},
}

impl From<H3Event> for ServerH3Event {
fn from(ev: H3Event) -> Self {
match ev {
H3Event::IncomingHeaders(incoming_headers) => Self::Headers {
incoming_headers,
priority: None,
H3Event::IncomingHeaders(incoming_headers) => {
// Server `incoming_headers` are exclusively created in
// `ServerHooks::handle_request`, which correctly serializes the
// RawPriorityValue and IsInEarlyData values.
//
// See `H3Driver::process_read_event` for implementation details.
Self::Headers {
incoming_headers,
priority: None,
is_in_early_data: IsInEarlyData::new(false),
}
},
_ => Self::Core(ev),
}
Expand Down Expand Up @@ -205,6 +232,7 @@ impl ServerHooks {
.send(ServerH3Event::Headers {
incoming_headers: headers,
priority: latest_priority_update,
is_in_early_data: IsInEarlyData::new(qconn.is_in_early_data()),
})
.map_err(|_| H3ConnectionError::ControllerWentAway)?;
driver.hooks.requests += 1;
Expand Down
4 changes: 3 additions & 1 deletion tokio-quiche/src/quic/io/connection_stage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,9 @@ impl ConnectionStage for Handshake {
&mut self, qconn: &mut QuicheConnection,
_ctx: &mut ConnectionStageContext<A>,
) -> ControlFlow<QuicResult<()>> {
if qconn.is_established() {
// Transition to RunningApplication if we have 1-RTT keys (handshake is
// complete) or if we have 0-RTT keys (in early data).
if qconn.is_established() || qconn.is_in_early_data() {
ControlFlow::Break(Ok(()))
} else {
ControlFlow::Continue(())
Expand Down
14 changes: 12 additions & 2 deletions tokio-quiche/src/quic/io/worker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -650,13 +650,23 @@ where
&mut self, qconn: &mut QuicheConnection, quic_application: &mut A,
) -> QuicResult<()> {
if quic_application.should_act() {
// Poll the application to make progress.
//
// Once the connection has been established (i.e. the handshake is
// complete), we only poll the application.
//
// The exception is 0-RTT in TLS 1.3, where the full handshake is
// still in progress but we have 0-RTT keys to process early data.
// This means TLS callbacks might only be polled on the next timeout
// or when a packet is received from the peer.
quic_application.wait_for_data(qconn).await.map_err(|err| {
to_box_error(format!(
"app_err={} while waiting for H3 data with AOQ::wait_for_data",
err
))
})
} else {
// Poll quiche to make progress on handshake callbacks.
self.wait_for_quiche(qconn, quic_application).await
}
}
Expand Down Expand Up @@ -739,8 +749,8 @@ where
A: ApplicationOverQuic,
{
// This makes an assumption that the waker being set in ex_data is stable
// accross the active task's lifetime. Moving a future that encompasses an
// async callback from this task accross a channel, for example, will
// across the active task's lifetime. Moving a future that encompasses an
// async callback from this task across a channel, for example, will
// cause issues as this waker will then be stale and attempt to
// wake the wrong task.
std::future::poll_fn(|cx| {
Expand Down
3 changes: 3 additions & 0 deletions tokio-quiche/src/settings/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,9 @@ fn make_quiche_config(
track_unknown_transport_params,
);
}
if params.settings.enable_early_data {
config.enable_early_data();
}

if should_log_keys {
config.log_keys();
Expand Down
6 changes: 6 additions & 0 deletions tokio-quiche/src/settings/quic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ pub struct QuicSettings {
#[serde(default = "QuicSettings::default_dgram_max_queue_len")]
pub dgram_send_max_queue_len: usize,

/// Configures whether to enable early data (0-RTT) support. Currently only
/// supported for servers.
///
/// Defaults to `false`.
pub enable_early_data: bool,

/// Sets the `initial_max_data` transport parameter.
///
/// Defaults to 10 MB.
Expand Down
6 changes: 5 additions & 1 deletion tokio-quiche/tests/fixtures/h3i_fixtures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,14 @@ pub fn h3i_config(url: &str) -> h3i::config::Config {
}

pub fn default_headers() -> Vec<Header> {
default_headers_with_authority("test.com")
}

pub fn default_headers_with_authority(host: &str) -> Vec<Header> {
vec![
Header::new(b":method", b"GET"),
Header::new(b":scheme", b"https"),
Header::new(b":authority", b"test.com"),
Header::new(b":authority", host.as_bytes()),
Header::new(b":path", b"/"),
]
}
Expand Down
1 change: 1 addition & 0 deletions tokio-quiche/tests/integration_tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ pub mod connection_close;
pub mod headers;
pub mod migration;
pub mod timeouts;
pub mod zero_rtt;

#[tokio::test]
async fn echo() {
Expand Down
Loading