netstack3_tcp/state/
info.rs

1// Copyright 2025 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
5use core::time::Duration;
6
7use derivative::Derivative;
8use netstack3_base::Instant;
9
10use crate::internal::buffer::{ReceiveBuffer, SendBuffer};
11use crate::internal::congestion::LossRecoveryMode;
12use crate::internal::counters::TcpCountersWithSocketInner;
13use crate::internal::state::{
14    CloseWait, Closed, Closing, Established, FinWait1, FinWait2, LastAck, Listen, Recv, RecvParams,
15    Send, State, SynRcvd, SynSent, TimeWait,
16};
17
18/// Information about a TCP socket.
19#[derive(Clone, Debug, PartialEq, Eq)]
20pub struct TcpSocketInfo<I> {
21    /// The current state of the TCP state machine.
22    pub state: netstack3_base::TcpSocketState,
23    /// The congestion control state.
24    pub ca_state: CongestionControlState,
25    /// The current RTO.
26    pub rto: Option<Duration>,
27    /// The estimated RTT.
28    pub rtt: Option<Duration>,
29    /// The RTT variance.
30    pub rtt_var: Option<Duration>,
31    /// The slow start threshold.
32    pub snd_ssthresh: u32,
33    /// The congestion window.
34    pub snd_cwnd: u32,
35    /// The number of retransmissions.
36    pub retransmits: u64,
37    /// Timestamp of the last ACK received.
38    pub last_ack_recv: Option<I>,
39    /// Segments sent.
40    pub segs_out: u64,
41    /// Segments received.
42    pub segs_in: u64,
43    /// Timestamp of the last data sent.
44    pub last_data_sent: Option<I>,
45}
46
47/// The state of congestion control.
48#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
49pub enum CongestionControlState {
50    /// Normal state, no congestion detected.
51    #[default]
52    Open,
53    /// Disorder detected (e.g. DUP-ACKs).
54    Disorder,
55    /// We received an Explicit Congestion Notification.
56    CongestionWindowReduced,
57    /// In recovery (Fast Recovery).
58    Recovery,
59    /// Loss detected (RTO).
60    Loss,
61}
62
63/// Helper struct to hold parameters extracted from [`Send`] that will end
64/// up in [`TcpSocketInfo`].
65#[derive(Debug, Clone, Derivative)]
66#[derivative(Default(bound = ""))]
67#[cfg_attr(test, derive(PartialEq, Eq))]
68pub(crate) struct SendInfo<I> {
69    rto: Option<Duration>,
70    rtt: Option<Duration>,
71    rtt_var: Option<Duration>,
72    snd_ssthresh: u32,
73    snd_cwnd: u32,
74    last_data_sent: Option<I>,
75    ca_state: CongestionControlState,
76}
77
78impl<I: Instant> SendInfo<I> {
79    pub(super) fn from_send<S: SendBuffer, const FIN: bool>(snd: &Send<I, S, FIN>) -> Self {
80        let cc = &snd.congestion_control;
81        let est = &snd.rtt_estimator;
82
83        Self {
84            snd_cwnd: cc.inspect_cwnd().cwnd(),
85            snd_ssthresh: cc.slow_start_threshold(),
86            ca_state: match cc.inspect_loss_recovery_mode() {
87                Some(LossRecoveryMode::FastRecovery) => CongestionControlState::Recovery,
88                Some(LossRecoveryMode::SackRecovery) => CongestionControlState::Recovery,
89                None => CongestionControlState::Open,
90            },
91            rtt: est.srtt(),
92            rtt_var: est.rtt_var(),
93            rto: Some(est.rto().into()),
94            last_data_sent: snd.last_data_sent,
95        }
96    }
97}
98
99/// Helper struct to hold parameters extracted from [`Recv`] that will end
100/// up in [`TcpSocketInfo`].
101#[derive(Debug, Clone, Derivative)]
102#[derivative(Default(bound = ""))]
103#[cfg_attr(test, derive(PartialEq, Eq))]
104struct RecvInfo<I> {
105    last_ack_recv: Option<I>,
106}
107
108impl<I: Instant> RecvInfo<I> {
109    fn from_recv<R: ReceiveBuffer>(rcv: &Recv<I, R>) -> Self {
110        Self { last_ack_recv: rcv.last_segment_at }
111    }
112
113    fn from_recv_params(rcv: &RecvParams<I>) -> Self {
114        let RecvParams { last_ack_recv, ack: _, wnd_scale: _, wnd: _, ts_opt: _ } = rcv;
115        Self { last_ack_recv: *last_ack_recv }
116    }
117}
118
119/// Helper struct to hold parameters extracted from socket counters that
120/// will end up in [`TcpSocketInfo`].
121struct CounterParams {
122    retransmits: u64,
123    segs_out: u64,
124    segs_in: u64,
125}
126
127impl CounterParams {
128    fn from_counters(counters: &TcpCountersWithSocketInner) -> Self {
129        Self {
130            retransmits: counters.retransmits.get(),
131            segs_out: counters.segments_sent.get(),
132            segs_in: counters.received_segments_dispatched.get(),
133        }
134    }
135}
136
137impl<I: Instant> TcpSocketInfo<I> {
138    /// Constructs a [`TcpSocketInfo`] from just the state machine state and
139    /// counters. This is useful when the full state machine doesn't exist.
140    pub(crate) fn from_partial_state(
141        state: netstack3_base::TcpSocketState,
142        counters: &TcpCountersWithSocketInner,
143    ) -> Self {
144        let SendInfo { rto, rtt, rtt_var, snd_ssthresh, snd_cwnd, last_data_sent, ca_state } =
145            SendInfo::default();
146        let RecvInfo { last_ack_recv } = RecvInfo::default();
147        let CounterParams { retransmits, segs_out, segs_in } =
148            CounterParams::from_counters(counters);
149
150        Self {
151            state,
152            retransmits,
153            segs_out,
154            ca_state,
155            rto,
156            rtt,
157            rtt_var,
158            snd_ssthresh,
159            snd_cwnd,
160            last_ack_recv,
161            segs_in,
162            last_data_sent,
163        }
164    }
165
166    /// Constructs a [`TcpSocketInfo`] from the full state machine state
167    /// and counters.
168    pub(crate) fn from_full_state<R, S, ActiveOpen>(
169        state: &State<I, R, S, ActiveOpen>,
170        counters: &TcpCountersWithSocketInner,
171    ) -> Self
172    where
173        R: ReceiveBuffer,
174        S: SendBuffer,
175    {
176        let (state, send_params, recv_params) = match state {
177            State::Closed(Closed { reason: _ }) => {
178                (netstack3_base::TcpSocketState::Close, SendInfo::default(), RecvInfo::default())
179            }
180            State::Listen(Listen {
181                iss: _,
182                timestamp_offset: _,
183                buffer_sizes: _,
184                device_mss: _,
185                default_mss: _,
186                user_timeout: _,
187            }) => {
188                (netstack3_base::TcpSocketState::Listen, SendInfo::default(), RecvInfo::default())
189            }
190            State::SynSent(SynSent {
191                iss: _,
192                timestamp: _,
193                retrans_timer: _,
194                active_open: _,
195                buffer_sizes: _,
196                device_mss: _,
197                default_mss: _,
198                rcv_wnd_scale: _,
199                ts_opt: _,
200            }) => {
201                (netstack3_base::TcpSocketState::SynSent, SendInfo::default(), RecvInfo::default())
202            }
203            State::SynRcvd(SynRcvd {
204                iss: _,
205                irs: _,
206                timestamp: _,
207                retrans_timer: _,
208                simultaneous_open: _,
209                buffer_sizes: _,
210                smss: _,
211                rcv_wnd_scale: _,
212                snd_wnd_scale: _,
213                sack_permitted: _,
214                // NB: This is of type RecvParams (unrelated), not Recv.
215                rcv: _,
216            }) => {
217                (netstack3_base::TcpSocketState::SynRecv, SendInfo::default(), RecvInfo::default())
218            }
219            State::Established(Established { snd, rcv }) => (
220                netstack3_base::TcpSocketState::Established,
221                SendInfo::from_send(snd.get()),
222                RecvInfo::from_recv(rcv.get()),
223            ),
224            State::FinWait1(FinWait1 { snd, rcv }) => (
225                netstack3_base::TcpSocketState::FinWait1,
226                SendInfo::from_send(snd.get()),
227                RecvInfo::from_recv(rcv.get()),
228            ),
229            State::FinWait2(FinWait2 { last_seq: _, rcv, timeout_at: _, snd_info }) => (
230                netstack3_base::TcpSocketState::FinWait2,
231                snd_info.clone(),
232                RecvInfo::from_recv(rcv),
233            ),
234            State::CloseWait(CloseWait { snd, closed_rcv }) => (
235                netstack3_base::TcpSocketState::CloseWait,
236                SendInfo::from_send(snd.get()),
237                RecvInfo::from_recv_params(closed_rcv),
238            ),
239            State::Closing(Closing { snd, closed_rcv }) => (
240                netstack3_base::TcpSocketState::Closing,
241                SendInfo::from_send(snd),
242                RecvInfo::from_recv_params(closed_rcv),
243            ),
244            State::LastAck(LastAck { snd, closed_rcv }) => (
245                netstack3_base::TcpSocketState::LastAck,
246                SendInfo::from_send(snd),
247                RecvInfo::from_recv_params(closed_rcv),
248            ),
249            State::TimeWait(TimeWait { last_seq: _, expiry: _, closed_rcv, snd_info }) => (
250                netstack3_base::TcpSocketState::TimeWait,
251                snd_info.clone(),
252                RecvInfo::from_recv_params(closed_rcv),
253            ),
254        };
255
256        let SendInfo { snd_cwnd, snd_ssthresh, ca_state, rtt, rtt_var, rto, last_data_sent } =
257            send_params;
258        let RecvInfo { last_ack_recv } = recv_params;
259        let CounterParams { retransmits, segs_out, segs_in } =
260            CounterParams::from_counters(counters);
261
262        Self {
263            state,
264            ca_state,
265            rto,
266            rtt,
267            rtt_var,
268            snd_ssthresh,
269            snd_cwnd,
270            retransmits,
271            last_ack_recv,
272            segs_out,
273            segs_in,
274            last_data_sent,
275        }
276    }
277}
278
279#[cfg(test)]
280mod tests {
281    use super::*;
282
283    use core::time::Duration;
284
285    use netstack3_base::testutil::FakeInstant;
286    use netstack3_base::{
287        EffectiveMss, Mss, MssSizeLimiters, SeqNum, TcpSocketState, Timestamp, WindowScale,
288        WindowSize,
289    };
290
291    use crate::internal::buffer::Assembler;
292    use crate::internal::congestion::CongestionControl;
293    use crate::internal::rtt::{Estimator, RttSampler};
294    use crate::internal::state::{Established, Listen, Recv, RecvBufferState, Send, State};
295    use crate::internal::timestamp::TimestampOptionState;
296    use crate::testutil::RingBuffer;
297
298    impl TcpCountersWithSocketInner {
299        fn new_for_test(retransmits: u64, segs_out: u64, segs_in: u64) -> Self {
300            let counters = Self::default();
301            counters.retransmits.add(retransmits);
302            counters.segments_sent.add(segs_out);
303            counters.received_segments_dispatched.add(segs_in);
304            counters
305        }
306    }
307
308    #[test]
309    fn test_from_partial_state() {
310        let state = TcpSocketState::Listen;
311        let counters = TcpCountersWithSocketInner::new_for_test(5, 10, 20);
312
313        let info = TcpSocketInfo::<FakeInstant>::from_partial_state(state, &counters);
314
315        assert_eq!(
316            info,
317            TcpSocketInfo {
318                state: TcpSocketState::Listen,
319                retransmits: 5,
320                segs_out: 10,
321                segs_in: 20,
322
323                // These are just the defaults.
324                ca_state: CongestionControlState::Open,
325                rto: None,
326                rtt: None,
327                rtt_var: None,
328                snd_ssthresh: 0,
329                snd_cwnd: 0,
330                last_ack_recv: None,
331                last_data_sent: None,
332            }
333        );
334    }
335
336    #[test]
337    fn test_from_full_state_listen() {
338        let state = State::<FakeInstant, RingBuffer, RingBuffer, ()>::Listen(Listen {
339            iss: SeqNum::new(100),
340            timestamp_offset: Timestamp::new(0),
341            buffer_sizes: Default::default(),
342            device_mss: Mss::new(1460).unwrap(),
343            default_mss: Mss::new(536).unwrap(),
344            user_timeout: None,
345        });
346
347        let counters = TcpCountersWithSocketInner::new_for_test(5, 10, 20);
348
349        let info = TcpSocketInfo::from_full_state(&state, &counters);
350
351        assert_eq!(
352            info,
353            TcpSocketInfo {
354                state: TcpSocketState::Listen,
355                retransmits: 5,
356                segs_out: 10,
357                segs_in: 20,
358
359                // These are all defaults.
360                ca_state: CongestionControlState::Open,
361                rto: None,
362                rtt: None,
363                rtt_var: None,
364                snd_ssthresh: 0,
365                snd_cwnd: 0,
366                last_ack_recv: None,
367                last_data_sent: None,
368            }
369        );
370    }
371
372    #[test]
373    fn test_from_full_state_established() {
374        let now = FakeInstant::from(Duration::from_secs(10));
375        let mss = Mss::new(1460).unwrap();
376        let effective_mss =
377            EffectiveMss::from_mss(mss, MssSizeLimiters { timestamp_enabled: false });
378
379        let mut rtt_estimator = Estimator::default();
380        let rtt = Duration::from_millis(50);
381        rtt_estimator.sample(rtt);
382
383        let mut congestion_control = CongestionControl::cubic_with_mss(effective_mss);
384        congestion_control.inflate_cwnd(u32::from(mss));
385
386        let send = Send {
387            nxt: SeqNum::new(200),
388            max: SeqNum::new(200),
389            una: SeqNum::new(100),
390            wnd: WindowSize::new(1000).unwrap(),
391            wnd_scale: WindowScale::default(),
392            wnd_max: WindowSize::new(1000).unwrap(),
393            wl1: SeqNum::new(100),
394            wl2: SeqNum::new(100),
395            last_push: SeqNum::new(200),
396            rtt_sampler: RttSampler::default(),
397            rtt_estimator,
398            timer: None,
399            congestion_control,
400            last_data_sent: Some(now - Duration::from_secs(1)),
401            buffer: RingBuffer::new(1000),
402        };
403
404        let recv = Recv {
405            buffer: RecvBufferState::Open {
406                buffer: RingBuffer::new(1000),
407                assembler: Assembler::new(SeqNum::new(50)),
408            },
409            remaining_quickacks: Default::default(),
410            last_segment_at: Some(now - Duration::from_secs(2)),
411            timer: None,
412            mss: effective_mss,
413            wnd_scale: WindowScale::default(),
414            last_window_update: (SeqNum::new(50), WindowSize::new(1000).unwrap()),
415            sack_permitted: false,
416            ts_opt: TimestampOptionState::Disabled,
417        };
418
419        let state = State::<FakeInstant, RingBuffer, RingBuffer, ()>::Established(Established {
420            snd: send.into(),
421            rcv: recv.into(),
422        });
423
424        let counters = TcpCountersWithSocketInner::new_for_test(5, 10, 20);
425        let info = TcpSocketInfo::from_full_state(&state, &counters);
426
427        assert_eq!(
428            info,
429            TcpSocketInfo {
430                state: TcpSocketState::Established,
431                ca_state: CongestionControlState::Open,
432                rto: Some(Duration::from_millis(200)),
433                rtt: Some(rtt),
434                rtt_var: Some(rtt / 2),
435                snd_ssthresh: u32::MAX,
436                snd_cwnd: 4380 + 1460,
437                retransmits: 5,
438                last_ack_recv: Some(now - Duration::from_secs(2)),
439                segs_out: 10,
440                segs_in: 20,
441                last_data_sent: Some(now - Duration::from_secs(1))
442            }
443        );
444    }
445
446    #[test]
447    fn test_from_full_state_recovery() {
448        let mss = Mss::new(1460).unwrap();
449        let effective_mss =
450            EffectiveMss::from_mss(mss, MssSizeLimiters { timestamp_enabled: false });
451
452        let mut congestion_control = CongestionControl::cubic_with_mss(effective_mss);
453        // Trigger recovery by receiving 3 duplicate ACKs
454        let ack = SeqNum::new(100);
455        let nxt = SeqNum::new(200);
456        let _ = congestion_control.on_dup_ack(ack, nxt);
457        let _ = congestion_control.on_dup_ack(ack, nxt);
458        let _ = congestion_control.on_dup_ack(ack, nxt);
459
460        let send = Send {
461            nxt,
462            max: nxt,
463            una: ack,
464            wnd: WindowSize::new(1000).unwrap(),
465            wnd_scale: WindowScale::default(),
466            wnd_max: WindowSize::new(1000).unwrap(),
467            wl1: ack,
468            wl2: ack,
469            last_push: nxt,
470            rtt_sampler: RttSampler::default(),
471            rtt_estimator: Estimator::default(),
472            timer: None,
473            congestion_control,
474            last_data_sent: None,
475            buffer: RingBuffer::new(1000),
476        };
477
478        let recv = Recv {
479            buffer: RecvBufferState::Open {
480                buffer: RingBuffer::new(1000),
481                assembler: Assembler::new(SeqNum::new(50)),
482            },
483            remaining_quickacks: Default::default(),
484            last_segment_at: None,
485            timer: None,
486            mss: effective_mss,
487            wnd_scale: WindowScale::default(),
488            last_window_update: (SeqNum::new(50), WindowSize::new(1000).unwrap()),
489            sack_permitted: false,
490            ts_opt: TimestampOptionState::Disabled,
491        };
492
493        let state = State::<FakeInstant, RingBuffer, RingBuffer, ()>::Established(Established {
494            snd: send.into(),
495            rcv: recv.into(),
496        });
497
498        let counters = TcpCountersWithSocketInner::new_for_test(5, 10, 20);
499        let info = TcpSocketInfo::from_full_state(&state, &counters);
500
501        assert_eq!(
502            info,
503            TcpSocketInfo {
504                state: TcpSocketState::Established,
505                retransmits: 5,
506                segs_out: 10,
507                segs_in: 20,
508                ca_state: CongestionControlState::Recovery,
509                rto: Some(Duration::from_secs(1)),
510                rtt: None,
511                rtt_var: None,
512                snd_ssthresh: 3066,
513                snd_cwnd: 7300,
514                last_ack_recv: None,
515                last_data_sent: None,
516            }
517        );
518    }
519}