netstack3_tcp/
rtt.rs

1// Copyright 2022 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! TCP RTT estimation per [RFC 6298](https://tools.ietf.org/html/rfc6298).
6use core::ops::Range;
7use core::time::Duration;
8
9use netstack3_base::{Instant, SeqNum};
10
11#[derive(Debug)]
12#[cfg_attr(test, derive(PartialEq, Eq))]
13pub(super) enum Estimator {
14    NoSample,
15    Measured {
16        /// The smoothed round-trip time.
17        srtt: Duration,
18        /// The round-trip time variation.
19        rtt_var: Duration,
20    },
21}
22
23impl Default for Estimator {
24    fn default() -> Self {
25        Self::NoSample
26    }
27}
28
29impl Estimator {
30    /// The following constants are defined in [RFC 6298 Section 2]:
31    ///
32    /// [RFC 6298 Section 2]: https://tools.ietf.org/html/rfc6298#section-2
33    const K: u32 = 4;
34    const G: Duration = Duration::from_millis(100);
35
36    /// Updates the estimates with a newly sampled RTT.
37    pub(super) fn sample(&mut self, rtt: Duration) {
38        match self {
39            Self::NoSample => {
40                // Per RFC 6298 section 2,
41                //   When the first RTT measurement R is made, the host MUST set
42                //   SRTT <- R
43                //   RTTVAR <- R/2
44                *self = Self::Measured { srtt: rtt, rtt_var: rtt / 2 }
45            }
46            Self::Measured { srtt, rtt_var } => {
47                // Per RFC 6298 section 2,
48                //   When a subsequent RTT measurement R' is made, a host MUST set
49                //     RTTVAR <- (1 - beta) * RTTVAR + beta * |SRTT - R'|
50                //     SRTT <- (1 - alpha) * SRTT + alpha * R'
51                //   ...
52                //   The above SHOULD be computed using alpha=1/8 and beta=1/4.
53                let diff = srtt.checked_sub(rtt).unwrap_or_else(|| rtt - *srtt);
54                // Using fixed point integer division below rather than using
55                // floating points just to define the exact constants.
56                *rtt_var = ((*rtt_var * 3) + diff) / 4;
57                *srtt = ((*srtt * 7) + rtt) / 8;
58            }
59        }
60    }
61
62    /// Returns the current retransmission timeout.
63    pub(super) fn rto(&self) -> Rto {
64        //   Until a round-trip time (RTT) measurement has been made for a
65        //   segment sent between the sender and receiver, the sender SHOULD
66        //   set RTO <- 1 second;
67        //   ...
68        //   RTO <- SRTT + max (G, K*RTTVAR)
69        match *self {
70            Estimator::NoSample => Rto::DEFAULT,
71            Estimator::Measured { srtt, rtt_var } => {
72                // `Duration::MAX` is 2^64 seconds which is about 6 * 10^11
73                // years. If the following expression panics due to overflow,
74                // we must have some serious errors in the estimator itself.
75                Rto::new(srtt + Self::G.max(rtt_var * Self::K))
76            }
77        }
78    }
79
80    pub(super) fn srtt(&self) -> Option<Duration> {
81        match self {
82            Self::NoSample => None,
83            Self::Measured { srtt, rtt_var: _ } => Some(*srtt),
84        }
85    }
86
87    pub(super) fn rtt_var(&self) -> Option<Duration> {
88        match self {
89            Self::NoSample => None,
90            Self::Measured { srtt: _, rtt_var } => Some(*rtt_var),
91        }
92    }
93}
94
95/// A retransmit timeout value.
96///
97/// This type serves as a witness for a valid retransmit timeout value that is
98/// clamped to the interval `[Rto::MIN, Rto::MAX]`. It can be transformed into a
99/// [`Duration`].
100#[derive(Debug, Eq, PartialEq, PartialOrd, Ord, Copy, Clone)]
101pub(super) struct Rto(Duration);
102
103impl Rto {
104    /// The minimum retransmit timeout value.
105    ///
106    /// [RFC 6298 Section 2] states:
107    ///
108    /// > Whenever RTO is computed, if it is less than 1 second, then the RTO
109    /// > SHOULD be rounded up to 1 second. [...] Therefore, this specification
110    /// > requires a large minimum RTO as a conservative approach, while at the
111    /// > same time acknowledging that at some future point, research may show
112    /// > that a smaller minimum RTO is acceptable or superior.
113    ///
114    /// We hard code the default value used by [Linux] here.
115    ///
116    /// [RFC 6298 Section 2]: https://datatracker.ietf.org/doc/html/rfc6298#section-2
117    /// [Linux]: https://github.com/torvalds/linux/blob/4701f33a10702d5fc577c32434eb62adde0a1ae1/include/net/tcp.h#L148
118    pub(super) const MIN: Rto = Rto(Duration::from_millis(200));
119
120    /// The maximum retransmit timeout value.
121    ///
122    /// [RFC 67298 Section 2] states:
123    ///
124    /// > (2.5) A maximum value MAY be placed on RTO provided it is at least 60
125    /// > seconds.
126    ///
127    /// We hard code the default value used by [Linux] here.
128    ///
129    /// [RFC 6298 Section 2]: https://datatracker.ietf.org/doc/html/rfc6298#section-2
130    /// [Linux]: https://github.com/torvalds/linux/blob/4701f33a10702d5fc577c32434eb62adde0a1ae1/include/net/tcp.h#L147
131    pub(super) const MAX: Rto = Rto(Duration::from_secs(120));
132
133    /// The default RTO value.
134    pub(super) const DEFAULT: Rto = Rto(Duration::from_secs(1));
135
136    /// Creates a new [`Rto`] by clamping `duration` to the allowed range.
137    pub(super) fn new(duration: Duration) -> Self {
138        Self(duration).clamp(Self::MIN, Self::MAX)
139    }
140
141    pub(super) fn get(&self) -> Duration {
142        let Self(inner) = self;
143        *inner
144    }
145
146    /// Returns the result of doubling this RTO value and saturating to the
147    /// valid range.
148    pub(super) fn double(&self) -> Self {
149        let Self(d) = self;
150        Self(d.saturating_mul(2)).min(Self::MAX)
151    }
152}
153
154impl From<Rto> for Duration {
155    fn from(Rto(value): Rto) -> Self {
156        value
157    }
158}
159
160impl Default for Rto {
161    fn default() -> Self {
162        Self::DEFAULT
163    }
164}
165
166/// RTT sampler keeps track of the current segment that is keeping track of RTT
167/// calculations.
168#[derive(Debug, Default)]
169#[cfg_attr(test, derive(PartialEq, Eq))]
170pub(super) enum RttSampler<I> {
171    #[default]
172    NotTracking,
173    Tracking {
174        range: Range<SeqNum>,
175        timestamp: I,
176    },
177}
178
179impl<I: Instant> RttSampler<I> {
180    /// Updates the `RttSampler` with a new segment that is about to be sent.
181    ///
182    /// - `now` is the current timestamp.
183    /// - `range` is the sequence number range in the newly produced segment.
184    /// - `snd_max` is the SND.MAX value *not considering* the new segment in `range`.
185    pub(super) fn on_will_send_segment(&mut self, now: I, range: Range<SeqNum>, snd_max: SeqNum) {
186        match self {
187            Self::NotTracking => {
188                // If we're currently not tracking any segments, we can consider
189                // this segment for RTT IFF at least part of `range` is new
190                // bytes.
191                if !range.end.after(snd_max) {
192                    return;
193                }
194                // The segment could be partially retransmitting some data, so
195                // the left edge of our tracking must be the latest between the
196                // start and snd_max.
197                let start = if range.start.before(snd_max) { snd_max } else { range.start };
198                *self = Self::Tracking { range: start..range.end, timestamp: now }
199            }
200            Self::Tracking { range: tracking, timestamp: _ } => {
201                // We need to discard this tracking segment if we retransmit
202                // anything prior to the right edge of the tracked segment.
203                if range.start.before(tracking.end) {
204                    *self = Self::NotTracking;
205                }
206            }
207        }
208    }
209
210    /// Updates the `RttSampler` with a new ack that arrived for the connection.
211    ///
212    /// - `now` is the current timestamp.
213    /// - `ack` is the acknowledgement number in the ACK segment.
214    ///
215    /// If the sampler was able to produce a new RTT sample, `Some` is returned.
216    ///
217    /// This function assumes that `ack` is a valid ACK number and is within the
218    /// window the sender is expecting to receive (i.e. it's not an ACK for data
219    /// we did not send).
220    pub(super) fn on_ack(&mut self, now: I, ack: SeqNum) -> Option<Duration> {
221        match self {
222            Self::NotTracking => None,
223            Self::Tracking { range, timestamp } => {
224                if ack.after(range.start) {
225                    // Any acknowledgement that is at or after the tracked range
226                    // is a valid rtt sample.
227                    let rtt = now.saturating_duration_since(*timestamp);
228                    // Segment has been acked, we're not going to be tracking it
229                    // anymore.
230                    *self = Self::NotTracking;
231                    Some(rtt)
232                } else {
233                    None
234                }
235            }
236        }
237    }
238}
239
240#[cfg(test)]
241mod test {
242    use super::*;
243    use netstack3_base::testutil::FakeInstant;
244    use test_case::test_case;
245
246    impl RttSampler<FakeInstant> {
247        fn from_range(Range { start, end }: Range<u32>) -> Self {
248            Self::Tracking {
249                range: SeqNum::new(start)..SeqNum::new(end),
250                timestamp: FakeInstant::default(),
251            }
252        }
253    }
254
255    #[test_case(Estimator::NoSample, Duration::from_secs(2) => Estimator::Measured {
256        srtt: Duration::from_secs(2),
257        rtt_var: Duration::from_secs(1)
258    })]
259    #[test_case(Estimator::Measured {
260        srtt: Duration::from_secs(1),
261        rtt_var: Duration::from_secs(1)
262    }, Duration::from_secs(2) => Estimator::Measured {
263        srtt: Duration::from_millis(1125),
264        rtt_var: Duration::from_secs(1)
265    })]
266    #[test_case(Estimator::Measured {
267        srtt: Duration::from_secs(1),
268        rtt_var: Duration::from_secs(2)
269    }, Duration::from_secs(1) => Estimator::Measured {
270        srtt: Duration::from_secs(1),
271        rtt_var: Duration::from_millis(1500)
272    })]
273    fn sample_rtt(mut estimator: Estimator, rtt: Duration) -> Estimator {
274        estimator.sample(rtt);
275        estimator
276    }
277
278    #[test_case(Estimator::NoSample => Rto::DEFAULT.get())]
279    #[test_case(Estimator::Measured {
280        srtt: Duration::from_secs(1),
281        rtt_var: Duration::from_secs(2),
282    } => Duration::from_secs(9))]
283    fn calculate_rto(estimator: Estimator) -> Duration {
284        estimator.rto().get()
285    }
286
287    // Useful for representing wrapping-around TCP seqnum ranges.
288    #[allow(clippy::reversed_empty_ranges)]
289    #[test_case(
290        RttSampler::NotTracking, 1..10, 1 => RttSampler::from_range(1..10)
291        ; "segment after SND.MAX"
292    )]
293    #[test_case(
294        RttSampler::NotTracking, 1..10, 10 => RttSampler::NotTracking
295        ; "segment before SND.MAX"
296    )]
297    #[test_case(
298        RttSampler::NotTracking, 1..10, 5 => RttSampler::from_range(5..10)
299        ; "segment contains SND.MAX"
300    )]
301    #[test_case(
302        RttSampler::from_range(1..10), 10..20, 10 => RttSampler::from_range(1..10)
303        ; "send further segments"
304    )]
305    #[test_case(
306        RttSampler::from_range(10..20), 1..10, 20 => RttSampler::NotTracking
307        ; "retransmit prior segments"
308    )]
309    #[test_case(
310        RttSampler::from_range(1..10), 1..10, 10 => RttSampler::NotTracking
311        ; "retransmit same segment"
312    )]
313    #[test_case(
314        RttSampler::from_range(1..10), 5..15, 15 => RttSampler::NotTracking
315        ; "retransmit same partial 1"
316    )]
317    #[test_case(
318        RttSampler::from_range(10..20), 5..15, 20 => RttSampler::NotTracking
319        ; "retransmit same partial 2"
320    )]
321    #[test_case(
322        RttSampler::NotTracking, (u32::MAX - 5)..5,
323        u32::MAX - 5 => RttSampler::from_range((u32::MAX - 5)..5)
324        ; "SND.MAX wraparound good"
325    )]
326    #[test_case(
327        RttSampler::NotTracking, (u32::MAX - 5)..5,
328        5 => RttSampler::NotTracking
329        ; "SND.MAX wraparound retransmit not tracking"
330    )]
331    #[test_case(
332        RttSampler::from_range(u32::MAX - 5..5), (u32::MAX - 5)..5,
333        5 => RttSampler::NotTracking
334        ; "SND.MAX wraparound retransmit tracking"
335    )]
336    #[test_case(
337        RttSampler::NotTracking, (u32::MAX - 5)..5, u32::MAX => RttSampler::from_range(u32::MAX..5)
338        ; "SND.MAX wraparound partial 1"
339    )]
340    #[test_case(
341        RttSampler::NotTracking, (u32::MAX - 5)..5, 1 => RttSampler::from_range(1..5)
342        ; "SND.MAX wraparound partial 2"
343    )]
344    fn rtt_sampler_on_segment(
345        mut sampler: RttSampler<FakeInstant>,
346        range: Range<u32>,
347        snd_max: u32,
348    ) -> RttSampler<FakeInstant> {
349        sampler.on_will_send_segment(
350            FakeInstant::default(),
351            SeqNum::new(range.start)..SeqNum::new(range.end),
352            SeqNum::new(snd_max),
353        );
354        sampler
355    }
356
357    const ACK_DELAY: Duration = Duration::from_millis(10);
358
359    #[test_case(
360        RttSampler::NotTracking, 10 => (None, RttSampler::NotTracking)
361        ; "not tracking"
362    )]
363    #[test_case(
364        RttSampler::from_range(1..10), 10 => (Some(ACK_DELAY), RttSampler::NotTracking)
365        ; "ack segment"
366    )]
367    #[test_case(
368        RttSampler::from_range(1..10), 20 => (Some(ACK_DELAY), RttSampler::NotTracking)
369        ; "ack after"
370    )]
371    #[test_case(
372        RttSampler::from_range(10..20), 9 => (None, RttSampler::from_range(10..20))
373        ; "ack before 1"
374    )]
375    #[test_case(
376        RttSampler::from_range(10..20), 10 => (None, RttSampler::from_range(10..20))
377        ; "ack before 2"
378    )]
379    #[test_case(
380        RttSampler::from_range(10..20), 11 => (Some(ACK_DELAY), RttSampler::NotTracking)
381        ; "ack single"
382    )]
383    #[test_case(
384        RttSampler::from_range(10..20), 15 => (Some(ACK_DELAY), RttSampler::NotTracking)
385        ; "ack partial"
386    )]
387    fn rtt_sampler_on_ack(
388        mut sampler: RttSampler<FakeInstant>,
389        ack: u32,
390    ) -> (Option<Duration>, RttSampler<FakeInstant>) {
391        let res = sampler.on_ack(FakeInstant::default() + ACK_DELAY, SeqNum::new(ack));
392        (res, sampler)
393    }
394}