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