implement handshake timeout setting
Currently applications need to implement this on their own, but to avoid
needless duplication we can offer this functionality directly in quiche.
diff --git a/apps/src/args.rs b/apps/src/args.rs
index 24234a9..7000526 100644
--- a/apps/src/args.rs
+++ b/apps/src/args.rs
@@ -39,6 +39,7 @@
pub max_stream_window: u64,
pub max_streams_bidi: u64,
pub max_streams_uni: u64,
+ pub handshake_timeout: u64,
pub idle_timeout: u64,
pub early_data: bool,
pub dump_packet_path: Option<String>,
@@ -131,6 +132,9 @@
let max_streams_uni = args.get_str("--max-streams-uni");
let max_streams_uni = max_streams_uni.parse::<u64>().unwrap();
+ let handshake_timeout = args.get_str("--handshake-timeout");
+ let handshake_timeout = handshake_timeout.parse::<u64>().unwrap();
+
let idle_timeout = args.get_str("--idle-timeout");
let idle_timeout = idle_timeout.parse::<u64>().unwrap();
@@ -199,6 +203,7 @@
max_stream_window,
max_streams_bidi,
max_streams_uni,
+ handshake_timeout,
idle_timeout,
early_data,
dump_packet_path,
@@ -228,6 +233,7 @@
max_stream_window: 16777216,
max_streams_bidi: 100,
max_streams_uni: 100,
+ handshake_timeout: 5000,
idle_timeout: 30000,
early_data: false,
dump_packet_path: None,
@@ -260,6 +266,7 @@
--max-stream-window BYTES Per-stream max receiver window [default: 16777216].
--max-streams-bidi STREAMS Number of allowed concurrent streams [default: 100].
--max-streams-uni STREAMS Number of allowed concurrent streams [default: 100].
+ --handshake-timeout TIMEOUT Handshake timeout in milliseconds [default: 5000].
--idle-timeout TIMEOUT Idle timeout in milliseconds [default: 30000].
--wire-version VERSION The version number to send to the server [default: babababa].
--http-version VERSION HTTP version to use [default: all].
diff --git a/apps/src/bin/quiche-server.rs b/apps/src/bin/quiche-server.rs
index ac632dc..8e1cf7e 100644
--- a/apps/src/bin/quiche-server.rs
+++ b/apps/src/bin/quiche-server.rs
@@ -108,6 +108,8 @@
config.set_application_protos(&conn_args.alpns).unwrap();
+ config.set_handshake_timeout(conn_args.handshake_timeout);
+
config.set_max_idle_timeout(conn_args.idle_timeout);
config.set_max_recv_udp_payload_size(max_datagram_size);
config.set_max_send_udp_payload_size(max_datagram_size);
diff --git a/apps/src/client.rs b/apps/src/client.rs
index 86fb650..5fbe5af 100644
--- a/apps/src/client.rs
+++ b/apps/src/client.rs
@@ -116,6 +116,8 @@
config.set_application_protos(&conn_args.alpns).unwrap();
+ config.set_handshake_timeout(conn_args.handshake_timeout);
+
config.set_max_idle_timeout(conn_args.idle_timeout);
config.set_max_recv_udp_payload_size(MAX_DATAGRAM_SIZE);
config.set_max_send_udp_payload_size(MAX_DATAGRAM_SIZE);
diff --git a/quiche/include/quiche.h b/quiche/include/quiche.h
index c0d1406..184d520 100644
--- a/quiche/include/quiche.h
+++ b/quiche/include/quiche.h
@@ -172,6 +172,9 @@
const uint8_t *protos,
size_t protos_len);
+// Sets the handshake timeout, in milliseconds, default is no timeout.
+void quiche_config_set_handshake_timeout(quiche_config *config, uint64_t v);
+
// Sets the `max_idle_timeout` transport parameter, in milliseconds, default is
// no timeout.
void quiche_config_set_max_idle_timeout(quiche_config *config, uint64_t v);
diff --git a/quiche/src/ffi.rs b/quiche/src/ffi.rs
index 0183730..2b3b36d 100644
--- a/quiche/src/ffi.rs
+++ b/quiche/src/ffi.rs
@@ -237,6 +237,11 @@
}
#[no_mangle]
+pub extern fn quiche_config_set_handshake_timeout(config: &mut Config, v: u64) {
+ config.set_handshake_timeout(v);
+}
+
+#[no_mangle]
pub extern fn quiche_config_set_max_idle_timeout(config: &mut Config, v: u64) {
config.set_max_idle_timeout(v);
}
diff --git a/quiche/src/lib.rs b/quiche/src/lib.rs
index a6788c3..6783937 100644
--- a/quiche/src/lib.rs
+++ b/quiche/src/lib.rs
@@ -728,6 +728,8 @@
max_connection_window: u64,
max_stream_window: u64,
+ handshake_timeout: u64,
+
disable_dcid_reuse: bool,
}
@@ -793,6 +795,8 @@
max_connection_window: MAX_CONNECTION_WINDOW,
max_stream_window: stream::MAX_STREAM_WINDOW,
+ handshake_timeout: 0,
+
disable_dcid_reuse: false,
})
}
@@ -972,6 +976,13 @@
self.set_application_protos(&protos_list)
}
+ /// Sets the timeout for the handshake.
+ ///
+ /// The default value is infinite, that is, no timeout is used.
+ pub fn set_handshake_timeout(&mut self, v: u64) {
+ self.handshake_timeout = v;
+ }
+
/// Sets the `max_idle_timeout` transport parameter, in milliseconds.
///
/// The default value is infinite, that is, no timeout is used.
@@ -1274,6 +1285,9 @@
/// TLS handshake state.
handshake: tls::Handshake,
+ /// The configured handshake timer deadline, if any is configured.
+ handshake_timer: Option<time::Instant>,
+
/// Serialized TLS session buffer.
///
/// This field is populated when a new session ticket is processed on the
@@ -1716,6 +1730,8 @@
scid: &ConnectionId, odcid: Option<&ConnectionId>, local: SocketAddr,
peer: SocketAddr, config: &Config, tls: tls::Handshake, is_server: bool,
) -> Result<Connection> {
+ let now = time::Instant::now();
+
let max_rx_data = config.local_transport_params.initial_max_data;
let scid_as_hex: Vec<String> =
@@ -1736,6 +1752,7 @@
config.path_challenge_recv_max_queue_len,
true,
);
+
// If we did stateless retry assume the peer's address is verified.
path.verified_peer_address = odcid.is_some();
// Assume clients validate the server's address implicitly.
@@ -1757,6 +1774,12 @@
reset_token,
);
+ let handshake_timer = if config.handshake_timeout > 0 {
+ Some(now + time::Duration::from_millis(config.handshake_timeout))
+ } else {
+ None
+ };
+
let mut conn = Connection {
version: config.version,
@@ -1776,6 +1799,8 @@
handshake: tls,
+ handshake_timer,
+
session: None,
recovery_config,
@@ -5530,10 +5555,6 @@
// processing the other timers.
self.draining_timer
} else {
- // Use the lowest timer value (i.e. "sooner") among idle and loss
- // detection timers. If they are both unset (i.e. `None`) then the
- // result is `None`, but if at least one of them is set then a
- // `Some(...)` value is returned.
let path_timer = self
.paths
.iter()
@@ -5546,8 +5567,16 @@
.as_ref()
.map(|key_update| key_update.timer);
- let timers = [self.idle_timer, path_timer, key_update_timer];
+ let timers = [
+ self.idle_timer,
+ self.handshake_timer,
+ path_timer,
+ key_update_timer,
+ ];
+ // Use the lowest timer value (i.e. "sooner"). If they are all unset
+ // (i.e. `None`) then the result is `None`, but if at least one of
+ // them is set then a `Some(...)` value is returned.
timers.iter().filter_map(|&x| x).min()
}
}
@@ -5599,6 +5628,16 @@
}
}
+ if let Some(timer) = self.handshake_timer {
+ if timer <= now {
+ trace!("{} handshake timeout expired", self.trace_id);
+
+ self.mark_closed();
+ self.timed_out = true;
+ return;
+ }
+ }
+
if let Some(timer) = self.pkt_num_spaces[packet::Epoch::Application]
.key_update
.as_ref()
@@ -6502,6 +6541,9 @@
self.drop_epoch_state(packet::Epoch::Handshake, now);
}
+ // Disarm handshake timer.
+ self.handshake_timer = None;
+
// Once the handshake is completed there's no point in processing
// 0-RTT packets anymore, so clear the buffer now.
self.undecryptable_pkts.clear();
@@ -8857,6 +8899,68 @@
}
#[test]
+ fn handshake_timeout_client() {
+ let mut config = Config::new(PROTOCOL_VERSION).unwrap();
+ config
+ .set_application_protos(&[b"proto1", b"proto2"])
+ .unwrap();
+ config.set_handshake_timeout(500);
+ config.verify_peer(false);
+
+ let mut pipe = testing::Pipe::with_client_config(&mut config).unwrap();
+
+ let timer = pipe.client.timeout().unwrap();
+ assert!(timer <= time::Duration::from_millis(500));
+
+ // Client sends initial flight.
+ let flight = testing::emit_flight(&mut pipe.client).unwrap();
+ testing::process_flight(&mut pipe.server, flight).unwrap();
+
+ std::thread::sleep(timer + time::Duration::from_millis(1));
+
+ pipe.client.on_timeout();
+ assert!(pipe.client.is_timed_out());
+
+ pipe.server.on_timeout();
+ assert!(!pipe.server.is_timed_out());
+ }
+
+ #[test]
+ fn handshake_timeout_server() {
+ let mut config = Config::new(PROTOCOL_VERSION).unwrap();
+ config
+ .load_cert_chain_from_pem_file("examples/cert.crt")
+ .unwrap();
+ config
+ .load_priv_key_from_pem_file("examples/cert.key")
+ .unwrap();
+ config
+ .set_application_protos(&[b"proto1", b"proto2"])
+ .unwrap();
+ config.set_handshake_timeout(500);
+
+ let mut pipe = testing::Pipe::with_server_config(&mut config).unwrap();
+
+ let timer = pipe.server.timeout().unwrap();
+ assert!(timer <= time::Duration::from_millis(500));
+
+ // Client sends initial flight.
+ let flight = testing::emit_flight(&mut pipe.client).unwrap();
+ testing::process_flight(&mut pipe.server, flight).unwrap();
+
+ // Server sends initial flight.
+ let _ = testing::emit_flight(&mut pipe.server).unwrap();
+
+ std::thread::sleep(timer + time::Duration::from_millis(1));
+
+ pipe.client.on_timeout();
+ assert!(!pipe.client.is_timed_out());
+
+ pipe.server.on_timeout();
+ assert!(pipe.server.is_timed_out());
+ }
+
+ #[test]
fn handshake_done() {
let mut pipe = testing::Pipe::new().unwrap();