Skip to content

Commit 07b04cb

Browse files
committed
Implement PMTU_RAISE_TIMER for PMTUD re-probing
After PMTUD settles on a PLPMTU it never probes again, so a path that later supports a larger MTU is never rediscovered. Arm a raise timer (default 600s) once a PMTU is found and re-enter the Search Phase when it expires, per RFC 8899 Section 5.1.1, reusing the existing optimistic binary search. The interval is configurable through Config, the C FFI, and tokio-quiche settings; a zero duration disables re-probing and preserves the previous settle-and-stop behavior.
1 parent 60ed92e commit 07b04cb

9 files changed

Lines changed: 389 additions & 87 deletions

File tree

quiche/include/quiche.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,14 @@ void quiche_config_grease(quiche_config *config, bool v);
177177
// Configures whether to do path MTU discovery.
178178
void quiche_config_discover_pmtu(quiche_config *config, bool v);
179179

180+
// Configures the maximum number of PMTUD probe attempts before treating a size
181+
// as failed.
182+
void quiche_config_set_pmtud_max_probes(quiche_config *config, uint8_t max_probes);
183+
184+
// Configures how long PMTUD stays on a discovered PMTU before re-entering the
185+
// Search Phase, in milliseconds. Zero disables periodic re-probing.
186+
void quiche_config_set_pmtud_raise_timer(quiche_config *config, uint64_t millis);
187+
180188
// Enables logging of secrets.
181189
void quiche_config_log_keys(quiche_config *config);
182190

quiche/src/ffi.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,13 @@ pub extern "C" fn quiche_config_set_pmtud_max_probes(
222222
config.set_pmtud_max_probes(max_probes);
223223
}
224224

