Skip to main content

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