| // 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}, |
| MulticastAddress, SpecifiedAddr, |
| }; |
| use packet::{BufferMut, SerializeError}; |
| use thiserror::Error; |
| |
| use crate::{ |
| context::{CounterContext, InstantContext, NonTestCtxMarker, TracingContext}, |
| device::{self, AnyDevice, DeviceIdContext, WeakId as _}, |
| filter::{ |
| FilterBindingsTypes, FilterHandler as _, FilterHandlerProvider, TransportPacketSerializer, |
| }, |
| ip::{ |
| device::{state::IpDeviceStateIpExt, IpDeviceAddr}, |
| types::{ResolvedRoute, RoutableIpAddr}, |
| EitherDeviceId, IpCounters, IpDeviceContext, IpExt, IpLayerIpExt, IpLayerPacketMetadata, |
| 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( |
| &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, |
| ) -> Result<IpSock<I, Self::WeakDeviceId>, IpSockCreationError>; |
| |
| /// 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>, |
| body: S, |
| mtu: Option<u32>, |
| options: &O, |
| ) -> Result<(), (S, IpSockSendError)> |
| where |
| S: TransportPacketSerializer, |
| S::Buffer: BufferMut, |
| O: SendOptions<I, Self::WeakDeviceId>; |
| |
| /// 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<E>> |
| where |
| S: TransportPacketSerializer, |
| S::Buffer: BufferMut, |
| F: FnOnce(SocketIpAddr<I::Addr>) -> Result<S, E>, |
| O: SendOptions<I, Self::WeakDeviceId>, |
| { |
| let tmp = self |
| .new_ip_socket(bindings_ctx, device, local_ip, remote_ip, proto) |
| .map_err(|err| SendOneShotIpPacketError::CreateAndSendError { err: err.into() })?; |
| let packet = get_body_from_src_ip(*tmp.local_ip()) |
| .map_err(SendOneShotIpPacketError::SerializeError)?; |
| self.send_ip_packet(bindings_ctx, &tmp, packet, mtu, options).map_err(|(_body, err)| { |
| SendOneShotIpPacketError::CreateAndSendError { err: err.into() } |
| }) |
| } |
| |
| /// 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> |
| where |
| S: TransportPacketSerializer, |
| S::Buffer: BufferMut, |
| F: FnOnce(SocketIpAddr<I::Addr>) -> S, |
| O: SendOptions<I, Self::WeakDeviceId>, |
| { |
| 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 } => err, |
| 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<E> { |
| CreateAndSendError { err: IpSockCreateAndSendError }, |
| 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( |
| &mut self, |
| bindings_ctx: &mut BC, |
| ip_sock: &IpSock<I, Self::WeakDeviceId>, |
| ) -> 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> { |
| /// The definition of the socket. |
| /// |
| /// This does not change for the lifetime of the socket. |
| definition: IpSockDefinition<I, D>, |
| } |
| |
| /// 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> IpSock<I, D> { |
| 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 |
| } |
| } |
| |
| // 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, |
| packet_metadata: IpLayerPacketMetadata<I, BC>, |
| ) -> 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>> + NonTestCtxMarker, |
| CC::DeviceId: crate::filter::InterfaceProperties<BC::DeviceClass>, |
| { |
| fn new_ip_socket( |
| &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, |
| ) -> Result<IpSock<I, CC::WeakDeviceId>, IpSockCreationError> { |
| let device = device |
| .as_ref() |
| .map(|d| d.as_strong_ref().ok_or(ResolveRouteError::Unreachable)) |
| .transpose()?; |
| 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. |
| let resolved_route = self.lookup_route(bindings_ctx, device, local_ip, remote_ip)?; |
| Ok(new_ip_socket(device, resolved_route, remote_ip, proto)) |
| } |
| |
| fn send_ip_packet<S, O>( |
| &mut self, |
| bindings_ctx: &mut BC, |
| ip_sock: &IpSock<I, CC::WeakDeviceId>, |
| body: S, |
| mtu: Option<u32>, |
| options: &O, |
| ) -> Result<(), (S, IpSockSendError)> |
| where |
| S: TransportPacketSerializer, |
| S::Buffer: BufferMut, |
| O: SendOptions<I, Self::WeakDeviceId>, |
| { |
| // 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, options) |
| } |
| } |
| |
| /// 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, D> { |
| /// 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>; |
| |
| /// Returns the interface selected for outgoing multicast packets.` |
| fn multicast_interface(&self) -> Option<&D>; |
| } |
| |
| /// Empty send options that never overrides default values. |
| #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] |
| pub(crate) struct DefaultSendOptions; |
| |
| impl<I: Ip, D> SendOptions<I, D> for DefaultSendOptions { |
| fn hop_limit(&self, _destination: &SpecifiedAddr<I::Addr>) -> Option<NonZeroU8> { |
| None |
| } |
| |
| fn multicast_interface(&self) -> Option<&D> { |
| None |
| } |
| } |
| |
| fn new_ip_socket<I, D>( |
| requested_device: Option<&D>, |
| route: ResolvedRoute<I, D>, |
| remote_ip: SocketIpAddr<I::Addr>, |
| proto: I::Proto, |
| ) -> IpSock<I, D::Weak> |
| where |
| I: IpLayerIpExt, |
| D: device::StrongId, |
| { |
| // 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: _ } = |
| route; |
| |
| // 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(requested_device) |
| .map(|d| d.downgrade()); |
| |
| let definition = |
| IpSockDefinition { local_ip: src_addr, remote_ip, device: socket_device, proto }; |
| IpSock { definition } |
| } |
| |
| fn send_ip_packet<I, S, BC, CC, O>( |
| core_ctx: &mut CC, |
| bindings_ctx: &mut BC, |
| socket: &IpSock<I, CC::WeakDeviceId>, |
| body: S, |
| mtu: Option<u32>, |
| options: &O, |
| ) -> 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, CC::WeakDeviceId> + ?Sized, |
| { |
| trace_duration!(bindings_ctx, c"ip::send_packet"); |
| |
| let IpSock { definition: IpSockDefinition { remote_ip, local_ip, device, proto } } = socket; |
| |
| let device = device.as_ref().or_else(|| { |
| remote_ip.addr().is_multicast().then(|| options.multicast_interface()).flatten() |
| }); |
| |
| let device = match device.map(|d| d.upgrade()) { |
| Some(Some(device)) => Some(device), |
| Some(None) => return Err((body, ResolveRouteError::Unreachable.into())), |
| None => 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); |
| |
| let mut packet_metadata = IpLayerPacketMetadata::default(); |
| match core_ctx.filter_handler().local_egress_hook(&mut packet, &device, &mut packet_metadata) { |
| crate::filter::Verdict::Drop => { |
| packet_metadata.acknowledge_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) = next_hop.into_next_hop_and_broadcast_marker(remote_ip); |
| |
| 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, |
| packet_metadata, |
| ) |
| .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( |
| &mut self, |
| bindings_ctx: &mut BC, |
| ip_sock: &IpSock<I, Self::WeakDeviceId>, |
| ) -> Result<Mms, MmsError> { |
| let IpSockDefinition { remote_ip, local_ip, device, proto: _ } = &ip_sock.definition; |
| let device = device |
| .as_ref() |
| .map(|d| d.upgrade().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::num::NonZeroUsize; |
| |
| use derivative::Derivative; |
| use net_types::{ |
| ip::{GenericOverIp, IpAddr, IpInvariant, Ipv4, Ipv4Addr, Ipv6}, |
| MulticastAddr, Witness as _, |
| }; |
| |
| use super::*; |
| use crate::{ |
| context::{testutil::FakeCoreCtx, SendFrameContext}, |
| device::testutil::{FakeStrongDeviceId, FakeWeakDeviceId}, |
| ip::{ |
| forwarding::{testutil::FakeIpForwardingCtx, ForwardingTable}, |
| testutil::FakeIpDeviceIdCtx, |
| types::Destination, |
| HopLimits, MulticastMembershipHandler, TransportIpContext, DEFAULT_HOP_LIMITS, |
| }, |
| }; |
| |
| /// 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, D> { |
| pub(crate) table: ForwardingTable<I, D>, |
| forwarding: FakeIpForwardingCtx<D>, |
| devices: HashMap<D, FakeDeviceState<I>>, |
| } |
| |
| impl<I: IpLayerIpExt, D> AsRef<Self> for FakeIpSocketCtx<I, D> { |
| fn as_ref(&self) -> &Self { |
| self |
| } |
| } |
| |
| impl<I: IpLayerIpExt, D> AsMut<Self> for FakeIpSocketCtx<I, D> { |
| fn as_mut(&mut self) -> &mut Self { |
| self |
| } |
| } |
| |
| impl<I: IpLayerIpExt, D: FakeStrongDeviceId, BC> TransportIpContext<I, BC> |
| for FakeIpSocketCtx<I, D> |
| { |
| fn get_default_hop_limits(&mut self, device: Option<&D>) -> HopLimits { |
| device.map_or(DEFAULT_HOP_LIMITS, |device| { |
| let hop_limit = self.get_device_state(device).default_hop_limit; |
| HopLimits { unicast: hop_limit, multicast: DEFAULT_HOP_LIMITS.multicast } |
| }) |
| } |
| |
| type DevicesWithAddrIter<'a> = Box<dyn Iterator<Item = D> + 'a>; |
| |
| fn get_devices_with_assigned_addr( |
| &mut self, |
| addr: SpecifiedAddr<<I>::Addr>, |
| ) -> Self::DevicesWithAddrIter<'_> { |
| Box::new(self.devices.iter().filter_map(move |(device, state)| { |
| state.addresses.contains(&addr).then(|| device.clone()) |
| })) |
| } |
| |
| fn confirm_reachable_with_destination( |
| &mut self, |
| _bindings_ctx: &mut BC, |
| _dst: SpecifiedAddr<<I>::Addr>, |
| _device: Option<&D>, |
| ) { |
| } |
| } |
| |
| impl<I: IpLayerIpExt, D: FakeStrongDeviceId> DeviceIdContext<AnyDevice> for FakeIpSocketCtx<I, D> { |
| type DeviceId = <FakeIpDeviceIdCtx<D> as DeviceIdContext<AnyDevice>>::DeviceId; |
| type WeakDeviceId = <FakeIpDeviceIdCtx<D> as DeviceIdContext<AnyDevice>>::WeakDeviceId; |
| } |
| |
| impl<I, D, BC> IpSocketHandler<I, BC> for FakeIpSocketCtx<I, D> |
| where |
| I: IpLayerIpExt, |
| D: FakeStrongDeviceId, |
| { |
| fn new_ip_socket( |
| &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, |
| ) -> Result<IpSock<I, Self::WeakDeviceId>, IpSockCreationError> { |
| let device = device |
| .as_ref() |
| .map(|d| d.as_strong_ref().ok_or(ResolveRouteError::Unreachable)) |
| .transpose()?; |
| let device = device.as_ref().map(|d| d.as_ref()); |
| let resolved_route = self.lookup_route(device, local_ip, remote_ip)?; |
| Ok(new_ip_socket(device, resolved_route, remote_ip, proto)) |
| } |
| |
| fn send_ip_packet<S, O>( |
| &mut self, |
| _bindings_ctx: &mut BC, |
| _socket: &IpSock<I, Self::WeakDeviceId>, |
| _body: S, |
| _mtu: Option<u32>, |
| _options: &O, |
| ) -> Result<(), (S, IpSockSendError)> |
| where |
| S: TransportPacketSerializer, |
| S::Buffer: BufferMut, |
| O: SendOptions<I, Self::WeakDeviceId>, |
| { |
| panic!("FakeIpSocketCtx can't send packets, wrap it in a FakeCoreCtx instead"); |
| } |
| } |
| |
| impl< |
| I: IpLayerIpExt + IpDeviceStateIpExt, |
| State: AsRef<FakeIpSocketCtx<I, DeviceId>> |
| + AsMut<FakeIpSocketCtx<I, DeviceId>> |
| + AsRef<FakeIpDeviceIdCtx<DeviceId>>, |
| Meta, |
| DeviceId: FakeStrongDeviceId, |
| BC, |
| > IpSocketHandler<I, BC> for FakeCoreCtx<State, Meta, DeviceId> |
| where |
| FakeCoreCtx<State, Meta, DeviceId>: |
| SendFrameContext<BC, SendIpPacketMeta<I, Self::DeviceId, SpecifiedAddr<I::Addr>>>, |
| { |
| fn new_ip_socket( |
| &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, |
| ) -> Result<IpSock<I, Self::WeakDeviceId>, IpSockCreationError> { |
| self.get_mut().as_mut().new_ip_socket(bindings_ctx, device, local_ip, remote_ip, proto) |
| } |
| |
| fn send_ip_packet<S, O>( |
| &mut self, |
| bindings_ctx: &mut BC, |
| socket: &IpSock<I, Self::WeakDeviceId>, |
| body: S, |
| mtu: Option<u32>, |
| options: &O, |
| ) -> Result<(), (S, IpSockSendError)> |
| where |
| S: TransportPacketSerializer, |
| S::Buffer: BufferMut, |
| O: SendOptions<I, Self::WeakDeviceId>, |
| { |
| let meta = match self.state.as_mut().resolve_send_meta(socket, mtu, options) { |
| Ok(meta) => meta, |
| Err(e) => { |
| return Err((body, e)); |
| } |
| }; |
| self.send_frame(bindings_ctx, meta, body).map_err(|s| (s, IpSockSendError::Mtu)) |
| } |
| } |
| |
| impl<I: IpLayerIpExt, D: FakeStrongDeviceId, BC> 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 value = self.get_device_state_mut(device).multicast_groups.entry(addr).or_insert(0); |
| *value = value.checked_add(1).unwrap(); |
| } |
| |
| fn leave_multicast_group( |
| &mut self, |
| _bindings_ctx: &mut BC, |
| device: &Self::DeviceId, |
| addr: MulticastAddr<<I as Ip>::Addr>, |
| ) { |
| let value = self |
| .get_device_state_mut(device) |
| .multicast_groups |
| .get_mut(&addr) |
| .unwrap_or_else(|| panic!("no entry for {addr} on {device:?}")); |
| *value = value.checked_sub(1).unwrap(); |
| } |
| |
| fn select_device_for_multicast_group( |
| &mut self, |
| addr: MulticastAddr<<I as Ip>::Addr>, |
| ) -> Result<Self::DeviceId, ResolveRouteError> { |
| let remote_ip = SocketIpAddr::new_from_multicast(addr); |
| self.lookup_route(None, None, remote_ip).map(|ResolvedRoute { device, .. }| device) |
| } |
| } |
| |
| impl< |
| I: IpLayerIpExt, |
| BC: InstantContext + TracingContext + FilterBindingsTypes, |
| D: FakeStrongDeviceId, |
| State: TransportIpContext<I, BC, DeviceId = D>, |
| Meta, |
| > TransportIpContext<I, BC> for FakeCoreCtx<State, Meta, D> |
| where |
| Self: IpSocketHandler<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> { |
| v4: FakeIpSocketCtx<Ipv4, D>, |
| v6: FakeIpSocketCtx<Ipv6, D>, |
| } |
| |
| impl<D: FakeStrongDeviceId> FakeDualStackIpSocketCtx<D> { |
| pub(crate) fn new<A: Into<SpecifiedAddr<IpAddr>>>( |
| devices: impl IntoIterator<Item = FakeDeviceConfig<D, A>>, |
| ) -> Self { |
| let partition = |
| |v: Vec<A>| -> (Vec<SpecifiedAddr<Ipv4Addr>>, Vec<SpecifiedAddr<Ipv6Addr>>) { |
| v.into_iter().fold((Vec::new(), Vec::new()), |(mut v4, mut v6), i| { |
| match IpAddr::from(i.into()) { |
| IpAddr::V4(a) => v4.push(a), |
| IpAddr::V6(a) => v6.push(a), |
| } |
| (v4, v6) |
| }) |
| }; |
| |
| let (v4, v6): (Vec<_>, Vec<_>) = devices |
| .into_iter() |
| .map(|FakeDeviceConfig { device, local_ips, remote_ips }| { |
| let (local_v4, local_v6) = partition(local_ips); |
| let (remote_v4, remote_v6) = partition(remote_ips); |
| ( |
| FakeDeviceConfig { |
| device: device.clone(), |
| local_ips: local_v4, |
| remote_ips: remote_v4, |
| }, |
| FakeDeviceConfig { device, local_ips: local_v6, remote_ips: remote_v6 }, |
| ) |
| }) |
| .unzip(); |
| Self { v4: FakeIpSocketCtx::new(v4), v6: FakeIpSocketCtx::new(v6) } |
| } |
| |
| fn inner_mut<I: IpLayerIpExt>(&mut self) -> &mut FakeIpSocketCtx<I, D> { |
| I::map_ip(IpInvariant(self), |IpInvariant(s)| &mut s.v4, |IpInvariant(s)| &mut s.v6) |
| } |
| |
| fn inner<I: IpLayerIpExt>(&self) -> &FakeIpSocketCtx<I, D> { |
| I::map_ip(IpInvariant(self), |IpInvariant(s)| &s.v4, |IpInvariant(s)| &s.v6) |
| } |
| |
| 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( |
| &mut self.v4.table, |
| ip, |
| device, |
| ), |
| IpAddr::V6(ip) => crate::ip::forwarding::testutil::add_on_link_forwarding_entry( |
| &mut self.v6.table, |
| ip, |
| device, |
| ), |
| } |
| } |
| |
| pub(crate) fn get_device_state_mut<I: IpLayerIpExt>( |
| &mut self, |
| device: &D, |
| ) -> &mut FakeDeviceState<I> { |
| self.inner_mut::<I>().get_device_state_mut(device) |
| } |
| |
| pub(crate) fn multicast_memberships<I: IpLayerIpExt>( |
| &self, |
| ) -> HashMap<(D, MulticastAddr<I::Addr>), NonZeroUsize> { |
| self.inner::<I>().multicast_memberships() |
| } |
| } |
| |
| impl<D: FakeStrongDeviceId> AsRef<Self> for FakeDualStackIpSocketCtx<D> { |
| fn as_ref(&self) -> &Self { |
| self |
| } |
| } |
| |
| impl<D: FakeStrongDeviceId> DeviceIdContext<AnyDevice> for FakeDualStackIpSocketCtx<D> { |
| type DeviceId = D; |
| type WeakDeviceId = D::Weak; |
| } |
| |
| impl<I: IpLayerIpExt, D: FakeStrongDeviceId, BC> IpSocketHandler<I, BC> |
| for FakeDualStackIpSocketCtx<D> |
| { |
| fn new_ip_socket( |
| &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, |
| ) -> Result<IpSock<I, Self::WeakDeviceId>, IpSockCreationError> { |
| IpSocketHandler::<I, BC>::new_ip_socket( |
| self.inner_mut::<I>(), |
| bindings_ctx, |
| device, |
| local_ip, |
| remote_ip, |
| proto, |
| ) |
| } |
| |
| fn send_ip_packet<S, O>( |
| &mut self, |
| bindings_ctx: &mut BC, |
| socket: &IpSock<I, Self::WeakDeviceId>, |
| body: S, |
| mtu: Option<u32>, |
| options: &O, |
| ) -> Result<(), (S, IpSockSendError)> |
| where |
| S: TransportPacketSerializer, |
| S::Buffer: BufferMut, |
| O: SendOptions<I, Self::WeakDeviceId>, |
| { |
| IpSocketHandler::<I, BC>::send_ip_packet( |
| self.inner_mut::<I>(), |
| bindings_ctx, |
| socket, |
| body, |
| mtu, |
| options, |
| ) |
| } |
| } |
| |
| impl<I: IpLayerIpExt, Meta, DeviceId: FakeStrongDeviceId, BC> IpSocketHandler<I, BC> |
| for FakeCoreCtx<FakeDualStackIpSocketCtx<DeviceId>, Meta, DeviceId> |
| where |
| FakeCoreCtx<FakeDualStackIpSocketCtx<DeviceId>, Meta, DeviceId>: |
| SendFrameContext<BC, SendIpPacketMeta<I, Self::DeviceId, SpecifiedAddr<I::Addr>>>, |
| { |
| fn new_ip_socket( |
| &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, |
| ) -> Result<IpSock<I, Self::WeakDeviceId>, IpSockCreationError> { |
| self.get_mut().new_ip_socket(bindings_ctx, device, local_ip, remote_ip, proto) |
| } |
| |
| fn send_ip_packet<S, O>( |
| &mut self, |
| bindings_ctx: &mut BC, |
| socket: &IpSock<I, Self::WeakDeviceId>, |
| body: S, |
| mtu: Option<u32>, |
| options: &O, |
| ) -> Result<(), (S, IpSockSendError)> |
| where |
| S: TransportPacketSerializer, |
| S::Buffer: BufferMut, |
| O: SendOptions<I, Self::WeakDeviceId>, |
| { |
| let meta = match self.state.inner_mut::<I>().resolve_send_meta(socket, mtu, options) { |
| Ok(meta) => meta, |
| Err(e) => { |
| return Err((body, e)); |
| } |
| }; |
| self.send_frame(bindings_ctx, meta, body).map_err(|s| (s, IpSockSendError::Mtu)) |
| } |
| } |
| |
| impl<I: IpLayerIpExt, D: FakeStrongDeviceId, BC> MulticastMembershipHandler<I, BC> |
| for FakeDualStackIpSocketCtx<D> |
| { |
| fn join_multicast_group( |
| &mut self, |
| bindings_ctx: &mut BC, |
| device: &Self::DeviceId, |
| addr: MulticastAddr<<I as Ip>::Addr>, |
| ) { |
| MulticastMembershipHandler::<I, BC>::join_multicast_group( |
| self.inner_mut::<I>(), |
| bindings_ctx, |
| device, |
| addr, |
| ) |
| } |
| |
| fn leave_multicast_group( |
| &mut self, |
| bindings_ctx: &mut BC, |
| device: &Self::DeviceId, |
| addr: MulticastAddr<<I as Ip>::Addr>, |
| ) { |
| MulticastMembershipHandler::<I, BC>::leave_multicast_group( |
| self.inner_mut::<I>(), |
| bindings_ctx, |
| device, |
| addr, |
| ) |
| } |
| |
| fn select_device_for_multicast_group( |
| &mut self, |
| addr: MulticastAddr<<I as Ip>::Addr>, |
| ) -> Result<Self::DeviceId, ResolveRouteError> { |
| MulticastMembershipHandler::<I, BC>::select_device_for_multicast_group( |
| self.inner_mut::<I>(), |
| addr, |
| ) |
| } |
| } |
| |
| impl<I: IpLayerIpExt, D: FakeStrongDeviceId, BC> TransportIpContext<I, BC> |
| for FakeDualStackIpSocketCtx<D> |
| { |
| fn get_default_hop_limits(&mut self, device: Option<&Self::DeviceId>) -> HopLimits { |
| TransportIpContext::<I, BC>::get_default_hop_limits(self.inner_mut::<I>(), device) |
| } |
| |
| type DevicesWithAddrIter<'a> = alloc::boxed::Box<dyn Iterator<Item = D> + 'a>; |
| |
| fn get_devices_with_assigned_addr( |
| &mut self, |
| addr: SpecifiedAddr<<I>::Addr>, |
| ) -> Self::DevicesWithAddrIter<'_> { |
| TransportIpContext::<I, BC>::get_devices_with_assigned_addr(self.inner_mut::<I>(), addr) |
| } |
| |
| 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.inner_mut::<I>(), |
| bindings_ctx, |
| dst, |
| device, |
| ) |
| } |
| } |
| |
| #[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>, |
| } |
| |
| pub(crate) struct FakeDeviceState<I: Ip> { |
| pub default_hop_limit: NonZeroU8, |
| pub addresses: Vec<SpecifiedAddr<I::Addr>>, |
| pub multicast_groups: HashMap<MulticastAddr<I::Addr>, usize>, |
| } |
| |
| impl<I: Ip> FakeDeviceState<I> { |
| pub fn is_in_multicast_group(&self, addr: &MulticastAddr<I::Addr>) -> bool { |
| self.multicast_groups.get(addr).is_some_and(|v| *v != 0) |
| } |
| } |
| |
| impl<I: IpLayerIpExt, D: FakeStrongDeviceId> FakeIpSocketCtx<I, D> { |
| /// Creates a new `FakeIpSocketCtx` with the given device |
| /// configs. |
| pub(crate) fn new( |
| device_configs: impl IntoIterator<Item = FakeDeviceConfig<D, SpecifiedAddr<I::Addr>>>, |
| ) -> Self { |
| let mut table = ForwardingTable::default(); |
| let mut devices = HashMap::default(); |
| for FakeDeviceConfig { device, local_ips, remote_ips } in device_configs { |
| for addr in remote_ips { |
| crate::ip::forwarding::testutil::add_on_link_forwarding_entry( |
| &mut table, |
| addr, |
| device.clone(), |
| ) |
| } |
| let state = FakeDeviceState { |
| default_hop_limit: crate::ip::DEFAULT_HOP_LIMITS.unicast, |
| addresses: local_ips, |
| multicast_groups: Default::default(), |
| }; |
| assert!( |
| devices.insert(device.clone(), state).is_none(), |
| "duplicate entries for {device:?}", |
| ); |
| } |
| |
| Self { table, devices, forwarding: Default::default() } |
| } |
| |
| pub(crate) fn get_device_state(&self, device: &D) -> &FakeDeviceState<I> { |
| self.devices.get(device).unwrap_or_else(|| panic!("no device {device:?}")) |
| } |
| |
| pub(crate) fn get_device_state_mut(&mut self, device: &D) -> &mut FakeDeviceState<I> { |
| self.devices.get_mut(device).unwrap_or_else(|| panic!("no device {device:?}")) |
| } |
| |
| pub(crate) fn multicast_memberships( |
| &self, |
| ) -> HashMap<(D, MulticastAddr<I::Addr>), NonZeroUsize> { |
| self.devices |
| .iter() |
| .map(|(device, state)| { |
| state.multicast_groups.iter().filter_map(|(group, count)| { |
| NonZeroUsize::new(*count).map(|count| ((device.clone(), *group), count)) |
| }) |
| }) |
| .flatten() |
| .collect() |
| } |
| |
| fn lookup_route( |
| &mut self, |
| device: Option<&D>, |
| local_ip: Option<IpDeviceAddr<I::Addr>>, |
| addr: RoutableIpAddr<I::Addr>, |
| ) -> Result<ResolvedRoute<I, D>, ResolveRouteError> { |
| let Self { table, devices, forwarding } = self; |
| let (destination, ()) = table |
| .lookup_filter_map(forwarding, device, addr.addr(), |_, d| match &local_ip { |
| None => Some(()), |
| Some(local_ip) => devices.get(d).and_then(|state| { |
| state.addresses.contains(local_ip.as_ref()).then_some(()) |
| }), |
| }) |
| .next() |
| .ok_or(ResolveRouteError::Unreachable)?; |
| |
| let Destination { device, next_hop } = destination; |
| let mut addrs = devices.get(device).unwrap().addresses.iter(); |
| let local_ip = match local_ip { |
| None => { |
| let addr = addrs.next().ok_or(ResolveRouteError::NoSrcAddr)?; |
| SocketIpAddr::new(addr.get()).expect("not valid socket addr") |
| } |
| Some(local_ip) => { |
| // We already constrained the set of devices so this |
| // should be a given. |
| assert!( |
| addrs.any(|a| a.get() == local_ip.addr()), |
| "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, |
| }) |
| } |
| |
| fn resolve_send_meta<O>( |
| &mut self, |
| socket: &IpSock<I, D::Weak>, |
| mtu: Option<u32>, |
| options: &O, |
| ) -> Result<SendIpPacketMeta<I, D, SpecifiedAddr<I::Addr>>, IpSockSendError> |
| where |
| O: SendOptions<I, D::Weak>, |
| { |
| let IpSockDefinition { remote_ip, local_ip, device, proto } = &socket.definition; |
| let device = device |
| .as_ref() |
| .map(|d| d.upgrade().ok_or(ResolveRouteError::Unreachable)) |
| .transpose()?; |
| let ResolvedRoute { src_addr, device, next_hop, local_delivery_device: _ } = |
| self.lookup_route(device.as_ref(), Some(*local_ip), *remote_ip)?; |
| |
| let remote_ip: &SpecifiedAddr<_> = remote_ip.as_ref(); |
| let (next_hop, broadcast) = next_hop.into_next_hop_and_broadcast_marker(*remote_ip); |
| Ok(SendIpPacketMeta { |
| device, |
| src_ip: src_addr.into(), |
| dst_ip: *remote_ip, |
| broadcast, |
| next_hop, |
| proto: *proto, |
| ttl: options.hop_limit(remote_ip), |
| mtu, |
| }) |
| } |
| } |
| } |
| |
| #[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}, |
| 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}, |
| 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, D> SendOptions<I, D> for WithHopLimit { |
| fn hop_limit(&self, _destination: &SpecifiedAddr<I::Addr>) -> Option<NonZeroU8> { |
| let Self(hop_limit) = self; |
| *hop_limit |
| } |
| |
| fn multicast_interface(&self) -> Option<&D> { |
| None |
| } |
| } |
| |
| #[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, |
| }, |
| }; |
| |
| 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, |
| ); |
| assert_eq!(res, get_expected_result(template)); |
| } |
| |
| #[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, |
| ) |
| .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, |
| &DefaultSendOptions, |
| ) |
| .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, |
| ) |
| .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, |
| &socket_options, |
| ) |
| .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()), |
| &socket_options, |
| ); |
| 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 |
| &socket_options, |
| ); |
| 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, |
| &socket_options, |
| ); |
| 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, |
| &socket_options, |
| ); |
| 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, D> SendOptions<A::Version, D> 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) |
| } |
| |
| fn multicast_interface(&self) -> Option<&D> { |
| None |
| } |
| } |
| |
| 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, |
| ) |
| .unwrap(); |
| |
| IpSocketHandler::<I, _>::send_ip_packet( |
| &mut core_ctx, |
| bindings_ctx, |
| &sock, |
| (&[0u8][..]).into_serializer(), |
| None, |
| &options, |
| ) |
| .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()); |
| } |
| } |
| |
| #[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, |
| ) |
| .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,); |
| } |
| } |