225+
#[no_mangle]
226+
pub extern "C" fn quiche_config_set_pmtud_raise_timer(
227+
config: &mut Config, millis: u64,
228+
) {
229+
config.set_pmtud_raise_timer(Duration::from_millis(millis));
230+
}
231+
225232
#[no_mangle]
226233
pub extern "C" fn quiche_config_log_keys(config: &mut Config) {
227234
config.log_keys();

quiche/src/lib.rs

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -579,6 +579,7 @@ pub struct Config {
579579

580580
pmtud: bool,
581581
pmtud_max_probes: u8,
582+
pmtud_raise_timer: Option<Duration>,
582583

583584
hystart: bool,
584585

@@ -660,6 +661,7 @@ impl Config {
660661
enable_send_streams_blocked: false,
661662
pmtud: false,
662663
pmtud_max_probes: pmtud::MAX_PROBES_DEFAULT,
664+
pmtud_raise_timer: Some(pmtud::PMTU_RAISE_TIMER_DEFAULT),
663665
hystart: true,
664666
pacing: true,
665667
max_pacing_rate: None,
@@ -786,6 +788,16 @@ impl Config {
786788
self.pmtud_max_probes = max_probes;
787789
}
788790

791+
/// Configures how long PMTUD stays on a discovered PMTU before re-entering
792+
/// the Search Phase to probe for a larger path MTU.
793+
///
794+
/// Defaults to 600s per [RFC 8899 Section 5.1.1](https://datatracker.ietf.org/doc/html/rfc8899#section-5.1.1).
795+
/// A duration of zero disables periodic re-probing, so PMTUD stops once a
796+
/// PMTU is found.
797+
pub fn set_pmtud_raise_timer(&mut self, raise_timer: Duration) {
798+
self.pmtud_raise_timer = (!raise_timer.is_zero()).then_some(raise_timer);
799+
}
800+
789801
/// Configures whether to send GREASE values.
790802
///
791803
/// The default value is `true`.
@@ -1356,6 +1368,11 @@ where
13561368
/// The configuration for recovery.
13571369
recovery_config: recovery::RecoveryConfig,
13581370

1371+
/// Period on the discovered PMTU before re-entering the Search Phase,
1372+
/// applied to paths that enable PMTUD during the handshake. [`None`]
1373+
/// disables periodic re-probing.
1374+
pmtud_raise_timer: Option<Duration>,
1375+
13591376
/// The path manager.
13601377
paths: path::PathMap,
13611378

@@ -2080,6 +2097,8 @@ impl<F: BufFactory> Connection<F> {
20802097

20812098
recovery_config,
20822099

2100+
pmtud_raise_timer: config.pmtud_raise_timer,
2101+
20832102
paths,
20842103
path_challenge_recv_max_queue_len: config
20852104
.path_challenge_recv_max_queue_len,
@@ -2683,7 +2702,10 @@ impl<F: BufFactory> Connection<F> {
26832702
) -> Result<()> {
26842703
let ex_data = tls::ExData::from_ssl_ref(ssl).ok_or(Error::TlsFail)?;
26852704

2686-
ex_data.pmtud = Some((discover, max_probes));
2705+
ex_data.pmtud = Some(pmtud::PmtudParams {
2706+
enable: discover,
2707+
max_probes,
2708+
});
26872709

26882710
Ok(())
26892711
}
@@ -3538,7 +3560,7 @@ impl<F: BufFactory> Connection<F> {
35383560
// Ensure the probe is within the supported MTU range
35393561
// before updating the max datagram size
35403562
if let Some(current_mtu) =
3541-
pmtud.successful_probe(mtu_probe)
3563+
pmtud.successful_probe(mtu_probe, now)
35423564
{
35433565
qlog_with_type!(
35443566
EventType::QuicEventType(
@@ -4295,7 +4317,7 @@ impl<F: BufFactory> Connection<F> {
42954317
if let Some(failed_probe) = mtu_probe {
42964318
if let Some(pmtud) = p.pmtud.as_mut() {
42974319
trace!("pmtud probe dropped: {failed_probe}");
4298-
pmtud.failed_probe(failed_probe);
4320+
pmtud.failed_probe(failed_probe, now);
42994321
}
43004322
}
43014323
},
@@ -7009,12 +7031,23 @@ impl<F: BufFactory> Connection<F> {
70097031
.filter_map(|(_, p)| p.recovery.loss_detection_timer())
70107032
.min();
70117033

7034+
let pmtud_raise_timer = self
7035+
.paths
7036+
.iter()
7037+
.filter_map(|(_, p)| p.pmtud.as_ref()?.raise_timer())
7038+
.min();
7039+
70127040
let key_update_timer = self.crypto_ctx[packet::Epoch::Application]
70137041
.key_update
70147042
.as_ref()
70157043
.map(|key_update| key_update.timer);
70167044

7017-
let timers = [self.idle_timer, path_timer, key_update_timer];
7045+
let timers = [
7046+
self.idle_timer,
7047+
path_timer,
7048+
pmtud_raise_timer,
7049+
key_update_timer,
7050+
];
70187051

70197052
timers.iter().filter_map(|&x| x).min()
70207053
}
@@ -7105,6 +7138,10 @@ impl<F: BufFactory> Connection<F> {
71057138
});
71067139
}
71077140
}
7141+
7142+
if let Some(pmtud) = p.pmtud.as_mut() {
7143+
pmtud.on_raise_timeout(now);
7144+
}
71087145
}
71097146

71107147
// Notify timeout events to the application.
@@ -8034,11 +8071,11 @@ impl<F: BufFactory> Connection<F> {
80348071
self.tx_cap_factor = ex_data.tx_cap_factor;
80358072
}
80368073

8037-
if let Some((discover, max_probes)) = ex_data.pmtud {
8074+
if let Some(params) = ex_data.pmtud {
80388075
self.paths.set_discover_pmtu_on_existing_paths(
8039-
discover,
8076+
params,
8077+
self.pmtud_raise_timer,
80408078
self.recovery_config.max_send_udp_payload_size,
8041-
max_probes,
80428079
);
80438080
}
80448081

quiche/src/path.rs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,11 @@ impl Path {
251251
.unwrap_or(c.max_send_udp_payload_size),
252252
c.max_send_udp_payload_size,
253253
);
254-
Some(pmtud::Pmtud::new(maximum_supported_mtu, c.pmtud_max_probes))
254+
Some(pmtud::Pmtud::new(
255+
maximum_supported_mtu,
256+
c.pmtud_max_probes,
257+
c.pmtud_raise_timer,
258+
))
255259
} else {
256260
None
257261
}
@@ -907,14 +911,15 @@ impl PathMap {
907911

908912
/// Configures path MTU discovery on all existing paths.
909913
pub fn set_discover_pmtu_on_existing_paths(
910-
&mut self, discover: bool, max_send_udp_payload_size: usize,
911-
pmtud_max_probes: u8,
914+
&mut self, params: pmtud::PmtudParams, raise_interval: Option<Duration>,
915+
max_send_udp_payload_size: usize,
912916
) {
913917
for (_, path) in self.paths.iter_mut() {
914-
path.pmtud = if discover {
918+
path.pmtud = if params.enable {
915919
Some(pmtud::Pmtud::new(
916920
max_send_udp_payload_size,
917-
pmtud_max_probes,
921+
params.max_probes,
922+
raise_interval,
918923
))
919924
} else {
920925
None

0 commit comments

Comments
 (0)