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