Skip to content

Commit e577935

Browse files
committed
feat[tokio-quiche]: implement server-side support for 0-rtt
1 parent 862ab1a commit e577935

File tree

15 files changed

+368
-6
lines changed

15 files changed

+368
-6
lines changed

h3i/src/client/sync_client.rs

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,10 @@ fn create_config(args: &Config, should_log_keys: bool) -> quiche::Config {
105105
config.set_max_stream_window(args.max_stream_window);
106106
config.grease(false);
107107

108+
if args.enable_early_data {
109+
config.enable_early_data();
110+
}
111+
108112
if args.enable_dgram {
109113
config.enable_dgram(
110114
true,
@@ -132,6 +136,16 @@ fn create_config(args: &Config, should_log_keys: bool) -> quiche::Config {
132136
pub fn connect(
133137
args: Config, actions: Vec<Action>,
134138
close_trigger_frames: Option<CloseTriggerFrames>,
139+
) -> std::result::Result<ConnectionSummary, ClientError> {
140+
connect_with_early_data(args, None, actions, close_trigger_frames)
141+
}
142+
143+
/// Connect to a server and execute provided early_action and actions.
144+
///
145+
/// See `connect` for additional documentation.
146+
pub fn connect_with_early_data(
147+
args: Config, early_actions: Option<Vec<Action>>, actions: Vec<Action>,
148+
close_trigger_frames: Option<CloseTriggerFrames>,
135149
) -> std::result::Result<ConnectionSummary, ClientError> {
136150
let mut buf = [0; 65535];
137151
let mut out = [0; MAX_DATAGRAM_SIZE];
@@ -178,11 +192,16 @@ pub fn connect(
178192
return Err(ClientError::Other("invalid socket".to_string()));
179193
};
180194

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

200+
if let Some(session) = &args.session {
201+
conn.set_session(session)
202+
.map_err(|error| ClientError::Other(error.to_string()))?;
203+
}
204+
186205
if let Some(keylog) = &mut keylog {
187206
if let Ok(keylog) = keylog.try_clone() {
188207
conn.set_keylog(Box::new(keylog));
@@ -195,8 +214,30 @@ pub fn connect(
195214

196215
let mut app_proto_selected = false;
197216

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

220+
let mut client = SyncClient::new(close_trigger_frames);
221+
// Send early data if connection is_in_early_data (resumption with 0-RTT was
222+
// successful) and if we have early_actions.
223+
if conn.is_in_early_data() {
224+
if let Some(early_actions) = early_actions {
225+
let mut early_action_iter = early_actions.iter();
226+
let mut wait_duration = None;
227+
let mut wait_instant = None;
228+
let mut waiting_for = WaitingFor::default();
229+
230+
check_duration_and_do_actions(
231+
&mut wait_duration,
232+
&mut wait_instant,
233+
&mut early_action_iter,
234+
&mut conn,
235+
&mut waiting_for,
236+
client.stream_parsers_mut(),
237+
);
238+
}
239+
}
240+
200241
while let Err(e) = socket.send_to(&out[..write], send_info.to) {
201242
if e.kind() == std::io::ErrorKind::WouldBlock {
202243
log::debug!(
@@ -216,7 +257,6 @@ pub fn connect(
216257
let mut wait_duration = None;
217258
let mut wait_instant = None;
218259

219-
let mut client = SyncClient::new(close_trigger_frames);
220260
let mut waiting_for = WaitingFor::default();
221261

222262
loop {

h3i/src/config.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ pub struct Config {
6363
pub max_stream_window: u64,
6464
/// Set the session to attempt resumption.
6565
pub session: Option<Vec<u8>>,
66+
/// Enables sending or receiving early data.
67+
pub enable_early_data: bool,
6668
/// Whether to enable datagram sending.
6769
pub enable_dgram: bool,
6870
/// Datagram receive queue length.
@@ -194,6 +196,7 @@ impl Config {
194196
max_window: self.max_window,
195197
max_stream_window: self.max_stream_window,
196198
session: None,
199+
enable_early_data: self.enable_early_data,
197200
enable_dgram: self.enable_dgram,
198201
dgram_recv_queue_len: self.dgram_recv_queue_len,
199202
dgram_send_queue_len: self.dgram_send_queue_len,
@@ -220,6 +223,7 @@ impl Default for Config {
220223
max_window: 25165824,
221224
max_stream_window: 16777216,
222225
session: None,
226+
enable_early_data: false,
223227
enable_dgram: true,
224228
dgram_recv_queue_len: 65536,
225229
dgram_send_queue_len: 65536,

h3i/src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,7 @@ fn config_from_clap() -> std::result::Result<Config, String> {
326326
max_window,
327327
max_stream_window,
328328
session: None,
329+
enable_early_data: false,
329330
enable_dgram,
330331
dgram_recv_queue_len,
331332
dgram_send_queue_len,

quiche/src/lib.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7153,6 +7153,17 @@ impl<F: BufFactory> Connection<F> {
71537153
self.handshake.is_in_early_data()
71547154
}
71557155

7156+
/// Returns the early data reason for the connection.
7157+
///
7158+
/// This status can be useful for logging and debugging. See [BoringSSL]
7159+
/// documentation for a definition of the reasons.
7160+
///
7161+
/// [BoringSSL]: https://commondatastorage.googleapis.com/chromium-boringssl-docs/ssl.h.html#ssl_early_data_reason_t
7162+
#[inline]
7163+
pub fn early_data_reason(&self) -> u32 {
7164+
self.handshake.early_data_reason()
7165+
}
7166+
71567167
/// Returns whether there is stream or DATAGRAM data available to read.
71577168
#[inline]
71587169
pub fn is_readable(&self) -> bool {

quiche/src/tls/boringssl.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,12 @@ impl Handshake {
274274
pub fn is_in_early_data(&self) -> bool {
275275
unsafe { SSL_in_early_data(self.as_ptr()) == 1 }
276276
}
277+
278+
pub fn early_data_reason(&self) -> u32 {
279+
let reuse_reason_status =
280+
unsafe { SSL_get_early_data_reason(self.as_ptr()) };
281+
reuse_reason_status.0
282+
}
277283
}
278284

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

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

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

354+
fn SSL_get_early_data_reason(ssl: *const SSL) -> ssl_early_data_reason_t;
355+
344356
fn SSL_SESSION_to_bytes(
345357
session: *const SSL_SESSION, out: *mut *mut u8, out_len: *mut usize,
346358
) -> c_int;

quiche/src/tls/openssl_quictls.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,10 @@ impl Handshake {
141141
false
142142
}
143143

144+
pub fn early_data_reason(&self) -> u32 {
145+
0
146+
}
147+
144148
pub fn set_session(&mut self, session: &[u8]) -> Result<()> {
145149
unsafe {
146150
let ctx = SSL_get_SSL_CTX(self.as_ptr());

tokio-quiche/examples/async_http3_server/server.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ where
147147
ServerH3Event::Headers {
148148
incoming_headers,
149149
priority,
150+
is_in_early_data: _,
150151
} => {
151152
// Received headers for a new stream from the H3Driver.
152153
self.handle_incoming_headers(incoming_headers, priority)

tokio-quiche/src/http3/driver/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ pub use self::client::ClientH3Driver;
8888
pub use self::client::ClientH3Event;
8989
pub use self::client::ClientRequestSender;
9090
pub use self::client::NewClientRequest;
91+
pub use self::server::IsInEarlyData;
9192
pub use self::server::RawPriorityValue;
9293
pub use self::server::ServerEventStream;
9394
pub use self::server::ServerH3Command;

tokio-quiche/src/http3/driver/server.rs

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,24 @@ impl Deref for RawPriorityValue {
7878
}
7979
}
8080

81+
/// Custom header info to track early data requests.
82+
#[derive(Clone, Debug)]
83+
pub struct IsInEarlyData(bool);
84+
85+
impl IsInEarlyData {
86+
fn new(is_in_early_data: bool) -> Self {
87+
IsInEarlyData(is_in_early_data)
88+
}
89+
}
90+
91+
impl Deref for IsInEarlyData {
92+
type Target = bool;
93+
94+
fn deref(&self) -> &Self::Target {
95+
&self.0
96+
}
97+
}
98+
8199
/// Events produced by [ServerH3Driver].
82100
#[derive(Debug)]
83101
pub enum ServerH3Event {
@@ -87,15 +105,24 @@ pub enum ServerH3Event {
87105
incoming_headers: IncomingH3Headers,
88106
/// The latest PRIORITY_UPDATE frame value, if any.
89107
priority: Option<RawPriorityValue>,
108+
is_in_early_data: IsInEarlyData,
90109
},
91110
}
92111

93112
impl From<H3Event> for ServerH3Event {
94113
fn from(ev: H3Event) -> Self {
95114
match ev {
96-
H3Event::IncomingHeaders(incoming_headers) => Self::Headers {
97-
incoming_headers,
98-
priority: None,
115+
H3Event::IncomingHeaders(incoming_headers) => {
116+
// Requests are exclusively handled by `Self::headers_received`,
117+
// which correctly serializers the RawPriorityValue and
118+
// IsInEarlyData values.
119+
//
120+
// See `H3Driver::process_read_event` for implementation details.
121+
Self::Headers {
122+
incoming_headers,
123+
priority: None,
124+
is_in_early_data: IsInEarlyData::new(false),
125+
}
99126
},
100127
_ => Self::Core(ev),
101128
}
@@ -205,6 +232,7 @@ impl ServerHooks {
205232
.send(ServerH3Event::Headers {
206233
incoming_headers: headers,
207234
priority: latest_priority_update,
235+
is_in_early_data: IsInEarlyData::new(qconn.is_in_early_data()),
208236
})
209237
.map_err(|_| H3ConnectionError::ControllerWentAway)?;
210238
driver.hooks.requests += 1;

tokio-quiche/src/quic/io/connection_stage.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,9 @@ impl ConnectionStage for Handshake {
122122
&mut self, qconn: &mut QuicheConnection,
123123
_ctx: &mut ConnectionStageContext<A>,
124124
) -> ControlFlow<QuicResult<()>> {
125-
if qconn.is_established() {
125+
// Transition to RunningApplication if we have 1-RTT keys (handshake is
126+
// complete) or if we have 0-RTT keys (in early data).
127+
if qconn.is_established() || qconn.is_in_early_data() {
126128
ControlFlow::Break(Ok(()))
127129
} else {
128130
ControlFlow::Continue(())

0 commit comments

Comments
 (0)