Skip to main content

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    /// Number of packets that could not be parsed and had to be dropped
113    /// without sending an ICMP response.
114    pub unparsable_packet: C,
115}
116
117impl<I: IpCountersIpExt> Inspectable for IpCounters<I> {
118    fn record<II: Inspector>(&self, inspector: &mut II) {
119        let IpCounters {
120            deliver_unicast,
121            deliver_multicast,
122            dispatch_receive_ip_packet,
123            dispatch_receive_ip_packet_other_host,
124            receive_ip_packet,
125            send_ip_packet,
126            forwarding_disabled,
127            forward,
128            no_route_to_host,
129            mtu_exceeded,
130            ttl_expired,
131            receive_icmp_error,
132            fragment_reassembly_error,
133            need_more_fragments,
134            invalid_fragment,
135            fragment_cache_full,
136            parameter_problem,
137            unspecified_destination,
138            unspecified_source,
139            invalid_source,
140            dropped,
141            drop_for_tentative,
142            tx_illegal_loopback_address,
143            version_rx,
144            multicast_no_interest,
145            invalid_cached_conntrack_entry,
146            fragmentation,
147            socket_egress_filter_dropped,
148            unparsable_packet,
149        } = self;
150        inspector.record_child("PacketTx", |inspector| {
151            inspector.record_counter("Sent", send_ip_packet);
152            inspector.record_counter("IllegalLoopbackAddress", tx_illegal_loopback_address);
153            inspector.record_counter("SocketEgressFilterDropped", socket_egress_filter_dropped);
154        });
155        inspector.record_child("PacketRx", |inspector| {
156            inspector.record_counter("Received", receive_ip_packet);
157            inspector.record_counter("Dispatched", dispatch_receive_ip_packet);
158            inspector.record_counter("OtherHost", dispatch_receive_ip_packet_other_host);
159            inspector.record_counter("ParameterProblem", parameter_problem);
160            inspector.record_counter("UnspecifiedDst", unspecified_destination);
161            inspector.record_counter("UnspecifiedSrc", unspecified_source);
162            inspector.record_counter("InvalidSrc", invalid_source);
163            inspector.record_counter("Dropped", dropped);
164            inspector.record_counter("DroppedTentativeDst", drop_for_tentative);
165            inspector.record_counter("MulticastNoInterest", multicast_no_interest);
166            inspector.record_counter("DeliveredUnicast", deliver_unicast);
167            inspector.record_counter("DeliveredMulticast", deliver_multicast);
168            inspector.record_counter("InvalidCachedConntrackEntry", invalid_cached_conntrack_entry);
169            inspector.record_counter("UnparsablePacket", unparsable_packet);
170            inspector.delegate_inspectable(version_rx);
171        });
172        inspector.record_child("Forwarding", |inspector| {
173            inspector.record_counter("Forwarded", forward);
174            inspector.record_counter("ForwardingDisabled", forwarding_disabled);
175            inspector.record_counter("NoRouteToHost", no_route_to_host);
176            inspector.record_counter("MtuExceeded", mtu_exceeded);
177            inspector.record_counter("TtlExpired", ttl_expired);
178        });
179        inspector.record_counter("RxIcmpError", receive_icmp_error);
180        inspector.record_child("FragmentsRx", |inspector| {
181            inspector.record_counter("ReassemblyError", fragment_reassembly_error);
182            inspector.record_counter("NeedMoreFragments", need_more_fragments);
183            inspector.record_counter("InvalidFragment", invalid_fragment);
184            inspector.record_counter("CacheFull", fragment_cache_full);
185        });
186        inspector.record_child("FragmentsTx", |inspector| {
187            let FragmentationCounters {
188                fragmentation_required,
189                fragments,
190                error_not_allowed,
191                error_mtu_too_small,
192                error_body_too_long,
193                error_inner_size_limit_exceeded,
194                error_fragmented_serializer,
195            } = fragmentation;
196            inspector.record_counter("FragmentationRequired", fragmentation_required);
197            inspector.record_counter("Fragments", fragments);
198            inspector.record_counter("ErrorNotAllowed", error_not_allowed);
199            inspector.record_counter("ErrorMtuTooSmall", error_mtu_too_small);
200            inspector.record_counter("ErrorBodyTooLong", error_body_too_long);
201            inspector
202                .record_counter("ErrorInnerSizeLimitExceeded", error_inner_size_limit_exceeded);
203            inspector.record_counter("ErrorFragmentedSerializer", error_fragmented_serializer);
204        });
205    }
206}
207
208/// IPv4-specific Rx counters.
209#[derive(Default, Debug, netstack3_macros::CounterCollection)]
210#[cfg_attr(any(test, feature = "testutils"), derive(PartialEq,))]
211pub struct Ipv4RxCounters<C = Counter> {
212    /// Count of incoming broadcast IPv4 packets delivered.
213    pub deliver_broadcast: C,
214}
215
216impl<C: CounterRepr> Inspectable for Ipv4RxCounters<C> {
217    fn record<I: Inspector>(&self, inspector: &mut I) {
218        let Self { deliver_broadcast } = self;
219        inspector.record_counter("DeliveredBroadcast", deliver_broadcast);
220    }
221}
222
223/// IPv6-specific Rx counters.
224#[derive(Default, Debug, netstack3_macros::CounterCollection)]
225#[cfg_attr(any(test, feature = "testutils"), derive(PartialEq,))]
226pub struct Ipv6RxCounters<C = Counter> {
227    /// Count of incoming IPv6 packets discarded while processing extension
228    /// headers.
229    pub extension_header_discard: C,
230    /// Count of incoming neighbor solicitations discarded as looped-back
231    /// DAD probes.
232    pub drop_looped_back_dad_probe: C,
233}
234
235impl<C: CounterRepr> Inspectable for Ipv6RxCounters<C> {
236    fn record<I: Inspector>(&self, inspector: &mut I) {
237        let Self { extension_header_discard, drop_looped_back_dad_probe } = self;
238        inspector.record_counter("DroppedExtensionHeader", extension_header_discard);
239        inspector.record_counter("DroppedLoopedBackDadProbe", drop_looped_back_dad_probe);
240    }
241}
242
243#[cfg(any(test, feature = "testutils"))]
244pub mod testutil {
245    use super::*;
246
247    use netstack3_base::{CounterCollection, ResourceCounterContext};
248
249    /// Expected values of [`IpCounters<I>`].
250    pub type IpCounterExpectations<I> = IpCounters<I, u64>;
251
252    impl<I: IpCountersIpExt> IpCounterExpectations<I> {
253        /// Constructs the expected counter state when the given count of IP
254        /// packets have been received & dispatched.
255        pub fn expect_dispatched(count: u64) -> Self {
256            IpCounterExpectations {
257                receive_ip_packet: count,
258                dispatch_receive_ip_packet: count,
259                deliver_unicast: count,
260                ..Default::default()
261            }
262        }
263
264        /// Assert that the counters tracked by `core_ctx` match expectations.
265        #[track_caller]
266        pub fn assert_counters<D, CC: ResourceCounterContext<D, IpCounters<I>>>(
267            self,
268            core_ctx: &CC,
269            device: &D,
270        ) {
271            assert_eq!(core_ctx.counters().cast::<u64>(), self, "stack-wide counters");
272            assert_eq!(
273                core_ctx.per_resource_counters(device).cast::<u64>(),
274                self,
275                "per-device counters"
276            );
277        }
278    }
279}