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