| // Copyright 2019 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| //! IPv4 and IPv6 sockets. |
| |
| use core::cmp::Ordering; |
| use core::convert::Infallible; |
| use core::num::{NonZeroU32, NonZeroU8}; |
| |
| use net_types::{ |
| ip::{Ip, Ipv6Addr, Ipv6SourceAddr, Mtu}, |
| SpecifiedAddr, |
| }; |
| use packet::{BufferMut, SerializeError}; |
| use thiserror::Error; |
| |
| use crate::{ |
| context::{CounterContext, InstantContext, NonTestCtxMarker, TracingContext}, |
| device::{AnyDevice, DeviceIdContext}, |
| filter::{ |
| FilterBindingsTypes, FilterHandler as _, FilterHandlerProvider, TransportPacketSerializer, |
| }, |
| ip::{ |
| device::{state::IpDeviceStateIpExt, IpDeviceAddr}, |
| types::{NextHop, ResolvedRoute, RoutableIpAddr}, |
| EitherDeviceId, IpCounters, IpDeviceContext, IpExt, IpLayerIpExt, ResolveRouteError, |
| SendIpPacketMeta, |
| }, |
| socket::address::SocketIpAddr, |
| trace_duration, |
| }; |
| |
| /// An execution context defining a type of IP socket. |
| pub trait IpSocketHandler<I: IpExt, BC>: DeviceIdContext<AnyDevice> { |
| /// Constructs a new [`IpSock`]. |
| /// |
| /// `new_ip_socket` constructs a new `IpSock` to the given remote IP |
| /// address from the given local IP address with the given IP protocol. If |
| /// no local IP address is given, one will be chosen automatically. If |
| /// `device` is `Some`, the socket will be bound to the given device - only |
| /// routes which egress over the device will be used. If no route is |
| /// available which egresses over the device - even if routes are available |
| /// which egress over other devices - the socket will be considered |
| /// unroutable. |
| /// |
| /// `new_ip_socket` returns an error if no route to the remote was found in |
| /// the forwarding table or if the given local IP address is not valid for |
| /// the found route. |
| fn new_ip_socket<O>( |
| &mut self, |
| bindings_ctx: &mut BC, |
| device: Option<EitherDeviceId<&Self::DeviceId, &Self::WeakDeviceId>>, |
| local_ip: Option<SocketIpAddr<I::Addr>>, |
| remote_ip: SocketIpAddr<I::Addr>, |
| proto: I::Proto, |
| options: O, |
| ) -> Result<IpSock<I, Self::WeakDeviceId, O>, (IpSockCreationError, O)>; |
| |
| /// Sends an IP packet on a socket. |
| /// |
| /// The generated packet has its metadata initialized from `socket`, |
| /// including the source and destination addresses, the Time To Live/Hop |
| /// Limit, and the Protocol/Next Header. The outbound device is also chosen |
| /// based on information stored in the socket. |
| /// |
| /// `mtu` may be used to optionally impose an MTU on the outgoing packet. |
| /// Note that the device's MTU will still be imposed on the packet. That is, |
| /// the smaller of `mtu` and the device's MTU will be imposed on the packet. |
| /// |
| /// If the socket is currently unroutable, an error is returned. |
| fn send_ip_packet<S, O>( |
| &mut self, |
| bindings_ctx: &mut BC, |
| socket: &IpSock<I, Self::WeakDeviceId, O>, |
| body: S, |
| mtu: Option<u32>, |
| ) -> Result<(), (S, IpSockSendError)> |
| where |
| S: TransportPacketSerializer, |
| S::Buffer: BufferMut, |
| O: SendOptions<I>; |
| |
| /// Creates a temporary IP socket and sends a single packet on it. |
| /// |
| /// `local_ip`, `remote_ip`, `proto`, and `options` are passed directly to |
| /// [`IpSocketHandler::new_ip_socket`]. `get_body_from_src_ip` is given the |
| /// source IP address for the packet - which may have been chosen |
| /// automatically if `local_ip` is `None` - and returns the body to be |
| /// encapsulated. This is provided in case the body's contents depend on the |
| /// chosen source IP address. |
| /// |
| /// If `device` is specified, the available routes are limited to those that |
| /// egress over the device. |
| /// |
| /// `mtu` may be used to optionally impose an MTU on the outgoing packet. |
| /// Note that the device's MTU will still be imposed on the packet. That is, |
| /// the smaller of `mtu` and the device's MTU will be imposed on the packet. |
| /// |
| /// # Errors |
| /// |
| /// If an error is encountered while constructing the temporary IP socket |
| /// or sending the packet, `options` will be returned along with the |
| /// error. `get_body_from_src_ip` is fallible, and if there's an error, |
| /// it will be returned as well. |
| fn send_oneshot_ip_packet_with_fallible_serializer<S, E, F, O>( |
| &mut self, |
| bindings_ctx: &mut BC, |
| device: Option<EitherDeviceId<&Self::DeviceId, &Self::WeakDeviceId>>, |
| local_ip: Option<IpDeviceAddr<I::Addr>>, |
| remote_ip: RoutableIpAddr<I::Addr>, |
| proto: I::Proto, |
| options: O, |
| get_body_from_src_ip: F, |
| mtu: Option<u32>, |
| ) -> Result<(), SendOneShotIpPacketError<O, E>> |
| where |
| S: TransportPacketSerializer, |
| S::Buffer: BufferMut, |
| F: FnOnce(SocketIpAddr<I::Addr>) -> Result<S, E>, |
| O: SendOptions<I>, |
| { |
| let tmp = self |
| .new_ip_socket(bindings_ctx, device, local_ip, remote_ip, proto, options) |
| .map_err(|(err, options)| SendOneShotIpPacketError::CreateAndSendError { |
| err: err.into(), |
| options, |
| })?; |
| let packet = get_body_from_src_ip(*tmp.local_ip()) |
| .map_err(SendOneShotIpPacketError::SerializeError)?; |
| self.send_ip_packet(bindings_ctx, &tmp, packet, mtu).map_err(|(_body, err)| { |
| let IpSock { options, definition: _ } = tmp; |
| SendOneShotIpPacketError::CreateAndSendError { err: err.into(), options } |
| }) |
| } |
| |
| /// Sends a one-shot IP packet but with a non-fallible serializer. |
| fn send_oneshot_ip_packet<S, F, O>( |
| &mut self, |
| bindings_ctx: &mut BC, |
| device: Option<EitherDeviceId<&Self::DeviceId, &Self::WeakDeviceId>>, |
| local_ip: Option<SocketIpAddr<I::Addr>>, |
| remote_ip: SocketIpAddr<I::Addr>, |
| proto: I::Proto, |
| options: O, |
| get_body_from_src_ip: F, |
| mtu: Option<u32>, |
| ) -> Result<(), (IpSockCreateAndSendError, O)> |
| where |
| S: TransportPacketSerializer, |
| S::Buffer: BufferMut, |
| F: FnOnce(SocketIpAddr<I::Addr>) -> S, |
| O: SendOptions<I>, |
| { |
| self.send_oneshot_ip_packet_with_fallible_serializer( |
| bindings_ctx, |
| device, |
| local_ip, |
| remote_ip, |
| proto, |
| options, |
| |ip| Ok::<_, Infallible>(get_body_from_src_ip(ip)), |
| mtu, |
| ) |
| .map_err(|err| match err { |
| SendOneShotIpPacketError::CreateAndSendError { err, options } => (err, options), |
| SendOneShotIpPacketError::SerializeError(infallible) => match infallible {}, |
| }) |
| } |
| } |
| |
| /// An error in sending a packet on an IP socket. |
| #[derive(Error, Copy, Clone, Debug, Eq, PartialEq)] |
| pub enum IpSockSendError { |
| /// An MTU was exceeded. |
| /// |
| /// This could be caused by an MTU at any layer of the stack, including both |
| /// device MTUs and packet format body size limits. |
| #[error("a maximum transmission unit (MTU) was exceeded")] |
| Mtu, |
| /// The socket is currently unroutable. |
| #[error("the socket is currently unroutable: {}", _0)] |
| Unroutable(#[from] ResolveRouteError), |
| } |
| |
| impl From<SerializeError<Infallible>> for IpSockSendError { |
| fn from(err: SerializeError<Infallible>) -> IpSockSendError { |
| match err { |
| SerializeError::Alloc(err) => match err {}, |
| SerializeError::SizeLimitExceeded => IpSockSendError::Mtu, |
| } |
| } |
| } |
| |
| /// An error in sending a packet on a temporary IP socket. |
| #[derive(Error, Copy, Clone, Debug)] |
| pub enum IpSockCreateAndSendError { |
| /// Cannot send via temporary socket. |
| #[error("cannot send via temporary socket: {}", _0)] |
| Send(#[from] IpSockSendError), |
| /// The temporary socket could not be created. |
| #[error("the temporary socket could not be created: {}", _0)] |
| Create(#[from] IpSockCreationError), |
| } |
| |
| #[derive(Debug)] |
| pub enum SendOneShotIpPacketError<O, E> { |
| CreateAndSendError { err: IpSockCreateAndSendError, options: O }, |
| SerializeError(E), |
| } |
| |
| /// Extension trait for `Ip` providing socket-specific functionality. |
| pub(crate) trait SocketIpExt: Ip + IpExt { |
| /// `Self::LOOPBACK_ADDRESS`, but wrapped in the `SocketIpAddr` type. |
| const LOOPBACK_ADDRESS_AS_SOCKET_IP_ADDR: SocketIpAddr<Self::Addr> = unsafe { |
| // SAFETY: The loopback address is a valid SocketIpAddr, as verified |
| // in the `loopback_addr_is_valid_socket_addr` test. |
| SocketIpAddr::new_from_specified_unchecked(Self::LOOPBACK_ADDRESS) |
| }; |
| } |
| |
| impl<I: Ip + IpExt> SocketIpExt for I {} |
| |
| #[cfg(test)] |
| mod socket_ip_ext_test { |
| use super::*; |
| use ip_test_macro::ip_test; |
| use net_types::ip::{Ipv4, Ipv6}; |
| |
| #[ip_test] |
| fn loopback_addr_is_valid_socket_addr<I: Ip + SocketIpExt>() { |
| // `LOOPBACK_ADDRESS_AS_SOCKET_IP_ADDR is defined with the "unchecked" |
| // constructor (which supports const construction). Verify here that the |
| // addr actually satisfies all the requirements (protecting against far |
| // away changes) |
| let _addr = SocketIpAddr::new(I::LOOPBACK_ADDRESS_AS_SOCKET_IP_ADDR.addr()) |
| .expect("loopback address should be a valid SocketIpAddr"); |
| } |
| } |
| |
| /// Maximum packet size, that is the maximum IP payload one packet can carry. |
| /// |
| /// More details from https://www.rfc-editor.org/rfc/rfc1122#section-3.3.2. |
| #[derive(Clone, Copy, Debug, PartialEq, Eq)] |
| pub(crate) struct Mms(NonZeroU32); |
| |
| impl Mms { |
| pub(crate) fn from_mtu<I: IpExt>(mtu: Mtu, options_size: u32) -> Option<Self> { |
| NonZeroU32::new(mtu.get().saturating_sub(I::IP_HEADER_LENGTH.get() + options_size)) |
| .map(|mms| Self(mms.min(I::IP_MAX_PAYLOAD_LENGTH))) |
| } |
| |
| pub(crate) fn get(&self) -> NonZeroU32 { |
| let Self(mms) = *self; |
| mms |
| } |
| } |
| |
| /// Possible errors when retrieving the maximum transport message size. |
| #[derive(Error, Copy, Clone, Debug, Eq, PartialEq)] |
| pub(crate) enum MmsError { |
| /// Cannot find the device that is used for the ip socket, possibly because |
| /// there is no route. |
| #[error("cannot find the device: {}", _0)] |
| NoDevice(#[from] ResolveRouteError), |
| /// The MTU provided by the device is too small such that there is no room |
| /// for a transport message at all. |
| #[error("invalid MTU: {:?}", _0)] |
| MTUTooSmall(Mtu), |
| } |
| |
| /// Gets device related information of an IP socket. |
| pub trait DeviceIpSocketHandler<I, BC>: DeviceIdContext<AnyDevice> |
| where |
| I: IpLayerIpExt, |
| { |
| /// Gets the maximum message size for the transport layer, it equals the |
| /// device MTU minus the IP header size. |
| /// |
| /// This corresponds to the GET_MAXSIZES call described in: |
| /// https://www.rfc-editor.org/rfc/rfc1122#section-3.4 |
| fn get_mms<O: SendOptions<I>>( |
| &mut self, |
| bindings_ctx: &mut BC, |
| ip_sock: &IpSock<I, Self::WeakDeviceId, O>, |
| ) -> Result<Mms, MmsError>; |
| } |
| |
| /// An error encountered when creating an IP socket. |
| #[derive(Error, Copy, Clone, Debug, Eq, PartialEq)] |
| pub enum IpSockCreationError { |
| /// An error occurred while looking up a route. |
| #[error("a route cannot be determined: {}", _0)] |
| Route(#[from] ResolveRouteError), |
| } |
| |
| /// An IP socket. |
| #[derive(Clone, Debug)] |
| #[cfg_attr(test, derive(PartialEq))] |
| pub struct IpSock<I: IpExt, D, O> { |
| /// The definition of the socket. |
| /// |
| /// This does not change for the lifetime of the socket. |
| definition: IpSockDefinition<I, D>, |
| /// Options set on the socket that are independent of the socket definition. |
| /// |
| /// TODO(https://fxbug.dev/42115343): use this to record multicast options. |
| #[allow(unused)] |
| options: O, |
| } |
| |
| /// The definition of an IP socket. |
| /// |
| /// These values are part of the socket's definition, and never change. |
| #[derive(Clone, Debug)] |
| #[cfg_attr(test, derive(PartialEq))] |
| pub(crate) struct IpSockDefinition<I: IpExt, D> { |
| remote_ip: SocketIpAddr<I::Addr>, |
| // Guaranteed to be unicast in its subnet since it's always equal to an |
| // address assigned to the local device. We can't use the `UnicastAddr` |
| // witness type since `Ipv4Addr` doesn't implement `UnicastAddress`. |
| // |
| // TODO(joshlf): Support unnumbered interfaces. Once we do that, a few |
| // issues arise: A) Does the unicast restriction still apply, and is that |
| // even well-defined for IPv4 in the absence of a subnet? B) Presumably we |
| // have to always bind to a particular interface? |
| local_ip: SocketIpAddr<I::Addr>, |
| device: Option<D>, |
| proto: I::Proto, |
| } |
| |
| impl<I: IpExt, D, O> IpSock<I, D, O> { |
| pub(crate) fn local_ip(&self) -> &SocketIpAddr<I::Addr> { |
| &self.definition.local_ip |
| } |
| |
| pub(crate) fn remote_ip(&self) -> &SocketIpAddr<I::Addr> { |
| &self.definition.remote_ip |
| } |
| |
| pub(crate) fn device(&self) -> Option<&D> { |
| self.definition.device.as_ref() |
| } |
| |
| pub(crate) fn proto(&self) -> I::Proto { |
| self.definition.proto |
| } |
| |
| pub(crate) fn options_mut(&mut self) -> &mut O { |
| &mut self.options |
| } |
| |
| /// Swaps in `new_options` for the existing options and returns the old |
| /// options. |
| pub(crate) fn replace_options(&mut self, new_options: O) -> O { |
| core::mem::replace(self.options_mut(), new_options) |
| } |
| |
| pub(crate) fn take_options(&mut self) -> O |
| where |
| O: Default, |
| { |
| self.replace_options(Default::default()) |
| } |
| } |
| |
| // TODO(joshlf): Once we support configuring transport-layer protocols using |
| // type parameters, use that to ensure that `proto` is the right protocol for |
| // the caller. We will still need to have a separate enforcement mechanism for |
| // raw IP sockets once we support those. |
| |
| /// The bindings execution context for IP sockets. |
| pub trait IpSocketBindingsContext: InstantContext + TracingContext + FilterBindingsTypes {} |
| impl<BC: InstantContext + TracingContext + FilterBindingsTypes> IpSocketBindingsContext for BC {} |
| |
| /// The context required in order to implement [`IpSocketHandler`]. |
| /// |
| /// Blanket impls of `IpSocketHandler` are provided in terms of |
| /// `IpSocketContext`. |
| pub trait IpSocketContext<I, BC: IpSocketBindingsContext>: |
| DeviceIdContext<AnyDevice> + FilterHandlerProvider<I, BC> |
| where |
| I: IpDeviceStateIpExt + IpExt, |
| { |
| /// Returns a route for a socket. |
| /// |
| /// If `device` is specified, the available routes are limited to those that |
| /// egress over the device. |
| fn lookup_route( |
| &mut self, |
| bindings_ctx: &mut BC, |
| device: Option<&Self::DeviceId>, |
| src_ip: Option<IpDeviceAddr<I::Addr>>, |
| dst_ip: RoutableIpAddr<I::Addr>, |
| ) -> Result<ResolvedRoute<I, Self::DeviceId>, ResolveRouteError>; |
| |
| /// Send an IP packet to the next-hop node. |
| fn send_ip_packet<S>( |
| &mut self, |
| bindings_ctx: &mut BC, |
| meta: SendIpPacketMeta<I, &Self::DeviceId, SpecifiedAddr<I::Addr>>, |
| body: S, |
| ) -> Result<(), S> |
| where |
| S: TransportPacketSerializer, |
| S::Buffer: BufferMut; |
| } |
| |
| impl<I, BC, CC> IpSocketHandler<I, BC> for CC |
| where |
| I: IpLayerIpExt + IpDeviceStateIpExt, |
| BC: IpSocketBindingsContext, |
| CC: IpSocketContext<I, BC> + CounterContext<IpCounters<I>>, |
| CC::DeviceId: crate::filter::InterfaceProperties<BC::DeviceClass>, |
| { |
| fn new_ip_socket<O>( |
| &mut self, |
| bindings_ctx: &mut BC, |
| device: Option<EitherDeviceId<&CC::DeviceId, &CC::WeakDeviceId>>, |
| local_ip: Option<SocketIpAddr<I::Addr>>, |
| remote_ip: SocketIpAddr<I::Addr>, |
| proto: I::Proto, |
| options: O, |
| ) -> Result<IpSock<I, CC::WeakDeviceId, O>, (IpSockCreationError, O)> { |
| let device = if let Some(device) = device.as_ref() { |
| if let Some(device) = device.as_strong_ref(self) { |
| Some(device) |
| } else { |
| return Err((IpSockCreationError::Route(ResolveRouteError::Unreachable), options)); |
| } |
| } else { |
| None |
| }; |
| |
| let device = device.as_ref().map(|d| d.as_ref()); |
| |
| // Make sure the remote is routable with a local address before creating |
| // the socket. We do not care about the actual destination here because |
| // we will recalculate it when we send a packet so that the best route |
| // available at the time is used for each outgoing packet. |
| // |
| // TODO(https://fxbug.dev/323389672): Cache a reference to the route to |
| // avoid the route lookup on send as long as the routing table hasn't |
| // changed in between these operations. |
| let ResolvedRoute { src_addr, device: route_device, local_delivery_device, next_hop: _ } = |
| match self.lookup_route(bindings_ctx, device, local_ip, remote_ip) { |
| Ok(r) => r, |
| Err(e) => return Err((e.into(), options)), |
| }; |
| |
| // If the source or destination address require a device, make sure to |
| // set that in the socket definition. Otherwise defer to what was provided. |
| let socket_device = (crate::socket::must_have_zone(src_addr.as_ref()) |
| || crate::socket::must_have_zone(remote_ip.as_ref())) |
| .then(|| { |
| // NB: The route device might be loopback, and in such cases |
| // we want to bind the socket to the device the source IP is |
| // assigned to instead. |
| local_delivery_device.unwrap_or(route_device) |
| }) |
| .as_ref() |
| .or(device) |
| .map(|d| self.downgrade_device_id(d)); |
| |
| let definition = |
| IpSockDefinition { local_ip: src_addr, remote_ip, device: socket_device, proto }; |
| Ok(IpSock { definition: definition, options }) |
| } |
| |
| fn send_ip_packet<S, O>( |
| &mut self, |
| bindings_ctx: &mut BC, |
| ip_sock: &IpSock<I, CC::WeakDeviceId, O>, |
| body: S, |
| mtu: Option<u32>, |
| ) -> Result<(), (S, IpSockSendError)> |
| where |
| S: TransportPacketSerializer, |
| S::Buffer: BufferMut, |
| O: SendOptions<I>, |
| { |
| // TODO(joshlf): Call `trace!` with relevant fields from the socket. |
| self.increment(|counters| &counters.send_ip_packet); |
| |
| send_ip_packet(self, bindings_ctx, ip_sock, body, mtu) |
| } |
| } |
| |
| /// Provides hooks for altering sending behavior of [`IpSock`]. |
| /// |
| /// Must be implemented by the socket option type of an `IpSock` when using it |
| /// to call [`IpSocketHandler::send_ip_packet`]. This is implemented as a trait |
| /// instead of an inherent impl on a type so that users of sockets that don't |
| /// need certain option types, like TCP for anything multicast-related, can |
| /// avoid allocating space for those options. |
| pub trait SendOptions<I: Ip> { |
| /// Returns the hop limit to set on a packet going to the given destination. |
| /// |
| /// If `Some(u)`, `u` will be used as the hop limit (IPv6) or TTL (IPv4) for |
| /// a packet going to the given destination. Otherwise the default value |
| /// will be used. |
| fn hop_limit(&self, destination: &SpecifiedAddr<I::Addr>) -> Option<NonZeroU8>; |
| } |
| |
| /// Empty send options that never overrides default values. |
| #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] |
| pub(crate) struct DefaultSendOptions; |
| |
| impl<I: Ip> SendOptions<I> for DefaultSendOptions { |
| fn hop_limit(&self, _destination: &SpecifiedAddr<I::Addr>) -> Option<NonZeroU8> { |
| None |
| } |
| } |
| |
| impl<I: Ip, S: SendOptions<I>> SendOptions<I> for &'_ S { |
| fn hop_limit(&self, destination: &SpecifiedAddr<<I as Ip>::Addr>) -> Option<NonZeroU8> { |
| S::hop_limit(self, destination) |
| } |
| } |
| |
| fn send_ip_packet<I, S, BC, CC, O>( |
| core_ctx: &mut CC, |
| bindings_ctx: &mut BC, |
| socket: &IpSock<I, CC::WeakDeviceId, O>, |
| body: S, |
| mtu: Option<u32>, |
| ) -> Result<(), (S, IpSockSendError)> |
| where |
| I: IpExt + IpDeviceStateIpExt + packet_formats::ip::IpExt, |
| S: TransportPacketSerializer, |
| S::Buffer: BufferMut, |
| BC: IpSocketBindingsContext, |
| CC: IpSocketContext<I, BC>, |
| CC::DeviceId: crate::filter::InterfaceProperties<BC::DeviceClass>, |
| O: SendOptions<I>, |
| { |
| trace_duration!(bindings_ctx, c"ip::send_packet"); |
| |
| let IpSock { definition: IpSockDefinition { remote_ip, local_ip, device, proto }, options } = |
| socket; |
| |
| let device = if let Some(device) = device { |
| let Some(device) = core_ctx.upgrade_weak_device_id(device) else { |
| return Err((body, ResolveRouteError::Unreachable.into())); |
| }; |
| Some(device) |
| } else { |
| None |
| }; |
| |
| let ResolvedRoute { src_addr: got_local_ip, local_delivery_device: _, device, next_hop } = |
| match core_ctx.lookup_route(bindings_ctx, device.as_ref(), Some(*local_ip), *remote_ip) { |
| Ok(o) => o, |
| Err(e) => return Err((body, IpSockSendError::Unroutable(e))), |
| }; |
| assert_eq!(local_ip, &got_local_ip); |
| |
| // TODO(https://fxbug.dev/318717702): when we implement NAT, perform re-routing |
| // after the LOCAL_EGRESS hook since the packet may have been changed. |
| let mut packet = crate::filter::TxPacket::new(local_ip.addr(), remote_ip.addr(), *proto, &body); |
| match core_ctx.filter_handler().local_egress_hook(&mut packet, &device) { |
| crate::filter::Verdict::Drop => return Ok(()), |
| crate::filter::Verdict::Accept => {} |
| } |
| |
| let remote_ip: SpecifiedAddr<_> = (*remote_ip).into(); |
| let local_ip: SpecifiedAddr<_> = (*local_ip).into(); |
| |
| let (next_hop, broadcast) = match next_hop { |
| NextHop::RemoteAsNeighbor => (remote_ip, None), |
| NextHop::Gateway(gateway) => (gateway, None), |
| NextHop::Broadcast(marker) => (remote_ip, Some(marker)), |
| }; |
| |
| IpSocketContext::send_ip_packet( |
| core_ctx, |
| bindings_ctx, |
| SendIpPacketMeta { |
| device: &device, |
| src_ip: local_ip, |
| dst_ip: remote_ip, |
| broadcast, |
| next_hop, |
| ttl: options.hop_limit(&remote_ip), |
| proto: *proto, |
| mtu, |
| }, |
| body, |
| ) |
| .map_err(|s| (s, IpSockSendError::Mtu)) |
| } |
| |
| impl< |
| I: IpLayerIpExt + IpDeviceStateIpExt, |
| BC: IpSocketBindingsContext, |
| CC: IpDeviceContext<I, BC> + IpSocketContext<I, BC> + NonTestCtxMarker, |
| > DeviceIpSocketHandler<I, BC> for CC |
| { |
| fn get_mms<O: SendOptions<I>>( |
| &mut self, |
| bindings_ctx: &mut BC, |
| ip_sock: &IpSock<I, Self::WeakDeviceId, O>, |
| ) -> Result<Mms, MmsError> { |
| let IpSockDefinition { remote_ip, local_ip, device, proto: _ } = &ip_sock.definition; |
| let device = device |
| .as_ref() |
| .map(|d| self.upgrade_weak_device_id(d).ok_or(ResolveRouteError::Unreachable)) |
| .transpose()?; |
| |
| let ResolvedRoute { src_addr: _, local_delivery_device: _, device, next_hop: _ } = self |
| .lookup_route(bindings_ctx, device.as_ref(), Some(*local_ip), *remote_ip) |
| .map_err(MmsError::NoDevice)?; |
| let mtu = IpDeviceContext::<I, BC>::get_mtu(self, &device); |
| // TODO(https://fxbug.dev/42072935): Calculate the options size when they |
| // are supported. |
| Mms::from_mtu::<I>(mtu, 0 /* no ip options used */).ok_or(MmsError::MTUTooSmall(mtu)) |
| } |
| } |
| |
| /// IPv6 source address selection as defined in [RFC 6724 Section 5]. |
| pub(crate) mod ipv6_source_address_selection { |
| use net_types::ip::{AddrSubnet, IpAddress as _}; |
| |
| use super::*; |
| |
| use crate::ip::device::{state::Ipv6AddressFlags, Ipv6DeviceAddr}; |
| |
| pub(crate) struct SasCandidate<D> { |
| pub(crate) addr_sub: AddrSubnet<Ipv6Addr, Ipv6DeviceAddr>, |
| pub(crate) flags: Ipv6AddressFlags, |
| pub(crate) device: D, |
| } |
| |
| /// Selects the source address for an IPv6 socket using the algorithm |
| /// defined in [RFC 6724 Section 5]. |
| /// |
| /// This algorithm is only applicable when the user has not explicitly |
| /// specified a source address. |
| /// |
| /// `remote_ip` is the remote IP address of the socket, `outbound_device` is |
| /// the device over which outbound traffic to `remote_ip` is sent (according |
| /// to the forwarding table), and `addresses` is an iterator of all |
| /// addresses on all devices. The algorithm works by iterating over |
| /// `addresses` and selecting the address which is most preferred according |
| /// to a set of selection criteria. |
| pub(crate) fn select_ipv6_source_address< |
| 'a, |
| D: PartialEq, |
| I: Iterator<Item = SasCandidate<D>>, |
| >( |
| remote_ip: Option<SpecifiedAddr<Ipv6Addr>>, |
| outbound_device: &D, |
| addresses: I, |
| ) -> Ipv6SourceAddr { |
| // Source address selection as defined in RFC 6724 Section 5. |
| // |
| // The algorithm operates by defining a partial ordering on available |
| // source addresses, and choosing one of the best address as defined by |
| // that ordering (given multiple best addresses, the choice from among |
| // those is implementation-defined). The partial order is defined in |
| // terms of a sequence of rules. If a given rule defines an order |
| // between two addresses, then that is their order. Otherwise, the next |
| // rule must be consulted, and so on until all of the rules are |
| // exhausted. |
| |
| let addr = addresses |
| // Tentative addresses are not considered available to the source |
| // selection algorithm. |
| .filter(|SasCandidate { addr_sub: _, flags, device: _ }| flags.assigned) |
| .max_by(|a, b| select_ipv6_source_address_cmp(remote_ip, outbound_device, a, b)) |
| .map(|SasCandidate { addr_sub, flags: _, device: _ }| addr_sub.addr()); |
| match addr { |
| Some(addr) => Ipv6SourceAddr::Unicast(addr), |
| None => Ipv6SourceAddr::Unspecified, |
| } |
| } |
| |
| /// Comparison operator used by `select_ipv6_source_address`. |
| fn select_ipv6_source_address_cmp<D: PartialEq>( |
| remote_ip: Option<SpecifiedAddr<Ipv6Addr>>, |
| outbound_device: &D, |
| a: &SasCandidate<D>, |
| b: &SasCandidate<D>, |
| ) -> Ordering { |
| // TODO(https://fxbug.dev/42123500): Implement rules 2, 4, 5.5, 6, and 7. |
| |
| let a_addr = a.addr_sub.addr().into_specified(); |
| let b_addr = b.addr_sub.addr().into_specified(); |
| |
| // Assertions required in order for this implementation to be valid. |
| |
| // Required by the implementation of Rule 1. |
| if let Some(remote_ip) = remote_ip { |
| debug_assert!(!(a_addr == remote_ip && b_addr == remote_ip)); |
| } |
| |
| // Addresses that are not considered assigned are not valid source |
| // addresses. |
| debug_assert!(a.flags.assigned); |
| debug_assert!(b.flags.assigned); |
| |
| rule_1(remote_ip, a_addr, b_addr) |
| .then_with(|| rule_3(a.flags.deprecated, b.flags.deprecated)) |
| .then_with(|| rule_5(outbound_device, &a.device, &b.device)) |
| .then_with(|| rule_8(remote_ip, a.addr_sub, b.addr_sub)) |
| } |
| |
| // Assumes that `a` and `b` are not both equal to `remote_ip`. |
| fn rule_1( |
| remote_ip: Option<SpecifiedAddr<Ipv6Addr>>, |
| a: SpecifiedAddr<Ipv6Addr>, |
| b: SpecifiedAddr<Ipv6Addr>, |
| ) -> Ordering { |
| let remote_ip = match remote_ip { |
| Some(remote_ip) => remote_ip, |
| None => return Ordering::Equal, |
| }; |
| if (a == remote_ip) != (b == remote_ip) { |
| // Rule 1: Prefer same address. |
| // |
| // Note that both `a` and `b` cannot be equal to `remote_ip` since |
| // that would imply that we had added the same address twice to the |
| // same device. |
| // |
| // If `(a == remote_ip) != (b == remote_ip)`, then exactly one of |
| // them is equal. If this inequality does not hold, then they must |
| // both be unequal to `remote_ip`. In the first case, we have a tie, |
| // and in the second case, the rule doesn't apply. In either case, |
| // we move onto the next rule. |
| if a == remote_ip { |
| Ordering::Greater |
| } else { |
| Ordering::Less |
| } |
| } else { |
| Ordering::Equal |
| } |
| } |
| |
| fn rule_3(a_deprecated: bool, b_deprecated: bool) -> Ordering { |
| match (a_deprecated, b_deprecated) { |
| (true, false) => Ordering::Less, |
| (true, true) | (false, false) => Ordering::Equal, |
| (false, true) => Ordering::Greater, |
| } |
| } |
| |
| fn rule_5<D: PartialEq>(outbound_device: &D, a_device: &D, b_device: &D) -> Ordering { |
| if (a_device == outbound_device) != (b_device == outbound_device) { |
| // Rule 5: Prefer outgoing interface. |
| if a_device == outbound_device { |
| Ordering::Greater |
| } else { |
| Ordering::Less |
| } |
| } else { |
| Ordering::Equal |
| } |
| } |
| |
| fn rule_8( |
| remote_ip: Option<SpecifiedAddr<Ipv6Addr>>, |
| a: AddrSubnet<Ipv6Addr, Ipv6DeviceAddr>, |
| b: AddrSubnet<Ipv6Addr, Ipv6DeviceAddr>, |
| ) -> Ordering { |
| let remote_ip = match remote_ip { |
| Some(remote_ip) => remote_ip, |
| None => return Ordering::Equal, |
| }; |
| // Per RFC 6724 Section 2.2: |
| // |
| // We define the common prefix length CommonPrefixLen(S, D) of a |
| // source address S and a destination address D as the length of the |
| // longest prefix (looking at the most significant, or leftmost, bits) |
| // that the two addresses have in common, up to the length of S's |
| // prefix (i.e., the portion of the address not including the |
| // interface ID). For example, CommonPrefixLen(fe80::1, fe80::2) is |
| // 64. |
| fn common_prefix_len( |
| src: AddrSubnet<Ipv6Addr, Ipv6DeviceAddr>, |
| dst: SpecifiedAddr<Ipv6Addr>, |
| ) -> u8 { |
| core::cmp::min(src.addr().common_prefix_len(&dst), src.subnet().prefix()) |
| } |
| |
| // Rule 8: Use longest matching prefix. |
| // |
| // Note that, per RFC 6724 Section 5: |
| // |
| // Rule 8 MAY be superseded if the implementation has other means of |
| // choosing among source addresses. For example, if the |
| // implementation somehow knows which source address will result in |
| // the "best" communications performance. |
| // |
| // We don't currently make use of this option, but it's an option for |
| // the future. |
| common_prefix_len(a, remote_ip).cmp(&common_prefix_len(b, remote_ip)) |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use net_declare::net_ip_v6; |
| |
| use super::*; |
| |
| #[test] |
| fn test_select_ipv6_source_address() { |
| // Test the comparison operator used by `select_ipv6_source_address` |
| // by separately testing each comparison condition. |
| |
| let remote = SpecifiedAddr::new(net_ip_v6!("2001:0db8:1::")).unwrap(); |
| let local0 = SpecifiedAddr::new(net_ip_v6!("2001:0db8:2::")).unwrap(); |
| let local1 = SpecifiedAddr::new(net_ip_v6!("2001:0db8:3::")).unwrap(); |
| let dev0 = &0; |
| let dev1 = &1; |
| let dev2 = &2; |
| |
| // Rule 1: Prefer same address |
| assert_eq!(rule_1(Some(remote), remote, local0), Ordering::Greater); |
| assert_eq!(rule_1(Some(remote), local0, remote), Ordering::Less); |
| assert_eq!(rule_1(Some(remote), local0, local1), Ordering::Equal); |
| assert_eq!(rule_1(None, local0, local1), Ordering::Equal); |
| |
| // Rule 3: Avoid deprecated states |
| assert_eq!(rule_3(false, true), Ordering::Greater); |
| assert_eq!(rule_3(true, false), Ordering::Less); |
| assert_eq!(rule_3(true, true), Ordering::Equal); |
| assert_eq!(rule_3(false, false), Ordering::Equal); |
| |
| // Rule 5: Prefer outgoing interface |
| assert_eq!(rule_5(dev0, dev0, dev2), Ordering::Greater); |
| assert_eq!(rule_5(dev0, dev2, dev0), Ordering::Less); |
| assert_eq!(rule_5(dev0, dev0, dev0), Ordering::Equal); |
| assert_eq!(rule_5(dev0, dev2, dev2), Ordering::Equal); |
| |
| // Rule 8: Use longest matching prefix. |
| { |
| let new_addr_entry = |addr, prefix_len| AddrSubnet::new(addr, prefix_len).unwrap(); |
| |
| // First, test that the longest prefix match is preferred when |
| // using addresses whose common prefix length is shorter than |
| // the subnet prefix length. |
| |
| // 4 leading 0x01 bytes. |
| let remote = SpecifiedAddr::new(net_ip_v6!("1111::")).unwrap(); |
| // 3 leading 0x01 bytes. |
| let local0 = new_addr_entry(net_ip_v6!("1110::"), 64); |
| // 2 leading 0x01 bytes. |
| let local1 = new_addr_entry(net_ip_v6!("1100::"), 64); |
| |
| assert_eq!(rule_8(Some(remote), local0, local1), Ordering::Greater); |
| assert_eq!(rule_8(Some(remote), local1, local0), Ordering::Less); |
| assert_eq!(rule_8(Some(remote), local0, local0), Ordering::Equal); |
| assert_eq!(rule_8(Some(remote), local1, local1), Ordering::Equal); |
| assert_eq!(rule_8(None, local0, local1), Ordering::Equal); |
| |
| // Second, test that the common prefix length is capped at the |
| // subnet prefix length. |
| |
| // 3 leading 0x01 bytes, but a subnet prefix length of 8 (1 byte). |
| let local0 = new_addr_entry(net_ip_v6!("1110::"), 8); |
| // 2 leading 0x01 bytes, but a subnet prefix length of 8 (1 byte). |
| let local1 = new_addr_entry(net_ip_v6!("1100::"), 8); |
| |
| assert_eq!(rule_8(Some(remote), local0, local1), Ordering::Equal); |
| assert_eq!(rule_8(Some(remote), local1, local0), Ordering::Equal); |
| assert_eq!(rule_8(Some(remote), local0, local0), Ordering::Equal); |
| assert_eq!(rule_8(Some(remote), local1, local1), Ordering::Equal); |
| assert_eq!(rule_8(None, local0, local1), Ordering::Equal); |
| } |
| |
| { |
| let new_addr_entry = |addr, device| SasCandidate { |
| addr_sub: AddrSubnet::new(addr, 128).unwrap(), |
| flags: Ipv6AddressFlags { deprecated: false, assigned: true }, |
| device, |
| }; |
| |
| // If no rules apply, then the two address entries are equal. |
| assert_eq!( |
| select_ipv6_source_address_cmp( |
| Some(remote), |
| dev0, |
| &new_addr_entry(*local0, *dev1), |
| &new_addr_entry(*local1, *dev2), |
| ), |
| Ordering::Equal |
| ); |
| } |
| } |
| |
| #[test] |
| fn test_select_ipv6_source_address_no_remote() { |
| // Verify that source address selection correctly applies all |
| // applicable rules when the remote is `None`. |
| let dev0 = &0; |
| let dev1 = &1; |
| let dev2 = &2; |
| |
| let local0 = SpecifiedAddr::new(net_ip_v6!("2001:0db8:2::")).unwrap(); |
| let local1 = SpecifiedAddr::new(net_ip_v6!("2001:0db8:3::")).unwrap(); |
| |
| let new_addr_entry = |addr, deprecated, device| SasCandidate { |
| addr_sub: AddrSubnet::new(addr, 128).unwrap(), |
| flags: Ipv6AddressFlags { deprecated, assigned: true }, |
| device, |
| }; |
| |
| // Verify that Rule 3 still applies (avoid deprecated states). |
| assert_eq!( |
| select_ipv6_source_address_cmp( |
| None, |
| dev0, |
| &new_addr_entry(*local0, false, *dev1), |
| &new_addr_entry(*local1, true, *dev2), |
| ), |
| Ordering::Greater |
| ); |
| |
| // Verify that Rule 5 still applies (Prefer outgoing interface). |
| assert_eq!( |
| select_ipv6_source_address_cmp( |
| None, |
| dev0, |
| &new_addr_entry(*local0, false, *dev0), |
| &new_addr_entry(*local1, false, *dev1), |
| ), |
| Ordering::Greater |
| ); |
| } |
| } |
| } |
| |
| /// Test fake implementations of the traits defined in the `socket` module. |
| #[cfg(test)] |
| pub(crate) mod testutil { |
| use alloc::{boxed::Box, collections::HashMap, vec::Vec}; |
| use core::{fmt::Debug, num::NonZeroUsize}; |
| |
| use derivative::Derivative; |
| use net_types::{ |
| ip::{GenericOverIp, IpAddr, IpInvariant, Ipv4, Ipv6}, |
| MulticastAddr, |
| }; |
| use packet::Serializer; |
| |
| use super::*; |
| use crate::{ |
| context::{ |
| testutil::{FakeBindingsCtx, FakeCoreCtx, FakeInstant}, |
| RngContext, SendFrameContext, |
| }, |
| device::testutil::{FakeStrongDeviceId, FakeWeakDeviceId}, |
| ip::{ |
| device::state::{ |
| AssignedAddress as _, DualStackIpDeviceState, IpDeviceState, |
| IpDeviceStateBindingsTypes, |
| }, |
| forwarding::{ |
| testutil::{DualStackForwardingTable, FakeIpForwardingCtx}, |
| ForwardingTable, |
| }, |
| icmp::{IcmpRxCounters, IcmpTxCounters, NdpCounters}, |
| testutil::FakeIpDeviceIdCtx, |
| types::Destination, |
| HopLimits, MulticastMembershipHandler, TransportIpContext, DEFAULT_HOP_LIMITS, |
| }, |
| sync::PrimaryRc, |
| testutil::DEFAULT_INTERFACE_METRIC, |
| }; |
| |
| /// A fake implementation of [`IpSocketContext`]. |
| /// |
| /// `IpSocketContext` is implemented for `FakeIpSocketCtx` and any |
| /// `FakeCtx<S>` where `S` implements `AsRef` and `AsMut` for |
| /// `FakeIpSocketCtx`. |
| #[derive(Derivative, GenericOverIp)] |
| #[generic_over_ip(I, Ip)] |
| #[derivative(Default(bound = ""))] |
| pub(crate) struct FakeIpSocketCtx<I: IpLayerIpExt + IpDeviceStateIpExt, D> { |
| pub(crate) table: ForwardingTable<I, D>, |
| device_state: HashMap<D, IpDeviceState<FakeInstant, I>>, |
| ip_forwarding_ctx: FakeIpForwardingCtx<D>, |
| pub(crate) counters: IpCounters<I>, |
| } |
| |
| impl<I: IpLayerIpExt + IpDeviceStateIpExt, D, BC: FilterBindingsTypes> |
| FilterHandlerProvider<I, BC> for FakeIpSocketCtx<I, D> |
| { |
| type Handler<'a> = crate::filter::NoopImpl where Self: 'a; |
| |
| fn filter_handler(&mut self) -> Self::Handler<'_> { |
| crate::filter::NoopImpl |
| } |
| } |
| |
| impl<I: IpLayerIpExt + IpDeviceStateIpExt, D> CounterContext<IpCounters<I>> |
| for FakeIpSocketCtx<I, D> |
| { |
| fn with_counters<O, F: FnOnce(&IpCounters<I>) -> O>(&self, cb: F) -> O { |
| cb(&self.counters) |
| } |
| } |
| |
| impl<I: IpLayerIpExt + IpDeviceStateIpExt, D> AsRef<FakeIpDeviceIdCtx<D>> |
| for FakeIpSocketCtx<I, D> |
| { |
| fn as_ref(&self) -> &FakeIpDeviceIdCtx<D> { |
| let FakeIpSocketCtx { device_state: _, table: _, ip_forwarding_ctx, counters: _ } = |
| self; |
| ip_forwarding_ctx.get_ref().as_ref() |
| } |
| } |
| |
| impl<I: IpLayerIpExt + IpDeviceStateIpExt, D> AsMut<FakeIpDeviceIdCtx<D>> |
| for FakeIpSocketCtx<I, D> |
| { |
| fn as_mut(&mut self) -> &mut FakeIpDeviceIdCtx<D> { |
| let FakeIpSocketCtx { device_state: _, table: _, ip_forwarding_ctx, counters: _ } = |
| self; |
| ip_forwarding_ctx.get_mut().as_mut() |
| } |
| } |
| |
| impl<I: IpLayerIpExt + IpDeviceStateIpExt, D> AsRef<Self> for FakeIpSocketCtx<I, D> { |
| fn as_ref(&self) -> &Self { |
| self |
| } |
| } |
| |
| impl<I: IpLayerIpExt + IpDeviceStateIpExt, D> AsMut<Self> for FakeIpSocketCtx<I, D> { |
| fn as_mut(&mut self) -> &mut Self { |
| self |
| } |
| } |
| |
| // TODO(https://fxbug.dev/331777445): remove this marker trait once tests in the |
| // transport-layer modules use a fake implementation of IpSocketHandler rather |
| // than the blanket impl, which requires the `InterfaceProperties` trait on the |
| // device ID for filtering purposes. |
| pub(crate) trait FakeFilterDeviceId<DeviceClass>: |
| FakeStrongDeviceId + crate::filter::InterfaceProperties<DeviceClass> |
| { |
| } |
| |
| impl<DeviceClass, D: FakeStrongDeviceId + crate::filter::InterfaceProperties<DeviceClass>> |
| FakeFilterDeviceId<DeviceClass> for D |
| { |
| } |
| |
| impl< |
| I: IpLayerIpExt + IpDeviceStateIpExt, |
| BC: InstantContext + TracingContext + FilterBindingsTypes, |
| DeviceId: FakeFilterDeviceId<BC::DeviceClass>, |
| > TransportIpContext<I, BC> for FakeIpSocketCtx<I, DeviceId> |
| { |
| fn get_default_hop_limits(&mut self, device: Option<&Self::DeviceId>) -> HopLimits { |
| device.map_or(DEFAULT_HOP_LIMITS, |device| { |
| let hop_limit = self.get_device_state(device).default_hop_limit.read().clone(); |
| HopLimits { unicast: hop_limit, multicast: DEFAULT_HOP_LIMITS.multicast } |
| }) |
| } |
| |
| type DevicesWithAddrIter<'a> = alloc::boxed::Box<dyn Iterator<Item = DeviceId> + 'a>; |
| |
| fn get_devices_with_assigned_addr( |
| &mut self, |
| addr: SpecifiedAddr<<I>::Addr>, |
| ) -> Self::DevicesWithAddrIter<'_> { |
| Box::new(self.find_devices_with_addr(addr)) |
| } |
| |
| fn confirm_reachable_with_destination( |
| &mut self, |
| _bindings_ctx: &mut BC, |
| _dst: SpecifiedAddr<<I>::Addr>, |
| _device: Option<&Self::DeviceId>, |
| ) { |
| } |
| } |
| |
| impl<I: IpLayerIpExt + IpDeviceStateIpExt, DeviceId: FakeStrongDeviceId> |
| DeviceIdContext<AnyDevice> for FakeIpSocketCtx<I, DeviceId> |
| { |
| type DeviceId = <FakeIpDeviceIdCtx<DeviceId> as DeviceIdContext<AnyDevice>>::DeviceId; |
| type WeakDeviceId = |
| <FakeIpDeviceIdCtx<DeviceId> as DeviceIdContext<AnyDevice>>::WeakDeviceId; |
| |
| fn downgrade_device_id(&self, device_id: &DeviceId) -> FakeWeakDeviceId<DeviceId> { |
| self.ip_forwarding_ctx.downgrade_device_id(device_id) |
| } |
| |
| fn upgrade_weak_device_id( |
| &self, |
| device_id: &FakeWeakDeviceId<DeviceId>, |
| ) -> Option<DeviceId> { |
| self.ip_forwarding_ctx.upgrade_weak_device_id(device_id) |
| } |
| } |
| |
| fn lookup_route<I: IpDeviceStateIpExt, D: FakeStrongDeviceId, Instant: crate::Instant>( |
| table: &ForwardingTable<I, D>, |
| ip_forwarding_ctx: &mut FakeIpForwardingCtx<D>, |
| device_state: &HashMap<D, impl AsRef<IpDeviceState<Instant, I>>>, |
| device: Option<&D>, |
| local_ip: Option<IpDeviceAddr<I::Addr>>, |
| addr: RoutableIpAddr<I::Addr>, |
| ) -> Result<ResolvedRoute<I, D>, ResolveRouteError> { |
| let (destination, ()) = table |
| .lookup_filter_map(ip_forwarding_ctx, device, addr.addr(), |_, d| match &local_ip { |
| None => Some(()), |
| Some(local_ip) => device_state.get(d).and_then(|state| { |
| state.as_ref().addrs.read().find(&local_ip.addr()).map(|_| ()) |
| }), |
| }) |
| .next() |
| .ok_or(ResolveRouteError::Unreachable)?; |
| |
| let Destination { device, next_hop } = destination; |
| let addrs = device_state.get(&device).unwrap().as_ref().addrs.read(); |
| let mut addrs = addrs.iter(); |
| let local_ip = match local_ip { |
| None => addrs.map(|e| e.addr()).next().ok_or(ResolveRouteError::NoSrcAddr)?, |
| Some(local_ip) => { |
| // We already constrained the set of devices so this |
| // should be a given. |
| assert!( |
| addrs.any(|e| e.addr() == local_ip), |
| "didn't find IP {:?} in {:?}", |
| local_ip, |
| addrs.collect::<Vec<_>>() |
| ); |
| local_ip |
| } |
| }; |
| |
| Ok(ResolvedRoute { |
| src_addr: local_ip, |
| device: device.clone(), |
| local_delivery_device: None, |
| next_hop, |
| }) |
| } |
| |
| impl< |
| I: IpLayerIpExt + IpDeviceStateIpExt, |
| BC: InstantContext + TracingContext + FilterBindingsTypes, |
| DeviceId: FakeFilterDeviceId<BC::DeviceClass>, |
| > IpSocketContext<I, BC> for FakeIpSocketCtx<I, DeviceId> |
| { |
| fn lookup_route( |
| &mut self, |
| _bindings_ctx: &mut BC, |
| device: Option<&Self::DeviceId>, |
| local_ip: Option<IpDeviceAddr<I::Addr>>, |
| addr: RoutableIpAddr<I::Addr>, |
| ) -> Result<ResolvedRoute<I, Self::DeviceId>, ResolveRouteError> { |
| let FakeIpSocketCtx { device_state, table, ip_forwarding_ctx, counters: _ } = self; |
| lookup_route(table, ip_forwarding_ctx, device_state, device, local_ip, addr) |
| } |
| |
| fn send_ip_packet<S>( |
| &mut self, |
| _bindings_ctx: &mut BC, |
| _meta: SendIpPacketMeta<I, &Self::DeviceId, SpecifiedAddr<I::Addr>>, |
| _body: S, |
| ) -> Result<(), S> { |
| panic!("FakeIpSocketCtx can't send packets, wrap it in a FakeCoreCtx instead"); |
| } |
| } |
| |
| impl< |
| I: IpLayerIpExt + IpDeviceStateIpExt, |
| S: AsRef<FakeIpSocketCtx<I, DeviceId>> |
| + AsMut<FakeIpSocketCtx<I, DeviceId>> |
| + AsRef<FakeIpDeviceIdCtx<DeviceId>>, |
| Id, |
| Meta, |
| Event: Debug, |
| DeviceId: FakeFilterDeviceId<()>, |
| BindingsCtxState, |
| > IpSocketContext<I, FakeBindingsCtx<Id, Event, BindingsCtxState, ()>> |
| for FakeCoreCtx<S, Meta, DeviceId> |
| where |
| FakeCoreCtx<S, Meta, DeviceId>: SendFrameContext< |
| FakeBindingsCtx<Id, Event, BindingsCtxState, ()>, |
| SendIpPacketMeta<I, Self::DeviceId, SpecifiedAddr<I::Addr>>, |
| >, |
| { |
| fn lookup_route( |
| &mut self, |
| bindings_ctx: &mut FakeBindingsCtx<Id, Event, BindingsCtxState, ()>, |
| device: Option<&Self::DeviceId>, |
| local_ip: Option<IpDeviceAddr<I::Addr>>, |
| addr: RoutableIpAddr<I::Addr>, |
| ) -> Result<ResolvedRoute<I, Self::DeviceId>, ResolveRouteError> { |
| self.get_mut().as_mut().lookup_route(bindings_ctx, device, local_ip, addr) |
| } |
| |
| fn send_ip_packet<SS>( |
| &mut self, |
| bindings_ctx: &mut FakeBindingsCtx<Id, Event, BindingsCtxState, ()>, |
| SendIpPacketMeta { device, src_ip, dst_ip, broadcast, next_hop, proto, ttl, mtu }: SendIpPacketMeta<I, &Self::DeviceId, SpecifiedAddr<I::Addr>>, |
| body: SS, |
| ) -> Result<(), SS> |
| where |
| SS: Serializer, |
| SS::Buffer: BufferMut, |
| { |
| let meta = SendIpPacketMeta { |
| device: device.clone(), |
| src_ip, |
| dst_ip, |
| broadcast, |
| next_hop, |
| proto, |
| ttl, |
| mtu, |
| }; |
| self.send_frame(bindings_ctx, meta, body) |
| } |
| } |
| |
| impl<I: IpLayerIpExt + IpDeviceStateIpExt, D: FakeStrongDeviceId> FakeIpSocketCtx<I, D> { |
| pub(crate) fn with_devices_state( |
| devices: impl IntoIterator< |
| Item = (D, IpDeviceState<FakeInstant, I>, Vec<SpecifiedAddr<I::Addr>>), |
| >, |
| ) -> Self { |
| let mut table = ForwardingTable::default(); |
| let mut device_state = HashMap::default(); |
| for (device, state, addrs) in devices { |
| for ip in addrs { |
| crate::ip::forwarding::testutil::add_on_link_forwarding_entry( |
| &mut table, |
| ip, |
| device.clone(), |
| ) |
| } |
| assert!( |
| device_state.insert(device.clone(), state).is_none(), |
| "duplicate entries for {device:?}", |
| ); |
| } |
| |
| FakeIpSocketCtx { |
| table, |
| device_state, |
| ip_forwarding_ctx: Default::default(), |
| counters: Default::default(), |
| } |
| } |
| |
| pub(crate) fn find_devices_with_addr( |
| &self, |
| addr: SpecifiedAddr<I::Addr>, |
| ) -> impl Iterator<Item = D> + '_ { |
| let Self { table: _, device_state, ip_forwarding_ctx: _, counters: _ } = self; |
| find_devices_with_addr::<I, _, _>(device_state, addr) |
| } |
| |
| pub(crate) fn get_device_state(&self, device: &D) -> &IpDeviceState<FakeInstant, I> { |
| let Self { device_state, table: _, ip_forwarding_ctx: _, counters: _ } = self; |
| device_state.get(device).unwrap_or_else(|| panic!("no device {device:?}")) |
| } |
| } |
| |
| fn find_devices_with_addr< |
| I: IpLayerIpExt + IpDeviceStateIpExt, |
| D: FakeStrongDeviceId, |
| Instant: crate::Instant, |
| >( |
| device_state: &HashMap<D, impl AsRef<IpDeviceState<Instant, I>>>, |
| addr: SpecifiedAddr<I::Addr>, |
| ) -> impl Iterator<Item = D> + '_ { |
| device_state.iter().filter_map(move |(device, state)| { |
| state |
| .as_ref() |
| .addrs |
| .read() |
| .find(&addr) |
| .map(|_: &PrimaryRc<I::AssignedAddress<_>>| device.clone()) |
| }) |
| } |
| |
| fn multicast_memberships< |
| I: IpDeviceStateIpExt, |
| D: FakeStrongDeviceId, |
| Instant: crate::Instant, |
| >( |
| device_state: &HashMap<D, impl AsRef<IpDeviceState<Instant, I>>>, |
| ) -> HashMap<(D, MulticastAddr<I::Addr>), NonZeroUsize> { |
| device_state |
| .iter() |
| .flat_map(|(device, device_state)| { |
| device_state |
| .as_ref() |
| .multicast_groups |
| .read() |
| .iter_counts() |
| .map(|(addr, count)| ((device.clone(), *addr), count)) |
| .collect::<Vec<_>>() |
| }) |
| .collect() |
| } |
| |
| impl< |
| I: IpLayerIpExt + IpDeviceStateIpExt, |
| D: FakeStrongDeviceId, |
| BC: RngContext + InstantContext<Instant = FakeInstant>, |
| > MulticastMembershipHandler<I, BC> for FakeIpSocketCtx<I, D> |
| { |
| fn join_multicast_group( |
| &mut self, |
| bindings_ctx: &mut BC, |
| device: &Self::DeviceId, |
| addr: MulticastAddr<<I as Ip>::Addr>, |
| ) { |
| let Self { device_state, table: _, ip_forwarding_ctx: _, counters: _ } = self; |
| let state = |
| device_state.get_mut(device).unwrap_or_else(|| panic!("no device {device:?}")); |
| state.multicast_groups.write().join_multicast_group(bindings_ctx, addr) |
| } |
| |
| fn leave_multicast_group( |
| &mut self, |
| _bindings_ctx: &mut BC, |
| device: &Self::DeviceId, |
| addr: MulticastAddr<<I as Ip>::Addr>, |
| ) { |
| let Self { device_state, table: _, ip_forwarding_ctx: _, counters: _ } = self; |
| let state = |
| device_state.get_mut(device).unwrap_or_else(|| panic!("no device {device:?}")); |
| state.multicast_groups.write().leave_multicast_group(addr) |
| } |
| |
| fn select_device_for_multicast_group( |
| &mut self, |
| addr: MulticastAddr<<I as Ip>::Addr>, |
| ) -> Result<Self::DeviceId, ResolveRouteError> { |
| let FakeIpSocketCtx { device_state, table, ip_forwarding_ctx, counters: _ } = self; |
| let remote_ip = SocketIpAddr::new_from_multicast(addr); |
| let ResolvedRoute { device, .. } = |
| lookup_route(table, ip_forwarding_ctx, device_state, None, None, remote_ip)?; |
| Ok(device) |
| } |
| } |
| |
| impl< |
| I: IpLayerIpExt + IpDeviceStateIpExt, |
| BC: InstantContext + TracingContext + FilterBindingsTypes, |
| D: FakeFilterDeviceId<BC::DeviceClass>, |
| State: TransportIpContext<I, BC, DeviceId = D> + CounterContext<IpCounters<I>>, |
| Meta, |
| > TransportIpContext<I, BC> for FakeCoreCtx<State, Meta, D> |
| where |
| Self: IpSocketContext<I, BC, DeviceId = D, WeakDeviceId = FakeWeakDeviceId<D>>, |
| { |
| type DevicesWithAddrIter<'a> = State::DevicesWithAddrIter<'a> |
| where Self: 'a; |
| |
| fn get_devices_with_assigned_addr( |
| &mut self, |
| addr: SpecifiedAddr<I::Addr>, |
| ) -> Self::DevicesWithAddrIter<'_> { |
| TransportIpContext::<I, BC>::get_devices_with_assigned_addr(self.get_mut(), addr) |
| } |
| |
| fn get_default_hop_limits(&mut self, device: Option<&Self::DeviceId>) -> HopLimits { |
| TransportIpContext::<I, BC>::get_default_hop_limits(self.get_mut(), device) |
| } |
| |
| fn confirm_reachable_with_destination( |
| &mut self, |
| bindings_ctx: &mut BC, |
| dst: SpecifiedAddr<I::Addr>, |
| device: Option<&Self::DeviceId>, |
| ) { |
| TransportIpContext::<I, BC>::confirm_reachable_with_destination( |
| self.get_mut(), |
| bindings_ctx, |
| dst, |
| device, |
| ) |
| } |
| } |
| |
| #[derive(Derivative)] |
| #[derivative(Default(bound = ""))] |
| pub(crate) struct FakeDualStackIpSocketCtx<D, BT: IpDeviceStateBindingsTypes> { |
| table: DualStackForwardingTable<D>, |
| device_state: HashMap<D, DualStackIpDeviceState<BT>>, |
| ip_forwarding_ctx: FakeIpForwardingCtx<D>, |
| pub(crate) v4_common_counters: IpCounters<Ipv4>, |
| pub(crate) v6_common_counters: IpCounters<Ipv6>, |
| pub(crate) tx_icmpv4_counters: IcmpTxCounters<Ipv4>, |
| pub(crate) rx_icmpv4_counters: IcmpRxCounters<Ipv4>, |
| pub(crate) tx_icmpv6_counters: IcmpTxCounters<Ipv6>, |
| pub(crate) rx_icmpv6_counters: IcmpRxCounters<Ipv6>, |
| pub(crate) ndp_counters: NdpCounters, |
| } |
| |
| impl<D: FakeStrongDeviceId, BT: IpDeviceStateBindingsTypes> FakeDualStackIpSocketCtx<D, BT> { |
| pub(crate) fn new<A: Into<SpecifiedAddr<IpAddr>>>( |
| devices: impl IntoIterator<Item = FakeDeviceConfig<D, A>>, |
| ) -> Self { |
| Self::with_devices_state(devices.into_iter().map( |
| |FakeDeviceConfig { device, local_ips, remote_ips }| { |
| let mut device_state = DualStackIpDeviceState::new(DEFAULT_INTERFACE_METRIC); |
| for ip in local_ips { |
| match IpAddr::from(ip.into()) { |
| IpAddr::V4(ip) => crate::ip::device::state::testutil::add_addr_subnet( |
| device_state.as_mut(), |
| ip, |
| ), |
| IpAddr::V6(ip) => crate::ip::device::state::testutil::add_addr_subnet( |
| device_state.as_mut(), |
| ip, |
| ), |
| } |
| } |
| (device, device_state, remote_ips) |
| }, |
| )) |
| } |
| |
| pub(crate) fn add_route(&mut self, device: D, ip: SpecifiedAddr<IpAddr>) { |
| match IpAddr::from(ip) { |
| IpAddr::V4(ip) => crate::ip::forwarding::testutil::add_on_link_forwarding_entry( |
| self.table.as_mut(), |
| ip, |
| device, |
| ), |
| IpAddr::V6(ip) => crate::ip::forwarding::testutil::add_on_link_forwarding_entry( |
| self.table.as_mut(), |
| ip, |
| device, |
| ), |
| } |
| } |
| |
| pub(crate) fn get_device_state(&self, device: &D) -> &DualStackIpDeviceState<BT> { |
| let Self { |
| device_state, |
| table: _, |
| ip_forwarding_ctx: _, |
| v4_common_counters: _, |
| v6_common_counters: _, |
| tx_icmpv4_counters: _, |
| rx_icmpv4_counters: _, |
| tx_icmpv6_counters: _, |
| rx_icmpv6_counters: _, |
| ndp_counters: _, |
| } = self; |
| device_state.get(device).unwrap_or_else(|| panic!("no device {device:?}")) |
| } |
| |
| pub(crate) fn find_devices_with_addr<I: IpLayerIpExt + IpDeviceStateIpExt>( |
| &self, |
| addr: SpecifiedAddr<I::Addr>, |
| ) -> impl Iterator<Item = D> + '_ { |
| let Self { |
| table: _, |
| device_state, |
| ip_forwarding_ctx: _, |
| v4_common_counters: _, |
| v6_common_counters: _, |
| tx_icmpv4_counters: _, |
| rx_icmpv4_counters: _, |
| tx_icmpv6_counters: _, |
| rx_icmpv6_counters: _, |
| ndp_counters: _, |
| } = self; |
| find_devices_with_addr::<I, _, _>(device_state, addr) |
| } |
| |
| pub(crate) fn multicast_memberships<I: IpDeviceStateIpExt>( |
| &self, |
| ) -> HashMap<(D, MulticastAddr<I::Addr>), NonZeroUsize> { |
| let Self { |
| device_state, |
| table: _, |
| ip_forwarding_ctx: _, |
| v4_common_counters: _, |
| v6_common_counters: _, |
| tx_icmpv4_counters: _, |
| rx_icmpv4_counters: _, |
| tx_icmpv6_counters: _, |
| rx_icmpv6_counters: _, |
| ndp_counters: _, |
| } = self; |
| multicast_memberships::<I, _, _>(device_state) |
| } |
| |
| pub(crate) fn with_devices_state( |
| devices: impl IntoIterator< |
| Item = (D, DualStackIpDeviceState<BT>, Vec<impl Into<SpecifiedAddr<IpAddr>>>), |
| >, |
| ) -> Self { |
| let mut table = DualStackForwardingTable::default(); |
| let mut device_state = HashMap::default(); |
| for (device, state, addrs) in devices { |
| for ip in addrs { |
| match IpAddr::from(ip.into()) { |
| IpAddr::V4(ip) => { |
| crate::ip::forwarding::testutil::add_on_link_forwarding_entry( |
| table.as_mut(), |
| ip, |
| device.clone(), |
| ); |
| } |
| IpAddr::V6(ip) => { |
| crate::ip::forwarding::testutil::add_on_link_forwarding_entry( |
| table.as_mut(), |
| ip, |
| device.clone(), |
| ); |
| } |
| } |
| } |
| assert!( |
| device_state.insert(device.clone(), state).is_none(), |
| "duplicate entries for {device:?}", |
| ); |
| } |
| Self { |
| table, |
| device_state, |
| ip_forwarding_ctx: FakeIpForwardingCtx::default(), |
| v4_common_counters: Default::default(), |
| v6_common_counters: Default::default(), |
| tx_icmpv4_counters: Default::default(), |
| rx_icmpv4_counters: Default::default(), |
| tx_icmpv6_counters: Default::default(), |
| rx_icmpv6_counters: Default::default(), |
| ndp_counters: Default::default(), |
| } |
| } |
| } |
| |
| impl<D, BT: IpDeviceStateBindingsTypes> FakeDualStackIpSocketCtx<D, BT> { |
| pub(crate) fn get_common_counters<I: IpLayerIpExt>(&self) -> &IpCounters<I> { |
| I::map_ip( |
| IpInvariant(self), |
| |IpInvariant(core_ctx)| &core_ctx.v4_common_counters, |
| |IpInvariant(core_ctx)| &core_ctx.v6_common_counters, |
| ) |
| } |
| |
| pub(crate) fn icmp_tx_counters<I: Ip>(&self) -> &IcmpTxCounters<I> { |
| I::map_ip( |
| IpInvariant(self), |
| |IpInvariant(core_ctx)| &core_ctx.tx_icmpv4_counters, |
| |IpInvariant(core_ctx)| &core_ctx.tx_icmpv6_counters, |
| ) |
| } |
| |
| pub(crate) fn icmp_rx_counters<I: Ip>(&self) -> &IcmpRxCounters<I> { |
| I::map_ip( |
| IpInvariant(self), |
| |IpInvariant(core_ctx)| &core_ctx.rx_icmpv4_counters, |
| |IpInvariant(core_ctx)| &core_ctx.rx_icmpv6_counters, |
| ) |
| } |
| } |
| |
| impl<I: IpExt, D, BC: FilterBindingsTypes + IpDeviceStateBindingsTypes> |
| FilterHandlerProvider<I, BC> for FakeDualStackIpSocketCtx<D, BC> |
| { |
| type Handler<'a> = crate::filter::NoopImpl where Self: 'a; |
| |
| fn filter_handler(&mut self) -> Self::Handler<'_> { |
| crate::filter::NoopImpl |
| } |
| } |
| |
| impl<I: IpLayerIpExt + IpDeviceStateIpExt, D, BT: IpDeviceStateBindingsTypes> |
| CounterContext<IpCounters<I>> for FakeDualStackIpSocketCtx<D, BT> |
| { |
| fn with_counters<O, F: FnOnce(&IpCounters<I>) -> O>(&self, cb: F) -> O { |
| cb(self.get_common_counters::<I>()) |
| } |
| } |
| |
| impl<D: FakeStrongDeviceId, BT: IpDeviceStateBindingsTypes> AsRef<Self> |
| for FakeDualStackIpSocketCtx<D, BT> |
| { |
| fn as_ref(&self) -> &Self { |
| self |
| } |
| } |
| |
| impl<D: FakeStrongDeviceId, BT: IpDeviceStateBindingsTypes> AsRef<FakeIpDeviceIdCtx<D>> |
| for FakeDualStackIpSocketCtx<D, BT> |
| { |
| fn as_ref(&self) -> &FakeIpDeviceIdCtx<D> { |
| self.ip_forwarding_ctx.get_ref().as_ref() |
| } |
| } |
| |
| impl<D: FakeStrongDeviceId, BT: IpDeviceStateBindingsTypes> AsMut<FakeIpDeviceIdCtx<D>> |
| for FakeDualStackIpSocketCtx<D, BT> |
| { |
| fn as_mut(&mut self) -> &mut FakeIpDeviceIdCtx<D> { |
| self.ip_forwarding_ctx.get_mut().as_mut() |
| } |
| } |
| |
| impl<DeviceId: FakeStrongDeviceId, BT: IpDeviceStateBindingsTypes> DeviceIdContext<AnyDevice> |
| for FakeDualStackIpSocketCtx<DeviceId, BT> |
| { |
| type DeviceId = DeviceId; |
| type WeakDeviceId = DeviceId::Weak; |
| |
| fn downgrade_device_id(&self, device_id: &DeviceId) -> Self::WeakDeviceId { |
| self.ip_forwarding_ctx.downgrade_device_id(device_id) |
| } |
| |
| fn upgrade_weak_device_id( |
| &self, |
| device_id: &FakeWeakDeviceId<DeviceId>, |
| ) -> Option<DeviceId> { |
| self.ip_forwarding_ctx.upgrade_weak_device_id(device_id) |
| } |
| } |
| |
| impl< |
| I: IpDeviceStateIpExt + IpExt, |
| DeviceId: FakeStrongDeviceId, |
| BC: IpSocketBindingsContext, |
| > IpSocketContext<I, BC> for FakeDualStackIpSocketCtx<DeviceId, BC> |
| { |
| fn lookup_route( |
| &mut self, |
| _bindings_ctx: &mut BC, |
| device: Option<&Self::DeviceId>, |
| local_ip: Option<IpDeviceAddr<I::Addr>>, |
| addr: RoutableIpAddr<I::Addr>, |
| ) -> Result<ResolvedRoute<I, Self::DeviceId>, ResolveRouteError> { |
| let Self { |
| table, |
| device_state, |
| ip_forwarding_ctx, |
| v4_common_counters: _, |
| v6_common_counters: _, |
| tx_icmpv4_counters: _, |
| rx_icmpv4_counters: _, |
| tx_icmpv6_counters: _, |
| rx_icmpv6_counters: _, |
| ndp_counters: _, |
| } = self; |
| lookup_route( |
| table.as_ref(), |
| ip_forwarding_ctx.as_mut(), |
| device_state, |
| device, |
| local_ip, |
| addr, |
| ) |
| } |
| |
| /// Send an IP packet to the next-hop node. |
| fn send_ip_packet<S>( |
| &mut self, |
| _bindings_ctx: &mut BC, |
| _meta: SendIpPacketMeta<I, &Self::DeviceId, SpecifiedAddr<I::Addr>>, |
| _body: S, |
| ) -> Result<(), S> { |
| panic!("FakeDualStackIpSocketCtx can't send packets, wrap it in a FakeCoreCtx instead"); |
| } |
| } |
| |
| impl< |
| I: IpDeviceStateIpExt + IpExt, |
| Id, |
| Meta, |
| Event: Debug, |
| DeviceId: FakeStrongDeviceId, |
| BindingsCtxState, |
| > IpSocketContext<I, FakeBindingsCtx<Id, Event, BindingsCtxState, ()>> |
| for FakeCoreCtx< |
| FakeDualStackIpSocketCtx<DeviceId, FakeBindingsCtx<Id, Event, BindingsCtxState, ()>>, |
| Meta, |
| DeviceId, |
| > |
| where |
| FakeCoreCtx< |
| FakeDualStackIpSocketCtx<DeviceId, FakeBindingsCtx<Id, Event, BindingsCtxState, ()>>, |
| Meta, |
| DeviceId, |
| >: SendFrameContext< |
| FakeBindingsCtx<Id, Event, BindingsCtxState, ()>, |
| SendIpPacketMeta<I, Self::DeviceId, SpecifiedAddr<I::Addr>>, |
| >, |
| { |
| fn lookup_route( |
| &mut self, |
| bindings_ctx: &mut FakeBindingsCtx<Id, Event, BindingsCtxState, ()>, |
| device: Option<&Self::DeviceId>, |
| local_ip: Option<IpDeviceAddr<I::Addr>>, |
| addr: RoutableIpAddr<I::Addr>, |
| ) -> Result<ResolvedRoute<I, Self::DeviceId>, ResolveRouteError> { |
| self.get_mut().lookup_route(bindings_ctx, device, local_ip, addr) |
| } |
| |
| fn send_ip_packet<SS>( |
| &mut self, |
| bindings_ctx: &mut FakeBindingsCtx<Id, Event, BindingsCtxState, ()>, |
| SendIpPacketMeta { device, src_ip, dst_ip, broadcast, next_hop, proto, ttl, mtu }: SendIpPacketMeta<I, &Self::DeviceId, SpecifiedAddr<I::Addr>>, |
| body: SS, |
| ) -> Result<(), SS> |
| where |
| SS: Serializer, |
| SS::Buffer: BufferMut, |
| { |
| let meta = SendIpPacketMeta { |
| device: device.clone(), |
| src_ip, |
| dst_ip, |
| broadcast, |
| next_hop, |
| proto, |
| ttl, |
| mtu, |
| }; |
| self.send_frame(bindings_ctx, meta, body) |
| } |
| } |
| |
| impl< |
| I: IpDeviceStateIpExt, |
| D: FakeStrongDeviceId, |
| BC: RngContext + InstantContext<Instant = FakeInstant>, |
| > MulticastMembershipHandler<I, BC> for FakeDualStackIpSocketCtx<D, BC> |
| { |
| fn join_multicast_group( |
| &mut self, |
| bindings_ctx: &mut BC, |
| device: &Self::DeviceId, |
| addr: MulticastAddr<<I as Ip>::Addr>, |
| ) { |
| let Self { |
| device_state, |
| table: _, |
| ip_forwarding_ctx: _, |
| v4_common_counters: _, |
| v6_common_counters: _, |
| tx_icmpv4_counters: _, |
| rx_icmpv4_counters: _, |
| tx_icmpv6_counters: _, |
| rx_icmpv6_counters: _, |
| ndp_counters: _, |
| } = self; |
| let state = |
| device_state.get_mut(device).unwrap_or_else(|| panic!("no device {device:?}")); |
| let state: &IpDeviceState<_, I> = state.as_ref(); |
| let mut groups = state.multicast_groups.write(); |
| groups.join_multicast_group(bindings_ctx, addr) |
| } |
| |
| fn leave_multicast_group( |
| &mut self, |
| _bindings_ctx: &mut BC, |
| device: &Self::DeviceId, |
| addr: MulticastAddr<<I as Ip>::Addr>, |
| ) { |
| let Self { |
| device_state, |
| table: _, |
| ip_forwarding_ctx: _, |
| v4_common_counters: _, |
| v6_common_counters: _, |
| tx_icmpv4_counters: _, |
| rx_icmpv4_counters: _, |
| tx_icmpv6_counters: _, |
| rx_icmpv6_counters: _, |
| ndp_counters: _, |
| } = self; |
| let state = |
| device_state.get_mut(device).unwrap_or_else(|| panic!("no device {device:?}")); |
| let state: &IpDeviceState<_, I> = state.as_ref(); |
| let mut groups = state.multicast_groups.write(); |
| groups.leave_multicast_group(addr) |
| } |
| |
| fn select_device_for_multicast_group( |
| &mut self, |
| addr: MulticastAddr<<I as Ip>::Addr>, |
| ) -> Result<Self::DeviceId, ResolveRouteError> { |
| let Self { device_state, table, ip_forwarding_ctx, .. } = self; |
| let remote_ip = SocketIpAddr::new_from_multicast(addr); |
| let ResolvedRoute { device, .. }: ResolvedRoute<I, _> = lookup_route( |
| table.as_ref(), |
| ip_forwarding_ctx, |
| device_state, |
| None, |
| None, |
| remote_ip, |
| )?; |
| Ok(device) |
| } |
| } |
| |
| impl< |
| I: IpLayerIpExt + IpDeviceStateIpExt, |
| BC: InstantContext + TracingContext + FilterBindingsTypes + 'static, |
| DeviceId: FakeFilterDeviceId<BC::DeviceClass>, |
| > TransportIpContext<I, BC> for FakeDualStackIpSocketCtx<DeviceId, BC> |
| { |
| fn get_default_hop_limits(&mut self, device: Option<&Self::DeviceId>) -> HopLimits { |
| device.map_or(DEFAULT_HOP_LIMITS, |device| { |
| let state: &IpDeviceState<_, I> = self.get_device_state(device).as_ref(); |
| let hop_limit = state.default_hop_limit.read().clone(); |
| HopLimits { unicast: hop_limit, multicast: DEFAULT_HOP_LIMITS.multicast } |
| }) |
| } |
| |
| type DevicesWithAddrIter<'a> = alloc::boxed::Box<dyn Iterator<Item = DeviceId> + 'a>; |
| |
| fn get_devices_with_assigned_addr( |
| &mut self, |
| addr: SpecifiedAddr<<I>::Addr>, |
| ) -> Self::DevicesWithAddrIter<'_> { |
| Box::new(self.find_devices_with_addr::<I>(addr)) |
| } |
| |
| fn confirm_reachable_with_destination( |
| &mut self, |
| _bindings_ctx: &mut BC, |
| _dst: SpecifiedAddr<<I>::Addr>, |
| _device: Option<&Self::DeviceId>, |
| ) { |
| } |
| } |
| |
| #[derive(Clone, GenericOverIp)] |
| #[generic_over_ip()] |
| pub(crate) struct FakeDeviceConfig<D, A> { |
| pub(crate) device: D, |
| pub(crate) local_ips: Vec<A>, |
| pub(crate) remote_ips: Vec<A>, |
| } |
| |
| impl<I: IpLayerIpExt + IpDeviceStateIpExt, D: FakeStrongDeviceId> FakeIpSocketCtx<I, D> { |
| /// Creates a new `FakeIpSocketCtx<Ipv6>` with the given device |
| /// configs. |
| pub(crate) fn new( |
| devices: impl IntoIterator<Item = FakeDeviceConfig<D, SpecifiedAddr<I::Addr>>>, |
| ) -> Self { |
| FakeIpSocketCtx::with_devices_state(devices.into_iter().map( |
| |FakeDeviceConfig { device, local_ips, remote_ips }| { |
| let mut device_state = IpDeviceState::default(); |
| for ip in local_ips { |
| crate::ip::device::state::testutil::add_addr_subnet(&mut device_state, ip); |
| } |
| (device, device_state, remote_ips) |
| }, |
| )) |
| } |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use alloc::vec::Vec; |
| |
| use assert_matches::assert_matches; |
| use const_unwrap::const_unwrap_option; |
| use ip_test_macro::ip_test; |
| |
| use net_types::{ |
| ip::{AddrSubnet, GenericOverIp, IpAddr, IpAddress, IpInvariant, Ipv4, Ipv4Addr, Ipv6}, |
| Witness, |
| }; |
| use packet::{Buf, InnerPacketBuilder, ParseBuffer, Serializer as _}; |
| use packet_formats::{ |
| ethernet::EthernetFrameLengthCheck, |
| icmp::{IcmpIpExt, IcmpUnusedCode}, |
| ip::{IpExt, IpPacket, Ipv4Proto}, |
| ipv4::{Ipv4OnlyMeta, Ipv4Packet}, |
| testutil::{parse_ethernet_frame, parse_ip_packet_in_ethernet_frame}, |
| }; |
| use test_case::test_case; |
| |
| use super::*; |
| use crate::{ |
| context::EventContext, |
| device::{ |
| loopback::{LoopbackCreationProperties, LoopbackDevice}, |
| testutil::FakeDeviceId, |
| DeviceId, EthernetLinkDevice, |
| }, |
| ip::{ |
| device::IpDeviceConfigurationContext as DeviceIpDeviceConfigurationContext, |
| types::{AddableEntryEither, AddableMetric, RawMetric}, |
| IpLayerEvent, IpStateContext, |
| }, |
| testutil::*, |
| UnlockedCoreCtx, |
| }; |
| |
| enum AddressType { |
| LocallyOwned, |
| Remote, |
| Unspecified { |
| // Indicates whether or not it should be possible for the stack to |
| // select an address when the client fails to specify one. |
| can_select: bool, |
| }, |
| Unroutable, |
| } |
| |
| enum DeviceType { |
| Unspecified, |
| OtherDevice, |
| LocalDevice, |
| } |
| |
| struct NewSocketTestCase { |
| local_ip_type: AddressType, |
| remote_ip_type: AddressType, |
| device_type: DeviceType, |
| expected_result: Result<(), IpSockCreationError>, |
| } |
| |
| trait IpSocketIpExt: Ip + TestIpExt + IcmpIpExt + IpExt + crate::ip::IpExt { |
| fn multicast_addr(host: u8) -> SpecifiedAddr<Self::Addr>; |
| } |
| |
| impl IpSocketIpExt for Ipv4 { |
| fn multicast_addr(host: u8) -> SpecifiedAddr<Self::Addr> { |
| let [a, b, c, _] = Ipv4::MULTICAST_SUBNET.network().ipv4_bytes(); |
| SpecifiedAddr::new(Ipv4Addr::new([a, b, c, host])).unwrap() |
| } |
| } |
| impl IpSocketIpExt for Ipv6 { |
| fn multicast_addr(host: u8) -> SpecifiedAddr<Self::Addr> { |
| let mut bytes = Ipv6::MULTICAST_SUBNET.network().ipv6_bytes(); |
| bytes[15] = host; |
| SpecifiedAddr::new(Ipv6Addr::from_bytes(bytes)).unwrap() |
| } |
| } |
| |
| #[derive(Copy, Clone, Debug, Eq, PartialEq)] |
| struct WithHopLimit(Option<NonZeroU8>); |
| |
| impl<I: Ip> SendOptions<I> for WithHopLimit { |
| fn hop_limit(&self, _destination: &SpecifiedAddr<I::Addr>) -> Option<NonZeroU8> { |
| let Self(hop_limit) = self; |
| *hop_limit |
| } |
| } |
| |
| #[netstack3_macros::context_ip_bounds(I, FakeBindingsCtx, crate)] |
| fn remove_all_local_addrs<I: crate::IpExt>(ctx: &mut FakeCtx) { |
| let devices = DeviceIpDeviceConfigurationContext::<I, _>::with_devices_and_state( |
| &mut ctx.core_ctx(), |
| |devices, _ctx| devices.collect::<Vec<_>>(), |
| ); |
| for device in devices { |
| #[derive(GenericOverIp)] |
| #[generic_over_ip(I, Ip)] |
| struct WrapVecAddrSubnet<I: Ip>(Vec<AddrSubnet<I::Addr>>); |
| |
| let WrapVecAddrSubnet(subnets) = I::map_ip( |
| IpInvariant((&mut ctx.core_ctx(), &device)), |
| |IpInvariant((core_ctx, device))| { |
| crate::ip::device::with_assigned_ipv4_addr_subnets(core_ctx, device, |addrs| { |
| WrapVecAddrSubnet(addrs.collect::<Vec<_>>()) |
| }) |
| }, |
| |IpInvariant((core_ctx, device))| { |
| crate::ip::device::testutil::with_assigned_ipv6_addr_subnets( |
| core_ctx, |
| device, |
| |addrs| WrapVecAddrSubnet(addrs.collect::<Vec<_>>()), |
| ) |
| }, |
| ); |
| |
| for subnet in subnets { |
| ctx.core_api() |
| .device_ip::<I>() |
| .del_ip_addr(&device, subnet.addr()) |
| .expect("failed to remove addr from device"); |
| } |
| } |
| } |
| |
| #[ip_test] |
| #[test_case(NewSocketTestCase { |
| local_ip_type: AddressType::Unroutable, |
| remote_ip_type: AddressType::Remote, |
| device_type: DeviceType::Unspecified, |
| expected_result: Err(ResolveRouteError::NoSrcAddr.into()), |
| }; "unroutable local to remote")] |
| #[test_case(NewSocketTestCase { |
| local_ip_type: AddressType::LocallyOwned, |
| remote_ip_type: AddressType::Unroutable, |
| device_type: DeviceType::Unspecified, |
| expected_result: Err(ResolveRouteError::Unreachable.into()), |
| }; "local to unroutable remote")] |
| #[test_case(NewSocketTestCase { |
| local_ip_type: AddressType::LocallyOwned, |
| remote_ip_type: AddressType::Remote, |
| device_type: DeviceType::Unspecified, |
| expected_result: Ok(()), |
| }; "local to remote")] |
| #[test_case(NewSocketTestCase { |
| local_ip_type: AddressType::Unspecified { can_select: true }, |
| remote_ip_type: AddressType::Remote, |
| device_type: DeviceType::Unspecified, |
| expected_result: Ok(()), |
| }; "unspecified to remote")] |
| #[test_case(NewSocketTestCase { |
| local_ip_type: AddressType::Unspecified { can_select: true }, |
| remote_ip_type: AddressType::Remote, |
| device_type: DeviceType::LocalDevice, |
| expected_result: Ok(()), |
| }; "unspecified to remote through local device")] |
| #[test_case(NewSocketTestCase { |
| local_ip_type: AddressType::Unspecified { can_select: true }, |
| remote_ip_type: AddressType::Remote, |
| device_type: DeviceType::OtherDevice, |
| expected_result: Err(ResolveRouteError::Unreachable.into()), |
| }; "unspecified to remote through other device")] |
| #[test_case(NewSocketTestCase { |
| local_ip_type: AddressType::Unspecified { can_select: false }, |
| remote_ip_type: AddressType::Remote, |
| device_type: DeviceType::Unspecified, |
| expected_result: Err(ResolveRouteError::NoSrcAddr.into()), |
| }; "new unspcified to remote can't select")] |
| #[test_case(NewSocketTestCase { |
| local_ip_type: AddressType::Remote, |
| remote_ip_type: AddressType::Remote, |
| device_type: DeviceType::Unspecified, |
| expected_result: Err(ResolveRouteError::NoSrcAddr.into()), |
| }; "new remote to remote")] |
| #[test_case(NewSocketTestCase { |
| local_ip_type: AddressType::LocallyOwned, |
| remote_ip_type: AddressType::LocallyOwned, |
| device_type: DeviceType::Unspecified, |
| expected_result: Ok(()), |
| }; "new local to local")] |
| #[test_case(NewSocketTestCase { |
| local_ip_type: AddressType::Unspecified { can_select: true }, |
| remote_ip_type: AddressType::LocallyOwned, |
| device_type: DeviceType::Unspecified, |
| expected_result: Ok(()), |
| }; "new unspecified to local")] |
| #[test_case(NewSocketTestCase { |
| local_ip_type: AddressType::Remote, |
| remote_ip_type: AddressType::LocallyOwned, |
| device_type: DeviceType::Unspecified, |
| expected_result: Err(ResolveRouteError::NoSrcAddr.into()), |
| }; "new remote to local")] |
| #[netstack3_macros::context_ip_bounds(I, FakeBindingsCtx, crate)] |
| fn test_new<I: Ip + IpSocketIpExt + crate::IpExt>(test_case: NewSocketTestCase) { |
| let cfg = I::FAKE_CONFIG; |
| let proto = I::ICMP_IP_PROTO; |
| |
| let FakeEventDispatcherConfig { local_ip, remote_ip, subnet, local_mac: _, remote_mac: _ } = |
| cfg; |
| let (mut ctx, device_ids) = FakeEventDispatcherBuilder::from_config(cfg).build(); |
| let loopback_device_id = ctx |
| .core_api() |
| .device::<LoopbackDevice>() |
| .add_device_with_default_state( |
| LoopbackCreationProperties { mtu: Mtu::new(u16::MAX as u32) }, |
| DEFAULT_INTERFACE_METRIC, |
| ) |
| .into(); |
| |
| crate::device::testutil::enable_device(&mut ctx, &loopback_device_id); |
| |
| let NewSocketTestCase { local_ip_type, remote_ip_type, expected_result, device_type } = |
| test_case; |
| |
| let local_device = match device_type { |
| DeviceType::Unspecified => None, |
| DeviceType::LocalDevice => Some(device_ids[0].clone().into()), |
| DeviceType::OtherDevice => Some(loopback_device_id.clone()), |
| }; |
| |
| let (expected_from_ip, from_ip) = match local_ip_type { |
| AddressType::LocallyOwned => (local_ip, Some(local_ip)), |
| AddressType::Remote => (remote_ip, Some(remote_ip)), |
| AddressType::Unspecified { can_select } => { |
| if !can_select { |
| remove_all_local_addrs::<I>(&mut ctx); |
| } |
| (local_ip, None) |
| } |
| AddressType::Unroutable => { |
| remove_all_local_addrs::<I>(&mut ctx); |
| (local_ip, Some(local_ip)) |
| } |
| }; |
| let to_ip = match remote_ip_type { |
| AddressType::LocallyOwned => local_ip, |
| AddressType::Remote => remote_ip, |
| AddressType::Unspecified { can_select: _ } => { |
| panic!("remote_ip_type cannot be unspecified") |
| } |
| AddressType::Unroutable => { |
| ctx.test_api().del_routes_to_subnet(subnet.into()).unwrap(); |
| remote_ip |
| } |
| }; |
| let Ctx { core_ctx, bindings_ctx } = &mut ctx; |
| |
| let get_expected_result = |template| expected_result.map(|()| template); |
| let weak_local_device = local_device.as_ref().map(|d| d.downgrade()); |
| let template = IpSock { |
| definition: IpSockDefinition { |
| remote_ip: SocketIpAddr::try_from(to_ip).unwrap(), |
| local_ip: SocketIpAddr::try_from(expected_from_ip).unwrap(), |
| device: weak_local_device.clone(), |
| proto, |
| }, |
| options: WithHopLimit(None), |
| }; |
| |
| let res = IpSocketHandler::<I, _>::new_ip_socket( |
| &mut core_ctx.context(), |
| bindings_ctx, |
| weak_local_device.as_ref().map(EitherDeviceId::Weak), |
| from_ip.map(|a| SocketIpAddr::try_from(a).unwrap()), |
| SocketIpAddr::try_from(to_ip).unwrap(), |
| proto, |
| WithHopLimit(None), |
| ) |
| .map_err(|(e, WithHopLimit(_))| e); |
| assert_eq!(res, get_expected_result(template.clone())); |
| |
| // Hop Limit is specified. |
| const SPECIFIED_HOP_LIMIT: NonZeroU8 = const_unwrap_option(NonZeroU8::new(1)); |
| assert_eq!( |
| IpSocketHandler::new_ip_socket( |
| &mut core_ctx.context(), |
| bindings_ctx, |
| weak_local_device.as_ref().map(EitherDeviceId::Weak), |
| from_ip.map(|a| SocketIpAddr::try_from(a).unwrap()), |
| SocketIpAddr::try_from(to_ip).unwrap(), |
| proto, |
| WithHopLimit(Some(SPECIFIED_HOP_LIMIT)), |
| ) |
| .map_err(|(e, WithHopLimit(_))| e), |
| { |
| // The template socket, but with the TTL set to 1. |
| let mut template_with_hop_limit = template; |
| let IpSock { definition: _, options } = &mut template_with_hop_limit; |
| *options = WithHopLimit(Some(SPECIFIED_HOP_LIMIT)); |
| get_expected_result(template_with_hop_limit) |
| } |
| ); |
| } |
| |
| #[ip_test] |
| #[test_case(AddressType::LocallyOwned, AddressType::LocallyOwned; "local to local")] |
| #[test_case(AddressType::Unspecified { can_select: true }, |
| AddressType::LocallyOwned; "unspecified to local")] |
| #[test_case(AddressType::LocallyOwned, AddressType::Remote; "local to remote")] |
| #[netstack3_macros::context_ip_bounds(I, FakeBindingsCtx, crate)] |
| fn test_send_local<I: Ip + IpSocketIpExt + crate::IpExt>( |
| from_addr_type: AddressType, |
| to_addr_type: AddressType, |
| ) where |
| for<'a> UnlockedCoreCtx<'a, FakeBindingsCtx>: IpSocketHandler<I, FakeBindingsCtx> |
| + DeviceIdContext<AnyDevice, DeviceId = DeviceId<FakeBindingsCtx>>, |
| { |
| set_logger_for_test(); |
| |
| use packet_formats::icmp::{IcmpEchoRequest, IcmpPacketBuilder}; |
| |
| let FakeEventDispatcherConfig::<I::Addr> { |
| subnet, |
| local_ip, |
| remote_ip, |
| local_mac, |
| remote_mac: _, |
| } = I::FAKE_CONFIG; |
| |
| let mut builder = FakeEventDispatcherBuilder::default(); |
| let device_idx = builder.add_device(local_mac); |
| let (mut ctx, device_ids) = builder.build(); |
| let device_id: DeviceId<_> = device_ids[device_idx].clone().into(); |
| |
| ctx.core_api() |
| .device_ip::<I>() |
| .add_ip_addr_subnet(&device_id, AddrSubnet::new(local_ip.get(), 16).unwrap()) |
| .unwrap(); |
| ctx.core_api() |
| .device_ip::<I>() |
| .add_ip_addr_subnet(&device_id, AddrSubnet::new(remote_ip.get(), 16).unwrap()) |
| .unwrap(); |
| ctx.test_api() |
| .add_route(AddableEntryEither::without_gateway( |
| subnet.into(), |
| device_id, |
| AddableMetric::ExplicitMetric(RawMetric(0)), |
| )) |
| .unwrap(); |
| |
| let loopback_device_id = ctx |
| .core_api() |
| .device::<LoopbackDevice>() |
| .add_device_with_default_state( |
| LoopbackCreationProperties { mtu: Mtu::new(u16::MAX as u32) }, |
| DEFAULT_INTERFACE_METRIC, |
| ) |
| .into(); |
| crate::device::testutil::enable_device(&mut ctx, &loopback_device_id); |
| let Ctx { core_ctx, bindings_ctx } = &mut ctx; |
| |
| let (expected_from_ip, from_ip) = match from_addr_type { |
| AddressType::LocallyOwned => (local_ip, Some(local_ip)), |
| AddressType::Remote => panic!("from_addr_type cannot be remote"), |
| AddressType::Unspecified { can_select: _ } => (local_ip, None), |
| AddressType::Unroutable => panic!("from_addr_type cannot be unroutable"), |
| }; |
| |
| let to_ip = match to_addr_type { |
| AddressType::LocallyOwned => local_ip, |
| AddressType::Remote => remote_ip, |
| AddressType::Unspecified { can_select: _ } => { |
| panic!("to_addr_type cannot be unspecified") |
| } |
| AddressType::Unroutable => panic!("to_addr_type cannot be unroutable"), |
| }; |
| |
| let sock = IpSocketHandler::<I, _>::new_ip_socket( |
| &mut core_ctx.context(), |
| bindings_ctx, |
| None, |
| from_ip.map(|a| SocketIpAddr::try_from(a).unwrap()), |
| SocketIpAddr::try_from(to_ip).unwrap(), |
| I::ICMP_IP_PROTO, |
| DefaultSendOptions, |
| ) |
| .unwrap(); |
| |
| let reply = IcmpEchoRequest::new(0, 0).reply(); |
| let body = &[1, 2, 3, 4]; |
| let buffer = Buf::new(body.to_vec(), ..) |
| .encapsulate(IcmpPacketBuilder::<I, _>::new( |
| expected_from_ip.get(), |
| to_ip.get(), |
| IcmpUnusedCode, |
| reply, |
| )) |
| .serialize_vec_outer() |
| .unwrap(); |
| |
| // Send an echo packet on the socket and validate that the packet is |
| // delivered locally. |
| IpSocketHandler::<I, _>::send_ip_packet( |
| &mut core_ctx.context(), |
| bindings_ctx, |
| &sock, |
| buffer.into_inner().buffer_view().as_ref().into_serializer(), |
| None, |
| ) |
| .unwrap(); |
| |
| assert!(handle_queued_rx_packets(&mut ctx)); |
| |
| assert_matches!(ctx.bindings_ctx.take_ethernet_frames()[..], []); |
| |
| assert_eq!(ctx.core_ctx.ip_counters::<I>().dispatch_receive_ip_packet.get(), 1); |
| } |
| |
| #[ip_test] |
| fn test_send<I: Ip + IpSocketIpExt + IpLayerIpExt>() |
| where |
| for<'a> UnlockedCoreCtx<'a, FakeBindingsCtx>: IpSocketHandler<I, FakeBindingsCtx> |
| + IpDeviceContext<I, FakeBindingsCtx, DeviceId = DeviceId<FakeBindingsCtx>> |
| + IpStateContext<I, FakeBindingsCtx>, |
| FakeBindingsCtx: EventContext<IpLayerEvent<DeviceId<FakeBindingsCtx>, I>>, |
| { |
| // Test various edge cases of the |
| // IpSocketContext::send_ip_packet` method. |
| |
| let cfg = I::FAKE_CONFIG; |
| let proto = I::ICMP_IP_PROTO; |
| let socket_options = WithHopLimit(Some(const_unwrap_option(NonZeroU8::new(1)))); |
| |
| let FakeEventDispatcherConfig::<_> { local_mac, remote_mac, local_ip, remote_ip, subnet } = |
| cfg; |
| |
| let (Ctx { core_ctx, mut bindings_ctx }, device_ids) = |
| FakeEventDispatcherBuilder::from_config(cfg).build(); |
| // Create a normal, routable socket. |
| let sock = IpSocketHandler::<I, _>::new_ip_socket( |
| &mut core_ctx.context(), |
| &mut bindings_ctx, |
| None, |
| None, |
| SocketIpAddr::try_from(remote_ip).unwrap(), |
| proto, |
| socket_options, |
| ) |
| .unwrap(); |
| |
| let curr_id = crate::ip::gen_ip_packet_id::<Ipv4, _, _>(&mut core_ctx.context()); |
| |
| let check_frame = |
| move |frame: &[u8], packet_count| match [local_ip.get(), remote_ip.get()].into() { |
| IpAddr::V4([local_ip, remote_ip]) => { |
| let (mut body, src_mac, dst_mac, _ethertype) = |
| parse_ethernet_frame(frame, EthernetFrameLengthCheck::NoCheck).unwrap(); |
| let packet = (&mut body).parse::<Ipv4Packet<&[u8]>>().unwrap(); |
| assert_eq!(src_mac, local_mac.get()); |
| assert_eq!(dst_mac, remote_mac.get()); |
| assert_eq!(packet.src_ip(), local_ip); |
| assert_eq!(packet.dst_ip(), remote_ip); |
| assert_eq!(packet.proto(), Ipv4::ICMP_IP_PROTO); |
| assert_eq!(packet.ttl(), 1); |
| let Ipv4OnlyMeta { id } = packet.version_specific_meta(); |
| assert_eq!(usize::from(id), usize::from(curr_id) + packet_count); |
| assert_eq!(body, [0]); |
| } |
| IpAddr::V6([local_ip, remote_ip]) => { |
| let (body, src_mac, dst_mac, src_ip, dst_ip, ip_proto, ttl) = |
| parse_ip_packet_in_ethernet_frame::<Ipv6>( |
| frame, |
| EthernetFrameLengthCheck::NoCheck, |
| ) |
| .unwrap(); |
| assert_eq!(body, [0]); |
| assert_eq!(src_mac, local_mac.get()); |
| assert_eq!(dst_mac, remote_mac.get()); |
| assert_eq!(src_ip, local_ip); |
| assert_eq!(dst_ip, remote_ip); |
| assert_eq!(ip_proto, Ipv6::ICMP_IP_PROTO); |
| assert_eq!(ttl, 1); |
| } |
| }; |
| let mut packet_count = 0; |
| assert_matches!(bindings_ctx.take_ethernet_frames()[..], []); |
| |
| // Send a packet on the socket and make sure that the right contents |
| // are sent. |
| IpSocketHandler::<I, _>::send_ip_packet( |
| &mut core_ctx.context(), |
| &mut bindings_ctx, |
| &sock, |
| (&[0u8][..]).into_serializer(), |
| None, |
| ) |
| .unwrap(); |
| let mut check_sent_frame = |bindings_ctx: &mut crate::testutil::FakeBindingsCtx| { |
| packet_count += 1; |
| let frames = bindings_ctx.take_ethernet_frames(); |
| let (dev, frame) = assert_matches!(&frames[..], [frame] => frame); |
| assert_eq!(dev, &device_ids[0]); |
| check_frame(&frame, packet_count); |
| }; |
| check_sent_frame(&mut bindings_ctx); |
| |
| // Send a packet while imposing an MTU that is large enough to fit the |
| // packet. |
| let small_body = [0; 1]; |
| let small_body_serializer = (&small_body).into_serializer(); |
| let res = IpSocketHandler::<I, _>::send_ip_packet( |
| &mut core_ctx.context(), |
| &mut bindings_ctx, |
| &sock, |
| small_body_serializer, |
| Some(Ipv6::MINIMUM_LINK_MTU.into()), |
| ); |
| assert_matches!(res, Ok(())); |
| check_sent_frame(&mut bindings_ctx); |
| |
| // Send a packet on the socket while imposing an MTU which will not |
| // allow a packet to be sent. |
| let res = IpSocketHandler::<I, _>::send_ip_packet( |
| &mut core_ctx.context(), |
| &mut bindings_ctx, |
| &sock, |
| small_body_serializer, |
| Some(1), // mtu |
| ); |
| assert_matches!(res, Err((_, IpSockSendError::Mtu))); |
| |
| assert_matches!(bindings_ctx.take_ethernet_frames()[..], []); |
| // Try sending a packet which will be larger than the device's MTU, |
| // and make sure it fails. |
| let res = IpSocketHandler::<I, _>::send_ip_packet( |
| &mut core_ctx.context(), |
| &mut bindings_ctx, |
| &sock, |
| (&[0; Ipv6::MINIMUM_LINK_MTU.get() as usize][..]).into_serializer(), |
| None, |
| ); |
| assert_matches!(res, Err((_, IpSockSendError::Mtu))); |
| |
| // Make sure that sending on an unroutable socket fails. |
| crate::ip::forwarding::testutil::del_routes_to_subnet::<I, _, _>( |
| &mut core_ctx.context(), |
| &mut bindings_ctx, |
| subnet, |
| ) |
| .unwrap(); |
| let res = IpSocketHandler::<I, _>::send_ip_packet( |
| &mut core_ctx.context(), |
| &mut bindings_ctx, |
| &sock, |
| small_body_serializer, |
| None, |
| ); |
| assert_matches!(res, Err((_, IpSockSendError::Unroutable(ResolveRouteError::Unreachable)))); |
| } |
| |
| #[ip_test] |
| #[netstack3_macros::context_ip_bounds(I, FakeBindingsCtx, crate)] |
| fn test_send_hop_limits<I: Ip + IpSocketIpExt + crate::IpExt>() { |
| set_logger_for_test(); |
| |
| #[derive(Copy, Clone, Debug)] |
| struct SetHopLimitFor<A>(SpecifiedAddr<A>); |
| |
| const SET_HOP_LIMIT: NonZeroU8 = const_unwrap_option(NonZeroU8::new(42)); |
| |
| impl<A: IpAddress> SendOptions<A::Version> for SetHopLimitFor<A> { |
| fn hop_limit(&self, destination: &SpecifiedAddr<A>) -> Option<NonZeroU8> { |
| let Self(expected_destination) = self; |
| (destination == expected_destination).then_some(SET_HOP_LIMIT) |
| } |
| } |
| |
| let FakeEventDispatcherConfig::<I::Addr> { |
| local_ip, |
| remote_ip: _, |
| local_mac, |
| subnet: _, |
| remote_mac: _, |
| } = I::FAKE_CONFIG; |
| |
| let mut builder = FakeEventDispatcherBuilder::default(); |
| let device_idx = builder.add_device(local_mac); |
| let (mut ctx, device_ids) = builder.build(); |
| let device_id: DeviceId<_> = device_ids[device_idx].clone().into(); |
| |
| ctx.core_api() |
| .device_ip::<I>() |
| .add_ip_addr_subnet(&device_id, AddrSubnet::new(local_ip.get(), 16).unwrap()) |
| .unwrap(); |
| |
| // Use multicast remote addresses since unicast addresses would trigger |
| // ARP/NDP requests. |
| ctx.test_api() |
| .add_route(AddableEntryEither::without_gateway( |
| I::MULTICAST_SUBNET.into(), |
| device_id, |
| AddableMetric::ExplicitMetric(RawMetric(0)), |
| )) |
| .expect("add device route"); |
| let remote_ip = I::multicast_addr(0); |
| let options = SetHopLimitFor(remote_ip); |
| let other_remote_ip = I::multicast_addr(1); |
| |
| let (mut core_ctx, bindings_ctx) = ctx.contexts(); |
| let mut send_to = |destination_ip| { |
| let sock = IpSocketHandler::<I, _>::new_ip_socket( |
| &mut core_ctx, |
| bindings_ctx, |
| None, |
| None, |
| destination_ip, |
| I::ICMP_IP_PROTO, |
| options, |
| ) |
| .unwrap(); |
| |
| IpSocketHandler::<I, _>::send_ip_packet( |
| &mut core_ctx, |
| bindings_ctx, |
| &sock, |
| (&[0u8][..]).into_serializer(), |
| None, |
| ) |
| .unwrap(); |
| }; |
| |
| // Send to two remote addresses: `remote_ip` and `other_remote_ip` and |
| // check that the frames were sent with the correct hop limits. |
| send_to(SocketIpAddr::try_from(remote_ip).unwrap()); |
| send_to(SocketIpAddr::try_from(other_remote_ip).unwrap()); |
| |
| let frames = bindings_ctx.take_ethernet_frames(); |
| let [df_remote, df_other_remote] = assert_matches!(&frames[..], [df1, df2] => [df1, df2]); |
| { |
| let (_dev, frame) = df_remote; |
| let (_body, _src_mac, _dst_mac, _src_ip, dst_ip, _ip_proto, hop_limit) = |
| parse_ip_packet_in_ethernet_frame::<I>(&frame, EthernetFrameLengthCheck::NoCheck) |
| .unwrap(); |
| assert_eq!(dst_ip, remote_ip.get()); |
| // The `SetHopLimit`-returned value should take precedence. |
| assert_eq!(hop_limit, SET_HOP_LIMIT.get()); |
| } |
| |
| { |
| let (_dev, frame) = df_other_remote; |
| let (_body, _src_mac, _dst_mac, _src_ip, dst_ip, _ip_proto, hop_limit) = |
| parse_ip_packet_in_ethernet_frame::<I>(&frame, EthernetFrameLengthCheck::NoCheck) |
| .unwrap(); |
| assert_eq!(dst_ip, other_remote_ip.get()); |
| // When the options object does not provide a hop limit the default |
| // is used. |
| assert_eq!(hop_limit, crate::ip::DEFAULT_HOP_LIMITS.unicast.get()); |
| } |
| } |
| |
| #[test] |
| fn manipulate_options() { |
| // The values here don't matter since we won't actually be using this |
| // socket to send anything. |
| const START_OPTION: usize = 23; |
| const DEFAULT_OPTION: usize = 0; |
| const NEW_OPTION: usize = 55; |
| let mut socket = IpSock::<Ipv4, FakeDeviceId, _> { |
| definition: IpSockDefinition { |
| remote_ip: SocketIpAddr::new(Ipv4::LOOPBACK_ADDRESS.get()).unwrap(), |
| local_ip: SocketIpAddr::new(Ipv4::LOOPBACK_ADDRESS.get()).unwrap(), |
| device: None, |
| proto: Ipv4Proto::Icmp, |
| }, |
| options: START_OPTION, |
| }; |
| |
| assert_eq!(socket.take_options(), START_OPTION); |
| assert_eq!(socket.replace_options(NEW_OPTION), DEFAULT_OPTION); |
| assert_eq!(socket.options, NEW_OPTION); |
| } |
| |
| #[ip_test] |
| #[test_case(true; "remove device")] |
| #[test_case(false; "dont remove device")] |
| #[netstack3_macros::context_ip_bounds(I, FakeBindingsCtx, crate)] |
| fn get_mms_device_removed<I: Ip + IpSocketIpExt + crate::IpExt>(remove_device: bool) |
| where |
| for<'a> UnlockedCoreCtx<'a, FakeBindingsCtx>: |
| IpSocketHandler<I, FakeBindingsCtx> + DeviceIpSocketHandler<I, FakeBindingsCtx>, |
| { |
| set_logger_for_test(); |
| |
| let FakeEventDispatcherConfig::<I::Addr> { |
| local_ip, |
| remote_ip: _, |
| local_mac, |
| subnet: _, |
| remote_mac: _, |
| } = I::FAKE_CONFIG; |
| |
| let mut builder = FakeEventDispatcherBuilder::default(); |
| let device_idx = builder.add_device(local_mac); |
| let (mut ctx, device_ids) = builder.build(); |
| let eth_device_id = device_ids[device_idx].clone(); |
| core::mem::drop(device_ids); |
| let device_id: DeviceId<_> = eth_device_id.clone().into(); |
| |
| ctx.core_api() |
| .device_ip::<I>() |
| .add_ip_addr_subnet(&device_id, AddrSubnet::new(local_ip.get(), 16).unwrap()) |
| .unwrap(); |
| ctx.test_api() |
| .add_route(AddableEntryEither::without_gateway( |
| I::MULTICAST_SUBNET.into(), |
| device_id.clone(), |
| AddableMetric::ExplicitMetric(RawMetric(0)), |
| )) |
| .unwrap(); |
| |
| let (mut core_ctx, bindings_ctx) = ctx.contexts(); |
| let ip_sock = IpSocketHandler::<I, _>::new_ip_socket( |
| &mut core_ctx, |
| bindings_ctx, |
| None, |
| None, |
| SocketIpAddr::try_from(I::multicast_addr(1)).unwrap(), |
| I::ICMP_IP_PROTO, |
| WithHopLimit(None), |
| ) |
| .unwrap(); |
| |
| let expected = if remove_device { |
| // Clear routes on the device before removing it. |
| ctx.test_api().del_device_routes(&device_id); |
| |
| // Don't keep any strong device IDs to the device before removing. |
| core::mem::drop(device_id); |
| ctx.core_api() |
| .device::<EthernetLinkDevice>() |
| .remove_device(eth_device_id) |
| .into_removed(); |
| Err(MmsError::NoDevice(ResolveRouteError::Unreachable)) |
| } else { |
| Ok(Mms::from_mtu::<I>( |
| IpDeviceContext::<I, _>::get_mtu(&mut ctx.core_ctx(), &device_id), |
| 0, /* no ip options/ext hdrs used */ |
| ) |
| .unwrap()) |
| }; |
| let (mut core_ctx, bindings_ctx) = ctx.contexts(); |
| assert_eq!(DeviceIpSocketHandler::get_mms(&mut core_ctx, bindings_ctx, &ip_sock), expected,); |
| } |
| } |