Skip to main content

netstack3_icmp_echo/
socket.rs

1// Copyright 2024 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//! ICMP Echo Sockets.
6
7use alloc::vec::Vec;
8use core::borrow::Borrow;
9use core::convert::Infallible as Never;
10use core::fmt::Debug;
11use core::marker::PhantomData;
12use core::num::{NonZeroU8, NonZeroU16};
13use core::ops::ControlFlow;
14use lock_order::lock::{DelegatedOrderedLockAccess, OrderedLockAccess, OrderedLockRef};
15
16use derivative::Derivative;
17use either::Either;
18use log::{debug, trace};
19use net_types::ip::{GenericOverIp, Ip, IpVersionMarker};
20use net_types::{SpecifiedAddr, Witness as _, ZonedAddr};
21use netstack3_base::socket::{
22    self, AddrIsMappedError, AddrVec, AddrVecIter, ConnAddr, ConnInfoAddr, ConnIpAddr,
23    IncompatibleError, InsertError, ListenerAddrInfo, MaybeDualStack, ShutdownType, SocketCookie,
24    SocketIpAddr, SocketMapAddrSpec, SocketMapAddrStateSpec, SocketMapConflictPolicy,
25    SocketMapStateSpec, SocketWritableListener,
26};
27use netstack3_base::socketmap::{IterShadows as _, SocketMap};
28use netstack3_base::sync::{RwLock, StrongRc};
29use netstack3_base::{
30    AnyDevice, ContextPair, CoreTxMetadataContext, CounterContext, DeviceIdContext, IcmpIpExt,
31    Inspector, InspectorDeviceExt, LocalAddressError, Mark, MarkDomain, Marks, PortAllocImpl,
32    ReferenceNotifiers, RemoveResourceResultWithContext, RngContext, SettingsContext, SocketError,
33    StrongDeviceIdentifier, UninstantiableWrapper, WeakDeviceIdentifier,
34};
35use netstack3_datagram::{
36    self as datagram, DatagramApi, DatagramBindingsTypes, DatagramFlowId, DatagramSocketMapSpec,
37    DatagramSocketSet, DatagramSocketSpec, DatagramSpecBoundStateContext, DatagramSpecStateContext,
38    DatagramStateContext, ExpectedUnboundError, NonDualStackConverter,
39    NonDualStackDatagramSpecBoundStateContext,
40};
41use netstack3_ip::icmp::{EchoTransportContextMarker, IcmpRxCounters};
42use netstack3_ip::socket::SocketHopLimits;
43use netstack3_ip::{
44    IpHeaderInfo, IpLayerIpExt, IpTransportContext, LocalDeliveryPacketInfo,
45    MulticastMembershipHandler, ReceiveIpPacketMeta, SocketMetadata, TransportIpContext,
46};
47use packet::{BufferMut, PacketBuilder, ParsablePacket as _, ParseBuffer};
48use packet_formats::icmp::{IcmpEchoReply, IcmpEchoRequest, IcmpPacketBuilder, IcmpPacketRaw};
49use packet_formats::ip::{IpProtoExt, Ipv4Proto, Ipv6Proto};
50
51use crate::internal::settings::IcmpEchoSettings;
52
53/// A marker trait for all IP extensions required by ICMP sockets.
54pub trait IpExt: datagram::IpExt + IcmpIpExt + IpLayerIpExt {}
55impl<O: datagram::IpExt + IcmpIpExt + IpLayerIpExt> IpExt for O {}
56
57/// Holds the stack's ICMP echo sockets.
58#[derive(Derivative, GenericOverIp)]
59#[derivative(Default(bound = ""))]
60#[generic_over_ip(I, Ip)]
61pub struct IcmpSockets<I: IpExt, D: WeakDeviceIdentifier, BT: IcmpEchoBindingsTypes> {
62    bound_and_id_allocator: RwLock<BoundSockets<I, D, BT>>,
63    // Destroy all_sockets last so the strong references in the demux are
64    // dropped before the primary references in the set.
65    all_sockets: RwLock<IcmpSocketSet<I, D, BT>>,
66}
67
68impl<I: IpExt, D: WeakDeviceIdentifier, BT: IcmpEchoBindingsTypes>
69    OrderedLockAccess<BoundSockets<I, D, BT>> for IcmpSockets<I, D, BT>
70{
71    type Lock = RwLock<BoundSockets<I, D, BT>>;
72    fn ordered_lock_access(&self) -> OrderedLockRef<'_, Self::Lock> {
73        OrderedLockRef::new(&self.bound_and_id_allocator)
74    }
75}
76
77impl<I: IpExt, D: WeakDeviceIdentifier, BT: IcmpEchoBindingsTypes>
78    OrderedLockAccess<IcmpSocketSet<I, D, BT>> for IcmpSockets<I, D, BT>
79{
80    type Lock = RwLock<IcmpSocketSet<I, D, BT>>;
81    fn ordered_lock_access(&self) -> OrderedLockRef<'_, Self::Lock> {
82        OrderedLockRef::new(&self.all_sockets)
83    }
84}
85
86/// An ICMP socket.
87#[derive(GenericOverIp, Derivative)]
88#[derivative(Eq(bound = ""), PartialEq(bound = ""), Hash(bound = ""))]
89#[generic_over_ip(I, Ip)]
90pub struct IcmpSocketId<I: IpExt, D: WeakDeviceIdentifier, BT: IcmpEchoBindingsTypes>(
91    datagram::StrongRc<I, D, Icmp<BT>>,
92);
93
94impl<I: IpExt, D: WeakDeviceIdentifier, BT: IcmpEchoBindingsTypes> IcmpSocketId<I, D, BT> {
95    /// Returns a `SocketCookie` for this socket.
96    pub fn socket_cookie(&self) -> SocketCookie {
97        let Self(inner) = self;
98        SocketCookie::new(inner.resource_token())
99    }
100}
101
102impl<CC, I, BT> SocketMetadata<CC> for IcmpSocketId<I, CC::WeakDeviceId, BT>
103where
104    CC: IcmpEchoStateContext<I, BT>,
105    I: IpExt,
106    BT: IcmpEchoBindingsTypes,
107{
108    fn socket_cookie(&self, _core_ctx: &mut CC) -> SocketCookie {
109        self.socket_cookie()
110    }
111    fn marks(&self, core_ctx: &mut CC) -> Marks {
112        core_ctx.with_socket_state(self, |_core_ctx, state| state.options().marks().clone())
113    }
114}
115
116impl<I: IpExt, D: WeakDeviceIdentifier, BT: IcmpEchoBindingsTypes> Clone
117    for IcmpSocketId<I, D, BT>
118{
119    #[cfg_attr(feature = "instrumented", track_caller)]
120    fn clone(&self) -> Self {
121        let Self(rc) = self;
122        Self(StrongRc::clone(rc))
123    }
124}
125
126impl<I: IpExt, D: WeakDeviceIdentifier, BT: IcmpEchoBindingsTypes>
127    From<datagram::StrongRc<I, D, Icmp<BT>>> for IcmpSocketId<I, D, BT>
128{
129    fn from(value: datagram::StrongRc<I, D, Icmp<BT>>) -> Self {
130        Self(value)
131    }
132}
133
134impl<I: IpExt, D: WeakDeviceIdentifier, BT: IcmpEchoBindingsTypes>
135    Borrow<datagram::StrongRc<I, D, Icmp<BT>>> for IcmpSocketId<I, D, BT>
136{
137    fn borrow(&self) -> &datagram::StrongRc<I, D, Icmp<BT>> {
138        let Self(rc) = self;
139        rc
140    }
141}
142
143impl<I: IpExt, D: WeakDeviceIdentifier, BT: IcmpEchoBindingsTypes>
144    PartialEq<WeakIcmpSocketId<I, D, BT>> for IcmpSocketId<I, D, BT>
145{
146    fn eq(&self, other: &WeakIcmpSocketId<I, D, BT>) -> bool {
147        let Self(rc) = self;
148        let WeakIcmpSocketId(weak) = other;
149        StrongRc::weak_ptr_eq(rc, weak)
150    }
151}
152
153impl<I: IpExt, D: WeakDeviceIdentifier, BT: IcmpEchoBindingsTypes> Debug
154    for IcmpSocketId<I, D, BT>
155{
156    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
157        let Self(rc) = self;
158        f.debug_tuple("IcmpSocketId").field(&StrongRc::debug_id(rc)).finish()
159    }
160}
161
162impl<I: IpExt, D: WeakDeviceIdentifier, BT: IcmpEchoBindingsTypes> IcmpSocketId<I, D, BT> {
163    /// Returns the inner state for this socket, to be used in conjunction with
164    /// lock ordering mechanisms.
165    #[cfg(any(test, feature = "testutils"))]
166    pub fn state(&self) -> &RwLock<IcmpSocketState<I, D, BT>> {
167        let Self(rc) = self;
168        rc.state()
169    }
170
171    /// Returns a means to debug outstanding references to this socket.
172    pub fn debug_references(&self) -> impl Debug {
173        let Self(rc) = self;
174        StrongRc::debug_references(rc)
175    }
176
177    /// Downgrades this ID to a weak reference.
178    pub fn downgrade(&self) -> WeakIcmpSocketId<I, D, BT> {
179        let Self(rc) = self;
180        WeakIcmpSocketId(StrongRc::downgrade(rc))
181    }
182
183    /// Returns external data associated with this socket.
184    pub fn external_data(&self) -> &BT::ExternalData<I> {
185        let Self(rc) = self;
186        rc.external_data()
187    }
188}
189
190impl<I: IpExt, D: WeakDeviceIdentifier, BT: IcmpEchoBindingsTypes>
191    DelegatedOrderedLockAccess<IcmpSocketState<I, D, BT>> for IcmpSocketId<I, D, BT>
192{
193    type Inner = datagram::ReferenceState<I, D, Icmp<BT>>;
194    fn delegate_ordered_lock_access(&self) -> &Self::Inner {
195        let Self(rc) = self;
196        &*rc
197    }
198}
199
200/// A weak reference to an ICMP socket.
201#[derive(GenericOverIp, Derivative)]
202#[derivative(Eq(bound = ""), PartialEq(bound = ""), Hash(bound = ""), Clone(bound = ""))]
203#[generic_over_ip(I, Ip)]
204pub struct WeakIcmpSocketId<I: IpExt, D: WeakDeviceIdentifier, BT: IcmpEchoBindingsTypes>(
205    datagram::WeakRc<I, D, Icmp<BT>>,
206);
207
208impl<I: IpExt, D: WeakDeviceIdentifier, BT: IcmpEchoBindingsTypes> PartialEq<IcmpSocketId<I, D, BT>>
209    for WeakIcmpSocketId<I, D, BT>
210{
211    fn eq(&self, other: &IcmpSocketId<I, D, BT>) -> bool {
212        PartialEq::eq(other, self)
213    }
214}
215
216impl<I: IpExt, D: WeakDeviceIdentifier, BT: IcmpEchoBindingsTypes> Debug
217    for WeakIcmpSocketId<I, D, BT>
218{
219    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
220        let Self(rc) = self;
221        f.debug_tuple("WeakIcmpSocketId").field(&rc.debug_id()).finish()
222    }
223}
224
225impl<I: IpExt, D: WeakDeviceIdentifier, BT: IcmpEchoBindingsTypes> WeakIcmpSocketId<I, D, BT> {
226    #[cfg_attr(feature = "instrumented", track_caller)]
227    pub fn upgrade(&self) -> Option<IcmpSocketId<I, D, BT>> {
228        let Self(rc) = self;
229        rc.upgrade().map(IcmpSocketId)
230    }
231}
232
233/// The set of ICMP sockets.
234pub type IcmpSocketSet<I, D, BT> = DatagramSocketSet<I, D, Icmp<BT>>;
235/// The state of an ICMP socket.
236pub type IcmpSocketState<I, D, BT> = datagram::SocketState<I, D, Icmp<BT>>;
237/// The tx metadata for an ICMP echo socket.
238pub type IcmpSocketTxMetadata<I, D, BT> = datagram::TxMetadata<I, D, Icmp<BT>>;
239
240/// Errors that Bindings may encounter when receiving an ICMP Echo datagram.
241pub enum ReceiveIcmpEchoError {
242    /// The socket's receive queue is full and can't hold the datagram.
243    QueueFull,
244}
245
246/// The context required by the ICMP layer in order to deliver events related to
247/// ICMP sockets.
248pub trait IcmpEchoBindingsContext<I: IpExt, D: StrongDeviceIdentifier>:
249    IcmpEchoBindingsTypes + ReferenceNotifiers + RngContext + SettingsContext<IcmpEchoSettings>
250{
251    /// Receives an ICMP echo reply.
252    fn receive_icmp_echo_reply<B: BufferMut>(
253        &mut self,
254        conn: &IcmpSocketId<I, D::Weak, Self>,
255        device_id: &D,
256        src_ip: I::Addr,
257        dst_ip: I::Addr,
258        id: u16,
259        data: B,
260    ) -> Result<(), ReceiveIcmpEchoError>;
261}
262
263/// The bindings context providing external types to ICMP sockets.
264///
265/// # Discussion
266///
267/// We'd like this trait to take an `I` type parameter instead of using GAT to
268/// get the IP version, however we end up with problems due to the shape of
269/// [`DatagramSocketSpec`] and the underlying support for dual stack sockets.
270///
271/// This is completely fine for all known implementations, except for a rough
272/// edge in fake tests bindings contexts that are already parameterized on I
273/// themselves. This is still better than relying on `Box<dyn Any>` to keep the
274/// external data in our references so we take the rough edge.
275pub trait IcmpEchoBindingsTypes: DatagramBindingsTypes + Sized + 'static {
276    /// Opaque bindings data held by core for a given IP version.
277    type ExternalData<I: Ip>: Debug + Send + Sync + 'static;
278    /// The listener notified when sockets' writable state changes.
279    type SocketWritableListener: SocketWritableListener + Debug + Send + Sync + 'static;
280}
281
282/// Resolve coherence issues by requiring a trait implementation with no type
283/// parameters, which makes the blanket implementations for the datagram specs
284/// viable.
285pub trait IcmpEchoContextMarker {}
286
287/// A Context that provides access to the sockets' states.
288pub trait IcmpEchoBoundStateContext<I: IcmpIpExt + IpExt, BC: IcmpEchoBindingsTypes>:
289    DeviceIdContext<AnyDevice> + IcmpEchoContextMarker
290{
291    /// The inner context providing IP socket access.
292    type IpSocketsCtx<'a>: TransportIpContext<I, BC>
293        + MulticastMembershipHandler<I, BC>
294        + DeviceIdContext<AnyDevice, DeviceId = Self::DeviceId, WeakDeviceId = Self::WeakDeviceId>
295        + CounterContext<IcmpRxCounters<I>>
296        + CoreTxMetadataContext<IcmpSocketTxMetadata<I, Self::WeakDeviceId, BC>, BC>;
297
298    /// Calls the function with a mutable reference to `IpSocketsCtx` and
299    /// a mutable reference to ICMP sockets.
300    fn with_icmp_ctx_and_sockets_mut<
301        O,
302        F: FnOnce(&mut Self::IpSocketsCtx<'_>, &mut BoundSockets<I, Self::WeakDeviceId, BC>) -> O,
303    >(
304        &mut self,
305        cb: F,
306    ) -> O;
307}
308
309/// A Context that provides access to the sockets' states.
310pub trait IcmpEchoStateContext<I: IcmpIpExt + IpExt, BC: IcmpEchoBindingsTypes>:
311    DeviceIdContext<AnyDevice> + IcmpEchoContextMarker
312{
313    /// The inner socket context.
314    type SocketStateCtx<'a>: IcmpEchoBoundStateContext<I, BC>
315        + DeviceIdContext<AnyDevice, DeviceId = Self::DeviceId, WeakDeviceId = Self::WeakDeviceId>;
316
317    /// Calls the function with mutable access to the set with all ICMP
318    /// sockets.
319    fn with_all_sockets_mut<O, F: FnOnce(&mut IcmpSocketSet<I, Self::WeakDeviceId, BC>) -> O>(
320        &mut self,
321        cb: F,
322    ) -> O;
323
324    /// Calls the function with immutable access to the set with all ICMP
325    /// sockets.
326    fn with_all_sockets<O, F: FnOnce(&IcmpSocketSet<I, Self::WeakDeviceId, BC>) -> O>(
327        &mut self,
328        cb: F,
329    ) -> O;
330
331    /// Calls the function without access to ICMP socket state.
332    fn with_bound_state_context<O, F: FnOnce(&mut Self::SocketStateCtx<'_>) -> O>(
333        &mut self,
334        cb: F,
335    ) -> O;
336
337    /// Calls the function with an immutable reference to the given socket's
338    /// state.
339    fn with_socket_state<
340        O,
341        F: FnOnce(&mut Self::SocketStateCtx<'_>, &IcmpSocketState<I, Self::WeakDeviceId, BC>) -> O,
342    >(
343        &mut self,
344        id: &IcmpSocketId<I, Self::WeakDeviceId, BC>,
345        cb: F,
346    ) -> O;
347
348    /// Calls the function with a mutable reference to the given socket's state.
349    fn with_socket_state_mut<
350        O,
351        F: FnOnce(&mut Self::SocketStateCtx<'_>, &mut IcmpSocketState<I, Self::WeakDeviceId, BC>) -> O,
352    >(
353        &mut self,
354        id: &IcmpSocketId<I, Self::WeakDeviceId, BC>,
355        cb: F,
356    ) -> O;
357
358    /// Call `f` with each socket's state.
359    fn for_each_socket<
360        F: FnMut(
361            &mut Self::SocketStateCtx<'_>,
362            &IcmpSocketId<I, Self::WeakDeviceId, BC>,
363            &IcmpSocketState<I, Self::WeakDeviceId, BC>,
364        ),
365    >(
366        &mut self,
367        cb: F,
368    );
369}
370
371/// Uninstantiatable type for implementing [`DatagramSocketSpec`].
372pub struct Icmp<BT>(PhantomData<BT>, Never);
373
374impl<BT: IcmpEchoBindingsTypes> DatagramSocketSpec for Icmp<BT> {
375    const NAME: &'static str = "ICMP_ECHO";
376    type AddrSpec = IcmpAddrSpec;
377
378    type SocketId<I: datagram::IpExt, D: WeakDeviceIdentifier> = IcmpSocketId<I, D, BT>;
379    type WeakSocketId<I: datagram::IpExt, D: WeakDeviceIdentifier> = WeakIcmpSocketId<I, D, BT>;
380
381    type OtherStackIpOptions<I: datagram::IpExt, D: WeakDeviceIdentifier> = ();
382
383    type SharingState = ();
384
385    type SocketMapSpec<I: datagram::IpExt + datagram::DualStackIpExt, D: WeakDeviceIdentifier> =
386        IcmpSocketMapStateSpec<I, D, BT>;
387
388    fn ip_proto<I: IpProtoExt>() -> I::Proto {
389        I::map_ip((), |()| Ipv4Proto::Icmp, |()| Ipv6Proto::Icmpv6)
390    }
391
392    fn make_bound_socket_map_id<I: datagram::IpExt, D: WeakDeviceIdentifier>(
393        s: &Self::SocketId<I, D>,
394    ) -> <Self::SocketMapSpec<I, D> as datagram::DatagramSocketMapSpec<
395        I,
396        D,
397        Self::AddrSpec,
398    >>::BoundSocketId{
399        s.clone()
400    }
401
402    type Serializer<I: datagram::IpExt, B: BufferMut> =
403        packet::Nested<B, IcmpPacketBuilder<I, IcmpEchoRequest>>;
404    type SerializeError = packet_formats::error::ParseError;
405
406    type ExternalData<I: Ip> = BT::ExternalData<I>;
407    type Settings = IcmpEchoSettings;
408
409    // NB: At present, there's no need to track per-socket ICMP counters.
410    type Counters<I: Ip> = ();
411    type SocketWritableListener = BT::SocketWritableListener;
412
413    // NB: `make_packet` does not add any extra bytes because applications send
414    // the ICMP header alongside the message which gets parsed and then rebuilt.
415    // That means we incur 0 extra cost here.
416    const FIXED_HEADER_SIZE: usize = 0;
417
418    fn make_packet<I: datagram::IpExt, B: BufferMut>(
419        mut body: B,
420        addr: &socket::ConnIpAddr<
421            I::Addr,
422            <Self::AddrSpec as SocketMapAddrSpec>::LocalIdentifier,
423            <Self::AddrSpec as SocketMapAddrSpec>::RemoteIdentifier,
424        >,
425    ) -> Result<Self::Serializer<I, B>, Self::SerializeError> {
426        let ConnIpAddr { local: (local_ip, id), remote: (remote_ip, ()) } = addr;
427        let icmp_echo: packet_formats::icmp::IcmpPacketRaw<I, &[u8], IcmpEchoRequest> =
428            body.parse()?;
429        debug!(
430            "preparing ICMP echo request {local_ip} to {remote_ip}: id={}, seq={}",
431            id,
432            icmp_echo.message().seq()
433        );
434        let icmp_builder = IcmpPacketBuilder::<I, _>::new(
435            local_ip.addr(),
436            remote_ip.addr(),
437            packet_formats::icmp::IcmpZeroCode,
438            IcmpEchoRequest::new(id.get(), icmp_echo.message().seq()),
439        );
440        Ok(icmp_builder.wrap_body(body))
441    }
442
443    fn try_alloc_listen_identifier<I: datagram::IpExt, D: WeakDeviceIdentifier>(
444        bindings_ctx: &mut impl RngContext,
445        is_available: impl Fn(
446            <Self::AddrSpec as SocketMapAddrSpec>::LocalIdentifier,
447        ) -> Result<(), datagram::InUseError>,
448    ) -> Option<<Self::AddrSpec as SocketMapAddrSpec>::LocalIdentifier> {
449        let mut port = IcmpPortAlloc::<I, D, BT>::rand_ephemeral(&mut bindings_ctx.rng());
450        for _ in IcmpPortAlloc::<I, D, BT>::EPHEMERAL_RANGE {
451            // We can unwrap here because we know that the EPHEMERAL_RANGE doesn't
452            // include 0.
453            let tryport = NonZeroU16::new(port.get()).unwrap();
454            match is_available(tryport) {
455                Ok(()) => return Some(tryport),
456                Err(datagram::InUseError {}) => port.next(),
457            }
458        }
459        None
460    }
461
462    type ListenerIpAddr<I: datagram::IpExt> = socket::ListenerIpAddr<I::Addr, NonZeroU16>;
463
464    type ConnIpAddr<I: datagram::IpExt> = ConnIpAddr<
465        I::Addr,
466        <Self::AddrSpec as SocketMapAddrSpec>::LocalIdentifier,
467        <Self::AddrSpec as SocketMapAddrSpec>::RemoteIdentifier,
468    >;
469
470    type ConnState<I: datagram::IpExt, D: WeakDeviceIdentifier> = datagram::ConnState<I, D, Self>;
471    // Store the remote port/id set by `connect`. This does not participate in
472    // demuxing, so not part of the socketmap, but we need to store it so that
473    // it can be reported later.
474    type ConnStateExtra = u16;
475
476    fn conn_info_from_state<I: IpExt, D: WeakDeviceIdentifier>(
477        state: &Self::ConnState<I, D>,
478    ) -> datagram::ConnInfo<I::Addr, D> {
479        let ConnAddr { ip, device } = state.addr();
480        let extra = state.extra();
481        let ConnInfoAddr { local: (local_ip, local_identifier), remote: (remote_ip, ()) } =
482            ip.clone().into();
483        datagram::ConnInfo::new(local_ip, local_identifier, remote_ip, *extra, || {
484            // The invariant that a zone is present if needed is upheld by connect.
485            device.clone().expect("device must be bound for addresses that require zones")
486        })
487    }
488
489    fn try_alloc_local_id<I: IpExt, D: WeakDeviceIdentifier, BC: RngContext>(
490        bound: &IcmpBoundSockets<I, D, BT>,
491        bindings_ctx: &mut BC,
492        flow: datagram::DatagramFlowId<I::Addr, ()>,
493    ) -> Option<NonZeroU16> {
494        let mut rng = bindings_ctx.rng();
495        netstack3_base::simple_randomized_port_alloc(&mut rng, &flow, &IcmpPortAlloc(bound), &())
496            .map(|p| NonZeroU16::new(p).expect("ephemeral ports should be non-zero"))
497    }
498
499    fn upgrade_socket_id<I: datagram::IpExt, D: WeakDeviceIdentifier>(
500        id: &Self::WeakSocketId<I, D>,
501    ) -> Option<Self::SocketId<I, D>> {
502        id.upgrade()
503    }
504
505    fn downgrade_socket_id<I: datagram::IpExt, D: WeakDeviceIdentifier>(
506        id: &Self::SocketId<I, D>,
507    ) -> Self::WeakSocketId<I, D> {
508        IcmpSocketId::downgrade(id)
509    }
510}
511
512/// Uninstantiatable type for implementing [`SocketMapAddrSpec`].
513pub enum IcmpAddrSpec {}
514
515impl SocketMapAddrSpec for IcmpAddrSpec {
516    type RemoteIdentifier = ();
517    type LocalIdentifier = NonZeroU16;
518}
519
520type IcmpBoundSockets<I, D, BT> = datagram::BoundDatagramSocketMap<I, D, Icmp<BT>>;
521
522struct IcmpPortAlloc<'a, I: IpExt, D: WeakDeviceIdentifier, BT: IcmpEchoBindingsTypes>(
523    &'a IcmpBoundSockets<I, D, BT>,
524);
525
526impl<I: IpExt, D: WeakDeviceIdentifier, BT: IcmpEchoBindingsTypes> PortAllocImpl
527    for IcmpPortAlloc<'_, I, D, BT>
528{
529    const EPHEMERAL_RANGE: core::ops::RangeInclusive<u16> = 1..=u16::MAX;
530    type Id = DatagramFlowId<I::Addr, ()>;
531    type PortAvailableArg = ();
532
533    fn is_port_available(&self, id: &Self::Id, port: u16, (): &()) -> bool {
534        let Self(socketmap) = self;
535        // We can safely unwrap here, because the ports received in
536        // `is_port_available` are guaranteed to be in `EPHEMERAL_RANGE`.
537        let port = NonZeroU16::new(port).unwrap();
538        let conn = ConnAddr {
539            ip: ConnIpAddr { local: (id.local_ip, port), remote: (id.remote_ip, ()) },
540            device: None,
541        };
542
543        // A port is free if there are no sockets currently using it, and if
544        // there are no sockets that are shadowing it.
545        AddrVec::from(conn).iter_shadows().all(|a| match &a {
546            AddrVec::Listen(l) => socketmap.listeners().get_by_addr(&l).is_none(),
547            AddrVec::Conn(c) => socketmap.conns().get_by_addr(&c).is_none(),
548        } && socketmap.get_shadower_counts(&a) == 0)
549    }
550}
551
552/// The demux state for ICMP echo sockets.
553#[derive(Derivative)]
554#[derivative(Default(bound = ""))]
555pub struct BoundSockets<I: IpExt, D: WeakDeviceIdentifier, BT: IcmpEchoBindingsTypes> {
556    pub(crate) socket_map: IcmpBoundSockets<I, D, BT>,
557}
558
559impl<I, BC, CC> NonDualStackDatagramSpecBoundStateContext<I, CC, BC> for Icmp<BC>
560where
561    I: IpExt + datagram::DualStackIpExt,
562    BC: IcmpEchoBindingsContext<I, CC::DeviceId>,
563    CC: DeviceIdContext<AnyDevice> + IcmpEchoContextMarker,
564{
565    fn nds_converter(_core_ctx: &CC) -> impl NonDualStackConverter<I, CC::WeakDeviceId, Self> {
566        ()
567    }
568}
569
570impl<I, BC, CC> DatagramSpecBoundStateContext<I, CC, BC> for Icmp<BC>
571where
572    I: IpExt + datagram::DualStackIpExt,
573    BC: IcmpEchoBindingsContext<I, CC::DeviceId>,
574    CC: IcmpEchoBoundStateContext<I, BC> + IcmpEchoContextMarker,
575{
576    type IpSocketsCtx<'a> = CC::IpSocketsCtx<'a>;
577
578    // ICMP sockets doesn't support dual-stack operations.
579    type DualStackContext = UninstantiableWrapper<CC>;
580
581    type NonDualStackContext = CC;
582
583    fn with_bound_sockets<
584        O,
585        F: FnOnce(&mut Self::IpSocketsCtx<'_>, &IcmpBoundSockets<I, CC::WeakDeviceId, BC>) -> O,
586    >(
587        core_ctx: &mut CC,
588        cb: F,
589    ) -> O {
590        IcmpEchoBoundStateContext::with_icmp_ctx_and_sockets_mut(
591            core_ctx,
592            |ctx, BoundSockets { socket_map }| cb(ctx, &socket_map),
593        )
594    }
595
596    fn with_bound_sockets_mut<
597        O,
598        F: FnOnce(&mut Self::IpSocketsCtx<'_>, &mut IcmpBoundSockets<I, CC::WeakDeviceId, BC>) -> O,
599    >(
600        core_ctx: &mut CC,
601        cb: F,
602    ) -> O {
603        IcmpEchoBoundStateContext::with_icmp_ctx_and_sockets_mut(
604            core_ctx,
605            |ctx, BoundSockets { socket_map }| cb(ctx, socket_map),
606        )
607    }
608
609    fn dual_stack_context(
610        core_ctx: &CC,
611    ) -> MaybeDualStack<&Self::DualStackContext, &Self::NonDualStackContext> {
612        MaybeDualStack::NotDualStack(core_ctx)
613    }
614
615    fn dual_stack_context_mut(
616        core_ctx: &mut CC,
617    ) -> MaybeDualStack<&mut Self::DualStackContext, &mut Self::NonDualStackContext> {
618        MaybeDualStack::NotDualStack(core_ctx)
619    }
620
621    fn with_transport_context<O, F: FnOnce(&mut Self::IpSocketsCtx<'_>) -> O>(
622        core_ctx: &mut CC,
623        cb: F,
624    ) -> O {
625        IcmpEchoBoundStateContext::with_icmp_ctx_and_sockets_mut(core_ctx, |ctx, _sockets| cb(ctx))
626    }
627}
628
629impl<I, BC, CC> DatagramSpecStateContext<I, CC, BC> for Icmp<BC>
630where
631    I: IpExt + datagram::DualStackIpExt,
632    BC: IcmpEchoBindingsContext<I, CC::DeviceId>,
633    CC: IcmpEchoStateContext<I, BC>,
634{
635    type SocketsStateCtx<'a> = CC::SocketStateCtx<'a>;
636
637    fn with_all_sockets_mut<O, F: FnOnce(&mut IcmpSocketSet<I, CC::WeakDeviceId, BC>) -> O>(
638        core_ctx: &mut CC,
639        cb: F,
640    ) -> O {
641        IcmpEchoStateContext::with_all_sockets_mut(core_ctx, cb)
642    }
643
644    fn with_all_sockets<O, F: FnOnce(&IcmpSocketSet<I, CC::WeakDeviceId, BC>) -> O>(
645        core_ctx: &mut CC,
646        cb: F,
647    ) -> O {
648        IcmpEchoStateContext::with_all_sockets(core_ctx, cb)
649    }
650
651    fn with_socket_state<
652        O,
653        F: FnOnce(&mut Self::SocketsStateCtx<'_>, &IcmpSocketState<I, CC::WeakDeviceId, BC>) -> O,
654    >(
655        core_ctx: &mut CC,
656        id: &IcmpSocketId<I, CC::WeakDeviceId, BC>,
657        cb: F,
658    ) -> O {
659        IcmpEchoStateContext::with_socket_state(core_ctx, id, cb)
660    }
661
662    fn with_socket_state_mut<
663        O,
664        F: FnOnce(&mut Self::SocketsStateCtx<'_>, &mut IcmpSocketState<I, CC::WeakDeviceId, BC>) -> O,
665    >(
666        core_ctx: &mut CC,
667        id: &IcmpSocketId<I, CC::WeakDeviceId, BC>,
668        cb: F,
669    ) -> O {
670        IcmpEchoStateContext::with_socket_state_mut(core_ctx, id, cb)
671    }
672
673    fn for_each_socket<
674        F: FnMut(
675            &mut Self::SocketsStateCtx<'_>,
676            &IcmpSocketId<I, CC::WeakDeviceId, BC>,
677            &IcmpSocketState<I, CC::WeakDeviceId, BC>,
678        ),
679    >(
680        core_ctx: &mut CC,
681        cb: F,
682    ) {
683        IcmpEchoStateContext::for_each_socket(core_ctx, cb)
684    }
685}
686
687/// An uninstantiable type providing a [`SocketMapStateSpec`] implementation for
688/// ICMP.
689pub struct IcmpSocketMapStateSpec<I, D, BT>(PhantomData<(I, D, BT)>, Never);
690
691impl<I: IpExt, D: WeakDeviceIdentifier, BT: IcmpEchoBindingsTypes> SocketMapStateSpec
692    for IcmpSocketMapStateSpec<I, D, BT>
693{
694    type ListenerId = IcmpSocketId<I, D, BT>;
695    type ConnId = IcmpSocketId<I, D, BT>;
696
697    type AddrVecTag = ();
698
699    type ListenerSharingState = ();
700    type ConnSharingState = ();
701
702    type ListenerAddrState = Self::ListenerId;
703
704    type ConnAddrState = Self::ConnId;
705    fn listener_tag(
706        ListenerAddrInfo { has_device: _, specified_addr: _ }: ListenerAddrInfo,
707        _state: &Self::ListenerAddrState,
708    ) -> Self::AddrVecTag {
709        ()
710    }
711    fn connected_tag(_has_device: bool, _state: &Self::ConnAddrState) -> Self::AddrVecTag {
712        ()
713    }
714}
715
716impl<I: IpExt, D: WeakDeviceIdentifier, BT: IcmpEchoBindingsTypes> SocketMapAddrStateSpec
717    for IcmpSocketId<I, D, BT>
718{
719    type Id = Self;
720
721    type SharingState = ();
722
723    type Inserter<'a>
724        = core::convert::Infallible
725    where
726        Self: 'a;
727
728    fn new(_new_sharing_state: &Self::SharingState, id: Self::Id) -> Self {
729        id
730    }
731
732    fn contains_id(&self, id: &Self::Id) -> bool {
733        self == id
734    }
735
736    fn try_get_inserter<'a, 'b>(
737        &'b mut self,
738        _new_sharing_state: &'a Self::SharingState,
739    ) -> Result<Self::Inserter<'b>, IncompatibleError> {
740        Err(IncompatibleError)
741    }
742
743    fn could_insert(
744        &self,
745        _new_sharing_state: &Self::SharingState,
746    ) -> Result<(), IncompatibleError> {
747        Err(IncompatibleError)
748    }
749
750    fn remove_by_id(&mut self, _id: Self::Id) -> socket::RemoveResult {
751        socket::RemoveResult::IsLast
752    }
753}
754
755impl<I: IpExt, D: WeakDeviceIdentifier, BT: IcmpEchoBindingsTypes>
756    DatagramSocketMapSpec<I, D, IcmpAddrSpec> for IcmpSocketMapStateSpec<I, D, BT>
757{
758    type BoundSocketId = IcmpSocketId<I, D, BT>;
759}
760
761impl<AA, I: IpExt, D: WeakDeviceIdentifier, BT: IcmpEchoBindingsTypes>
762    SocketMapConflictPolicy<AA, (), I, D, IcmpAddrSpec> for IcmpSocketMapStateSpec<I, D, BT>
763where
764    AA: Into<AddrVec<I, D, IcmpAddrSpec>> + Clone,
765{
766    fn check_insert_conflicts(
767        _new_sharing_state: &(),
768        addr: &AA,
769        socketmap: &SocketMap<AddrVec<I, D, IcmpAddrSpec>, socket::Bound<Self>>,
770    ) -> Result<(), socket::InsertError> {
771        let addr: AddrVec<_, _, _> = addr.clone().into();
772        // Having a value present at a shadowed address is disqualifying.
773        if addr.iter_shadows().any(|a| socketmap.get(&a).is_some()) {
774            return Err(InsertError::ShadowAddrExists);
775        }
776
777        // Likewise, the presence of a value that shadows the target address is
778        // also disqualifying.
779        if socketmap.descendant_counts(&addr).len() != 0 {
780            return Err(InsertError::ShadowerExists);
781        }
782        Ok(())
783    }
784}
785
786/// The ICMP Echo sockets API.
787pub struct IcmpEchoSocketApi<I: Ip, C>(C, IpVersionMarker<I>);
788
789impl<I: Ip, C> IcmpEchoSocketApi<I, C> {
790    /// Creates a new API instance.
791    pub fn new(ctx: C) -> Self {
792        Self(ctx, IpVersionMarker::new())
793    }
794}
795
796/// A local alias for [`IcmpSocketId`] for use in [`IcmpEchoSocketApi`].
797///
798/// TODO(https://github.com/rust-lang/rust/issues/8995): Make this an inherent
799/// associated type.
800type IcmpApiSocketId<I, C> = IcmpSocketId<
801    I,
802    <<C as ContextPair>::CoreContext as DeviceIdContext<AnyDevice>>::WeakDeviceId,
803    <C as ContextPair>::BindingsContext,
804>;
805
806impl<I, C> IcmpEchoSocketApi<I, C>
807where
808    I: datagram::IpExt,
809    C: ContextPair,
810    C::CoreContext: IcmpEchoStateContext<I, C::BindingsContext>
811        // NB: This bound is somewhat redundant to StateContext but it helps the
812        // compiler know we're using ICMP datagram sockets.
813        + DatagramStateContext<I, C::BindingsContext, Icmp<C::BindingsContext>>,
814    C::BindingsContext:
815        IcmpEchoBindingsContext<I, <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId>,
816{
817    fn core_ctx(&mut self) -> &mut C::CoreContext {
818        let Self(pair, IpVersionMarker { .. }) = self;
819        pair.core_ctx()
820    }
821
822    fn datagram(&mut self) -> &mut DatagramApi<I, C, Icmp<C::BindingsContext>> {
823        let Self(pair, IpVersionMarker { .. }) = self;
824        DatagramApi::wrap(pair)
825    }
826
827    /// Creates a new unbound ICMP socket with default external data.
828    pub fn create(&mut self) -> IcmpApiSocketId<I, C>
829    where
830        <C::BindingsContext as IcmpEchoBindingsTypes>::ExternalData<I>: Default,
831        <C::BindingsContext as IcmpEchoBindingsTypes>::SocketWritableListener: Default,
832    {
833        self.create_with(Default::default(), Default::default())
834    }
835
836    /// Creates a new unbound ICMP socket with provided external data.
837    pub fn create_with(
838        &mut self,
839        external_data: <C::BindingsContext as IcmpEchoBindingsTypes>::ExternalData<I>,
840        writable_listener: <C::BindingsContext as IcmpEchoBindingsTypes>::SocketWritableListener,
841    ) -> IcmpApiSocketId<I, C> {
842        self.datagram().create(external_data, writable_listener)
843    }
844
845    /// Connects an ICMP socket to remote IP.
846    ///
847    /// If the socket is never bound, an local ID will be allocated.
848    pub fn connect(
849        &mut self,
850        id: &IcmpApiSocketId<I, C>,
851        remote_ip: Option<
852            ZonedAddr<
853                SpecifiedAddr<I::Addr>,
854                <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId,
855            >,
856        >,
857        remote_id: u16,
858    ) -> Result<(), datagram::ConnectError> {
859        self.datagram().connect(id, remote_ip, (), remote_id)
860    }
861
862    /// Binds an ICMP socket to a local IP address and a local ID.
863    ///
864    /// Both the IP and the ID are optional. When IP is missing, the "any" IP is
865    /// assumed; When the ID is missing, it will be allocated.
866    pub fn bind(
867        &mut self,
868        id: &IcmpApiSocketId<I, C>,
869        local_ip: Option<
870            ZonedAddr<
871                SpecifiedAddr<I::Addr>,
872                <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId,
873            >,
874        >,
875        icmp_id: Option<NonZeroU16>,
876    ) -> Result<(), Either<ExpectedUnboundError, LocalAddressError>> {
877        self.datagram().listen(id, local_ip, icmp_id)
878    }
879
880    /// Gets the information about an ICMP socket.
881    pub fn get_info(
882        &mut self,
883        id: &IcmpApiSocketId<I, C>,
884    ) -> datagram::SocketInfo<I::Addr, <C::CoreContext as DeviceIdContext<AnyDevice>>::WeakDeviceId>
885    {
886        self.datagram().get_info(id)
887    }
888
889    /// Sets the bound device for a socket.
890    ///
891    /// Sets the device to be used for sending and receiving packets for a
892    /// socket. If the socket is not currently bound to a local address and
893    /// port, the device will be used when binding.
894    pub fn set_device(
895        &mut self,
896        id: &IcmpApiSocketId<I, C>,
897        device_id: Option<&<C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId>,
898    ) -> Result<(), SocketError> {
899        self.datagram().set_device(id, device_id)
900    }
901
902    /// Gets the device the specified socket is bound to.
903    pub fn get_bound_device(
904        &mut self,
905        id: &IcmpApiSocketId<I, C>,
906    ) -> Option<<C::CoreContext as DeviceIdContext<AnyDevice>>::WeakDeviceId> {
907        self.datagram().get_bound_device(id)
908    }
909
910    /// Disconnects an ICMP socket.
911    pub fn disconnect(
912        &mut self,
913        id: &IcmpApiSocketId<I, C>,
914    ) -> Result<(), datagram::ExpectedConnError> {
915        self.datagram().disconnect_connected(id)
916    }
917
918    /// Shuts down an ICMP socket.
919    pub fn shutdown(
920        &mut self,
921        id: &IcmpApiSocketId<I, C>,
922        shutdown_type: ShutdownType,
923    ) -> Result<(), datagram::ExpectedConnError> {
924        self.datagram().shutdown_connected(id, shutdown_type)
925    }
926
927    /// Gets the current shutdown state of an ICMP socket.
928    pub fn get_shutdown(&mut self, id: &IcmpApiSocketId<I, C>) -> Option<ShutdownType> {
929        self.datagram().get_shutdown_connected(id)
930    }
931
932    /// Closes an ICMP socket.
933    pub fn close(
934        &mut self,
935        id: IcmpApiSocketId<I, C>,
936    ) -> RemoveResourceResultWithContext<
937        <C::BindingsContext as IcmpEchoBindingsTypes>::ExternalData<I>,
938        C::BindingsContext,
939    > {
940        self.datagram().close(id)
941    }
942
943    /// Gets unicast IP hop limit for ICMP sockets.
944    pub fn get_unicast_hop_limit(&mut self, id: &IcmpApiSocketId<I, C>) -> NonZeroU8 {
945        self.datagram().get_ip_hop_limits(id).unicast
946    }
947
948    /// Gets multicast IP hop limit for ICMP sockets.
949    pub fn get_multicast_hop_limit(&mut self, id: &IcmpApiSocketId<I, C>) -> NonZeroU8 {
950        self.datagram().get_ip_hop_limits(id).multicast
951    }
952
953    /// Sets unicast IP hop limit for ICMP sockets.
954    pub fn set_unicast_hop_limit(
955        &mut self,
956        id: &IcmpApiSocketId<I, C>,
957        hop_limit: Option<NonZeroU8>,
958    ) {
959        self.datagram().update_ip_hop_limit(id, SocketHopLimits::set_unicast(hop_limit))
960    }
961
962    /// Sets multicast IP hop limit for ICMP sockets.
963    pub fn set_multicast_hop_limit(
964        &mut self,
965        id: &IcmpApiSocketId<I, C>,
966        hop_limit: Option<NonZeroU8>,
967    ) {
968        self.datagram().update_ip_hop_limit(id, SocketHopLimits::set_multicast(hop_limit))
969    }
970
971    /// Gets the loopback multicast option.
972    pub fn get_multicast_loop(&mut self, id: &IcmpApiSocketId<I, C>) -> bool {
973        self.datagram().get_multicast_loop(id)
974    }
975
976    /// Sets the loopback multicast option.
977    pub fn set_multicast_loop(&mut self, id: &IcmpApiSocketId<I, C>, value: bool) {
978        self.datagram().set_multicast_loop(id, value);
979    }
980
981    /// Sets the socket mark for the socket domain.
982    pub fn set_mark(&mut self, id: &IcmpApiSocketId<I, C>, domain: MarkDomain, mark: Mark) {
983        self.datagram().set_mark(id, domain, mark)
984    }
985
986    /// Gets the socket mark for the socket domain.
987    pub fn get_mark(&mut self, id: &IcmpApiSocketId<I, C>, domain: MarkDomain) -> Mark {
988        self.datagram().get_mark(id, domain)
989    }
990
991    /// Sets the send buffer maximum size to `size`.
992    pub fn set_send_buffer(&mut self, id: &IcmpApiSocketId<I, C>, size: usize) {
993        self.datagram().set_send_buffer(id, size)
994    }
995
996    /// Returns the current maximum send buffer size.
997    pub fn send_buffer(&mut self, id: &IcmpApiSocketId<I, C>) -> usize {
998        self.datagram().send_buffer(id)
999    }
1000
1001    /// Sends an ICMP packet through a connection.
1002    ///
1003    /// The socket must be connected in order for the operation to succeed.
1004    pub fn send<B: BufferMut>(
1005        &mut self,
1006        id: &IcmpApiSocketId<I, C>,
1007        body: B,
1008    ) -> Result<(), datagram::SendError<packet_formats::error::ParseError>> {
1009        self.datagram().send_conn(id, body)
1010    }
1011
1012    /// Sends an ICMP packet with an remote address.
1013    ///
1014    /// The socket doesn't need to be connected.
1015    pub fn send_to<B: BufferMut>(
1016        &mut self,
1017        id: &IcmpApiSocketId<I, C>,
1018        remote_ip: Option<
1019            ZonedAddr<
1020                SpecifiedAddr<I::Addr>,
1021                <C::CoreContext as DeviceIdContext<AnyDevice>>::DeviceId,
1022            >,
1023        >,
1024        body: B,
1025    ) -> Result<
1026        (),
1027        either::Either<LocalAddressError, datagram::SendToError<packet_formats::error::ParseError>>,
1028    > {
1029        self.datagram().send_to(id, remote_ip, (), body)
1030    }
1031
1032    /// Collects all currently opened sockets, returning a cloned reference for
1033    /// each one.
1034    pub fn collect_all_sockets(&mut self) -> Vec<IcmpApiSocketId<I, C>> {
1035        self.datagram().collect_all_sockets()
1036    }
1037
1038    /// Provides inspect data for ICMP echo sockets.
1039    pub fn inspect<N>(&mut self, inspector: &mut N)
1040    where
1041        N: Inspector
1042            + InspectorDeviceExt<<C::CoreContext as DeviceIdContext<AnyDevice>>::WeakDeviceId>,
1043        for<'a> N::ChildInspector<'a>:
1044            InspectorDeviceExt<<C::CoreContext as DeviceIdContext<AnyDevice>>::WeakDeviceId>,
1045    {
1046        DatagramStateContext::for_each_socket(self.core_ctx(), |_ctx, socket_id, socket_state| {
1047            inspector.record_debug_child(socket_id, |inspector| {
1048                socket_state.record_common_info(inspector);
1049            });
1050        });
1051    }
1052}
1053
1054/// An [`IpTransportContext`] implementation for handling ICMP Echo replies.
1055///
1056/// This special implementation will panic if it receives any packets that are
1057/// not ICMP echo replies and any error that are not originally an ICMP Echo
1058/// request.
1059pub enum IcmpEchoIpTransportContext {}
1060
1061impl EchoTransportContextMarker for IcmpEchoIpTransportContext {}
1062
1063impl<I: IpExt, BC: IcmpEchoBindingsContext<I, CC::DeviceId>, CC: IcmpEchoBoundStateContext<I, BC>>
1064    IpTransportContext<I, BC, CC> for IcmpEchoIpTransportContext
1065{
1066    type EarlyDemuxSocket = Never;
1067
1068    fn early_demux<B: ParseBuffer>(
1069        _core_ctx: &mut CC,
1070        _device: &CC::DeviceId,
1071        _src_ip: I::Addr,
1072        _dst_ip: I::Addr,
1073        _buffer: B,
1074    ) -> Option<Self::EarlyDemuxSocket> {
1075        None
1076    }
1077
1078    fn receive_icmp_error(
1079        core_ctx: &mut CC,
1080        _bindings_ctx: &mut BC,
1081        _device: &CC::DeviceId,
1082        original_src_ip: Option<SpecifiedAddr<I::Addr>>,
1083        original_dst_ip: SpecifiedAddr<I::Addr>,
1084        mut original_body: &[u8],
1085        err: I::ErrorCode,
1086    ) {
1087        let echo_request = original_body
1088            .parse::<IcmpPacketRaw<I, _, IcmpEchoRequest>>()
1089            .expect("received non-echo request");
1090
1091        let original_src_ip = match original_src_ip {
1092            Some(ip) => ip,
1093            None => {
1094                trace!("IcmpIpTransportContext::receive_icmp_error: unspecified source IP address");
1095                return;
1096            }
1097        };
1098        let original_src_ip: SocketIpAddr<_> = match original_src_ip.try_into() {
1099            Ok(ip) => ip,
1100            Err(AddrIsMappedError {}) => {
1101                trace!("IcmpIpTransportContext::receive_icmp_error: mapped source IP address");
1102                return;
1103            }
1104        };
1105        let original_dst_ip: SocketIpAddr<_> = match original_dst_ip.try_into() {
1106            Ok(ip) => ip,
1107            Err(AddrIsMappedError {}) => {
1108                trace!("IcmpIpTransportContext::receive_icmp_error: mapped destination IP address");
1109                return;
1110            }
1111        };
1112
1113        // The netstack will never generate an ID of 0, so we can just throw
1114        // this away for being invalid.
1115        let Some(id) = NonZeroU16::new(echo_request.message().id()) else {
1116            debug!(
1117                "ICMP received ICMP error {:?} from {:?}, to {:?} with an ID of 0",
1118                err, original_dst_ip, original_src_ip,
1119            );
1120            return;
1121        };
1122
1123        core_ctx.with_icmp_ctx_and_sockets_mut(|core_ctx, sockets| {
1124            if let Some(conn) = sockets.socket_map.conns().get_by_addr(&ConnAddr {
1125                ip: ConnIpAddr { local: (original_src_ip, id), remote: (original_dst_ip, ()) },
1126                device: None,
1127            }) {
1128                // NB: At the moment bindings has no need to consume ICMP
1129                // errors, so we swallow them here.
1130                debug!(
1131                    "ICMP received ICMP error {:?} from {:?}, to {:?} on socket {:?}",
1132                    err, original_dst_ip, original_src_ip, conn
1133                );
1134                CounterContext::<IcmpRxCounters<I>>::counters(core_ctx)
1135                    .error_delivered_to_socket
1136                    .increment()
1137            } else {
1138                trace!(
1139                    "IcmpIpTransportContext::receive_icmp_error: Got ICMP error message for \
1140                    nonexistent ICMP echo socket; either the socket responsible has since been \
1141                    removed, or the error message was sent in error or corrupted"
1142                );
1143            }
1144        })
1145    }
1146
1147    fn receive_ip_packet<B: BufferMut, H: IpHeaderInfo<I>>(
1148        core_ctx: &mut CC,
1149        bindings_ctx: &mut BC,
1150        device: &CC::DeviceId,
1151        src_ip: I::RecvSrcAddr,
1152        dst_ip: SpecifiedAddr<I::Addr>,
1153        mut buffer: B,
1154        info: &LocalDeliveryPacketInfo<I, H>,
1155        _early_demux_socket: Option<Never>,
1156    ) -> Result<(), (B, I::IcmpError)> {
1157        let LocalDeliveryPacketInfo { meta, header_info: _, marks: _ } = info;
1158        let ReceiveIpPacketMeta { broadcast: _, transparent_override } = meta;
1159        if let Some(delivery) = transparent_override.as_ref() {
1160            unreachable!(
1161                "cannot perform transparent local delivery {delivery:?} to an ICMP socket; \
1162                transparent proxy rules can only be configured for TCP and UDP packets"
1163            );
1164        }
1165        // NB: We're doing raw parsing here just to extract the ID and body to
1166        // send up to bindings. The IP layer has performed full validation
1167        // including checksum for us.
1168        let echo_reply =
1169            buffer.parse::<IcmpPacketRaw<I, _, IcmpEchoReply>>().expect("received non-echo reply");
1170        // We never generate requests with ID zero due to local socket map.
1171        let Some(id) = NonZeroU16::new(echo_reply.message().id()) else { return Ok(()) };
1172
1173        // Undo parse so we give out the full ICMP header.
1174        let meta = echo_reply.parse_metadata();
1175        buffer.undo_parse(meta);
1176
1177        let src_ip = match SpecifiedAddr::new(src_ip.into_addr()) {
1178            Some(src_ip) => src_ip,
1179            None => {
1180                trace!("receive_icmp_echo_reply: unspecified source address");
1181                return Ok(());
1182            }
1183        };
1184        let src_ip: SocketIpAddr<_> = match src_ip.try_into() {
1185            Ok(src_ip) => src_ip,
1186            Err(AddrIsMappedError {}) => {
1187                trace!("receive_icmp_echo_reply: mapped source address");
1188                return Ok(());
1189            }
1190        };
1191        let dst_ip: SocketIpAddr<_> = match dst_ip.try_into() {
1192            Ok(dst_ip) => dst_ip,
1193            Err(AddrIsMappedError {}) => {
1194                trace!("receive_icmp_echo_reply: mapped destination address");
1195                return Ok(());
1196            }
1197        };
1198
1199        core_ctx.with_icmp_ctx_and_sockets_mut(|core_ctx, sockets| {
1200            let mut addrs_to_search = AddrVecIter::<I, CC::WeakDeviceId, IcmpAddrSpec>::with_device(
1201                ConnIpAddr { local: (dst_ip, id), remote: (src_ip, ()) }.into(),
1202                device.downgrade(),
1203            );
1204            let socket = match addrs_to_search.try_for_each(|addr_vec| {
1205                match addr_vec {
1206                    AddrVec::Conn(c) => {
1207                        if let Some(id) = sockets.socket_map.conns().get_by_addr(&c) {
1208                            return ControlFlow::Break(id);
1209                        }
1210                    }
1211                    AddrVec::Listen(l) => {
1212                        if let Some(id) = sockets.socket_map.listeners().get_by_addr(&l) {
1213                            return ControlFlow::Break(id);
1214                        }
1215                    }
1216                }
1217                ControlFlow::Continue(())
1218            }) {
1219                ControlFlow::Continue(()) => None,
1220                ControlFlow::Break(id) => Some(id),
1221            };
1222            if let Some(socket) = socket {
1223                trace!("receive_icmp_echo_reply: Received echo reply for local socket");
1224                match bindings_ctx.receive_icmp_echo_reply(
1225                    socket,
1226                    device,
1227                    src_ip.addr(),
1228                    dst_ip.addr(),
1229                    id.get(),
1230                    buffer,
1231                ) {
1232                    Ok(()) => {}
1233                    Err(ReceiveIcmpEchoError::QueueFull) => {
1234                        core_ctx.counters().queue_full.increment();
1235                    }
1236                }
1237                return;
1238            }
1239            // TODO(https://fxbug.dev/42124755): Neither the ICMPv4 or ICMPv6 RFCs
1240            // explicitly state what to do in case we receive an "unsolicited"
1241            // echo reply. We only expose the replies if we have a registered
1242            // connection for the IcmpAddr of the incoming reply for now. Given
1243            // that a reply should only be sent in response to a request, an
1244            // ICMP unreachable-type message is probably not appropriate for
1245            // unsolicited replies. However, it's also possible that we sent a
1246            // request and then closed the socket before receiving the reply, so
1247            // this doesn't necessarily indicate a buggy or malicious remote
1248            // host. We should figure this out definitively.
1249            //
1250            // If we do decide to send an ICMP error message, the appropriate
1251            // thing to do is probably to have this function return a `Result`,
1252            // and then have the top-level implementation of
1253            // `IpTransportContext::receive_ip_packet` return the
1254            // appropriate error.
1255            trace!("receive_icmp_echo_reply: Received echo reply with no local socket");
1256        });
1257        Ok(())
1258    }
1259}
1260
1261#[cfg(test)]
1262mod tests {
1263    use alloc::rc::Rc;
1264    use alloc::vec;
1265    use core::cell::RefCell;
1266    use core::ops::{Deref, DerefMut};
1267
1268    use assert_matches::assert_matches;
1269    use ip_test_macro::ip_test;
1270    use net_declare::net_ip_v6;
1271    use net_types::Witness;
1272    use net_types::ip::Ipv6;
1273    use netstack3_base::socket::StrictlyZonedAddr;
1274    use netstack3_base::testutil::{
1275        FakeBindingsCtx, FakeCoreCtx, FakeDeviceId, FakeSocketWritableListener, FakeWeakDeviceId,
1276        TestIpExt,
1277    };
1278    use netstack3_base::{CtxPair, Icmpv4ErrorCode, Icmpv6ErrorCode};
1279    use netstack3_ip::socket::testutil::{FakeDeviceConfig, FakeIpSocketCtx, InnerFakeIpSocketCtx};
1280    use netstack3_ip::{LocalDeliveryPacketInfo, SendIpPacketMeta};
1281    use packet::{Buf, EmptyBuf, Serializer};
1282    use packet_formats::icmp::{
1283        IcmpDestUnreachable, IcmpPacket, IcmpParseArgs, IcmpZeroCode, Icmpv4DestUnreachableCode,
1284        Icmpv6DestUnreachableCode,
1285    };
1286
1287    use super::*;
1288
1289    const REMOTE_ID: u16 = 27;
1290    const ICMP_ID: NonZeroU16 = NonZeroU16::new(10).unwrap();
1291    const SEQ_NUM: u16 = 0xF0;
1292
1293    /// Utilities for accessing locked internal state in tests.
1294    impl<I: IpExt, D: WeakDeviceIdentifier, BT: IcmpEchoBindingsTypes> IcmpSocketId<I, D, BT> {
1295        fn get(&self) -> impl Deref<Target = IcmpSocketState<I, D, BT>> + '_ {
1296            self.state().read()
1297        }
1298
1299        fn get_mut(&self) -> impl DerefMut<Target = IcmpSocketState<I, D, BT>> + '_ {
1300            self.state().write()
1301        }
1302    }
1303
1304    struct FakeIcmpCoreCtxState<I: IpExt> {
1305        bound_sockets:
1306            Rc<RefCell<BoundSockets<I, FakeWeakDeviceId<FakeDeviceId>, FakeIcmpBindingsCtx<I>>>>,
1307        all_sockets: IcmpSocketSet<I, FakeWeakDeviceId<FakeDeviceId>, FakeIcmpBindingsCtx<I>>,
1308        ip_socket_ctx: FakeIpSocketCtx<I, FakeDeviceId>,
1309        rx_counters: IcmpRxCounters<I>,
1310    }
1311
1312    impl<I: IpExt> InnerFakeIpSocketCtx<I, FakeDeviceId> for FakeIcmpCoreCtxState<I> {
1313        fn fake_ip_socket_ctx_mut(&mut self) -> &mut FakeIpSocketCtx<I, FakeDeviceId> {
1314            &mut self.ip_socket_ctx
1315        }
1316    }
1317
1318    impl<I: IpExt + TestIpExt> Default for FakeIcmpCoreCtxState<I> {
1319        fn default() -> Self {
1320            Self {
1321                bound_sockets: Default::default(),
1322                all_sockets: Default::default(),
1323                ip_socket_ctx: FakeIpSocketCtx::new(core::iter::once(FakeDeviceConfig {
1324                    device: FakeDeviceId,
1325                    local_ips: vec![I::TEST_ADDRS.local_ip],
1326                    remote_ips: vec![I::TEST_ADDRS.remote_ip],
1327                })),
1328                rx_counters: Default::default(),
1329            }
1330        }
1331    }
1332
1333    type FakeIcmpCoreCtx<I> = FakeCoreCtx<
1334        FakeIcmpCoreCtxState<I>,
1335        SendIpPacketMeta<I, FakeDeviceId, SpecifiedAddr<<I as Ip>::Addr>>,
1336        FakeDeviceId,
1337    >;
1338    type FakeIcmpBindingsCtx<I> = FakeBindingsCtx<(), (), FakeIcmpBindingsCtxState<I>, ()>;
1339    type FakeIcmpCtx<I> = CtxPair<FakeIcmpCoreCtx<I>, FakeIcmpBindingsCtx<I>>;
1340
1341    #[derive(Derivative)]
1342    #[derivative(Default)]
1343
1344    struct FakeIcmpBindingsCtxState<I: IpExt> {
1345        received: Vec<ReceivedEchoPacket<I>>,
1346        #[derivative(Default(value = "usize::MAX"))]
1347        max_size: usize,
1348    }
1349
1350    #[derive(Debug)]
1351    struct ReceivedEchoPacket<I: IpExt> {
1352        src_ip: I::Addr,
1353        dst_ip: I::Addr,
1354        socket: IcmpSocketId<I, FakeWeakDeviceId<FakeDeviceId>, FakeIcmpBindingsCtx<I>>,
1355        id: u16,
1356        data: Vec<u8>,
1357    }
1358
1359    impl<I: IpExt> IcmpEchoContextMarker for FakeIcmpCoreCtx<I> {}
1360
1361    impl<I: IpExt> CounterContext<IcmpRxCounters<I>> for FakeIcmpCoreCtxState<I> {
1362        fn counters(&self) -> &IcmpRxCounters<I> {
1363            &self.rx_counters
1364        }
1365    }
1366
1367    impl<I: IpExt> IcmpEchoBoundStateContext<I, FakeIcmpBindingsCtx<I>> for FakeIcmpCoreCtx<I> {
1368        type IpSocketsCtx<'a> = Self;
1369
1370        fn with_icmp_ctx_and_sockets_mut<
1371            O,
1372            F: FnOnce(
1373                &mut Self::IpSocketsCtx<'_>,
1374                &mut BoundSockets<I, Self::WeakDeviceId, FakeIcmpBindingsCtx<I>>,
1375            ) -> O,
1376        >(
1377            &mut self,
1378            cb: F,
1379        ) -> O {
1380            let bound_sockets = self.state.bound_sockets.clone();
1381            let mut bound_sockets = bound_sockets.borrow_mut();
1382            cb(self, &mut bound_sockets)
1383        }
1384    }
1385
1386    impl<I: IpExt> IcmpEchoStateContext<I, FakeIcmpBindingsCtx<I>> for FakeIcmpCoreCtx<I> {
1387        type SocketStateCtx<'a> = Self;
1388
1389        fn with_all_sockets_mut<
1390            O,
1391            F: FnOnce(&mut IcmpSocketSet<I, Self::WeakDeviceId, FakeIcmpBindingsCtx<I>>) -> O,
1392        >(
1393            &mut self,
1394            cb: F,
1395        ) -> O {
1396            cb(&mut self.state.all_sockets)
1397        }
1398
1399        fn with_all_sockets<
1400            O,
1401            F: FnOnce(&IcmpSocketSet<I, Self::WeakDeviceId, FakeIcmpBindingsCtx<I>>) -> O,
1402        >(
1403            &mut self,
1404            cb: F,
1405        ) -> O {
1406            cb(&self.state.all_sockets)
1407        }
1408
1409        fn with_socket_state<
1410            O,
1411            F: FnOnce(
1412                &mut Self::SocketStateCtx<'_>,
1413                &IcmpSocketState<I, Self::WeakDeviceId, FakeIcmpBindingsCtx<I>>,
1414            ) -> O,
1415        >(
1416            &mut self,
1417            id: &IcmpSocketId<I, Self::WeakDeviceId, FakeIcmpBindingsCtx<I>>,
1418            cb: F,
1419        ) -> O {
1420            cb(self, &id.get())
1421        }
1422
1423        fn with_socket_state_mut<
1424            O,
1425            F: FnOnce(
1426                &mut Self::SocketStateCtx<'_>,
1427                &mut IcmpSocketState<I, Self::WeakDeviceId, FakeIcmpBindingsCtx<I>>,
1428            ) -> O,
1429        >(
1430            &mut self,
1431            id: &IcmpSocketId<I, Self::WeakDeviceId, FakeIcmpBindingsCtx<I>>,
1432            cb: F,
1433        ) -> O {
1434            cb(self, &mut id.get_mut())
1435        }
1436
1437        fn with_bound_state_context<O, F: FnOnce(&mut Self::SocketStateCtx<'_>) -> O>(
1438            &mut self,
1439            cb: F,
1440        ) -> O {
1441            cb(self)
1442        }
1443
1444        fn for_each_socket<
1445            F: FnMut(
1446                &mut Self::SocketStateCtx<'_>,
1447                &IcmpSocketId<I, Self::WeakDeviceId, FakeIcmpBindingsCtx<I>>,
1448                &IcmpSocketState<I, Self::WeakDeviceId, FakeIcmpBindingsCtx<I>>,
1449            ),
1450        >(
1451            &mut self,
1452            mut cb: F,
1453        ) {
1454            let socks = self
1455                .state
1456                .all_sockets
1457                .keys()
1458                .map(|id| IcmpSocketId::from(id.clone()))
1459                .collect::<Vec<_>>();
1460            for id in socks {
1461                cb(self, &id, &id.get());
1462            }
1463        }
1464    }
1465
1466    impl<I: IpExt> IcmpEchoBindingsContext<I, FakeDeviceId> for FakeIcmpBindingsCtx<I> {
1467        fn receive_icmp_echo_reply<B: BufferMut>(
1468            &mut self,
1469            socket: &IcmpSocketId<I, FakeWeakDeviceId<FakeDeviceId>, FakeIcmpBindingsCtx<I>>,
1470            _device_id: &FakeDeviceId,
1471            src_ip: I::Addr,
1472            dst_ip: I::Addr,
1473            id: u16,
1474            data: B,
1475        ) -> Result<(), ReceiveIcmpEchoError> {
1476            if self.state.received.len() < self.state.max_size {
1477                self.state.received.push(ReceivedEchoPacket {
1478                    src_ip,
1479                    dst_ip,
1480                    id,
1481                    data: data.to_flattened_vec(),
1482                    socket: socket.clone(),
1483                });
1484                Ok(())
1485            } else {
1486                Err(ReceiveIcmpEchoError::QueueFull)
1487            }
1488        }
1489    }
1490
1491    impl<I: IpExt> IcmpEchoBindingsTypes for FakeIcmpBindingsCtx<I> {
1492        type ExternalData<II: Ip> = ();
1493        type SocketWritableListener = FakeSocketWritableListener;
1494    }
1495
1496    #[test]
1497    fn test_connect_dual_stack_fails() {
1498        // Verify that connecting to an ipv4-mapped-ipv6 address fails, as ICMP
1499        // sockets do not support dual-stack operations.
1500        let mut ctx = FakeIcmpCtx::<Ipv6>::default();
1501        let mut api = IcmpEchoSocketApi::<Ipv6, _>::new(ctx.as_mut());
1502        let conn = api.create();
1503        assert_eq!(
1504            api.connect(
1505                &conn,
1506                Some(ZonedAddr::Unzoned(
1507                    SpecifiedAddr::new(net_ip_v6!("::ffff:192.0.2.1")).unwrap(),
1508                )),
1509                REMOTE_ID,
1510            ),
1511            Err(datagram::ConnectError::RemoteUnexpectedlyMapped)
1512        );
1513    }
1514
1515    #[ip_test(I)]
1516    fn send_invalid_icmp_echo<I: TestIpExt + IpExt>() {
1517        let mut ctx = FakeIcmpCtx::<I>::default();
1518        let mut api = IcmpEchoSocketApi::<I, _>::new(ctx.as_mut());
1519        let conn = api.create();
1520        api.connect(&conn, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.remote_ip)), REMOTE_ID).unwrap();
1521
1522        let pb = IcmpPacketBuilder::<I, _>::new(
1523            I::TEST_ADDRS.local_ip.get(),
1524            I::TEST_ADDRS.remote_ip.get(),
1525            IcmpZeroCode,
1526            packet_formats::icmp::IcmpEchoReply::new(0, 1),
1527        );
1528        let buf =
1529            pb.wrap_body(Buf::new(Vec::new(), ..)).serialize_vec_outer().unwrap().into_inner();
1530        assert_matches!(
1531            api.send(&conn, buf),
1532            Err(datagram::SendError::SerializeError(
1533                packet_formats::error::ParseError::NotExpected
1534            ))
1535        );
1536    }
1537
1538    #[ip_test(I)]
1539    fn get_info<I: TestIpExt + IpExt>() {
1540        let mut ctx = FakeIcmpCtx::<I>::default();
1541        let mut api = IcmpEchoSocketApi::<I, _>::new(ctx.as_mut());
1542
1543        let id = api.create();
1544        assert_eq!(api.get_info(&id), datagram::SocketInfo::Unbound);
1545
1546        api.bind(&id, None, Some(ICMP_ID)).unwrap();
1547        assert_eq!(
1548            api.get_info(&id),
1549            datagram::SocketInfo::Listener(datagram::ListenerInfo {
1550                local_ip: None,
1551                local_identifier: ICMP_ID
1552            })
1553        );
1554
1555        api.connect(&id, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.remote_ip)), REMOTE_ID).unwrap();
1556        assert_eq!(
1557            api.get_info(&id),
1558            datagram::SocketInfo::Connected(datagram::ConnInfo {
1559                local_ip: StrictlyZonedAddr::new_unzoned_or_panic(I::TEST_ADDRS.local_ip),
1560                local_identifier: ICMP_ID,
1561                remote_ip: StrictlyZonedAddr::new_unzoned_or_panic(I::TEST_ADDRS.remote_ip),
1562                remote_identifier: REMOTE_ID,
1563            })
1564        );
1565    }
1566
1567    #[ip_test(I)]
1568    fn send<I: TestIpExt + IpExt>() {
1569        let mut ctx = FakeIcmpCtx::<I>::default();
1570        let mut api = IcmpEchoSocketApi::<I, _>::new(ctx.as_mut());
1571        let sock = api.create();
1572
1573        api.bind(&sock, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.local_ip)), Some(ICMP_ID)).unwrap();
1574        api.connect(&sock, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.remote_ip)), REMOTE_ID).unwrap();
1575
1576        let packet = Buf::new([1u8, 2, 3, 4], ..)
1577            .wrap_in(IcmpPacketBuilder::<I, _>::new(
1578                I::UNSPECIFIED_ADDRESS,
1579                I::UNSPECIFIED_ADDRESS,
1580                IcmpZeroCode,
1581                // Use 0 here to show that this is filled by the API.
1582                IcmpEchoRequest::new(0, SEQ_NUM),
1583            ))
1584            .serialize_vec_outer()
1585            .unwrap()
1586            .unwrap_b();
1587        api.send(&sock, Buf::new(packet, ..)).unwrap();
1588        let frames = ctx.core_ctx.frames.take_frames();
1589        let (SendIpPacketMeta { device: _, src_ip, dst_ip, .. }, body) =
1590            assert_matches!(&frames[..], [f] => f);
1591        assert_eq!(dst_ip, &I::TEST_ADDRS.remote_ip);
1592
1593        let mut body = &body[..];
1594        let echo_req: IcmpPacket<I, _, IcmpEchoRequest> =
1595            body.parse_with(IcmpParseArgs::new(src_ip.get(), dst_ip.get())).unwrap();
1596        assert_eq!(echo_req.message().id(), ICMP_ID.get());
1597        assert_eq!(echo_req.message().seq(), SEQ_NUM);
1598    }
1599
1600    #[ip_test(I)]
1601    fn receive<I: TestIpExt + IpExt>() {
1602        let mut ctx = FakeIcmpCtx::<I>::default();
1603        let mut api = IcmpEchoSocketApi::<I, _>::new(ctx.as_mut());
1604        let sock = api.create();
1605
1606        api.bind(&sock, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.local_ip)), Some(ICMP_ID)).unwrap();
1607
1608        let reply = IcmpPacketBuilder::<I, _>::new(
1609            // Use whatever here this is not validated by this module.
1610            I::UNSPECIFIED_ADDRESS,
1611            I::UNSPECIFIED_ADDRESS,
1612            IcmpZeroCode,
1613            IcmpEchoReply::new(ICMP_ID.get(), SEQ_NUM),
1614        )
1615        .wrap_body(Buf::new([1u8, 2, 3, 4], ..))
1616        .serialize_vec_outer()
1617        .unwrap();
1618
1619        let CtxPair { core_ctx, bindings_ctx } = &mut ctx;
1620        let src_ip = I::TEST_ADDRS.remote_ip;
1621        let dst_ip = I::TEST_ADDRS.local_ip;
1622        <IcmpEchoIpTransportContext as IpTransportContext<I, _, _>>::receive_ip_packet(
1623            core_ctx,
1624            bindings_ctx,
1625            &FakeDeviceId,
1626            I::RecvSrcAddr::new(src_ip.get()).unwrap(),
1627            dst_ip,
1628            reply.clone(),
1629            &LocalDeliveryPacketInfo::default(),
1630            None,
1631        )
1632        .unwrap();
1633
1634        let received = core::mem::take(&mut bindings_ctx.state.received);
1635        let ReceivedEchoPacket {
1636            src_ip: got_src_ip,
1637            dst_ip: got_dst_ip,
1638            socket: got_socket,
1639            id: got_id,
1640            data: got_data,
1641        } = assert_matches!(&received[..], [f] => f);
1642        assert_eq!(got_src_ip, &src_ip.get());
1643        assert_eq!(got_dst_ip, &dst_ip.get());
1644        assert_eq!(got_socket, &sock);
1645        assert_eq!(got_id, &ICMP_ID.get());
1646        assert_eq!(&got_data[..], reply.as_ref());
1647    }
1648
1649    #[ip_test(I)]
1650    fn receive_no_socket<I: TestIpExt + IpExt>() {
1651        let mut ctx = FakeIcmpCtx::<I>::default();
1652        let mut api = IcmpEchoSocketApi::<I, _>::new(ctx.as_mut());
1653        let sock = api.create();
1654
1655        const BIND_ICMP_ID: NonZeroU16 = NonZeroU16::new(10).unwrap();
1656        const OTHER_ICMP_ID: NonZeroU16 = NonZeroU16::new(16).unwrap();
1657
1658        api.bind(&sock, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.local_ip)), Some(BIND_ICMP_ID))
1659            .unwrap();
1660
1661        let reply = IcmpPacketBuilder::<I, _>::new(
1662            // Use whatever here this is not validated by this module.
1663            I::UNSPECIFIED_ADDRESS,
1664            I::UNSPECIFIED_ADDRESS,
1665            IcmpZeroCode,
1666            IcmpEchoReply::new(OTHER_ICMP_ID.get(), SEQ_NUM),
1667        )
1668        .wrap_body(EmptyBuf)
1669        .serialize_vec_outer()
1670        .unwrap();
1671
1672        let CtxPair { core_ctx, bindings_ctx } = &mut ctx;
1673        <IcmpEchoIpTransportContext as IpTransportContext<I, _, _>>::receive_ip_packet(
1674            core_ctx,
1675            bindings_ctx,
1676            &FakeDeviceId,
1677            I::RecvSrcAddr::new(I::TEST_ADDRS.remote_ip.get()).unwrap(),
1678            I::TEST_ADDRS.local_ip,
1679            reply,
1680            &LocalDeliveryPacketInfo::default(),
1681            None,
1682        )
1683        .unwrap();
1684        assert_matches!(&bindings_ctx.state.received[..], []);
1685    }
1686
1687    #[ip_test(I)]
1688    fn receive_queue_full<I: TestIpExt + IpExt>() {
1689        let mut ctx = FakeIcmpCtx::<I>::default();
1690        let mut api = IcmpEchoSocketApi::<I, _>::new(ctx.as_mut());
1691        let sock = api.create();
1692
1693        api.bind(&sock, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.local_ip)), Some(ICMP_ID)).unwrap();
1694
1695        // Simulate a full RX queue.
1696        let CtxPair { core_ctx, bindings_ctx } = &mut ctx;
1697        bindings_ctx.state.max_size = 0;
1698
1699        let reply = IcmpPacketBuilder::<I, _>::new(
1700            // Use whatever here this is not validated by this module.
1701            I::UNSPECIFIED_ADDRESS,
1702            I::UNSPECIFIED_ADDRESS,
1703            IcmpZeroCode,
1704            IcmpEchoReply::new(ICMP_ID.get(), SEQ_NUM),
1705        )
1706        .wrap_body(Buf::new([1u8, 2, 3, 4], ..))
1707        .serialize_vec_outer()
1708        .unwrap();
1709
1710        let src_ip = I::TEST_ADDRS.remote_ip;
1711        let dst_ip = I::TEST_ADDRS.local_ip;
1712        <IcmpEchoIpTransportContext as IpTransportContext<I, _, _>>::receive_ip_packet(
1713            core_ctx,
1714            bindings_ctx,
1715            &FakeDeviceId,
1716            I::RecvSrcAddr::new(src_ip.get()).unwrap(),
1717            dst_ip,
1718            reply,
1719            &LocalDeliveryPacketInfo::default(),
1720            None,
1721        )
1722        .unwrap();
1723
1724        assert_eq!(core_ctx.counters().queue_full.get(), 1);
1725    }
1726
1727    #[ip_test(I)]
1728    #[test_case::test_matrix(
1729        [MarkDomain::Mark1, MarkDomain::Mark2],
1730        [None, Some(0), Some(1)]
1731    )]
1732    fn icmp_socket_marks<I: TestIpExt + IpExt>(domain: MarkDomain, mark: Option<u32>) {
1733        let mut ctx = FakeIcmpCtx::<I>::default();
1734        let mut api = IcmpEchoSocketApi::<I, _>::new(ctx.as_mut());
1735        let socket = api.create();
1736
1737        // Doesn't have a mark by default.
1738        assert_eq!(api.get_mark(&socket, domain), Mark(None));
1739
1740        let mark = Mark(mark);
1741        // We can set and get back the mark.
1742        api.set_mark(&socket, domain, mark);
1743        assert_eq!(api.get_mark(&socket, domain), mark);
1744    }
1745
1746    /// A test for the crash seen in https://fxbug.dev/445389479.
1747    ///
1748    /// Because the netstack assumes it could never send an ICMP echo request
1749    /// with an ID of 0, that it would never see an echo request inside an ICMP
1750    /// error with an ID of 0.
1751    #[ip_test(I)]
1752    fn icmp_error_with_inner_icmp_echo_with_id_0<I: TestIpExt + IpExt>() {
1753        let mut ctx = FakeIcmpCtx::<I>::default();
1754
1755        let src_ip = I::TEST_ADDRS.remote_ip;
1756        let dst_ip = I::TEST_ADDRS.local_ip;
1757
1758        let original_body = Buf::new([1, 2, 3, 4], ..)
1759            .wrap_in(IcmpPacketBuilder::<I, _>::new(
1760                *src_ip,
1761                *dst_ip,
1762                IcmpZeroCode,
1763                // The zero below is sorta invalid because we'd never send an
1764                // ICMP echo with an ID of 0.
1765                IcmpEchoRequest::new(0, 1),
1766            ))
1767            .serialize_vec_outer()
1768            .unwrap()
1769            .unwrap_b();
1770
1771        let CtxPair { core_ctx, bindings_ctx } = &mut ctx;
1772        <IcmpEchoIpTransportContext as IpTransportContext<I, _, _>>::receive_icmp_error(
1773            core_ctx,
1774            bindings_ctx,
1775            &FakeDeviceId,
1776            Some(src_ip),
1777            dst_ip,
1778            original_body.as_ref(),
1779            I::map_ip_out(
1780                (),
1781                |()| {
1782                    Icmpv4ErrorCode::DestUnreachable(
1783                        Icmpv4DestUnreachableCode::DestNetworkUnreachable,
1784                        IcmpDestUnreachable::default(),
1785                    )
1786                },
1787                |()| Icmpv6ErrorCode::DestUnreachable(Icmpv6DestUnreachableCode::AddrUnreachable),
1788            ),
1789        )
1790    }
1791}