netstack3_udp/
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 UDP events.
6
7use net_types::ip::{Ip, IpMarked};
8use netstack3_base::{
9    Counter, CounterContext, Inspectable, Inspector, InspectorExt as _, ResourceCounterContext,
10    WeakDeviceIdentifier,
11};
12use netstack3_datagram::IpExt;
13
14use crate::internal::base::{UdpBindingsTypes, UdpSocketId};
15
16/// A marker trait to simplify bounds for UDP counters.
17pub trait UdpCounterContext<I: IpExt, D: WeakDeviceIdentifier, BT: UdpBindingsTypes>:
18    ResourceCounterContext<UdpSocketId<I, D, BT>, UdpCountersWithSocket<I>>
19    + CounterContext<UdpCountersWithoutSocket<I>>
20{
21}
22
23impl<I, D, BT, CC> UdpCounterContext<I, D, BT> for CC
24where
25    I: IpExt,
26    D: WeakDeviceIdentifier,
27    BT: UdpBindingsTypes,
28    CC: ResourceCounterContext<UdpSocketId<I, D, BT>, UdpCountersWithSocket<I>>
29        + CounterContext<UdpCountersWithoutSocket<I>>,
30{
31}
32
33/// Counters for UDP 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 UdpCountersWithoutSocket<I> = IpMarked<I, UdpCountersWithoutSocketInner>;
39
40/// The IP agnostic version of [`UdpCountersWithoutSocket`].
41///
42/// The counter type `C` is generic to facilitate testing.
43#[derive(Default)]
44#[cfg_attr(test, derive(Debug, PartialEq))]
45pub struct UdpCountersWithoutSocketInner<C = Counter> {
46    /// Count of ICMP error messages received.
47    pub rx_icmp_error: C,
48    /// Count of UDP datagrams received from the IP layer, including error
49    /// cases.
50    pub rx: C,
51    /// Count of incoming UDP datagrams dropped because it contained a mapped IP
52    /// address in the header.
53    pub rx_mapped_addr: C,
54    /// Count of incoming UDP datagrams dropped because of an unknown
55    /// destination port.
56    pub rx_unknown_dest_port: C,
57    /// Count of incoming UDP datagrams dropped because their UDP header was in
58    /// a malformed state.
59    pub rx_malformed: C,
60}
61
62/// Counters for UDP events that can be attributed to an individual socket.
63///
64/// These counters are tracked stack wide and per socket.
65///
66/// Note on dual stack sockets: These counters are tracked for `SockI`.
67// TODO(https://fxbug.dev/396127493): For some of these events, it would be
68// better to track them for `WireI` (e.g. `received_segments_dispatched`,
69// `segments_sent`, etc.). Doing so may require splitting up the struct and/or
70// reworking the `ResourceCounterContext` trait.
71pub type UdpCountersWithSocket<I> = IpMarked<I, UdpCountersWithSocketInner>;
72
73/// The IP agnostic version of [`UdpCountersWithSocket`].
74///
75/// The counter type `C` is generic to facilitate testing.
76#[derive(Default, Debug)]
77#[cfg_attr(test, derive(PartialEq))]
78pub struct UdpCountersWithSocketInner<C = Counter> {
79    /// Count of UDP datagrams that were delivered to a socket. Because some
80    /// datagrams may be delivered to multiple sockets (e.g. multicast traffic)
81    /// this counter may exceed the total number of individual UDP datagrams
82    /// received by the stack.
83    pub rx_delivered: C,
84    /// Count of outgoing UDP datagrams sent from the socket layer, including
85    /// error cases.
86    pub tx: C,
87    /// Count of outgoing UDP datagrams which failed to be sent out of the
88    /// transport layer.
89    pub tx_error: C,
90}
91
92/// A composition of the UDP counters with and without a socket.
93pub struct CombinedUdpCounters<'a, I: Ip> {
94    /// The UDP counters that can be associated with a socket.
95    pub with_socket: &'a UdpCountersWithSocket<I>,
96    /// The UDP counters that cannot be associated with a socket.
97    ///
98    /// This field is optional so that the same [`Inspectable`] implementation
99    /// can be used for both the stack-wide counters and the per-socket
100    /// counters.
101    pub without_socket: Option<&'a UdpCountersWithoutSocket<I>>,
102}
103
104impl<I: Ip> Inspectable for CombinedUdpCounters<'_, I> {
105    fn record<II: Inspector>(&self, inspector: &mut II) {
106        let CombinedUdpCounters { with_socket, without_socket } = self;
107        let UdpCountersWithSocketInner { rx_delivered, tx, tx_error } = with_socket.as_ref();
108
109        // Note: Organize the "without socket" counters into helper struct to
110        // make the optionality more ergonomic to handle.
111        struct WithoutSocketRx<'a> {
112            rx: &'a Counter,
113            rx_mapped_addr: &'a Counter,
114            rx_unknown_dest_port: &'a Counter,
115            rx_malformed: &'a Counter,
116        }
117        struct WithoutSocketError<'a> {
118            rx_icmp_error: &'a Counter,
119        }
120        let (without_socket_rx, without_socket_error) = match without_socket.map(AsRef::as_ref) {
121            None => (None, None),
122            Some(UdpCountersWithoutSocketInner {
123                rx_icmp_error,
124                rx,
125                rx_mapped_addr,
126                rx_unknown_dest_port,
127                rx_malformed,
128            }) => (
129                Some(WithoutSocketRx { rx, rx_mapped_addr, rx_unknown_dest_port, rx_malformed }),
130                Some(WithoutSocketError { rx_icmp_error }),
131            ),
132        };
133        inspector.record_child("Rx", |inspector| {
134            inspector.record_counter("Delivered", rx_delivered);
135            if let Some(WithoutSocketRx {
136                rx,
137                rx_mapped_addr,
138                rx_unknown_dest_port,
139                rx_malformed,
140            }) = without_socket_rx
141            {
142                inspector.record_counter("Received", rx);
143                inspector.record_child("Errors", |inspector| {
144                    inspector.record_counter("MappedAddr", rx_mapped_addr);
145                    inspector.record_counter("UnknownDstPort", rx_unknown_dest_port);
146                    inspector.record_counter("Malformed", rx_malformed);
147                });
148            }
149        });
150        inspector.record_child("Tx", |inspector| {
151            inspector.record_counter("Sent", tx);
152            inspector.record_counter("Errors", tx_error);
153        });
154        if let Some(WithoutSocketError { rx_icmp_error }) = without_socket_error {
155            inspector.record_counter("IcmpErrors", rx_icmp_error);
156        }
157    }
158}
159
160#[cfg(test)]
161pub(crate) mod testutil {
162    use super::*;
163
164    pub(crate) type CounterExpectationsWithSocket = UdpCountersWithSocketInner<u64>;
165
166    impl From<&UdpCountersWithSocketInner> for CounterExpectationsWithSocket {
167        fn from(counters: &UdpCountersWithSocketInner) -> CounterExpectationsWithSocket {
168            let UdpCountersWithSocketInner { rx_delivered, tx, tx_error } = counters;
169            CounterExpectationsWithSocket {
170                rx_delivered: rx_delivered.get(),
171                tx: tx.get(),
172                tx_error: tx_error.get(),
173            }
174        }
175    }
176
177    pub(crate) type CounterExpectationsWithoutSocket = UdpCountersWithoutSocketInner<u64>;
178
179    impl From<&UdpCountersWithoutSocketInner> for CounterExpectationsWithoutSocket {
180        fn from(counters: &UdpCountersWithoutSocketInner) -> CounterExpectationsWithoutSocket {
181            let UdpCountersWithoutSocketInner {
182                rx_icmp_error,
183                rx,
184                rx_mapped_addr,
185                rx_unknown_dest_port,
186                rx_malformed,
187            } = counters;
188            CounterExpectationsWithoutSocket {
189                rx_icmp_error: rx_icmp_error.get(),
190                rx: rx.get(),
191                rx_mapped_addr: rx_mapped_addr.get(),
192                rx_unknown_dest_port: rx_unknown_dest_port.get(),
193                rx_malformed: rx_malformed.get(),
194            }
195        }
196    }
197}