netstack3_ip/
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 IP events.
6
7use core::fmt::Debug;
8use net_types::ip::{GenericOverIp, Ip, Ipv4, Ipv6};
9use netstack3_base::{
10    Counter, CounterCollectionSpec, CounterRepr, Inspectable, Inspector, InspectorExt as _,
11    TestOnlyPartialEq,
12};
13
14use crate::internal::fragmentation::FragmentationCounters;
15
16/// An IP extension trait supporting counters at the IP layer.
17pub trait IpCountersIpExt: Ip {
18    /// Receive counters.
19    type RxCounters: CounterCollectionSpec<CounterCollection<Counter>: Inspectable>
20        + Default
21        + Debug
22        + TestOnlyPartialEq;
23}
24
25impl IpCountersIpExt for Ipv4 {
26    type RxCounters = Ipv4RxCounters;
27}
28
29impl IpCountersIpExt for Ipv6 {
30    type RxCounters = Ipv6RxCounters;
31}
32
33/// Ip layer counters.
34#[derive(Default, Debug, GenericOverIp)]
35#[generic_over_ip(I, Ip)]
36#[cfg_attr(
37    any(test, feature = "testutils"),
38    derive(PartialEq, netstack3_macros::CounterCollection)
39)]
40pub struct IpCounters<I: IpCountersIpExt, C: CounterRepr = Counter> {
41    /// Count of incoming IP unicast packets delivered.
42    pub deliver_unicast: C,
43    /// Count of incoming IP multicast packets delivered.
44    pub deliver_multicast: C,
45    /// Count of incoming IP packets that are dispatched to the appropriate protocol.
46    pub dispatch_receive_ip_packet: C,
47    /// Count of incoming IP packets destined to another host.
48    pub dispatch_receive_ip_packet_other_host: C,
49    /// Count of incoming IP packets received by the stack.
50    pub receive_ip_packet: C,
51    /// Count of sent outgoing IP packets.
52    pub send_ip_packet: C,
53    /// Count of packets to be forwarded which are instead dropped because
54    /// forwarding is disabled.
55    pub forwarding_disabled: C,
56    /// Count of incoming packets forwarded to another host.
57    pub forward: C,
58    /// Count of incoming packets which cannot be forwarded because there is no
59    /// route to the destination host.
60    pub no_route_to_host: C,
61    /// Count of incoming packets which cannot be forwarded because the MTU has
62    /// been exceeded.
63    pub mtu_exceeded: C,
64    /// Count of incoming packets which cannot be forwarded because the TTL has
65    /// expired.
66    pub ttl_expired: C,
67    /// Count of ICMP error messages received.
68    pub receive_icmp_error: C,
69    /// Count of IP fragment reassembly errors.
70    pub fragment_reassembly_error: C,
71    /// Count of IP fragments that could not be reassembled because more
72    /// fragments were needed.
73    pub need_more_fragments: C,
74    /// Count of IP fragments that could not be reassembled because the fragment
75    /// was invalid.
76    pub invalid_fragment: C,
77    /// Count of IP fragments that could not be reassembled because the stack's
78    /// per-IP-protocol fragment cache was full.
79    pub fragment_cache_full: C,
80    /// Count of incoming IP packets not delivered because of a parameter problem.
81    pub parameter_problem: C,
82    /// Count of incoming IP packets with an unspecified destination address.
83    pub unspecified_destination: C,
84    /// Count of incoming IP packets with an unspecified source address.
85    pub unspecified_source: C,
86    /// Count of incoming IP packets with an invalid source address.
87    /// See the definitions of [`net_types::ip::Ipv4SourceAddr`] and
88    /// [`net_types::ip::Ipv6SourceAddr`] for the exact requirements.
89    pub invalid_source: C,
90    /// Count of incoming IP packets dropped.
91    pub dropped: C,
92    /// Count of incoming packets dropped because the destination address
93    /// is only tentatively assigned to the device.
94    pub drop_for_tentative: C,
95    /// Number of frames rejected because they'd cause illegal loopback
96    /// addresses on the wire.
97    pub tx_illegal_loopback_address: C,
98    /// Version specific rx counters.
99    pub version_rx: <I::RxCounters as CounterCollectionSpec>::CounterCollection<C>,
100    /// Count of incoming IP multicast packets that were dropped because
101    /// The stack doesn't have any sockets that belong to the multicast group,
102    /// and the stack isn't configured to forward the multicast packet.
103    pub multicast_no_interest: C,
104    /// Count of looped-back packets that held a cached conntrack entry that could
105    /// not be downcasted to the expected type. This would happen if, for example, a
106    /// packet was modified to a different IP version between EGRESS and INGRESS.
107    pub invalid_cached_conntrack_entry: C,
108    /// IP fragmentation counters.
109    pub fragmentation: FragmentationCounters<C>,
110    /// Number of packets filtered out by the socket egress filter.
111    pub socket_egress_filter_dropped: C,
112}
113
114impl<I: IpCountersIpExt> Inspectable for IpCounters<I> {
115    fn record<II: Inspector>(&self, inspector: &mut II) {
116        let IpCounters {
117            deliver_unicast,
118            deliver_multicast,
119            dispatch_receive_ip_packet,
120            dispatch_receive_ip_packet_other_host,
121            receive_ip_packet,
122            send_ip_packet,
123            forwarding_disabled,
124            forward,
125            no_route_to_host,
126            mtu_exceeded,
127            ttl_expired,
128            receive_icmp_error,
129            fragment_reassembly_error,
130            need_more_fragments,
131            invalid_fragment,
132            fragment_cache_full,
133            parameter_problem,
134            unspecified_destination,
135            unspecified_source,
136            invalid_source,
137            dropped,
138            drop_for_tentative,
139            tx_illegal_loopback_address,
140            version_rx,
141            multicast_no_interest,
142            invalid_cached_conntrack_entry,
143            fragmentation,
144            socket_egress_filter_dropped,
145        } = self;
146        inspector.record_child("PacketTx", |inspector| {
147            inspector.record_counter("Sent", send_ip_packet);
148            inspector.record_counter("IllegalLoopbackAddress", tx_illegal_loopback_address);
149            inspector.record_counter("SocketEgressFilterDropped", socket_egress_filter_dropped);
150        });
151        inspector.record_child("PacketRx", |inspector| {
152            inspector.record_counter("Received", receive_ip_packet);
153            inspector.record_counter("Dispatched", dispatch_receive_ip_packet);
154            inspector.record_counter("OtherHost", dispatch_receive_ip_packet_other_host);
155            inspector.record_counter("ParameterProblem", parameter_problem);
156            inspector.record_counter("UnspecifiedDst", unspecified_destination);
157            inspector.record_counter("UnspecifiedSrc", unspecified_source);
158            inspector.record_counter("InvalidSrc", invalid_source);
159            inspector.record_counter("Dropped", dropped);
160            inspector.record_counter("DroppedTentativeDst", drop_for_tentative);
161            inspector.record_counter("MulticastNoInterest", multicast_no_interest);
162            inspector.record_counter("DeliveredUnicast", deliver_unicast);
163            inspector.record_counter("DeliveredMulticast", deliver_multicast);
164            inspector.record_counter("InvalidCachedConntrackEntry", invalid_cached_conntrack_entry);
165            inspector.delegate_inspectable(version_rx);
166        });
167        inspector.record_child("Forwarding", |inspector| {
168            inspector.record_counter("Forwarded", forward);
169            inspector.record_counter("ForwardingDisabled", forwarding_disabled);
170            inspector.record_counter("NoRouteToHost", no_route_to_host);
171            inspector.record_counter("MtuExceeded", mtu_exceeded);
172            inspector.record_counter("TtlExpired", ttl_expired);
173        });
174        inspector.record_counter("RxIcmpError", receive_icmp_error);
175        inspector.record_child("FragmentsRx", |inspector| {
176            inspector.record_counter("ReassemblyError", fragment_reassembly_error);
177            inspector.record_counter("NeedMoreFragments", need_more_fragments);
178            inspector.record_counter("InvalidFragment", invalid_fragment);
179            inspector.record_counter("CacheFull", fragment_cache_full);
180        });
181        inspector.record_child("FragmentsTx", |inspector| {
182            let FragmentationCounters {
183                fragmentation_required,
184                fragments,
185                error_not_allowed,
186                error_mtu_too_small,
187                error_body_too_long,
188                error_inner_size_limit_exceeded,
189                error_fragmented_serializer,
190            } = fragmentation;
191            inspector.record_counter("FragmentationRequired", fragmentation_required);
192            inspector.record_counter("Fragments", fragments);
193            inspector.record_counter("ErrorNotAllowed", error_not_allowed);
194            inspector.record_counter("ErrorMtuTooSmall", error_mtu_too_small);
195            inspector.record_counter("ErrorBodyTooLong", error_body_too_long);
196            inspector
197                .record_counter("ErrorInnerSizeLimitExceeded", error_inner_size_limit_exceeded);
198            inspector.record_counter("ErrorFragmentedSerializer", error_fragmented_serializer);
199        });
200    }
201}
202
203/// IPv4-specific Rx counters.
204#[derive(Default, Debug, netstack3_macros::CounterCollection)]
205#[cfg_attr(any(test, feature = "testutils"), derive(PartialEq,))]
206pub struct Ipv4RxCounters<C = Counter> {
207    /// Count of incoming broadcast IPv4 packets delivered.
208    pub deliver_broadcast: C,
209}
210
211impl<C: CounterRepr> Inspectable for Ipv4RxCounters<C> {
212    fn record<I: Inspector>(&self, inspector: &mut I) {
213        let Self { deliver_broadcast } = self;
214        inspector.record_counter("DeliveredBroadcast", deliver_broadcast);
215    }
216}
217
218/// IPv6-specific Rx counters.
219#[derive(Default, Debug, netstack3_macros::CounterCollection)]
220#[cfg_attr(any(test, feature = "testutils"), derive(PartialEq,))]
221pub struct Ipv6RxCounters<C = Counter> {
222    /// Count of incoming IPv6 packets discarded while processing extension
223    /// headers.
224    pub extension_header_discard: C,
225    /// Count of incoming neighbor solicitations discarded as looped-back
226    /// DAD probes.
227    pub drop_looped_back_dad_probe: C,
228}
229
230impl<C: CounterRepr> Inspectable for Ipv6RxCounters<C> {
231    fn record<I: Inspector>(&self, inspector: &mut I) {
232        let Self { extension_header_discard, drop_looped_back_dad_probe } = self;
233        inspector.record_counter("DroppedExtensionHeader", extension_header_discard);
234        inspector.record_counter("DroppedLoopedBackDadProbe", drop_looped_back_dad_probe);
235    }
236}
237
238#[cfg(any(test, feature = "testutils"))]
239pub mod testutil {
240    use super::*;
241
242    use netstack3_base::{CounterCollection, ResourceCounterContext};
243
244    /// Expected values of [`IpCounters<I>`].
245    pub type IpCounterExpectations<I> = IpCounters<I, u64>;
246
247    impl<I: IpCountersIpExt> IpCounterExpectations<I> {
248        /// Constructs the expected counter state when the given count of IP
249        /// packets have been received & dispatched.
250        pub fn expect_dispatched(count: u64) -> Self {
251            IpCounterExpectations {
252                receive_ip_packet: count,
253                dispatch_receive_ip_packet: count,
254                deliver_unicast: count,
255                ..Default::default()
256            }
257        }
258
259        /// Assert that the counters tracked by `core_ctx` match expectations.
260        #[track_caller]
261        pub fn assert_counters<D, CC: ResourceCounterContext<D, IpCounters<I>>>(
262            self,
263            core_ctx: &CC,
264            device: &D,
265        ) {
266            assert_eq!(core_ctx.counters().cast::<u64>(), self, "stack-wide counters");
267            assert_eq!(
268                core_ctx.per_resource_counters(device).cast::<u64>(),
269                self,
270                "per-device counters"
271            );
272        }
273    }
274}