netstack3_tcp/
counters.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
5//! Facilities for tracking the counts of various TCP events.
6
7use net_types::ip::{Ip, IpMarked};
8use netstack3_base::socket::EitherStack;
9use netstack3_base::{
10    Counter, CounterContext, Inspectable, Inspector, InspectorExt as _, ResourceCounterContext,
11    WeakDeviceIdentifier,
12};
13
14use crate::internal::socket::{DualStackIpExt, TcpBindingsTypes, TcpSocketId};
15
16/// A marker trait to simplify bounds for TCP counters.
17pub trait TcpCounterContext<I: DualStackIpExt, D: WeakDeviceIdentifier, BT: TcpBindingsTypes>:
18    ResourceCounterContext<TcpSocketId<I, D, BT>, TcpCountersWithSocket<I>>
19    + CounterContext<TcpCountersWithoutSocket<I>>
20{
21}
22
23impl<I, D, BT, CC> TcpCounterContext<I, D, BT> for CC
24where
25    I: DualStackIpExt,
26    D: WeakDeviceIdentifier,
27    BT: TcpBindingsTypes,
28    CC: ResourceCounterContext<TcpSocketId<I, D, BT>, TcpCountersWithSocket<I>>
29        + CounterContext<TcpCountersWithoutSocket<I>>,
30{
31}
32
33/// Counters for TCP events that cannot be attributed to an individual socket.
34///
35/// These counters are tracked stack wide.
36///
37/// Note on dual stack sockets: These counters are tracked for `WireI`.
38pub type TcpCountersWithoutSocket<I> = IpMarked<I, TcpCountersWithoutSocketInner>;
39
40/// The IP agnostic version of [`TcpCountersWithoutSocket`].
41///
42/// The counter type `C` is generic to facilitate testing.
43#[derive(Default, Debug)]
44#[cfg_attr(test, derive(PartialEq))]
45pub struct TcpCountersWithoutSocketInner<C = Counter> {
46    /// Count of received IP packets that were dropped because they had
47    /// unexpected IP addresses (either src or dst).
48    pub invalid_ip_addrs_received: C,
49    /// Count of received TCP segments that were dropped because they could not
50    /// be parsed.
51    pub invalid_segments_received: C,
52    /// Count of received TCP segments that were valid.
53    pub valid_segments_received: C,
54    /// Count of received TCP segments that were not associated with any
55    /// existing sockets.
56    pub received_segments_no_dispatch: C,
57    /// Count of received segments whose checksums were invalid.
58    pub checksum_errors: C,
59}
60
61/// Counters for TCP events that can be attributed to an individual socket.
62///
63/// These counters are tracked stack wide and per socket.
64///
65/// Note on dual stack sockets: These counters are tracked for `SockI`.
66// TODO(https://fxbug.dev/396127493): For some of these events, it would be
67// better to track them for `WireI` (e.g. `received_segments_dispatched`,
68// `segments_sent`, etc.). Doing so may require splitting up the struct and/or
69// reworking the `ResourceCounterContext` trait.
70pub type TcpCountersWithSocket<I> = IpMarked<I, TcpCountersWithSocketInner<Counter>>;
71
72/// The IP agnostic version of [`TcpCountersWithSocket`].
73///
74/// The counter type `C` is generic to facilitate testing.
75// TODO(https://fxbug.dev/42052878): Add counters for SYN cookies.
76#[derive(Default, Debug)]
77#[cfg_attr(test, derive(PartialEq))]
78pub struct TcpCountersWithSocketInner<C = Counter> {
79    /// Count of received TCP segments that were successfully dispatched to a
80    /// socket.
81    pub received_segments_dispatched: C,
82    /// Count of received TCP segments that were dropped because the listener
83    /// queue was full.
84    pub listener_queue_overflow: C,
85    /// Count of TCP segments that failed to send.
86    pub segment_send_errors: C,
87    /// Count of TCP segments that were sent.
88    pub segments_sent: C,
89    /// Count of passive open attempts that failed because the stack doesn't
90    /// have route to the peer.
91    pub passive_open_no_route_errors: C,
92    /// Count of passive connections that have been opened.
93    pub passive_connection_openings: C,
94    /// Count of active open attempts that have failed because the stack doesn't
95    /// have a route to the peer.
96    pub active_open_no_route_errors: C,
97    /// Count of active connections that have been opened.
98    pub active_connection_openings: C,
99    /// Count of all failed connection attempts, including both passive and
100    /// active opens.
101    pub failed_connection_attempts: C,
102    /// Count of port reservation attempts that failed.
103    pub failed_port_reservations: C,
104    /// Count of received segments with the RST flag set.
105    pub resets_received: C,
106    /// Count of sent segments with the RST flag set.
107    pub resets_sent: C,
108    /// Count of received segments with the SYN flag set.
109    pub syns_received: C,
110    /// Count of sent segments with the SYN flag set.
111    pub syns_sent: C,
112    /// Count of received segments with the FIN flag set.
113    pub fins_received: C,
114    /// Count of sent segments with the FIN flag set.
115    pub fins_sent: C,
116    /// Count of retransmission timeouts.
117    pub timeouts: C,
118    /// Count of retransmissions of segments.
119    pub retransmits: C,
120    /// Count of retransmissions of segments while in slow start.
121    pub slow_start_retransmits: C,
122    /// Count of retransmissions of segments while in fast recovery.
123    pub fast_retransmits: C,
124    /// Count of retransmissions of segments while in SACK recovery.
125    pub sack_retransmits: C,
126    /// Count of times fast recovery was initiated to recover from packet loss.
127    pub fast_recovery: C,
128    /// Count of times SACK recovery was initiated to recover from packet loss.
129    pub sack_recovery: C,
130    /// Count of times an established TCP connection transitioned to CLOSED.
131    pub established_closed: C,
132    /// Count of times an established TCP connection transitioned to CLOSED due
133    /// to a RST segment.
134    pub established_resets: C,
135    /// Count of times an established TCP connection transitioned to CLOSED due
136    /// to a timeout (e.g. a keep-alive or retransmit timeout).
137    pub established_timedout: C,
138    /// Count of times loss recovery completes successfully.
139    pub loss_recovered: C,
140    /// Count of times duplicate ACKs were received.
141    pub dup_acks: C,
142}
143
144/// A composition of the TCP Counters with and without a socket.
145pub struct CombinedTcpCounters<'a, I: Ip> {
146    /// The TCP Counters that can be associated with a socket.
147    pub with_socket: &'a TcpCountersWithSocket<I>,
148    /// The TCP Counters that cannot be associated with a socket.
149    ///
150    /// This field is optional so that the same [`Inspectable`] implementation
151    /// can be used for both the stack-wide counters, and the per-socket
152    /// counters.
153    pub without_socket: Option<&'a TcpCountersWithoutSocket<I>>,
154}
155
156impl<I: Ip> Inspectable for CombinedTcpCounters<'_, I> {
157    fn record<II: Inspector>(&self, inspector: &mut II) {
158        let CombinedTcpCounters { with_socket, without_socket } = self;
159        let TcpCountersWithSocketInner {
160            received_segments_dispatched,
161            listener_queue_overflow,
162            segment_send_errors,
163            segments_sent,
164            passive_open_no_route_errors,
165            passive_connection_openings,
166            active_open_no_route_errors,
167            active_connection_openings,
168            failed_connection_attempts,
169            failed_port_reservations,
170            resets_received,
171            resets_sent,
172            syns_received,
173            syns_sent,
174            fins_received,
175            fins_sent,
176            timeouts,
177            retransmits,
178            slow_start_retransmits,
179            fast_retransmits,
180            sack_retransmits,
181            fast_recovery,
182            sack_recovery,
183            established_closed,
184            established_resets,
185            established_timedout,
186            loss_recovered,
187            dup_acks,
188        } = with_socket.as_ref();
189
190        // Note: Organize the "without socket" counters into helper structs to
191        // make the optionality more ergonomic to handle.
192        struct WithoutSocketRx<'a> {
193            valid_segments_received: &'a Counter,
194        }
195        struct WithoutSocketRxError<'a> {
196            invalid_ip_addrs_received: &'a Counter,
197            invalid_segments_received: &'a Counter,
198            received_segments_no_dispatch: &'a Counter,
199            checksum_errors: &'a Counter,
200        }
201        let (without_socket_rx, without_socket_rx_error) = match without_socket.map(AsRef::as_ref) {
202            None => (None, None),
203            Some(TcpCountersWithoutSocketInner {
204                invalid_ip_addrs_received,
205                invalid_segments_received,
206                valid_segments_received,
207                received_segments_no_dispatch,
208                checksum_errors,
209            }) => (
210                Some(WithoutSocketRx { valid_segments_received }),
211                Some(WithoutSocketRxError {
212                    invalid_ip_addrs_received,
213                    invalid_segments_received,
214                    received_segments_no_dispatch,
215                    checksum_errors,
216                }),
217            ),
218        };
219
220        inspector.record_child("Rx", |inspector| {
221            if let Some(WithoutSocketRx { valid_segments_received }) = without_socket_rx {
222                inspector.record_counter("ValidSegmentsReceived", valid_segments_received);
223            }
224            inspector.record_counter("ReceivedSegmentsDispatched", received_segments_dispatched);
225            inspector.record_counter("ResetsReceived", resets_received);
226            inspector.record_counter("SynsReceived", syns_received);
227            inspector.record_counter("FinsReceived", fins_received);
228            inspector.record_counter("DupAcks", dup_acks);
229            inspector.record_child("Errors", |inspector| {
230                inspector.record_counter("ListenerQueueOverflow", listener_queue_overflow);
231                inspector.record_counter("PassiveOpenNoRouteErrors", passive_open_no_route_errors);
232                if let Some(WithoutSocketRxError {
233                    invalid_ip_addrs_received,
234                    invalid_segments_received,
235                    received_segments_no_dispatch,
236                    checksum_errors,
237                }) = without_socket_rx_error
238                {
239                    inspector.record_counter("InvalidIpAddrsReceived", invalid_ip_addrs_received);
240                    inspector.record_counter("InvalidSegmentsReceived", invalid_segments_received);
241                    inspector.record_counter(
242                        "ReceivedSegmentsNoDispatch",
243                        received_segments_no_dispatch,
244                    );
245                    inspector.record_counter("ChecksumErrors", checksum_errors);
246                }
247            })
248        });
249        inspector.record_child("Tx", |inspector| {
250            inspector.record_counter("SegmentsSent", segments_sent);
251            inspector.record_counter("ResetsSent", resets_sent);
252            inspector.record_counter("SynsSent", syns_sent);
253            inspector.record_counter("FinsSent", fins_sent);
254            inspector.record_counter("Timeouts", timeouts);
255            inspector.record_counter("Retransmits", retransmits);
256            inspector.record_counter("SlowStartRetransmits", slow_start_retransmits);
257            inspector.record_counter("FastRetransmits", fast_retransmits);
258            inspector.record_counter("SackRetransmits", sack_retransmits);
259            inspector.record_child("Errors", |inspector| {
260                inspector.record_counter("SegmentSendErrors", segment_send_errors);
261                inspector.record_counter("ActiveOpenNoRouteErrors", active_open_no_route_errors);
262            });
263        });
264        inspector.record_counter("PassiveConnectionOpenings", passive_connection_openings);
265        inspector.record_counter("ActiveConnectionOpenings", active_connection_openings);
266        inspector.record_counter("FastRecovery", fast_recovery);
267        inspector.record_counter("SackRecovery", sack_recovery);
268        inspector.record_counter("LossRecovered", loss_recovered);
269        inspector.record_counter("EstablishedClosed", established_closed);
270        inspector.record_counter("EstablishedResets", established_resets);
271        inspector.record_counter("EstablishedTimedout", established_timedout);
272        inspector.record_child("Errors", |inspector| {
273            inspector.record_counter("FailedConnectionOpenings", failed_connection_attempts);
274            inspector.record_counter("FailedPortReservations", failed_port_reservations);
275        })
276    }
277}
278
279/// Holds references to the stack-wide and per-socket counters.
280///
281/// This is used to easily increment both counters in contexts that don't have
282/// access to `ResourceCounterContext`. This is currently used by the TCP state
283/// machine, which does not operate on a core context so that it can remain
284/// IP agnostic (and thus avoid duplicate code generation).
285pub(crate) struct TcpCountersRefs<'a> {
286    pub(crate) stack_wide: &'a TcpCountersWithSocketInner,
287    pub(crate) per_socket: &'a TcpCountersWithSocketInner,
288}
289
290impl<'a> TcpCountersRefs<'a> {
291    pub(crate) fn from_ctx<I: Ip, R, CC: ResourceCounterContext<R, TcpCountersWithSocket<I>>>(
292        ctx: &'a CC,
293        resource: &'a R,
294    ) -> Self {
295        TcpCountersRefs {
296            stack_wide: ctx.counters(),
297            per_socket: ctx.per_resource_counters(resource),
298        }
299    }
300
301    pub(crate) fn increment<F: Fn(&TcpCountersWithSocketInner<Counter>) -> &Counter>(&self, cb: F) {
302        let Self { stack_wide, per_socket } = self;
303        cb(stack_wide).increment();
304        cb(per_socket).increment();
305    }
306}
307
308/// Increments the stack-wide counters, and optionally the per-resouce counters.
309///
310/// Used to increment counters that can, but are not required to, have an
311/// associated socket. For example, sent segment counts.
312pub(crate) fn increment_counter_with_optional_socket_id<I, CC, BT, D, F>(
313    core_ctx: &CC,
314    socket_id: Option<&TcpSocketId<I, D, BT>>,
315    cb: F,
316) where
317    I: DualStackIpExt,
318    CC: TcpCounterContext<I, D, BT>,
319    D: WeakDeviceIdentifier,
320    BT: TcpBindingsTypes,
321    F: Fn(&TcpCountersWithSocket<I>) -> &Counter,
322{
323    match socket_id {
324        Some(id) => core_ctx.increment_both(id, cb),
325        None => cb(core_ctx.counters()).increment(),
326    }
327}
328
329/// Increments the stack-wide counters, and optionally the per-resouce counters.
330///
331/// Used to increment counters that can, but are not required to, have an
332/// associated socket. For example, received segment counts.
333pub(crate) fn increment_counter_with_optional_demux_id<I, CC, BT, D, F>(
334    core_ctx: &CC,
335    demux_id: Option<&I::DemuxSocketId<D, BT>>,
336    cb: F,
337) where
338    I: DualStackIpExt,
339    CC: TcpCounterContext<I, D, BT> + TcpCounterContext<I::OtherVersion, D, BT>,
340    D: WeakDeviceIdentifier,
341    BT: TcpBindingsTypes,
342    F: Fn(&TcpCountersWithSocketInner) -> &Counter,
343{
344    match demux_id {
345        Some(id) => increment_counter_for_demux_id::<I, _, _, _, _>(core_ctx, &id, cb),
346        None => {
347            cb(CounterContext::<TcpCountersWithSocket<I>>::counters(core_ctx).as_ref()).increment()
348        }
349    }
350}
351
352/// Increment a counter for TCP demux_ids, which may exist in either stack.
353pub(crate) fn increment_counter_for_demux_id<I, D, BT, CC, F>(
354    core_ctx: &CC,
355    demux_id: &I::DemuxSocketId<D, BT>,
356    cb: F,
357) where
358    I: DualStackIpExt,
359    D: WeakDeviceIdentifier,
360    BT: TcpBindingsTypes,
361    CC: TcpCounterContext<I, D, BT> + TcpCounterContext<I::OtherVersion, D, BT>,
362    F: Fn(&TcpCountersWithSocketInner<Counter>) -> &Counter,
363{
364    match I::as_dual_stack_ip_socket(demux_id) {
365        EitherStack::ThisStack(socket_id) => core_ctx
366            .increment_both(socket_id, |counters: &TcpCountersWithSocket<I>| cb(counters.as_ref())),
367        EitherStack::OtherStack(socket_id) => core_ctx
368            .increment_both(socket_id, |counters: &TcpCountersWithSocket<I::OtherVersion>| {
369                cb(counters.as_ref())
370            }),
371    }
372}
373
374#[cfg(test)]
375pub(crate) mod testutil {
376    use super::*;
377
378    pub(crate) type CounterExpectations = TcpCountersWithSocketInner<u64>;
379
380    impl From<&TcpCountersWithSocketInner> for CounterExpectations {
381        fn from(counters: &TcpCountersWithSocketInner) -> CounterExpectations {
382            let TcpCountersWithSocketInner {
383                received_segments_dispatched,
384                listener_queue_overflow,
385                segment_send_errors,
386                segments_sent,
387                passive_open_no_route_errors,
388                passive_connection_openings,
389                active_open_no_route_errors,
390                active_connection_openings,
391                failed_connection_attempts,
392                failed_port_reservations,
393                resets_received,
394                resets_sent,
395                syns_received,
396                syns_sent,
397                fins_received,
398                fins_sent,
399                timeouts,
400                retransmits,
401                slow_start_retransmits,
402                fast_retransmits,
403                sack_retransmits,
404                fast_recovery,
405                sack_recovery,
406                established_closed,
407                established_resets,
408                established_timedout,
409                loss_recovered,
410                dup_acks,
411            } = counters;
412            TcpCountersWithSocketInner {
413                received_segments_dispatched: received_segments_dispatched.get(),
414                listener_queue_overflow: listener_queue_overflow.get(),
415                segment_send_errors: segment_send_errors.get(),
416                segments_sent: segments_sent.get(),
417                passive_open_no_route_errors: passive_open_no_route_errors.get(),
418                passive_connection_openings: passive_connection_openings.get(),
419                active_open_no_route_errors: active_open_no_route_errors.get(),
420                active_connection_openings: active_connection_openings.get(),
421                failed_connection_attempts: failed_connection_attempts.get(),
422                failed_port_reservations: failed_port_reservations.get(),
423                resets_received: resets_received.get(),
424                resets_sent: resets_sent.get(),
425                syns_received: syns_received.get(),
426                syns_sent: syns_sent.get(),
427                fins_received: fins_received.get(),
428                fins_sent: fins_sent.get(),
429                timeouts: timeouts.get(),
430                retransmits: retransmits.get(),
431                slow_start_retransmits: slow_start_retransmits.get(),
432                fast_retransmits: fast_retransmits.get(),
433                sack_retransmits: sack_retransmits.get(),
434                fast_recovery: fast_recovery.get(),
435                sack_recovery: sack_recovery.get(),
436                established_closed: established_closed.get(),
437                established_resets: established_resets.get(),
438                established_timedout: established_timedout.get(),
439                loss_recovered: loss_recovered.get(),
440                dup_acks: dup_acks.get(),
441            }
442        }
443    }
444
445    pub(crate) type CounterExpectationsWithoutSocket = TcpCountersWithoutSocketInner<u64>;
446
447    impl From<&TcpCountersWithoutSocketInner> for CounterExpectationsWithoutSocket {
448        fn from(counters: &TcpCountersWithoutSocketInner) -> CounterExpectationsWithoutSocket {
449            let TcpCountersWithoutSocketInner {
450                invalid_ip_addrs_received,
451                invalid_segments_received,
452                valid_segments_received,
453                received_segments_no_dispatch,
454                checksum_errors,
455            } = counters;
456            TcpCountersWithoutSocketInner {
457                invalid_ip_addrs_received: invalid_ip_addrs_received.get(),
458                invalid_segments_received: invalid_segments_received.get(),
459                valid_segments_received: valid_segments_received.get(),
460                received_segments_no_dispatch: received_segments_no_dispatch.get(),
461                checksum_errors: checksum_errors.get(),
462            }
463        }
464    }
465}