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
88/// A retransmit timeout value.
89///
90/// This type serves as a witness for a valid retransmit timeout value that is
91/// clamped to the interval `[Rto::MIN, Rto::MAX]`. It can be transformed into a
92/// [`Duration`].
93#[derive(Debug, Eq, PartialEq, PartialOrd, Ord, Copy, Clone)]
94pub(super) struct Rto(Duration);
95
96impl Rto {
97    /// The minimum retransmit timeout value.
98    ///
99    /// [RFC 6298 Section 2] states:
100    ///
101    /// > Whenever RTO is computed, if it is less than 1 second, then the RTO
102    /// > SHOULD be rounded up to 1 second. [...] Therefore, this specification
103    /// > requires a large minimum RTO as a conservative approach, while at the
104    /// > same time acknowledging that at some future point, research may show
105    /// > that a smaller minimum RTO is acceptable or superior.
106    ///
107    /// We hard code the default value used by [Linux] here.
108    ///
109    /// [RFC 6298 Section 2]: https://datatracker.ietf.org/doc/html/rfc6298#section-2
110    /// [Linux]: https://github.com/torvalds/linux/blob/4701f33a10702d5fc577c32434eb62adde0a1ae1/include/net/tcp.h#L148
111    pub(super) const MIN: Rto = Rto(Duration::from_millis(200));
112
113    /// The maximum retransmit timeout value.
114    ///
115    /// [RFC 67298 Section 2] states:
116    ///
117    /// > (2.5) A maximum value MAY be placed on RTO provided it is at least 60
118    /// > seconds.
119    ///
120    /// We hard code the default value used by [Linux] here.
121    ///
122    /// [RFC 6298 Section 2]: https://datatracker.ietf.org/doc/html/rfc6298#section-2
123    /// [Linux]: https://github.com/torvalds/linux/blob/4701f33a10702d5fc577c32434eb62adde0a1ae1/include/net/tcp.h#L147
124    pub(super) const MAX: Rto = Rto(Duration::from_secs(120));
125
126    /// The default RTO value.
127    pub(super) const DEFAULT: Rto = Rto(Duration::from_secs(1));
128
129    /// Creates a new [`Rto`] by clamping `duration` to the allowed range.
130    pub(super) fn new(duration: Duration) -> Self {
131        Self(duration).clamp(Self::MIN, Self::MAX)
132    }
133
134    pub(super) fn get(&self) -> Duration {
135        let Self(inner) = self;
136        *inner
137    }
138
139    /// Returns the result of doubling this RTO value and saturating to the
140    /// valid range.
141    pub(super) fn double(&self) -> Self {
142        let Self(d) = self;
143        Self(d.saturating_mul(2)).min(Self::MAX)
144    }
145}
146
147impl From<Rto> for Duration {
148    fn from(Rto(value): Rto) -> Self {
149        value
150    }
151}
152
153impl Default for Rto {
154    fn default() -> Self {
155        Self::DEFAULT
156    }
157}
158
159/// RTT sampler keeps track of the current segment that is keeping track of RTT
160/// calculations.
161#[derive(Debug, Default)]
162#[cfg_attr(test, derive(PartialEq, Eq))]
163pub(super) enum RttSampler<I> {
164    #[default]
165    NotTracking,
166    Tracking {
167        range: Range<SeqNum>,
168        timestamp: I,
169    },
170}
171
172impl<I: Instant> RttSampler<I> {
173    /// Updates the `RttSampler` with a new segment that is about to be sent.
174    ///
175    /// - `now` is the current timestamp.
176    /// - `range` is the sequence number range in the newly produced segment.
177    /// - `snd_max` is the SND.MAX value *not considering* the new segment in `range`.
178    pub(super) fn on_will_send_segment(&mut self, now: I, range: Range<SeqNum>, snd_max: SeqNum) {
179        match self {
180            Self::NotTracking => {
181                // If we're currently not tracking any segments, we can consider
182                // this segment for RTT IFF at least part of `range` is new
183                // bytes.
184                if !range.end.after(snd_max) {
185                    return;
186                }
187                // The segment could be partially retransmitting some data, so
188                // the left edge of our tracking must be the latest between the
189                // start and snd_max.
190                let start = if range.start.before(snd_max) { snd_max } else { range.start };
191                *self = Self::Tracking { range: start..range.end, timestamp: now }
192            }
193            Self::Tracking { range: tracking, timestamp: _ } => {
194                // We need to discard this tracking segment if we retransmit
195                // anything prior to the right edge of the tracked segment.
196                if range.start.before(tracking.end) {
197                    *self = Self::NotTracking;
198                }
199            }
200        }
201    }
202
203    /// Updates the `RttSampler` with a new ack that arrived for the connection.
204    ///
205    /// - `now` is the current timestamp.
206    /// - `ack` is the acknowledgement number in the ACK segment.
207    ///
208    /// If the sampler was able to produce a new RTT sample, `Some` is returned.
209    ///
210    /// This function assumes that `ack` is a valid ACK number and is within the
211    /// window the sender is expecting to receive (i.e. it's not an ACK for data
212    /// we did not send).
213    pub(super) fn on_ack(&mut self, now: I, ack: SeqNum) -> Option<Duration> {
214        match self {
215            Self::NotTracking => None,
216            Self::Tracking { range, timestamp } => {
217                if ack.after(range.start) {
218                    // Any acknowledgement that is at or after the tracked range
219                    // is a valid rtt sample.
220                    let rtt = now.saturating_duration_since(*timestamp);
221                    // Segment has been acked, we're not going to be tracking it
222                    // anymore.
223                    *self = Self::NotTracking;
224                    Some(rtt)
225                } else {
226                    None
227                }
228            }
229        }
230    }
231}
232
233#[cfg(test)]
234mod test {
235    use super::*;
236    use netstack3_base::testutil::FakeInstant;
237    use test_case::test_case;
238
239    impl RttSampler<FakeInstant> {
240        fn from_range(Range { start, end }: Range<u32>) -> Self {
241            Self::Tracking {
242                range: SeqNum::new(start)..SeqNum::new(end),
243                timestamp: FakeInstant::default(),
244            }
245        }
246    }
247
248    #[test_case(Estimator::NoSample, Duration::from_secs(2) => Estimator::Measured {
249        srtt: Duration::from_secs(2),
250        rtt_var: Duration::from_secs(1)
251    })]
252    #[test_case(Estimator::Measured {
253        srtt: Duration::from_secs(1),
254        rtt_var: Duration::from_secs(1)
255    }, Duration::from_secs(2) => Estimator::Measured {
256        srtt: Duration::from_millis(1125),
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(2)
262    }, Duration::from_secs(1) => Estimator::Measured {
263        srtt: Duration::from_secs(1),
264        rtt_var: Duration::from_millis(1500)
265    })]
266    fn sample_rtt(mut estimator: Estimator, rtt: Duration) -> Estimator {
267        estimator.sample(rtt);
268        estimator
269    }
270
271    #[test_case(Estimator::NoSample => Rto::DEFAULT.get())]
272    #[test_case(Estimator::Measured {
273        srtt: Duration::from_secs(1),
274        rtt_var: Duration::from_secs(2),
275    } => Duration::from_secs(9))]
276    fn calculate_rto(estimator: Estimator) -> Duration {
277        estimator.rto().get()
278    }
279
280    // Useful for representing wrapping-around TCP seqnum ranges.
281    #[allow(clippy::reversed_empty_ranges)]
282    #[test_case(
283        RttSampler::NotTracking, 1..10, 1 => RttSampler::from_range(1..10)
284        ; "segment after SND.MAX"
285    )]
286    #[test_case(
287        RttSampler::NotTracking, 1..10, 10 => RttSampler::NotTracking
288        ; "segment before SND.MAX"
289    )]
290    #[test_case(
291        RttSampler::NotTracking, 1..10, 5 => RttSampler::from_range(5..10)
292        ; "segment contains SND.MAX"
293    )]
294    #[test_case(
295        RttSampler::from_range(1..10), 10..20, 10 => RttSampler::from_range(1..10)
296        ; "send further segments"
297    )]
298    #[test_case(
299        RttSampler::from_range(10..20), 1..10, 20 => RttSampler::NotTracking
300        ; "retransmit prior segments"
301    )]
302    #[test_case(
303        RttSampler::from_range(1..10), 1..10, 10 => RttSampler::NotTracking
304        ; "retransmit same segment"
305    )]
306    #[test_case(
307        RttSampler::from_range(1..10), 5..15, 15 => RttSampler::NotTracking
308        ; "retransmit same partial 1"
309    )]
310    #[test_case(
311        RttSampler::from_range(10..20), 5..15, 20 => RttSampler::NotTracking
312        ; "retransmit same partial 2"
313    )]
314    #[test_case(
315        RttSampler::NotTracking, (u32::MAX - 5)..5,
316        u32::MAX - 5 => RttSampler::from_range((u32::MAX - 5)..5)
317        ; "SND.MAX wraparound good"
318    )]
319    #[test_case(
320        RttSampler::NotTracking, (u32::MAX - 5)..5,
321        5 => RttSampler::NotTracking
322        ; "SND.MAX wraparound retransmit not tracking"
323    )]
324    #[test_case(
325        RttSampler::from_range(u32::MAX - 5..5), (u32::MAX - 5)..5,
326        5 => RttSampler::NotTracking
327        ; "SND.MAX wraparound retransmit tracking"
328    )]
329    #[test_case(
330        RttSampler::NotTracking, (u32::MAX - 5)..5, u32::MAX => RttSampler::from_range(u32::MAX..5)
331        ; "SND.MAX wraparound partial 1"
332    )]
333    #[test_case(
334        RttSampler::NotTracking, (u32::MAX - 5)..5, 1 => RttSampler::from_range(1..5)
335        ; "SND.MAX wraparound partial 2"
336    )]
337    fn rtt_sampler_on_segment(
338        mut sampler: RttSampler<FakeInstant>,
339        range: Range<u32>,
340        snd_max: u32,
341    ) -> RttSampler<FakeInstant> {
342        sampler.on_will_send_segment(
343            FakeInstant::default(),
344            SeqNum::new(range.start)..SeqNum::new(range.end),
345            SeqNum::new(snd_max),
346        );
347        sampler
348    }
349
350    const ACK_DELAY: Duration = Duration::from_millis(10);
351
352    #[test_case(
353        RttSampler::NotTracking, 10 => (None, RttSampler::NotTracking)
354        ; "not tracking"
355    )]
356    #[test_case(
357        RttSampler::from_range(1..10), 10 => (Some(ACK_DELAY), RttSampler::NotTracking)
358        ; "ack segment"
359    )]
360    #[test_case(
361        RttSampler::from_range(1..10), 20 => (Some(ACK_DELAY), RttSampler::NotTracking)
362        ; "ack after"
363    )]
364    #[test_case(
365        RttSampler::from_range(10..20), 9 => (None, RttSampler::from_range(10..20))
366        ; "ack before 1"
367    )]
368    #[test_case(
369        RttSampler::from_range(10..20), 10 => (None, RttSampler::from_range(10..20))
370        ; "ack before 2"
371    )]
372    #[test_case(
373        RttSampler::from_range(10..20), 11 => (Some(ACK_DELAY), RttSampler::NotTracking)
374        ; "ack single"
375    )]
376    #[test_case(
377        RttSampler::from_range(10..20), 15 => (Some(ACK_DELAY), RttSampler::NotTracking)
378        ; "ack partial"
379    )]
380    fn rtt_sampler_on_ack(
381        mut sampler: RttSampler<FakeInstant>,
382        ack: u32,
383    ) -> (Option<Duration>, RttSampler<FakeInstant>) {
384        let res = sampler.on_ack(FakeInstant::default() + ACK_DELAY, SeqNum::new(ack));
385        (res, sampler)
386    }
387}