| // 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 alloc::vec::Vec; |
| use core::cmp::Ordering; |
| use core::convert::Infallible; |
| use core::num::NonZeroU8; |
| |
| use net_types::ip::{Ip, Ipv4, Ipv4Addr, Ipv6, Ipv6Addr}; |
| use net_types::{SpecifiedAddr, UnicastAddr}; |
| use packet::{Buf, BufferMut, SerializeError, Serializer}; |
| use packet_formats::ip::{Ipv4Proto, Ipv6Proto}; |
| use thiserror::Error; |
| |
| use crate::{ |
| context::{CounterContext, InstantContext}, |
| ip::{ |
| device::state::{IpDeviceStateIpExt, Ipv6AddressEntry}, |
| forwarding::Destination, |
| IpDeviceIdContext, IpExt, SendIpPacketMeta, |
| }, |
| }; |
| |
| /// A socket identifying a connection between a local and remote IP host. |
| pub(crate) trait IpSocket<I: Ip> { |
| /// Get the local IP address. |
| fn local_ip(&self) -> &SpecifiedAddr<I::Addr>; |
| |
| /// Get the remote IP address. |
| fn remote_ip(&self) -> &SpecifiedAddr<I::Addr>; |
| } |
| |
| /// An execution context defining a type of IP socket. |
| pub trait IpSocketHandler<I: IpExt, C>: IpDeviceIdContext<I> { |
| /// A builder carrying optional parameters passed to [`new_ip_socket`]. |
| /// |
| /// [`new_ip_socket`]: crate::ip::socket::IpSocketHandler::new_ip_socket |
| type Builder: Default; |
| |
| /// Constructs a new [`Self::IpSocket`]. |
| /// |
| /// `new_ip_socket` constructs a new `Self::IpSocket` 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. |
| /// |
| /// The builder may be used to override certain default parameters. Passing |
| /// `None` for the `builder` parameter is equivalent to passing |
| /// `Some(Default::default())`. |
| fn new_ip_socket( |
| &mut self, |
| ctx: &mut C, |
| device: Option<Self::DeviceId>, |
| local_ip: Option<SpecifiedAddr<I::Addr>>, |
| remote_ip: SpecifiedAddr<I::Addr>, |
| proto: I::Proto, |
| builder: Option<Self::Builder>, |
| ) -> Result<IpSock<I, Self::DeviceId>, IpSockCreationError>; |
| } |
| |
| /// 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] IpSockUnroutableError), |
| } |
| |
| impl From<SerializeError<Infallible>> for IpSockSendError { |
| fn from(err: SerializeError<Infallible>) -> IpSockSendError { |
| match err { |
| SerializeError::Alloc(err) => match err {}, |
| SerializeError::Mtu => IpSockSendError::Mtu, |
| } |
| } |
| } |
| |
| /// An error in sending a packet on a temporary IP socket. |
| #[derive(Error, Copy, Clone, Debug)] |
| pub enum IpSockCreateAndSendError { |
| /// 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 temporary socket could not be created. |
| #[error("the temporary socket could not be created: {}", _0)] |
| Create(#[from] IpSockCreationError), |
| } |
| |
| /// An extension of [`IpSocketHandler`] adding the ability to send packets on an |
| /// IP socket. |
| pub trait BufferIpSocketHandler<I: IpExt, C, B: BufferMut>: IpSocketHandler<I, C> { |
| /// 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: Serializer<Buffer = B>>( |
| &mut self, |
| ctx: &mut C, |
| socket: &IpSock<I, Self::DeviceId>, |
| body: S, |
| mtu: Option<u32>, |
| ) -> Result<(), (S, IpSockSendError)>; |
| |
| /// Creates a temporary IP socket and sends a single packet on it. |
| /// |
| /// `local_ip`, `remote_ip`, `proto`, and `builder` 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 sending the packet, the body returned |
| /// from `get_body_from_src_ip` will be returned along with the error. If an |
| /// error is encountered while constructing the temporary IP socket, |
| /// `get_body_from_src_ip` will be called on an arbitrary IP address in |
| /// order to obtain a body to return. In the case where a buffer was passed |
| /// by ownership to `get_body_from_src_ip`, this allows the caller to |
| /// recover that buffer. |
| fn send_oneshot_ip_packet<S: Serializer<Buffer = B>, F: FnOnce(SpecifiedAddr<I::Addr>) -> S>( |
| &mut self, |
| ctx: &mut C, |
| device: Option<Self::DeviceId>, |
| local_ip: Option<SpecifiedAddr<I::Addr>>, |
| remote_ip: SpecifiedAddr<I::Addr>, |
| proto: I::Proto, |
| builder: Option<Self::Builder>, |
| get_body_from_src_ip: F, |
| mtu: Option<u32>, |
| ) -> Result<(), (S, IpSockCreateAndSendError)> { |
| // We use a `match` instead of `map_err` because `map_err` would require passing a closure |
| // which takes ownership of `get_body_from_src_ip`, which we also use in the success case. |
| match self.new_ip_socket(ctx, device, local_ip, remote_ip, proto, builder) { |
| Err(err) => Err((get_body_from_src_ip(I::LOOPBACK_ADDRESS), err.into())), |
| Ok(tmp) => self |
| .send_ip_packet(ctx, &tmp, get_body_from_src_ip(*tmp.local_ip()), mtu) |
| .map_err(|(body, err)| match err { |
| IpSockSendError::Mtu => (body, IpSockCreateAndSendError::Mtu), |
| IpSockSendError::Unroutable(_) => { |
| unreachable!("socket which was just created should still be routable") |
| } |
| }), |
| } |
| } |
| } |
| |
| /// An error encountered when creating an IP socket. |
| #[derive(Error, Copy, Clone, Debug, Eq, PartialEq)] |
| pub enum IpSockCreationError { |
| /// The specified local IP address is not a unicast address in its subnet. |
| /// |
| /// For IPv4, this means that the address is a member of a subnet to which |
| /// we are attached, but in that subnet, it is not a unicast address. For |
| /// IPv6, whether or not an address is unicast is a property of the address |
| /// and does not depend on what subnets we're attached to. |
| #[error("the specified local IP address is not a unicast address in its subnet")] |
| LocalAddrNotUnicast, |
| /// An error occurred while looking up a route. |
| #[error("a route cannot be determined: {}", _0)] |
| Route(#[from] IpSockRouteError), |
| } |
| |
| /// An error encountered when looking up a route for an IP socket. |
| #[derive(Error, Copy, Clone, Debug, Eq, PartialEq)] |
| pub enum IpSockRouteError { |
| /// No local IP address was specified, and one could not be automatically |
| /// selected. |
| #[error("a local IP address could not be automatically selected")] |
| NoLocalAddrAvailable, |
| /// The socket is unroutable. |
| #[error("the socket is unroutable: {}", _0)] |
| Unroutable(#[from] IpSockUnroutableError), |
| } |
| |
| /// An error encountered when attempting to compute the routing information on |
| /// an IP socket. |
| /// |
| /// An `IpSockUnroutableError` can occur when creating a socket or when updating |
| /// an existing socket in response to changes to the forwarding table or to the |
| /// set of IP addresses assigned to devices. |
| #[derive(Error, Copy, Clone, Debug, Eq, PartialEq)] |
| pub enum IpSockUnroutableError { |
| /// The specified local IP address is not one of our assigned addresses. |
| /// |
| /// For IPv6, this error will also be returned if the specified local IP |
| /// address exists on one of our devices, but it is in the "temporary" |
| /// state. |
| #[error("the specified local IP address is not one of our assigned addresses")] |
| LocalAddrNotAssigned, |
| /// No route exists to the specified remote IP address. |
| #[error("no route exists to the remote IP address")] |
| NoRouteToRemoteAddr, |
| } |
| |
| /// A builder for IPv4 sockets. |
| /// |
| /// [`IpSocketContext::new_ip_socket`] accepts optional configuration in the |
| /// form of a `SocketBuilder`. All configurations have default values that are |
| /// used if a custom value is not provided. |
| #[derive(Default)] |
| pub struct Ipv4SocketBuilder { |
| // NOTE(joshlf): These fields are `Option`s rather than being set to a |
| // default value in `Default::default` because global defaults may be set |
| // per-stack at runtime, meaning that default values cannot be known at |
| // compile time. |
| ttl: Option<NonZeroU8>, |
| } |
| |
| impl Ipv4SocketBuilder { |
| /// Set the Time to Live (TTL) field that will be set on outbound IPv4 |
| /// packets. |
| /// |
| /// The TTL must be non-zero. Per [RFC 1122 Section 3.2.1.7] and [RFC 1812 |
| /// Section 4.2.2.9], hosts and routers (respectively) must not originate |
| /// IPv4 packets with a TTL of zero. |
| /// |
| /// [RFC 1122 Section 3.2.1.7]: https://tools.ietf.org/html/rfc1122#section-3.2.1.7 |
| /// [RFC 1812 Section 4.2.2.9]: https://tools.ietf.org/html/rfc1812#section-4.2.2.9 |
| #[allow(dead_code)] // TODO(joshlf): Remove once this is used |
| pub(crate) fn ttl(&mut self, ttl: NonZeroU8) -> &mut Ipv4SocketBuilder { |
| self.ttl = Some(ttl); |
| self |
| } |
| } |
| |
| /// A builder for IPv6 sockets. |
| /// |
| /// [`IpSocketContext::new_ip_socket`] accepts optional configuration in the |
| /// form of a `SocketBuilder`. All configurations have default values that are |
| /// used if a custom value is not provided. |
| #[derive(Default)] |
| pub struct Ipv6SocketBuilder { |
| // NOTE(joshlf): These fields are `Option`s rather than being set to a |
| // default value in `Default::default` because global defaults may be set |
| // per-stack at runtime, meaning that default values cannot be known at |
| // compile time. |
| hop_limit: Option<NonZeroU8>, |
| } |
| |
| impl Ipv6SocketBuilder { |
| /// Sets the Hop Limit field that will be set on outbound IPv6 packets. |
| #[allow(dead_code)] // TODO(joshlf): Remove once this is used |
| pub(crate) fn hop_limit(&mut self, hop_limit: NonZeroU8) -> &mut Ipv6SocketBuilder { |
| self.hop_limit = Some(hop_limit); |
| self |
| } |
| } |
| |
| /// The production implementation of the [`IpSocket`] trait. |
| #[derive(Clone)] |
| #[cfg_attr(test, derive(Debug, PartialEq))] |
| pub struct IpSock<I: IpExt, D> { |
| defn: IpSockDefinition<I, D>, |
| } |
| |
| /// The definition of an IP socket. |
| /// |
| /// These values are part of the socket's definition, and never change. |
| #[derive(Clone)] |
| #[cfg_attr(test, derive(Debug, PartialEq))] |
| struct IpSockDefinition<I: IpExt, D> { |
| remote_ip: SpecifiedAddr<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: SpecifiedAddr<I::Addr>, |
| #[cfg_attr(not(test), allow(unused))] |
| device: Option<D>, |
| hop_limit: Option<NonZeroU8>, |
| proto: I::Proto, |
| } |
| |
| impl<I: IpExt, D> IpSocket<I> for IpSock<I, D> { |
| fn local_ip(&self) -> &SpecifiedAddr<I::Addr> { |
| &self.defn.local_ip |
| } |
| |
| fn remote_ip(&self) -> &SpecifiedAddr<I::Addr> { |
| &self.defn.remote_ip |
| } |
| } |
| |
| // 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 route for a socket. |
| pub(super) struct IpSockRoute<I: Ip, D> { |
| /// The local IP to use for the socket. |
| pub(super) local_ip: SpecifiedAddr<I::Addr>, |
| |
| /// The destination for packets originating from the socket. |
| pub(super) destination: Destination<I::Addr, D>, |
| } |
| |
| /// The non-synchronized execution context for IP sockets. |
| pub(super) trait IpSocketNonSyncContext: InstantContext {} |
| impl<C: InstantContext> IpSocketNonSyncContext for C {} |
| |
| /// The context required in order to implement [`IpSocketHandler`]. |
| /// |
| /// Blanket impls of `IpSocketHandler` are provided in terms of |
| /// `IpSocketContext`. |
| pub(super) trait IpSocketContext<I, C: IpSocketNonSyncContext>: |
| IpDeviceIdContext<I> + CounterContext |
| where |
| I: IpDeviceStateIpExt<C::Instant>, |
| { |
| /// 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( |
| &self, |
| ctx: &mut C, |
| device: Option<Self::DeviceId>, |
| src_ip: Option<SpecifiedAddr<I::Addr>>, |
| dst_ip: SpecifiedAddr<I::Addr>, |
| ) -> Result<IpSockRoute<I, Self::DeviceId>, IpSockRouteError>; |
| } |
| |
| /// The context required in order to implement [`BufferIpSocketHandler`]. |
| /// |
| /// Blanket impls of `BufferIpSocketHandler` are provided in terms of |
| /// `BufferIpSocketContext`. |
| pub(super) trait BufferIpSocketContext<I, C: IpSocketNonSyncContext, B: BufferMut>: |
| IpSocketContext<I, C> |
| where |
| I: IpDeviceStateIpExt<C::Instant> + packet_formats::ip::IpExt, |
| { |
| /// Send an IP packet to the next-hop node. |
| fn send_ip_packet<S: Serializer<Buffer = B>>( |
| &mut self, |
| ctx: &mut C, |
| meta: SendIpPacketMeta<I, Self::DeviceId, SpecifiedAddr<I::Addr>>, |
| body: S, |
| ) -> Result<(), S>; |
| } |
| |
| impl<C: IpSocketNonSyncContext, SC: IpSocketContext<Ipv4, C>> IpSocketHandler<Ipv4, C> for SC { |
| type Builder = Ipv4SocketBuilder; |
| |
| fn new_ip_socket( |
| &mut self, |
| ctx: &mut C, |
| device: Option<SC::DeviceId>, |
| local_ip: Option<SpecifiedAddr<Ipv4Addr>>, |
| remote_ip: SpecifiedAddr<Ipv4Addr>, |
| proto: Ipv4Proto, |
| builder: Option<Ipv4SocketBuilder>, |
| ) -> Result<IpSock<Ipv4, SC::DeviceId>, IpSockCreationError> { |
| // 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 IpSockRoute { local_ip, destination: _ } = |
| self.lookup_route(ctx, device, local_ip, remote_ip)?; |
| |
| let Ipv4SocketBuilder { ttl } = builder.unwrap_or_default(); |
| |
| let defn = IpSockDefinition { local_ip, remote_ip, device, proto, hop_limit: ttl }; |
| Ok(IpSock { defn }) |
| } |
| } |
| |
| impl<C: IpSocketNonSyncContext, SC: IpSocketContext<Ipv6, C>> IpSocketHandler<Ipv6, C> for SC { |
| type Builder = Ipv6SocketBuilder; |
| |
| fn new_ip_socket( |
| &mut self, |
| ctx: &mut C, |
| device: Option<SC::DeviceId>, |
| local_ip: Option<SpecifiedAddr<Ipv6Addr>>, |
| remote_ip: SpecifiedAddr<Ipv6Addr>, |
| proto: Ipv6Proto, |
| builder: Option<Ipv6SocketBuilder>, |
| ) -> Result<IpSock<Ipv6, SC::DeviceId>, IpSockCreationError> { |
| // 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 IpSockRoute { local_ip, destination: _ } = |
| self.lookup_route(ctx, device, local_ip, remote_ip)?; |
| |
| let Ipv6SocketBuilder { hop_limit } = builder.unwrap_or_default(); |
| |
| let defn = IpSockDefinition { local_ip, remote_ip, device, proto, hop_limit }; |
| Ok(IpSock { defn }) |
| } |
| } |
| |
| fn send_ip_packet< |
| I: IpExt + IpDeviceStateIpExt<C::Instant> + packet_formats::ip::IpExt, |
| B: BufferMut, |
| S: Serializer<Buffer = B>, |
| C: IpSocketNonSyncContext, |
| SC: BufferIpSocketContext<I, C, B> |
| + BufferIpSocketContext<I, C, Buf<Vec<u8>>> |
| + IpSocketContext<I, C>, |
| >( |
| sync_ctx: &mut SC, |
| ctx: &mut C, |
| IpSock { defn: IpSockDefinition { remote_ip, local_ip, device, hop_limit, proto } }: &IpSock< |
| I, |
| SC::DeviceId, |
| >, |
| body: S, |
| mtu: Option<u32>, |
| ) -> Result<(), (S, IpSockSendError)> { |
| let IpSockRoute { local_ip: got_local_ip, destination: Destination { device, next_hop } } = |
| match sync_ctx.lookup_route(ctx, *device, Some(*local_ip), *remote_ip) { |
| Ok(o) => o, |
| Err(IpSockRouteError::NoLocalAddrAvailable) => { |
| unreachable!("local IP {} was specified", local_ip) |
| } |
| Err(IpSockRouteError::Unroutable(e)) => { |
| return Err((body, IpSockSendError::Unroutable(e))) |
| } |
| }; |
| |
| assert_eq!(*local_ip, got_local_ip); |
| |
| BufferIpSocketContext::send_ip_packet( |
| sync_ctx, |
| ctx, |
| SendIpPacketMeta { |
| device, |
| src_ip: *local_ip, |
| dst_ip: *remote_ip, |
| next_hop, |
| ttl: *hop_limit, |
| proto: *proto, |
| mtu, |
| }, |
| body, |
| ) |
| .map_err(|s| (s, IpSockSendError::Mtu)) |
| } |
| |
| impl< |
| B: BufferMut, |
| C: IpSocketNonSyncContext, |
| SC: BufferIpSocketContext<Ipv4, C, B> |
| + BufferIpSocketContext<Ipv4, C, Buf<Vec<u8>>> |
| + IpSocketContext<Ipv4, C>, |
| > BufferIpSocketHandler<Ipv4, C, B> for SC |
| { |
| fn send_ip_packet<S: Serializer<Buffer = B>>( |
| &mut self, |
| ctx: &mut C, |
| ip_sock: &IpSock<Ipv4, SC::DeviceId>, |
| body: S, |
| mtu: Option<u32>, |
| ) -> Result<(), (S, IpSockSendError)> { |
| // TODO(joshlf): Call `trace!` with relevant fields from the socket. |
| self.increment_counter("send_ipv4_packet"); |
| |
| send_ip_packet(self, ctx, ip_sock, body, mtu) |
| } |
| } |
| |
| impl< |
| B: BufferMut, |
| C: IpSocketNonSyncContext, |
| SC: BufferIpSocketContext<Ipv6, C, B> + BufferIpSocketContext<Ipv6, C, Buf<Vec<u8>>>, |
| > BufferIpSocketHandler<Ipv6, C, B> for SC |
| { |
| fn send_ip_packet<S: Serializer<Buffer = B>>( |
| &mut self, |
| ctx: &mut C, |
| ip_sock: &IpSock<Ipv6, SC::DeviceId>, |
| body: S, |
| mtu: Option<u32>, |
| ) -> Result<(), (S, IpSockSendError)> { |
| // TODO(joshlf): Call `trace!` with relevant fields from the socket. |
| self.increment_counter("send_ipv6_packet"); |
| |
| send_ip_packet(self, ctx, ip_sock, body, mtu) |
| } |
| } |
| |
| /// IPv6 source address selection as defined in [RFC 6724 Section 5]. |
| pub(super) mod ipv6_source_address_selection { |
| use net_types::ip::IpAddress as _; |
| |
| use super::*; |
| |
| /// 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: Copy + PartialEq, |
| Instant: 'a, |
| I: Iterator<Item = (&'a Ipv6AddressEntry<Instant>, D)>, |
| >( |
| remote_ip: SpecifiedAddr<Ipv6Addr>, |
| outbound_device: D, |
| addresses: I, |
| ) -> Option<UnicastAddr<Ipv6Addr>> { |
| // 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. |
| |
| addresses |
| // Tentative addresses are not considered available to the source |
| // selection algorithm. |
| .filter(|(a, _)| !a.state.is_tentative()) |
| .max_by(|(a, a_device), (b, b_device)| { |
| select_ipv6_source_address_cmp( |
| remote_ip, |
| outbound_device, |
| a, |
| *a_device, |
| b, |
| *b_device, |
| ) |
| }) |
| .map(|(addr, _device)| addr.addr_sub().addr()) |
| } |
| |
| /// Comparison operator used by `select_ipv6_source_address`. |
| fn select_ipv6_source_address_cmp<Instant, D: Copy + PartialEq>( |
| remote_ip: SpecifiedAddr<Ipv6Addr>, |
| outbound_device: D, |
| a: &Ipv6AddressEntry<Instant>, |
| a_device: D, |
| b: &Ipv6AddressEntry<Instant>, |
| b_device: D, |
| ) -> Ordering { |
| // TODO(fxbug.dev/46822): 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. |
| debug_assert!(!(a_addr == remote_ip && b_addr == remote_ip)); |
| |
| // Tentative addresses are not valid source addresses since they are |
| // not considered assigned. |
| debug_assert!(!a.state.is_tentative()); |
| debug_assert!(!b.state.is_tentative()); |
| |
| rule_1(remote_ip, a_addr, b_addr) |
| .then_with(|| rule_3(a.deprecated, b.deprecated)) |
| .then_with(|| rule_5(outbound_device, a_device, b_device)) |
| .then_with(|| rule_8(remote_ip, a, b)) |
| } |
| |
| // Assumes that `a` and `b` are not both equal to `remote_ip`. |
| fn rule_1( |
| remote_ip: SpecifiedAddr<Ipv6Addr>, |
| a: SpecifiedAddr<Ipv6Addr>, |
| b: SpecifiedAddr<Ipv6Addr>, |
| ) -> Ordering { |
| 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<Instant>( |
| remote_ip: SpecifiedAddr<Ipv6Addr>, |
| a: &Ipv6AddressEntry<Instant>, |
| b: &Ipv6AddressEntry<Instant>, |
| ) -> Ordering { |
| // 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<Instant>( |
| src: &Ipv6AddressEntry<Instant>, |
| dst: SpecifiedAddr<Ipv6Addr>, |
| ) -> u8 { |
| core::cmp::min( |
| src.addr_sub().addr().common_prefix_len(&dst), |
| src.addr_sub().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_types::ip::AddrSubnet; |
| |
| use super::*; |
| use crate::{ |
| device::DeviceId, |
| ip::device::state::{AddrConfig, AddressState}, |
| }; |
| |
| #[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(Ipv6Addr::from_bytes([ |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 168, 0, 1, |
| ])) |
| .unwrap(); |
| let local0 = SpecifiedAddr::new(Ipv6Addr::from_bytes([ |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 168, 0, 2, |
| ])) |
| .unwrap(); |
| let local1 = SpecifiedAddr::new(Ipv6Addr::from_bytes([ |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 168, 0, 3, |
| ])) |
| .unwrap(); |
| let dev0 = DeviceId::new_ethernet(0); |
| let dev1 = DeviceId::new_ethernet(1); |
| let dev2 = DeviceId::new_ethernet(2); |
| |
| // Rule 1: Prefer same address |
| assert_eq!(rule_1(remote, remote, local0), Ordering::Greater); |
| assert_eq!(rule_1(remote, local0, remote), Ordering::Less); |
| assert_eq!(rule_1(remote, 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 = |bytes, prefix_len| { |
| Ipv6AddressEntry::<()>::new( |
| AddrSubnet::new(Ipv6Addr::from_bytes(bytes), prefix_len).unwrap(), |
| AddressState::Assigned, |
| AddrConfig::Manual, |
| ) |
| }; |
| |
| // 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(Ipv6Addr::from_bytes([ |
| 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| ])) |
| .unwrap(); |
| // 3 leading 0x01 bytes. |
| let local0 = new_addr_entry([1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 64); |
| // 2 leading 0x01 bytes. |
| let local1 = new_addr_entry([1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 64); |
| |
| assert_eq!(rule_8(remote, &local0, &local1), Ordering::Greater); |
| assert_eq!(rule_8(remote, &local1, &local0), Ordering::Less); |
| assert_eq!(rule_8(remote, &local0, &local0), Ordering::Equal); |
| assert_eq!(rule_8(remote, &local1, &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([1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 8); |
| // 2 leading 0x01 bytes, but a subnet prefix length of 8 (1 byte). |
| let local1 = new_addr_entry([1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 8); |
| |
| assert_eq!(rule_8(remote, &local0, &local1), Ordering::Equal); |
| assert_eq!(rule_8(remote, &local1, &local0), Ordering::Equal); |
| assert_eq!(rule_8(remote, &local0, &local0), Ordering::Equal); |
| assert_eq!(rule_8(remote, &local1, &local1), Ordering::Equal); |
| } |
| |
| { |
| let new_addr_entry = |addr| { |
| Ipv6AddressEntry::<()>::new( |
| AddrSubnet::new(addr, 128).unwrap(), |
| AddressState::Assigned, |
| AddrConfig::Manual, |
| ) |
| }; |
| |
| // If no rules apply, then the two address entries are equal. |
| assert_eq!( |
| select_ipv6_source_address_cmp( |
| remote, |
| dev0, |
| &new_addr_entry(*local0), |
| dev1, |
| &new_addr_entry(*local1), |
| dev2 |
| ), |
| Ordering::Equal |
| ); |
| } |
| } |
| } |
| } |
| |
| /// Test mock implementations of the traits defined in the `socket` module. |
| #[cfg(test)] |
| pub(crate) mod testutil { |
| use alloc::{collections::HashMap, vec::Vec}; |
| use core::fmt::Debug; |
| |
| use net_types::{ |
| ip::{AddrSubnet, IpAddress, Subnet}, |
| Witness, |
| }; |
| |
| use super::*; |
| use crate::{ |
| context::{ |
| testutil::{DummyInstant, DummyNonSyncCtx, DummySyncCtx}, |
| FrameContext, |
| }, |
| ip::{ |
| device::state::{AddrConfig, AddressState, AssignedAddress as _, IpDeviceState}, |
| forwarding::ForwardingTable, |
| DummyDeviceId, IpDeviceId, SendIpPacketMeta, |
| }, |
| }; |
| |
| /// A dummy implementation of [`IpSocketContext`]. |
| /// |
| /// `IpSocketContext` is implemented for any `DummyCtx<S>` where `S` |
| /// implements `AsRef` and `AsMut` for `DummyIpSocketCtx`. |
| pub(crate) struct DummyIpSocketCtx<I: IpDeviceStateIpExt<DummyInstant>, D> { |
| pub(crate) table: ForwardingTable<I, D>, |
| device_state: HashMap<D, IpDeviceState<DummyInstant, I>>, |
| } |
| |
| impl< |
| I: IpDeviceStateIpExt<DummyInstant>, |
| S: AsRef<DummyIpSocketCtx<I, DeviceId>> + AsMut<DummyIpSocketCtx<I, DeviceId>>, |
| Id, |
| Meta, |
| Event: Debug, |
| DeviceId: IpDeviceId + 'static, |
| NonSyncCtxState, |
| > IpSocketContext<I, DummyNonSyncCtx<Id, Event, NonSyncCtxState>> |
| for DummySyncCtx<S, Meta, DeviceId> |
| { |
| fn lookup_route( |
| &self, |
| _ctx: &mut DummyNonSyncCtx<Id, Event, NonSyncCtxState>, |
| device: Option<Self::DeviceId>, |
| local_ip: Option<SpecifiedAddr<I::Addr>>, |
| addr: SpecifiedAddr<I::Addr>, |
| ) -> Result<IpSockRoute<I, Self::DeviceId>, IpSockRouteError> { |
| let destination = self |
| .get_ref() |
| .as_ref() |
| .table |
| .lookup(device, addr) |
| .ok_or(IpSockUnroutableError::NoRouteToRemoteAddr)?; |
| |
| let Destination { device, next_hop: _ } = &destination; |
| local_ip |
| .map_or_else( |
| || { |
| self.get_ref() |
| .as_ref() |
| .device_state |
| .get(&device) |
| .unwrap() |
| .iter_addrs() |
| .map(|e| e.addr()) |
| .next() |
| .ok_or(IpSockRouteError::NoLocalAddrAvailable) |
| }, |
| |local_ip| { |
| self.get_ref() |
| .as_ref() |
| .device_state |
| .get(&device) |
| .unwrap() |
| .iter_addrs() |
| .any(|e| e.addr() == local_ip) |
| .then(|| local_ip) |
| .ok_or(IpSockUnroutableError::LocalAddrNotAssigned.into()) |
| }, |
| ) |
| .map(|local_ip| IpSockRoute { local_ip, destination }) |
| } |
| } |
| |
| impl< |
| I: IpDeviceStateIpExt<DummyInstant> + packet_formats::ip::IpExt, |
| B: BufferMut, |
| S: AsRef<DummyIpSocketCtx<I, DeviceId>> + AsMut<DummyIpSocketCtx<I, DeviceId>>, |
| Id, |
| Meta, |
| Event: Debug, |
| DeviceId, |
| NonSyncCtxState, |
| > BufferIpSocketContext<I, DummyNonSyncCtx<Id, Event, NonSyncCtxState>, B> |
| for DummySyncCtx<S, Meta, DeviceId> |
| where |
| DummySyncCtx<S, Meta, DeviceId>: FrameContext< |
| DummyNonSyncCtx<Id, Event, NonSyncCtxState>, |
| B, |
| SendIpPacketMeta<I, Self::DeviceId, SpecifiedAddr<I::Addr>>, |
| > + IpSocketContext<I, DummyNonSyncCtx<Id, Event, NonSyncCtxState>>, |
| { |
| fn send_ip_packet<SS: Serializer<Buffer = B>>( |
| &mut self, |
| ctx: &mut DummyNonSyncCtx<Id, Event, NonSyncCtxState>, |
| meta: SendIpPacketMeta<I, Self::DeviceId, SpecifiedAddr<I::Addr>>, |
| body: SS, |
| ) -> Result<(), SS> { |
| self.send_frame(ctx, meta, body) |
| } |
| } |
| |
| impl<I: IpDeviceStateIpExt<DummyInstant>, D: IpDeviceId> DummyIpSocketCtx<I, D> { |
| pub(crate) fn with_devices_state( |
| devices: impl IntoIterator< |
| Item = (D, IpDeviceState<DummyInstant, 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 { |
| assert_eq!( |
| table.add_device_route( |
| Subnet::new(ip.get(), <I::Addr as IpAddress>::BYTES * 8).unwrap(), |
| device, |
| ), |
| Ok(()) |
| ); |
| } |
| assert!( |
| device_state.insert(device, state).is_none(), |
| "duplicate entries for {}", |
| device |
| ); |
| } |
| |
| DummyIpSocketCtx { table, device_state } |
| } |
| |
| pub(crate) fn find_device_with_addr(&self, addr: SpecifiedAddr<I::Addr>) -> Option<D> { |
| let Self { table: _, device_state } = self; |
| device_state.iter().find_map(|(device, state)| { |
| state.find_addr(&addr).map(|_: &I::AssignedAddress| *device) |
| }) |
| } |
| } |
| |
| pub(crate) struct DummyDeviceConfig<D, A: IpAddress> { |
| pub(crate) device: D, |
| pub(crate) local_ips: Vec<SpecifiedAddr<A>>, |
| pub(crate) remote_ips: Vec<SpecifiedAddr<A>>, |
| } |
| |
| impl<D: IpDeviceId> DummyIpSocketCtx<Ipv4, D> { |
| /// Creates a new `DummyIpSocketCtx<Ipv4>` with the given device |
| /// configs. |
| pub(crate) fn new_ipv4( |
| devices: impl IntoIterator<Item = DummyDeviceConfig<D, Ipv4Addr>>, |
| ) -> Self { |
| DummyIpSocketCtx::with_devices_state(devices.into_iter().map( |
| |DummyDeviceConfig { device, local_ips, remote_ips }| { |
| let mut device_state = IpDeviceState::default(); |
| for ip in local_ips { |
| // Users of this utility don't care about subnet prefix length, |
| // so just pick a reasonable one. |
| device_state |
| .add_addr(AddrSubnet::new(ip.get(), 32).unwrap()) |
| .expect("add address"); |
| } |
| (device, device_state, remote_ips) |
| }, |
| )) |
| } |
| } |
| |
| impl DummyIpSocketCtx<Ipv4, DummyDeviceId> { |
| /// Creates a new `DummyIpSocketCtx<Ipv4>`. |
| pub(crate) fn new_dummy_ipv4( |
| local_ips: Vec<SpecifiedAddr<Ipv4Addr>>, |
| remote_ips: Vec<SpecifiedAddr<Ipv4Addr>>, |
| ) -> Self { |
| Self::new_ipv4([DummyDeviceConfig { device: DummyDeviceId, local_ips, remote_ips }]) |
| } |
| } |
| |
| impl<D: IpDeviceId> DummyIpSocketCtx<Ipv6, D> { |
| /// Creates a new `DummyIpSocketCtx<Ipv6>` with the given device |
| /// configs. |
| pub(crate) fn new_ipv6( |
| devices: impl IntoIterator<Item = DummyDeviceConfig<D, Ipv6Addr>>, |
| ) -> Self { |
| DummyIpSocketCtx::with_devices_state(devices.into_iter().map( |
| |DummyDeviceConfig { device, local_ips, remote_ips }| { |
| let mut device_state = IpDeviceState::default(); |
| for ip in local_ips { |
| // Users of this utility don't care about subnet prefix length, |
| // so just pick a reasonable one. |
| device_state |
| .add_addr(Ipv6AddressEntry::new( |
| // Users of this utility don't care about subnet prefix |
| // length, so just pick a reasonable one. |
| AddrSubnet::new(ip.get(), 128).unwrap(), |
| AddressState::Assigned, |
| AddrConfig::Manual, |
| )) |
| .expect("add address"); |
| } |
| (device, device_state, remote_ips) |
| }, |
| )) |
| } |
| } |
| |
| impl DummyIpSocketCtx<Ipv6, DummyDeviceId> { |
| /// Creates a new `DummyIpSocketCtx<Ipv6>`. |
| pub(crate) fn new_dummy_ipv6( |
| local_ips: Vec<SpecifiedAddr<Ipv6Addr>>, |
| remote_ips: Vec<SpecifiedAddr<Ipv6Addr>>, |
| ) -> Self { |
| Self::new_ipv6([DummyDeviceConfig { device: DummyDeviceId, local_ips, remote_ips }]) |
| } |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use net_types::{ |
| ip::{AddrSubnet, SubnetEither}, |
| Witness, |
| }; |
| use packet::{Buf, InnerPacketBuilder, ParseBuffer}; |
| use packet_formats::{ |
| ip::IpPacket, |
| ipv4::{Ipv4OnlyMeta, Ipv4Packet, Ipv4PacketBuilder}, |
| ipv6::Ipv6PacketBuilder, |
| testutil::{parse_ethernet_frame, parse_ip_packet_in_ethernet_frame}, |
| }; |
| use specialize_ip_macro::{ip_test, specialize_ip}; |
| |
| use super::*; |
| use crate::{device::DeviceId, testutil::*, Ctx}; |
| |
| 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>, |
| } |
| |
| #[specialize_ip] |
| fn test_new<I: Ip>(test_case: NewSocketTestCase) { |
| #[ipv4] |
| let (cfg, proto) = (DUMMY_CONFIG_V4, Ipv4Proto::Icmp); |
| |
| #[ipv6] |
| let (cfg, proto) = (DUMMY_CONFIG_V6, Ipv6Proto::Icmpv6); |
| |
| let DummyEventDispatcherConfig { local_ip, remote_ip, subnet, local_mac: _, remote_mac: _ } = |
| cfg; |
| let Ctx { mut sync_ctx, mut non_sync_ctx } = |
| DummyEventDispatcherBuilder::from_config(cfg).build(); |
| let loopback_device_id = crate::add_loopback_device(&mut sync_ctx, u16::MAX.into()) |
| .expect("create the loopback interface"); |
| crate::device::testutil::enable_device( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| loopback_device_id, |
| ); |
| |
| let NewSocketTestCase { local_ip_type, remote_ip_type, expected_result, device_type } = |
| test_case; |
| |
| #[ipv4] |
| let remove_all_local_addrs = |
| |sync_ctx: &mut crate::testutil::DummySyncCtx, |
| ctx: &mut crate::testutil::DummyNonSyncCtx| { |
| let devices = crate::ip::device::iter_ipv4_devices(sync_ctx) |
| .map(|(device, _state)| device) |
| .collect::<Vec<_>>(); |
| for device in devices { |
| let subnets = |
| crate::ip::device::get_assigned_ipv4_addr_subnets(sync_ctx, device) |
| .collect::<Vec<_>>(); |
| for subnet in subnets { |
| crate::device::del_ip_addr(sync_ctx, ctx, device, &subnet.addr()) |
| .expect("failed to remove addr from device"); |
| } |
| } |
| }; |
| |
| #[ipv6] |
| let remove_all_local_addrs = |
| |sync_ctx: &mut crate::testutil::DummySyncCtx, |
| ctx: &mut crate::testutil::DummyNonSyncCtx| { |
| let devices = crate::ip::device::iter_ipv6_devices(sync_ctx) |
| .map(|(device, _state)| device) |
| .collect::<Vec<_>>(); |
| for device in devices { |
| let subnets = |
| crate::ip::device::get_assigned_ipv6_addr_subnets(sync_ctx, device) |
| .collect::<Vec<_>>(); |
| for subnet in subnets { |
| crate::device::del_ip_addr(sync_ctx, ctx, device, &subnet.addr()) |
| .expect("failed to remove addr from device"); |
| } |
| } |
| }; |
| |
| const LOCAL_DEVICE: DeviceId = DeviceId::new_ethernet(0); |
| const OTHER_DEVICE: DeviceId = DeviceId::new_ethernet(1); |
| let local_device = match device_type { |
| DeviceType::Unspecified => None, |
| DeviceType::LocalDevice => Some(LOCAL_DEVICE), |
| DeviceType::OtherDevice => Some(OTHER_DEVICE), |
| }; |
| |
| 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(&mut sync_ctx, &mut non_sync_ctx); |
| } |
| (local_ip, None) |
| } |
| AddressType::Unroutable => { |
| remove_all_local_addrs(&mut sync_ctx, &mut non_sync_ctx); |
| (local_ip, Some(local_ip)) |
| } |
| }; |
| |
| let (to_ip, device) = match remote_ip_type { |
| AddressType::LocallyOwned => ( |
| local_ip, |
| IpDeviceIdContext::<I>::loopback_id(&sync_ctx) |
| .expect("local test should have loopback device"), |
| ), |
| AddressType::Remote => (remote_ip, LOCAL_DEVICE), |
| AddressType::Unspecified { can_select: _ } => { |
| panic!("remote_ip_type cannot be unspecified") |
| } |
| AddressType::Unroutable => { |
| match subnet.into() { |
| SubnetEither::V4(subnet) => { |
| crate::ip::del_route::<Ipv4, _, _>(&mut sync_ctx, &mut non_sync_ctx, subnet) |
| .expect("failed to delete IPv4 device route") |
| } |
| SubnetEither::V6(subnet) => { |
| crate::ip::del_route::<Ipv6, _, _>(&mut sync_ctx, &mut non_sync_ctx, subnet) |
| .expect("failed to delete IPv6 device route") |
| } |
| } |
| |
| (remote_ip, LOCAL_DEVICE) |
| } |
| }; |
| |
| #[ipv4] |
| let builder = Ipv4PacketBuilder::new( |
| expected_from_ip, |
| to_ip, |
| crate::ip::DEFAULT_TTL.get(), |
| Ipv4Proto::Icmp, |
| ); |
| |
| #[ipv6] |
| let builder = Ipv6PacketBuilder::new( |
| expected_from_ip, |
| to_ip, |
| crate::ip::DEFAULT_TTL.get(), |
| Ipv6Proto::Icmpv6, |
| ); |
| |
| let get_expected_result = |template| expected_result.map(|()| template); |
| |
| let template = IpSock { |
| defn: IpSockDefinition { |
| remote_ip: to_ip, |
| local_ip: expected_from_ip, |
| device: local_device, |
| proto, |
| hop_limit: None, |
| }, |
| }; |
| |
| let res = IpSocketHandler::<I, _>::new_ip_socket( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| local_device, |
| from_ip, |
| to_ip, |
| proto, |
| None, |
| ); |
| assert_eq!(res, get_expected_result(template.clone())); |
| |
| #[ipv4] |
| { |
| // TTL is specified. |
| let mut builder = Ipv4SocketBuilder::default(); |
| let _: &mut Ipv4SocketBuilder = builder.ttl(NonZeroU8::new(1).unwrap()); |
| assert_eq!( |
| IpSocketHandler::<Ipv4, _>::new_ip_socket( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| local_device, |
| from_ip, |
| to_ip, |
| proto, |
| Some(builder), |
| ), |
| { |
| // The template socket, but with the TTL set to 1. |
| let mut x = template.clone(); |
| let IpSock::<Ipv4, DeviceId> { defn } = &mut x; |
| defn.hop_limit = NonZeroU8::new(1); |
| get_expected_result(x) |
| } |
| ); |
| } |
| |
| #[ipv6] |
| { |
| // Hop Limit is specified. |
| const SPECIFIED_HOP_LIMIT: u8 = 1; |
| let mut builder = Ipv6SocketBuilder::default(); |
| let _: &mut Ipv6SocketBuilder = |
| builder.hop_limit(NonZeroU8::new(SPECIFIED_HOP_LIMIT).unwrap()); |
| assert_eq!( |
| IpSocketHandler::<Ipv6, _>::new_ip_socket( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| local_device, |
| from_ip, |
| to_ip, |
| proto, |
| Some(builder), |
| ), |
| { |
| let mut template_with_hop_limit = template.clone(); |
| let IpSock::<Ipv6, DeviceId> { defn } = &mut template_with_hop_limit; |
| defn.hop_limit = NonZeroU8::new(SPECIFIED_HOP_LIMIT); |
| let builder = |
| Ipv6PacketBuilder::new(expected_from_ip, to_ip, SPECIFIED_HOP_LIMIT, proto); |
| get_expected_result(template_with_hop_limit) |
| } |
| ); |
| } |
| } |
| |
| #[ip_test] |
| fn test_new_unroutable_local_to_remote<I: Ip>() { |
| test_new::<I>(NewSocketTestCase { |
| local_ip_type: AddressType::Unroutable, |
| remote_ip_type: AddressType::Remote, |
| device_type: DeviceType::Unspecified, |
| expected_result: Err(IpSockRouteError::Unroutable( |
| IpSockUnroutableError::LocalAddrNotAssigned, |
| ) |
| .into()), |
| }); |
| } |
| |
| #[ip_test] |
| fn test_new_local_to_unroutable_remote<I: Ip>() { |
| test_new::<I>(NewSocketTestCase { |
| local_ip_type: AddressType::LocallyOwned, |
| remote_ip_type: AddressType::Unroutable, |
| device_type: DeviceType::Unspecified, |
| expected_result: Err(IpSockRouteError::Unroutable( |
| IpSockUnroutableError::NoRouteToRemoteAddr, |
| ) |
| .into()), |
| }); |
| } |
| |
| #[ip_test] |
| fn test_new_local_to_remote<I: Ip>() { |
| test_new::<I>(NewSocketTestCase { |
| local_ip_type: AddressType::LocallyOwned, |
| remote_ip_type: AddressType::Remote, |
| device_type: DeviceType::Unspecified, |
| expected_result: Ok(()), |
| }); |
| } |
| |
| #[ip_test] |
| fn test_new_unspecified_to_remote<I: Ip>() { |
| test_new::<I>(NewSocketTestCase { |
| local_ip_type: AddressType::Unspecified { can_select: true }, |
| remote_ip_type: AddressType::Remote, |
| device_type: DeviceType::Unspecified, |
| expected_result: Ok(()), |
| }); |
| } |
| |
| #[ip_test] |
| fn test_new_unspecified_to_remote_through_local_device<I: Ip>() { |
| test_new::<I>(NewSocketTestCase { |
| local_ip_type: AddressType::Unspecified { can_select: true }, |
| remote_ip_type: AddressType::Remote, |
| device_type: DeviceType::LocalDevice, |
| expected_result: Ok(()), |
| }); |
| } |
| |
| #[ip_test] |
| fn test_new_unspecified_to_remote_through_other_device<I: Ip>() { |
| test_new::<I>(NewSocketTestCase { |
| local_ip_type: AddressType::Unspecified { can_select: true }, |
| remote_ip_type: AddressType::Remote, |
| device_type: DeviceType::OtherDevice, |
| expected_result: Err(IpSockRouteError::Unroutable( |
| IpSockUnroutableError::NoRouteToRemoteAddr, |
| ) |
| .into()), |
| }); |
| } |
| |
| #[ip_test] |
| fn test_new_unspecified_to_remote_cant_select<I: Ip>() { |
| test_new::<I>(NewSocketTestCase { |
| local_ip_type: AddressType::Unspecified { can_select: false }, |
| remote_ip_type: AddressType::Remote, |
| device_type: DeviceType::Unspecified, |
| expected_result: Err(IpSockRouteError::NoLocalAddrAvailable.into()), |
| }); |
| } |
| |
| #[ip_test] |
| fn test_new_remote_to_remote<I: Ip>() { |
| test_new::<I>(NewSocketTestCase { |
| local_ip_type: AddressType::Remote, |
| remote_ip_type: AddressType::Remote, |
| device_type: DeviceType::Unspecified, |
| expected_result: Err(IpSockRouteError::Unroutable( |
| IpSockUnroutableError::LocalAddrNotAssigned, |
| ) |
| .into()), |
| }); |
| } |
| |
| #[ip_test] |
| fn test_new_local_to_local<I: Ip>() { |
| test_new::<I>(NewSocketTestCase { |
| local_ip_type: AddressType::LocallyOwned, |
| remote_ip_type: AddressType::LocallyOwned, |
| device_type: DeviceType::Unspecified, |
| expected_result: Ok(()), |
| }); |
| } |
| |
| #[ip_test] |
| fn test_new_unspecified_to_local<I: Ip>() { |
| test_new::<I>(NewSocketTestCase { |
| local_ip_type: AddressType::Unspecified { can_select: true }, |
| remote_ip_type: AddressType::LocallyOwned, |
| device_type: DeviceType::Unspecified, |
| expected_result: Ok(()), |
| }); |
| } |
| |
| #[ip_test] |
| fn test_new_remote_to_local<I: Ip>() { |
| test_new::<I>(NewSocketTestCase { |
| local_ip_type: AddressType::Remote, |
| remote_ip_type: AddressType::LocallyOwned, |
| device_type: DeviceType::Unspecified, |
| expected_result: Err(IpSockRouteError::Unroutable( |
| IpSockUnroutableError::LocalAddrNotAssigned, |
| ) |
| .into()), |
| }); |
| } |
| |
| #[specialize_ip] |
| fn test_send_local<I: Ip>(from_addr_type: AddressType, to_addr_type: AddressType) { |
| set_logger_for_test(); |
| |
| use packet_formats::icmp::{IcmpEchoRequest, IcmpPacketBuilder, IcmpUnusedCode}; |
| |
| #[ipv4] |
| let (subnet, local_ip, remote_ip, local_mac, proto, socket_builder) = { |
| let DummyEventDispatcherConfig::<Ipv4Addr> { |
| subnet, |
| local_ip, |
| remote_ip, |
| local_mac, |
| remote_mac: _, |
| } = DUMMY_CONFIG_V4; |
| |
| (subnet, local_ip, remote_ip, local_mac, Ipv4Proto::Icmp, Ipv4SocketBuilder::default()) |
| }; |
| |
| #[ipv6] |
| let (subnet, local_ip, remote_ip, local_mac, proto, socket_builder) = { |
| let DummyEventDispatcherConfig::<Ipv6Addr> { |
| subnet, |
| local_ip, |
| remote_ip, |
| local_mac, |
| remote_mac: _, |
| } = DUMMY_CONFIG_V6; |
| |
| ( |
| subnet, |
| local_ip, |
| remote_ip, |
| local_mac, |
| Ipv6Proto::Icmpv6, |
| Ipv6SocketBuilder::default(), |
| ) |
| }; |
| |
| let mut builder = DummyEventDispatcherBuilder::default(); |
| let device_id = DeviceId::new_ethernet(builder.add_device(local_mac)); |
| let Ctx { mut sync_ctx, mut non_sync_ctx } = builder.build(); |
| crate::device::add_ip_addr_subnet( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| device_id, |
| AddrSubnet::new(local_ip.get(), 16).unwrap(), |
| ) |
| .unwrap(); |
| crate::device::add_ip_addr_subnet( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| device_id, |
| AddrSubnet::new(remote_ip.get(), 16).unwrap(), |
| ) |
| .unwrap(); |
| match subnet.into() { |
| SubnetEither::V4(subnet) => crate::ip::add_device_route::<Ipv4, _, _>( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| subnet, |
| device_id, |
| ) |
| .expect("install IPv4 device route on a fresh stack without routes"), |
| SubnetEither::V6(subnet) => crate::ip::add_device_route::<Ipv6, _, _>( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| subnet, |
| device_id, |
| ) |
| .expect("install IPv6 device route on a fresh stack without routes"), |
| } |
| |
| let loopback_device_id = crate::add_loopback_device(&mut sync_ctx, u16::MAX.into()) |
| .expect("create the loopback interface"); |
| crate::device::testutil::enable_device( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| loopback_device_id, |
| ); |
| |
| 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 sync_ctx, |
| &mut non_sync_ctx, |
| None, |
| from_ip, |
| to_ip, |
| proto, |
| Some(socket_builder), |
| ) |
| .unwrap(); |
| |
| let reply = IcmpEchoRequest::new(0, 0).reply(); |
| let body = &[1, 2, 3, 4]; |
| let buffer = Buf::new(body.to_vec(), ..) |
| .encapsulate(IcmpPacketBuilder::<I, &[u8], _>::new( |
| expected_from_ip, |
| to_ip, |
| IcmpUnusedCode, |
| reply, |
| )) |
| .serialize_vec_outer() |
| .unwrap(); |
| |
| // Send an echo packet on the socket and validate that the packet is |
| // delivered locally. |
| BufferIpSocketHandler::<I, _, _>::send_ip_packet( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| &sock, |
| buffer.into_inner().buffer_view().as_ref().into_serializer(), |
| None, |
| ) |
| .unwrap(); |
| |
| assert_eq!(non_sync_ctx.frames_sent().len(), 0); |
| |
| #[ipv4] |
| assert_eq!(get_counter_val(&mut sync_ctx, "dispatch_receive_ipv4_packet"), 1); |
| |
| #[ipv6] |
| assert_eq!(get_counter_val(&mut sync_ctx, "dispatch_receive_ipv6_packet"), 1); |
| } |
| |
| #[ip_test] |
| fn test_send_local_to_local<I: Ip>() { |
| test_send_local::<I>(AddressType::LocallyOwned, AddressType::LocallyOwned); |
| } |
| |
| #[ip_test] |
| fn test_send_unspecified_to_local<I: Ip>() { |
| test_send_local::<I>( |
| AddressType::Unspecified { can_select: true }, |
| AddressType::LocallyOwned, |
| ); |
| } |
| |
| #[ip_test] |
| fn test_send_local_to_remote<I: Ip>() { |
| test_send_local::<I>(AddressType::LocallyOwned, AddressType::Remote); |
| } |
| |
| #[ip_test] |
| #[specialize_ip] |
| fn test_send<I: Ip>() { |
| // Test various edge cases of the |
| // `BufferIpSocketContext::send_ip_packet` method. |
| |
| #[ipv4] |
| let (cfg, socket_builder, proto) = { |
| let mut builder = Ipv4SocketBuilder::default(); |
| let _: &mut Ipv4SocketBuilder = builder.ttl(NonZeroU8::new(1).unwrap()); |
| (DUMMY_CONFIG_V4, builder, Ipv4Proto::Icmp) |
| }; |
| |
| #[ipv6] |
| let (cfg, socket_builder, proto) = { |
| let mut builder = Ipv6SocketBuilder::default(); |
| let _: &mut Ipv6SocketBuilder = builder.hop_limit(NonZeroU8::new(1).unwrap()); |
| (DUMMY_CONFIG_V6, builder, Ipv6Proto::Icmpv6) |
| }; |
| |
| let DummyEventDispatcherConfig::<_> { local_mac, remote_mac, local_ip, remote_ip, subnet } = |
| cfg; |
| |
| let Ctx { mut sync_ctx, mut non_sync_ctx } = |
| DummyEventDispatcherBuilder::from_config(cfg.clone()).build(); |
| |
| // Create a normal, routable socket. |
| let sock = IpSocketHandler::<I, _>::new_ip_socket( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| None, |
| None, |
| remote_ip, |
| proto, |
| Some(socket_builder), |
| ) |
| .unwrap(); |
| |
| #[ipv4] |
| let curr_id = crate::ip::gen_ipv4_packet_id(&mut sync_ctx); |
| |
| #[ipv4] |
| let check_frame = move |frame: &[u8], packet_count| { |
| let (mut body, src_mac, dst_mac, _ethertype) = parse_ethernet_frame(frame).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.get()); |
| assert_eq!(packet.dst_ip(), remote_ip.get()); |
| assert_eq!(packet.proto(), 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]); |
| }; |
| |
| #[ipv6] |
| let check_frame = move |frame: &[u8], _packet_count| { |
| let (body, src_mac, dst_mac, src_ip, dst_ip, ip_proto, ttl) = |
| parse_ip_packet_in_ethernet_frame::<Ipv6>(frame).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.get()); |
| assert_eq!(dst_ip, remote_ip.get()); |
| assert_eq!(ip_proto, proto); |
| assert_eq!(ttl, 1); |
| }; |
| let mut packet_count = 0; |
| assert_eq!(non_sync_ctx.frames_sent().len(), packet_count); |
| |
| // Send a packet on the socket and make sure that the right contents |
| // are sent. |
| BufferIpSocketHandler::<I, _, _>::send_ip_packet( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| &sock, |
| (&[0u8][..]).into_serializer(), |
| None, |
| ) |
| .unwrap(); |
| let mut check_sent_frame = |non_sync_ctx: &crate::testutil::DummyNonSyncCtx| { |
| packet_count += 1; |
| assert_eq!(non_sync_ctx.frames_sent().len(), packet_count); |
| let (dev, frame) = &non_sync_ctx.frames_sent()[packet_count - 1]; |
| assert_eq!(dev, &DeviceId::new_ethernet(0)); |
| check_frame(&frame, packet_count); |
| }; |
| check_sent_frame(&non_sync_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 = BufferIpSocketHandler::<I, _, _>::send_ip_packet( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| &sock, |
| small_body_serializer, |
| Some(Ipv6::MINIMUM_LINK_MTU.into()), |
| ); |
| assert_matches::assert_matches!(res, Ok(())); |
| check_sent_frame(&non_sync_ctx); |
| |
| // Send a packet on the socket while imposing an MTU which will not |
| // allow a packet to be sent. |
| let res = BufferIpSocketHandler::<I, _, _>::send_ip_packet( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| &sock, |
| small_body_serializer, |
| Some(1), // mtu |
| ); |
| assert_matches::assert_matches!(res, Err((_, IpSockSendError::Mtu))); |
| |
| assert_eq!(non_sync_ctx.frames_sent().len(), packet_count); |
| // Try sending a packet which will be larger than the device's MTU, |
| // and make sure it fails. |
| let res = BufferIpSocketHandler::<I, _, _>::send_ip_packet( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| &sock, |
| (&[0; crate::ip::Ipv6::MINIMUM_LINK_MTU as usize][..]).into_serializer(), |
| None, |
| ); |
| assert_matches::assert_matches!(res, Err((_, IpSockSendError::Mtu))); |
| |
| // Make sure that sending on an unroutable socket fails. |
| crate::ip::del_route::<I, _, _>(&mut sync_ctx, &mut non_sync_ctx, subnet).unwrap(); |
| let res = BufferIpSocketHandler::<I, _, _>::send_ip_packet( |
| &mut sync_ctx, |
| &mut non_sync_ctx, |
| &sock, |
| small_body_serializer, |
| None, |
| ); |
| assert_matches::assert_matches!( |
| res, |
| Err((_, IpSockSendError::Unroutable(IpSockUnroutableError::NoRouteToRemoteAddr))) |
| ); |
| } |
| } |