1pub(crate) const MAX_PROBES_DEFAULT: u8 = 3;
23
24const MIN_PLPMTU: usize = crate::MIN_CLIENT_INITIAL_LEN;
28
29#[derive(Default)]
30pub struct Pmtud {
31 pmtu: Option<usize>,
34
35 probe_size: usize,
38
39 maximum_supported_mtu: usize,
41
42 smallest_failed_probe_size: Option<usize>,
44
45 largest_successful_probe_size: Option<usize>,
47
48 in_flight: bool,
50
51 probe_failure_count: u8,
53
54 max_probes: u8,
57}
58
59impl Pmtud {
60 pub fn new(maximum_supported_mtu: usize, max_probes: u8) -> Self {
64 let max_probes = if max_probes == 0 {
65 warn!(
66 "max_probes is 0, using default value {}",
67 MAX_PROBES_DEFAULT
68 );
69 MAX_PROBES_DEFAULT
70 } else {
71 max_probes
72 };
73
74 Self {
75 maximum_supported_mtu,
76 probe_size: maximum_supported_mtu,
77 max_probes,
78 ..Default::default()
79 }
80 }
81
82 pub fn should_probe(&self) -> bool {
87 !self.in_flight &&
88 self.pmtu.is_none() &&
89 self.smallest_failed_probe_size != Some(MIN_PLPMTU)
90 }
91
92 fn set_probe_size(&mut self, probe_size: usize) {
94 self.probe_size = std::cmp::min(probe_size, self.maximum_supported_mtu);
95 }
96
97 pub fn get_probe_size(&self) -> usize {
99 self.probe_size
100 }
101
102 pub fn get_current_mtu(&self) -> usize {
105 self.largest_successful_probe_size.unwrap_or(MIN_PLPMTU)
106 }
107
108 pub fn get_pmtu(&self) -> Option<usize> {
110 self.pmtu
111 }
112
113 fn update_probe_size(&mut self) {
118 match (
119 self.smallest_failed_probe_size,
120 self.largest_successful_probe_size,
121 ) {
122 (Some(failed_probe_size), Some(successful_probe_size)) => {
124 if failed_probe_size <= successful_probe_size {
127 warn!(
128 "Inconsistent PMTUD probing results. Restarting PMTUD. \
129 failed_probe_size: {failed_probe_size}, \
130 successful_probe_size: {successful_probe_size}",
131 );
132
133 return self.restart_pmtud();
134 }
135
136 if failed_probe_size - successful_probe_size <= 1 {
138 debug!("Found PMTU: {successful_probe_size}");
139 self.set_pmtu(successful_probe_size);
140 } else {
141 self.probe_size =
142 (successful_probe_size + failed_probe_size) / 2
143 }
144 },
145
146 (Some(failed_probe_size), None) =>
149 self.probe_size = (MIN_PLPMTU + failed_probe_size) / 2,
150
151 (None, Some(successful_probe_size)) => {
155 self.set_pmtu(successful_probe_size);
156 },
157
158 (None, None) => self.probe_size = self.maximum_supported_mtu,
160 }
161 }
162
163 pub fn set_in_flight(&mut self, in_flight: bool) {
165 self.in_flight = in_flight;
166 }
167
168 pub fn successful_probe(&mut self, probe_size: usize) -> Option<usize> {
170 self.probe_failure_count = 0;
171
172 self.largest_successful_probe_size = std::cmp::max(
173 Some(probe_size.min(self.maximum_supported_mtu)),
175 self.largest_successful_probe_size,
176 );
177
178 self.update_probe_size();
179 self.in_flight = false;
180
181 self.largest_successful_probe_size
182 }
183
184 pub fn failed_probe(&mut self, probe_size: usize) {
186 let probe_size = std::cmp::max(probe_size, MIN_PLPMTU);
188 self.probe_failure_count += 1;
189
190 if self.probe_failure_count < self.max_probes {
191 debug!(
192 "Probe size {} failed ({}/{}), will retry",
193 probe_size, self.probe_failure_count, self.max_probes
194 );
195 self.in_flight = false;
196 return;
197 }
198
199 debug!(
200 "Probe size {} failed {} times, treating as MTU limitation",
201 probe_size, self.probe_failure_count
202 );
203
204 self.smallest_failed_probe_size = Some(
208 self.smallest_failed_probe_size
209 .map_or(probe_size, |s| s.min(probe_size)),
210 );
211
212 self.probe_failure_count = 0;
213 self.update_probe_size();
214 self.in_flight = false;
215 }
216
217 fn restart_pmtud(&mut self) {
220 self.set_probe_size(self.maximum_supported_mtu);
221 self.smallest_failed_probe_size = None;
222 self.largest_successful_probe_size = None;
223 self.pmtu = None;
224 self.probe_failure_count = 0;
225 }
226
227 pub fn revalidate_pmtu(&mut self) {
231 if let Some(pmtu) = self.pmtu {
232 self.set_probe_size(pmtu);
233 self.pmtu = None;
234 self.probe_failure_count = 0;
235 self.largest_successful_probe_size = None;
236 }
237 }
238
239 fn set_pmtu(&mut self, successful_probe_size: usize) {
240 self.pmtu = Some(successful_probe_size);
241 self.probe_size = successful_probe_size;
242 self.probe_failure_count = 0;
243 }
244}
245
246impl std::fmt::Debug for Pmtud {
247 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
248 write!(f, "pmtu={:?} ", self.pmtu)?;
249 write!(f, "probe_size={:?} ", self.probe_size)?;
250 write!(f, "should_probe={:?} ", self.should_probe())?;
251 write!(
252 f,
253 "failures={}/{} ",
254 self.probe_failure_count, self.max_probes
255 )?;
256 Ok(())
257 }
258}
259
260#[cfg(test)]
261mod tests {
262 use super::*;
263
264 #[test]
265 fn pmtud_initial_state() {
266 let pmtud = Pmtud::new(1350, 1);
267 assert_eq!(pmtud.get_current_mtu(), 1200);
268 assert_eq!(pmtud.get_probe_size(), 1350);
269 assert!(pmtud.should_probe());
270 }
271
272 #[test]
273 fn pmtud_max_probes_zero_uses_default() {
274 let pmtud = Pmtud::new(1500, 0);
275 assert_eq!(pmtud.max_probes, MAX_PROBES_DEFAULT);
276 }
277
278 #[test]
279 fn pmtud_max_probes_set_to_provided_value() {
280 let pmtud = Pmtud::new(1500, 5);
281 assert_eq!(pmtud.max_probes, 5);
282 assert_ne!(pmtud.max_probes, MAX_PROBES_DEFAULT);
283 }
284
285 #[test]
286 fn pmtud_binary_search_algorithm() {
287 let mut pmtud = Pmtud::new(1500, 1);
288
289 assert_eq!(pmtud.get_probe_size(), 1500);
291
292 pmtud.failed_probe(1500);
294 assert_eq!(pmtud.get_probe_size(), 1350);
296
297 pmtud.failed_probe(1350);
299 assert_eq!(pmtud.get_probe_size(), 1275);
301
302 pmtud.failed_probe(1275);
303 assert_eq!(pmtud.get_probe_size(), 1237);
305
306 pmtud.failed_probe(1237);
307 assert_eq!(pmtud.get_probe_size(), 1218);
309
310 pmtud.failed_probe(1218);
311 assert_eq!(pmtud.get_probe_size(), 1209);
313
314 pmtud.failed_probe(1209);
315 assert_eq!(pmtud.get_probe_size(), 1204);
317
318 pmtud.failed_probe(1204);
319 assert_eq!(pmtud.get_probe_size(), 1202);
321
322 pmtud.failed_probe(1202);
323 assert_eq!(pmtud.get_probe_size(), 1201);
325
326 pmtud.failed_probe(1201);
327 assert_eq!(pmtud.get_probe_size(), 1200);
329 }
330
331 #[test]
332 fn pmtud_successful_probe() {
333 let mut pmtud = Pmtud::new(1400, 1);
334
335 pmtud.successful_probe(1400);
337
338 assert_eq!(pmtud.get_current_mtu(), 1400);
339 }
340
341 #[test]
347 fn test_pmtud_reset() {
348 let mut pmtud = Pmtud::new(1350, 1);
349 pmtud.successful_probe(1350);
350 assert_eq!(pmtud.pmtu, Some(1350));
351 assert!(!pmtud.should_probe());
352
353 pmtud.restart_pmtud();
355
356 pmtud_test_runner(&mut pmtud, 1237);
358 }
359
360 #[test]
362 fn test_pmtud_errant_probe() {
363 let mut pmtud = Pmtud::new(1350, 1);
364 pmtud.successful_probe(1500);
365 assert_eq!(pmtud.pmtu, Some(1350));
368 assert!(!pmtud.should_probe());
369
370 pmtud.restart_pmtud();
371
372 pmtud.failed_probe(1100);
375 assert_eq!(pmtud.pmtu, None);
376 assert_eq!(pmtud.get_probe_size(), 1200);
377 assert!(!pmtud.should_probe());
378 }
379
380 #[test]
385 fn test_pmtu_equal_to_min_supported_mtu() {
386 let mut pmtud = Pmtud::new(1350, 1);
387 pmtud_test_runner(&mut pmtud, 1200);
388 }
389
390 #[test]
395 fn test_pmtu_greater_than_min_supported_mtu() {
396 let mut pmtud = Pmtud::new(1350, 1);
397 pmtud_test_runner(&mut pmtud, 1500);
398 }
399
400 #[test]
405 fn test_pmtu_less_than_min_supported_mtu() {
406 let mut pmtud = Pmtud::new(1350, 1);
407 pmtud_test_runner(&mut pmtud, 1100);
408 }
409
410 #[test]
416 fn test_pmtu_revalidation() {
417 let mut pmtud = Pmtud::new(1350, 1);
418 pmtud.set_probe_size(1350);
419 pmtud.successful_probe(1350);
420
421 pmtud.revalidate_pmtu();
423 fail_probe_max_times(&mut pmtud, 1350);
424
425 pmtud_test_runner(&mut pmtud, 1250);
427 }
428
429 #[test]
430 fn pmtud_revalidation_tolerates_random_packet_loss() {
431 let mut pmtud = Pmtud::new(1500, MAX_PROBES_DEFAULT);
432
433 pmtud.successful_probe(1500);
434 assert_eq!(pmtud.get_pmtu(), Some(1500));
435
436 pmtud.revalidate_pmtu();
437 assert_eq!(pmtud.get_pmtu(), None);
438 assert!(pmtud.largest_successful_probe_size.is_none());
439
440 pmtud.failed_probe(1500);
441 assert_eq!(pmtud.probe_failure_count, 1);
442 assert!(pmtud.pmtu.is_none());
443
444 pmtud.failed_probe(1500);
445 assert_eq!(pmtud.probe_failure_count, 2);
446
447 pmtud.successful_probe(1500);
448 assert_eq!(pmtud.get_pmtu(), Some(1500));
449 assert_eq!(pmtud.probe_failure_count, 0);
450 }
451
452 #[test]
455 fn pmtud_revalidation_failure_binary_searches_not_restarts() {
456 let mut pmtud = Pmtud::new(1500, 1);
457
458 pmtud.successful_probe(1500);
459 assert_eq!(pmtud.get_pmtu(), Some(1500));
460
461 pmtud.revalidate_pmtu();
463 assert!(pmtud.largest_successful_probe_size.is_none());
464
465 pmtud.failed_probe(1500);
467
468 assert_eq!(pmtud.smallest_failed_probe_size, Some(1500));
469 assert!(pmtud.largest_successful_probe_size.is_none());
470 assert_eq!(pmtud.get_probe_size(), 1350); }
472
473 #[test]
474 fn pmtud_tolerates_initial_packet_loss() {
475 let mut pmtud = Pmtud::new(1500, MAX_PROBES_DEFAULT);
476
477 pmtud.failed_probe(1500);
478 assert_eq!(pmtud.probe_failure_count, 1);
479 assert!(pmtud.smallest_failed_probe_size.is_none());
480
481 pmtud.failed_probe(1500);
482 assert_eq!(pmtud.probe_failure_count, 2);
483 assert!(pmtud.smallest_failed_probe_size.is_none());
484
485 pmtud.successful_probe(1500);
486 assert_eq!(pmtud.get_pmtu(), Some(1500));
487 assert_eq!(pmtud.probe_failure_count, 0);
488 }
489
490 #[test]
491 fn pmtud_confirms_failure_after_max_probes() {
492 let mut pmtud = Pmtud::new(1500, 1);
493
494 pmtud.failed_probe(1500);
495
496 assert_eq!(pmtud.smallest_failed_probe_size, Some(1500));
497 assert!(pmtud.pmtu.is_none());
498 assert!(pmtud.get_probe_size() < 1500);
499 assert!(pmtud.get_probe_size() >= MIN_PLPMTU);
500 }
501
502 #[test]
503 fn pmtud_binary_search_no_slowdown() {
504 let mut pmtud = Pmtud::new(1500, 2);
505
506 fail_probe_max_times(&mut pmtud, 1500);
507 assert!(pmtud.pmtu.is_none());
508
509 let search_size_1 = pmtud.get_probe_size();
510 assert!(search_size_1 < 1500);
511
512 pmtud.successful_probe(search_size_1);
513 assert_eq!(pmtud.probe_failure_count, 0);
514
515 let search_size_2 = pmtud.get_probe_size();
516 pmtud.failed_probe(search_size_2);
517
518 assert!(pmtud.pmtu.is_none());
519 assert_eq!(pmtud.probe_failure_count, 1);
520 }
521
522 #[test]
530 fn pmtud_convergence_with_intermittent_loss() {
531 let mut pmtud = Pmtud::new(1500, 3);
532 let target_mtu = 1337;
533
534 while pmtud.get_pmtu().is_none() {
535 let probe_size = pmtud.get_probe_size();
536
537 if probe_size <= target_mtu {
538 pmtud.failed_probe(probe_size);
540 assert_eq!(pmtud.probe_failure_count, 1);
541
542 pmtud.successful_probe(probe_size);
544 assert_eq!(pmtud.probe_failure_count, 0); } else {
546 let old_probe_size = probe_size;
548 fail_probe_max_times(&mut pmtud, probe_size);
549
550 assert_eq!(pmtud.probe_failure_count, 0);
552 if pmtud.get_pmtu().is_none() {
553 assert!(pmtud.get_probe_size() < old_probe_size);
554 }
555 }
556 }
557
558 assert_eq!(pmtud.get_pmtu(), Some(target_mtu));
559 }
560
561 #[test]
562 fn pmtud_failure_at_min_plpmtu() {
563 let mut pmtud = Pmtud::new(1500, MAX_PROBES_DEFAULT);
564
565 pmtud.failed_probe(100);
566 pmtud.failed_probe(100);
567 pmtud.failed_probe(100);
568
569 assert_eq!(pmtud.smallest_failed_probe_size, Some(MIN_PLPMTU));
570 }
571
572 #[test]
573 fn pmtud_in_flight_cleared_on_all_outcomes() {
574 let mut pmtud = Pmtud::new(1500, 1);
575
576 pmtud.set_in_flight(true);
577 assert!(pmtud.in_flight);
578
579 pmtud.failed_probe(1500);
580 assert!(!pmtud.in_flight);
581
582 pmtud.set_in_flight(true);
583
584 pmtud.successful_probe(1500);
585 assert!(!pmtud.in_flight);
586 }
587
588 #[test]
589 fn pmtud_update_probe_size_initial_state() {
590 let mut pmtud = Pmtud::new(1500, 1);
591
592 pmtud.probe_size = 1200;
595
596 pmtud.update_probe_size();
599
600 assert_eq!(pmtud.probe_size, 1500);
601 }
602
603 fn fail_probe_max_times(pmtud: &mut Pmtud, size: usize) {
606 for _ in 0..pmtud.max_probes {
607 pmtud.failed_probe(size);
608 }
609 }
610
611 fn pmtud_test_runner(pmtud: &mut Pmtud, test_pmtu: usize) {
617 while pmtud.get_probe_size() >= MIN_PLPMTU {
619 let probe_size = pmtud.get_probe_size();
621
622 if probe_size <= test_pmtu {
623 pmtud.successful_probe(probe_size);
624 } else {
625 fail_probe_max_times(pmtud, probe_size);
626 }
627
628 pmtud.update_probe_size();
630
631 if pmtud.get_probe_size() == probe_size && probe_size == MIN_PLPMTU {
634 break;
635 }
636
637 if pmtud.get_pmtu().is_some() {
639 break;
640 }
641 }
642
643 if test_pmtu < MIN_PLPMTU {
645 assert_eq!(pmtud.get_pmtu(), None);
646 } else if test_pmtu > pmtud.maximum_supported_mtu {
647 assert_eq!(pmtud.get_pmtu(), Some(pmtud.maximum_supported_mtu));
648 } else {
649 assert_eq!(pmtud.get_pmtu(), Some(test_pmtu));
650 }
651 }
652}