| // Copyright 2018 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. |
| |
| use alloc::{borrow::Cow, vec::Vec}; |
| use core::{ |
| borrow::Borrow, |
| cmp::Ordering, |
| fmt::Debug, |
| hash::Hash, |
| num::{NonZeroU16, NonZeroU32, NonZeroU8}, |
| sync::atomic::{self, AtomicU16}, |
| }; |
| |
| use const_unwrap::const_unwrap_option; |
| use derivative::Derivative; |
| use explicit::ResultExt as _; |
| use lock_order::lock::UnlockedAccess; |
| use lock_order::{ |
| lock::{LockFor, RwLockFor}, |
| relation::LockBefore, |
| wrap::prelude::*, |
| }; |
| #[cfg(test)] |
| use net_types::ip::IpVersion; |
| use net_types::{ |
| ip::{ |
| GenericOverIp, Ip, IpAddress, Ipv4, Ipv4Addr, Ipv6, Ipv6Addr, Ipv6SourceAddr, Mtu, Subnet, |
| }, |
| MulticastAddr, SpecifiedAddr, UnicastAddr, Witness, |
| }; |
| use packet::{Buf, BufferMut, ParseMetadata, Serializer}; |
| use packet_formats::{ |
| error::IpParseError, |
| ip::{IpPacket as _, IpPacketBuilder as _, IpProto, Ipv4Proto, Ipv6Proto}, |
| ipv4::{Ipv4FragmentType, Ipv4Packet}, |
| ipv6::Ipv6Packet, |
| }; |
| use thiserror::Error; |
| use tracing::{debug, error, trace}; |
| |
| use crate::{ |
| context::{ |
| CoreTimerContext, CounterContext, EventContext, HandleableTimer, InstantContext, |
| NestedIntoCoreTimerCtx, NonTestCtxMarker, TimerContext, TimerHandler, TracingContext, |
| }, |
| counters::Counter, |
| data_structures::token_bucket::TokenBucket, |
| device::{AnyDevice, DeviceId, DeviceIdContext, FrameDestination, Id, StrongId, WeakDeviceId}, |
| filter::{ |
| ConntrackConnection, FilterBindingsContext, FilterBindingsTypes, FilterHandler as _, |
| FilterHandlerProvider, FilterIpMetadata, ForwardedPacket, IngressVerdict, IpPacket, |
| MaybeTransportPacket, NestedWithInnerIpPacket, |
| }, |
| inspect::{Inspectable, Inspector}, |
| ip::{ |
| device::{ |
| self, slaac::SlaacCounters, state::IpDeviceStateIpExt, IpDeviceAddr, |
| IpDeviceBindingsContext, IpDeviceIpExt, IpDeviceSendContext, |
| }, |
| forwarding::{ForwardingTable, IpForwardingDeviceContext}, |
| icmp::{ |
| self, |
| socket::{IcmpEchoBindingsTypes, IcmpSocketId, IcmpSocketSet, IcmpSocketState}, |
| IcmpBindingsTypes, IcmpErrorHandler, IcmpHandlerIpExt, IcmpIpExt, |
| IcmpIpTransportContext, Icmpv4Error, Icmpv4ErrorCode, Icmpv4ErrorKind, Icmpv4State, |
| Icmpv4StateBuilder, Icmpv6ErrorCode, Icmpv6ErrorKind, Icmpv6State, Icmpv6StateBuilder, |
| InnerIcmpContext, |
| }, |
| ipv6, |
| ipv6::Ipv6PacketAction, |
| path_mtu::{PmtuBindingsTypes, PmtuCache, PmtuTimerId}, |
| reassembly::{ |
| FragmentBindingsTypes, FragmentHandler, FragmentProcessingState, FragmentTimerId, |
| IpPacketFragmentCache, |
| }, |
| socket::{IpSocketBindingsContext, IpSocketContext, IpSocketHandler}, |
| types::{ |
| self, Destination, IpTypesIpExt, NextHop, ResolvedRoute, RoutableIpAddr, |
| WrapBroadcastMarker, |
| }, |
| }, |
| socket::datagram, |
| sync::{LockGuard, Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard}, |
| transport::{tcp::socket::TcpIpTransportContext, udp::UdpIpTransportContext}, |
| uninstantiable::UninstantiableWrapper, |
| BindingsContext, BindingsTypes, CoreCtx, StackState, |
| }; |
| |
| /// Default IPv4 TTL. |
| pub(crate) const DEFAULT_TTL: NonZeroU8 = const_unwrap_option(NonZeroU8::new(64)); |
| |
| /// Hop limits for packets sent to multicast and unicast destinations. |
| #[derive(Copy, Clone, Debug, Eq, PartialEq)] |
| pub struct HopLimits { |
| pub(crate) unicast: NonZeroU8, |
| pub(crate) multicast: NonZeroU8, |
| } |
| |
| /// Default hop limits for sockets. |
| pub(crate) const DEFAULT_HOP_LIMITS: HopLimits = |
| HopLimits { unicast: DEFAULT_TTL, multicast: const_unwrap_option(NonZeroU8::new(1)) }; |
| |
| /// The IPv6 subnet that contains all addresses; `::/0`. |
| // Safe because 0 is less than the number of IPv6 address bits. |
| pub(crate) const IPV6_DEFAULT_SUBNET: Subnet<Ipv6Addr> = |
| unsafe { Subnet::new_unchecked(Ipv6::UNSPECIFIED_ADDRESS, 0) }; |
| |
| /// An error encountered when receiving a transport-layer packet. |
| #[derive(Debug)] |
| pub(crate) struct TransportReceiveError { |
| inner: TransportReceiveErrorInner, |
| } |
| |
| impl TransportReceiveError { |
| // NOTE: We don't expose a constructor for the "protocol unsupported" case. |
| // This ensures that the only way that we send a "protocol unsupported" |
| // error is if the implementation of `IpTransportContext` provided for a |
| // given protocol number is `()`. That's because `()` is the only type whose |
| // `receive_ip_packet` function is implemented in this module, and thus it's |
| // the only type that is able to construct a "protocol unsupported" |
| // `TransportReceiveError`. |
| |
| /// Constructs a new `TransportReceiveError` to indicate an unreachable |
| /// port. |
| pub(crate) fn new_port_unreachable() -> TransportReceiveError { |
| TransportReceiveError { inner: TransportReceiveErrorInner::PortUnreachable } |
| } |
| } |
| |
| #[derive(Debug)] |
| enum TransportReceiveErrorInner { |
| ProtocolUnsupported, |
| PortUnreachable, |
| } |
| |
| /// Sidecar metadata passed along with the packet. |
| /// |
| /// NOTE: This metadata may be reset after a packet goes through reassembly, and |
| /// consumers must be able to handle this case. |
| #[derive(Derivative)] |
| #[derivative(Default(bound = ""))] |
| pub struct IpLayerPacketMetadata<I: packet_formats::ip::IpExt, BT: FilterBindingsTypes> { |
| conntrack_connection: Option<ConntrackConnection<I, BT>>, |
| #[cfg(debug_assertions)] |
| drop_check: IpLayerPacketMetadataDropCheck, |
| } |
| |
| /// A type that asserts, on drop, that it was intentionally being dropped. |
| /// |
| /// NOTE: Unfortunately, debugging this requires backtraces, since track_caller |
| /// won't do what we want (https://github.com/rust-lang/rust/issues/116942). |
| /// Since this is only enabled in debug, the assumption is that stacktraces are |
| /// enabled. |
| #[cfg(debug_assertions)] |
| #[derive(Default)] |
| struct IpLayerPacketMetadataDropCheck { |
| okay_to_drop: bool, |
| } |
| |
| impl<I: packet_formats::ip::IpExt, BT: FilterBindingsTypes> IpLayerPacketMetadata<I, BT> { |
| #[cfg(debug_assertions)] |
| pub(crate) fn acknowledge_drop(&mut self) { |
| self.drop_check.okay_to_drop = true; |
| } |
| |
| #[cfg(not(debug_assertions))] |
| pub(crate) fn acknowledge_drop(&mut self) {} |
| } |
| |
| #[cfg(debug_assertions)] |
| impl Drop for IpLayerPacketMetadataDropCheck { |
| fn drop(&mut self) { |
| if !self.okay_to_drop { |
| panic!( |
| "IpLayerPacketMetadata dropped without acknowledgement. https://fxbug.dev/334127474" |
| ); |
| } |
| } |
| } |
| |
| impl<I: packet_formats::ip::IpExt, BT: FilterBindingsTypes> FilterIpMetadata<I, BT> |
| for IpLayerPacketMetadata<I, BT> |
| { |
| fn take_conntrack_connection(&mut self) -> Option<ConntrackConnection<I, BT>> { |
| self.conntrack_connection.take() |
| } |
| |
| fn replace_conntrack_connection( |
| &mut self, |
| conn: ConntrackConnection<I, BT>, |
| ) -> Option<ConntrackConnection<I, BT>> { |
| self.conntrack_connection.replace(conn) |
| } |
| } |
| |
| /// An [`Ip`] extension trait adding functionality specific to the IP layer. |
| pub trait IpExt: packet_formats::ip::IpExt + IcmpIpExt + IpTypesIpExt { |
| /// The type used to specify an IP packet's source address in a call to |
| /// [`IpTransportContext::receive_ip_packet`]. |
| /// |
| /// For IPv4, this is `Ipv4Addr`. For IPv6, this is [`Ipv6SourceAddr`]. |
| type RecvSrcAddr: Into<Self::Addr>; |
| /// The length of an IP header without any IP options. |
| const IP_HEADER_LENGTH: NonZeroU32; |
| /// The maximum payload size an IP payload can have. |
| const IP_MAX_PAYLOAD_LENGTH: NonZeroU32; |
| } |
| |
| impl IpExt for Ipv4 { |
| type RecvSrcAddr = Ipv4Addr; |
| const IP_HEADER_LENGTH: NonZeroU32 = |
| const_unwrap_option(NonZeroU32::new(packet_formats::ipv4::HDR_PREFIX_LEN as u32)); |
| const IP_MAX_PAYLOAD_LENGTH: NonZeroU32 = |
| const_unwrap_option(NonZeroU32::new(u16::MAX as u32 - Self::IP_HEADER_LENGTH.get())); |
| } |
| |
| impl IpExt for Ipv6 { |
| type RecvSrcAddr = Ipv6SourceAddr; |
| const IP_HEADER_LENGTH: NonZeroU32 = |
| const_unwrap_option(NonZeroU32::new(packet_formats::ipv6::IPV6_FIXED_HDR_LEN as u32)); |
| const IP_MAX_PAYLOAD_LENGTH: NonZeroU32 = const_unwrap_option(NonZeroU32::new(u16::MAX as u32)); |
| } |
| |
| #[derive(Debug, GenericOverIp)] |
| #[generic_over_ip(I, Ip)] |
| pub(crate) struct TransparentLocalDelivery<I: IpExt> { |
| pub addr: SpecifiedAddr<I::Addr>, |
| pub port: NonZeroU16, |
| } |
| |
| /// The execution context provided by a transport layer protocol to the IP |
| /// layer. |
| /// |
| /// An implementation for `()` is provided which indicates that a particular |
| /// transport layer protocol is unsupported. |
| pub(crate) trait IpTransportContext<I: IpExt, BC, CC: DeviceIdContext<AnyDevice> + ?Sized> { |
| /// Receive an ICMP error message. |
| /// |
| /// All arguments beginning with `original_` are fields from the IP packet |
| /// that triggered the error. The `original_body` is provided here so that |
| /// the error can be associated with a transport-layer socket. `device` |
| /// identifies the device that received the ICMP error message packet. |
| /// |
| /// While ICMPv4 error messages are supposed to contain the first 8 bytes of |
| /// the body of the offending packet, and ICMPv6 error messages are supposed |
| /// to contain as much of the offending packet as possible without violating |
| /// the IPv6 minimum MTU, the caller does NOT guarantee that either of these |
| /// hold. It is `receive_icmp_error`'s responsibility to handle any length |
| /// of `original_body`, and to perform any necessary validation. |
| fn receive_icmp_error( |
| core_ctx: &mut CC, |
| bindings_ctx: &mut BC, |
| device: &CC::DeviceId, |
| original_src_ip: Option<SpecifiedAddr<I::Addr>>, |
| original_dst_ip: SpecifiedAddr<I::Addr>, |
| original_body: &[u8], |
| err: I::ErrorCode, |
| ); |
| |
| /// Receive a transport layer packet in an IP packet. |
| /// |
| /// In the event of an unreachable port, `receive_ip_packet` returns the |
| /// buffer in its original state (with the transport packet un-parsed) in |
| /// the `Err` variant. |
| fn receive_ip_packet<B: BufferMut>( |
| core_ctx: &mut CC, |
| bindings_ctx: &mut BC, |
| device: &CC::DeviceId, |
| src_ip: I::RecvSrcAddr, |
| dst_ip: SpecifiedAddr<I::Addr>, |
| buffer: B, |
| transport_override: Option<TransparentLocalDelivery<I>>, |
| ) -> Result<(), (B, TransportReceiveError)>; |
| } |
| |
| impl<I: IpExt, BC, CC: DeviceIdContext<AnyDevice> + ?Sized> IpTransportContext<I, BC, CC> for () { |
| fn receive_icmp_error( |
| _core_ctx: &mut CC, |
| _bindings_ctx: &mut BC, |
| _device: &CC::DeviceId, |
| _original_src_ip: Option<SpecifiedAddr<I::Addr>>, |
| _original_dst_ip: SpecifiedAddr<I::Addr>, |
| _original_body: &[u8], |
| err: I::ErrorCode, |
| ) { |
| trace!("IpTransportContext::receive_icmp_error: Received ICMP error message ({:?}) for unsupported IP protocol", err); |
| } |
| |
| fn receive_ip_packet<B: BufferMut>( |
| _core_ctx: &mut CC, |
| _bindings_ctx: &mut BC, |
| _device: &CC::DeviceId, |
| _src_ip: I::RecvSrcAddr, |
| _dst_ip: SpecifiedAddr<I::Addr>, |
| buffer: B, |
| _transport_override: Option<TransparentLocalDelivery<I>>, |
| ) -> Result<(), (B, TransportReceiveError)> { |
| Err(( |
| buffer, |
| TransportReceiveError { inner: TransportReceiveErrorInner::ProtocolUnsupported }, |
| )) |
| } |
| } |
| |
| /// The execution context provided by the IP layer to transport layer protocols. |
| pub trait TransportIpContext<I: IpExt, BC>: |
| DeviceIdContext<AnyDevice> + IpSocketHandler<I, BC> |
| { |
| type DevicesWithAddrIter<'s>: Iterator<Item = Self::DeviceId> |
| where |
| Self: 's; |
| |
| /// Is this one of our local addresses, and is it in the assigned state? |
| /// |
| /// Returns an iterator over all the local interfaces for which `addr` is an |
| /// associated address, and, for IPv6, for which it is in the "assigned" |
| /// state. |
| fn get_devices_with_assigned_addr( |
| &mut self, |
| addr: SpecifiedAddr<I::Addr>, |
| ) -> Self::DevicesWithAddrIter<'_>; |
| |
| /// Get default hop limits. |
| /// |
| /// If `device` is not `None` and exists, its hop limits will be returned. |
| /// Otherwise the system defaults are returned. |
| fn get_default_hop_limits(&mut self, device: Option<&Self::DeviceId>) -> HopLimits; |
| |
| /// Confirms the provided destination is reachable. |
| /// |
| /// Implementations must retrieve the next hop given the provided |
| /// destination and confirm neighbor reachability for the resolved target |
| /// device. |
| fn confirm_reachable_with_destination( |
| &mut self, |
| bindings_ctx: &mut BC, |
| dst: SpecifiedAddr<I::Addr>, |
| device: Option<&Self::DeviceId>, |
| ); |
| } |
| |
| /// Abstraction over the ability to join and leave multicast groups. |
| pub(crate) trait MulticastMembershipHandler<I: Ip, BC>: DeviceIdContext<AnyDevice> { |
| /// Requests that the specified device join the given multicast group. |
| /// |
| /// If this method is called multiple times with the same device and |
| /// address, the device will remain joined to the multicast group until |
| /// [`MulticastTransportIpContext::leave_multicast_group`] has been called |
| /// the same number of times. |
| fn join_multicast_group( |
| &mut self, |
| bindings_ctx: &mut BC, |
| device: &Self::DeviceId, |
| addr: MulticastAddr<I::Addr>, |
| ); |
| |
| /// Requests that the specified device leave the given multicast group. |
| /// |
| /// Each call to this method must correspond to an earlier call to |
| /// [`MulticastTransportIpContext::join_multicast_group`]. The device |
| /// remains a member of the multicast group so long as some call to |
| /// `join_multicast_group` has been made without a corresponding call to |
| /// `leave_multicast_group`. |
| fn leave_multicast_group( |
| &mut self, |
| bindings_ctx: &mut BC, |
| device: &Self::DeviceId, |
| addr: MulticastAddr<I::Addr>, |
| ); |
| |
| /// Selects a default device with which to join the given multicast group. |
| /// |
| /// The selection is made by consulting the routing table; If there is no |
| /// route available to the given address, an error is returned. |
| fn select_device_for_multicast_group( |
| &mut self, |
| addr: MulticastAddr<I::Addr>, |
| ) -> Result<Self::DeviceId, ResolveRouteError>; |
| } |
| |
| // TODO(joshlf): With all 256 protocol numbers (minus reserved ones) given their |
| // own associated type in both traits, running `cargo check` on a 2018 MacBook |
| // Pro takes over a minute. Eventually - and before we formally publish this as |
| // a library - we should identify the bottleneck in the compiler and optimize |
| // it. For the time being, however, we only support protocol numbers that we |
| // actually use (TCP and UDP). |
| |
| #[netstack3_macros::instantiate_ip_impl_block(I)] |
| impl< |
| I: IpExt, |
| BC, |
| CC: IpDeviceContext<I, BC> + IpSocketHandler<I, BC> + IpStateContext<I, BC> + NonTestCtxMarker, |
| > TransportIpContext<I, BC> for CC |
| { |
| type DevicesWithAddrIter<'s> = <Vec<CC::DeviceId> as IntoIterator>::IntoIter where CC: 's; |
| |
| fn get_devices_with_assigned_addr( |
| &mut self, |
| addr: SpecifiedAddr<<I as Ip>::Addr>, |
| ) -> Self::DevicesWithAddrIter<'_> { |
| self.with_address_statuses(addr, |it| { |
| it.filter_map(|(device, state)| is_unicast_assigned::<I>(&state).then_some(device)) |
| .collect::<Vec<_>>() |
| }) |
| .into_iter() |
| } |
| |
| fn get_default_hop_limits(&mut self, device: Option<&Self::DeviceId>) -> HopLimits { |
| match device { |
| Some(device) => HopLimits { |
| unicast: IpDeviceStateContext::<I, _>::get_hop_limit(self, device), |
| ..DEFAULT_HOP_LIMITS |
| }, |
| None => DEFAULT_HOP_LIMITS, |
| } |
| } |
| |
| fn confirm_reachable_with_destination( |
| &mut self, |
| bindings_ctx: &mut BC, |
| dst: SpecifiedAddr<<I as Ip>::Addr>, |
| device: Option<&Self::DeviceId>, |
| ) { |
| match self |
| .with_ip_routing_table(|core_ctx, routes| routes.lookup(core_ctx, device, dst.get())) |
| { |
| Some(Destination { next_hop, device }) => { |
| let neighbor = match next_hop { |
| NextHop::RemoteAsNeighbor => dst, |
| NextHop::Gateway(gateway) => gateway, |
| NextHop::Broadcast(marker) => { |
| I::map_ip::<_, ()>( |
| WrapBroadcastMarker(marker), |
| |WrapBroadcastMarker(())| { |
| tracing::debug!( |
| "can't confirm {dst:?}@{device:?} as reachable: \ |
| dst is a broadcast address" |
| ); |
| }, |
| |WrapBroadcastMarker(never)| match never {}, |
| ); |
| return; |
| } |
| }; |
| self.confirm_reachable(bindings_ctx, &device, neighbor); |
| } |
| None => { |
| tracing::debug!("can't confirm {dst:?}@{device:?} as reachable: no route"); |
| } |
| } |
| } |
| } |
| |
| #[derive(Copy, Clone)] |
| pub enum EitherDeviceId<S, W> { |
| Strong(S), |
| Weak(W), |
| } |
| |
| impl<S: PartialEq, W: PartialEq + PartialEq<S>> PartialEq for EitherDeviceId<S, W> { |
| fn eq(&self, other: &EitherDeviceId<S, W>) -> bool { |
| match (self, other) { |
| (EitherDeviceId::Strong(this), EitherDeviceId::Strong(other)) => this == other, |
| (EitherDeviceId::Strong(this), EitherDeviceId::Weak(other)) => other == this, |
| (EitherDeviceId::Weak(this), EitherDeviceId::Strong(other)) => this == other, |
| (EitherDeviceId::Weak(this), EitherDeviceId::Weak(other)) => this == other, |
| } |
| } |
| } |
| |
| impl<S: Id, W: crate::device::WeakId<Strong = S>> EitherDeviceId<&'_ S, &'_ W> { |
| pub(crate) fn as_strong_ref<'a>(&'a self) -> Option<Cow<'a, S>> { |
| match self { |
| EitherDeviceId::Strong(s) => Some(Cow::Borrowed(s)), |
| EitherDeviceId::Weak(w) => w.upgrade().map(Cow::Owned), |
| } |
| } |
| } |
| |
| impl<S, W> EitherDeviceId<S, W> { |
| pub(crate) fn as_ref<'a, S2, W2>(&'a self) -> EitherDeviceId<&'a S2, &'a W2> |
| where |
| S: Borrow<S2>, |
| W: Borrow<W2>, |
| { |
| match self { |
| EitherDeviceId::Strong(s) => EitherDeviceId::Strong(s.borrow()), |
| EitherDeviceId::Weak(w) => EitherDeviceId::Weak(w.borrow()), |
| } |
| } |
| } |
| |
| impl<S: crate::device::StrongId<Weak = W>, W: crate::device::WeakId<Strong = S>> |
| EitherDeviceId<S, W> |
| { |
| pub(crate) fn as_strong<'a>(&'a self) -> Option<Cow<'a, S>> { |
| match self { |
| EitherDeviceId::Strong(s) => Some(Cow::Borrowed(s)), |
| EitherDeviceId::Weak(w) => w.upgrade().map(Cow::Owned), |
| } |
| } |
| |
| pub(crate) fn as_weak<'a>(&'a self) -> Cow<'a, W> { |
| match self { |
| EitherDeviceId::Strong(s) => Cow::Owned(s.downgrade()), |
| EitherDeviceId::Weak(w) => Cow::Borrowed(w), |
| } |
| } |
| } |
| |
| /// The status of an IP address on an interface. |
| pub enum AddressStatus<S> { |
| Present(S), |
| Unassigned, |
| } |
| |
| impl<S> AddressStatus<S> { |
| fn into_present(self) -> Option<S> { |
| match self { |
| Self::Present(s) => Some(s), |
| Self::Unassigned => None, |
| } |
| } |
| } |
| |
| impl<S: GenericOverIp<I>, I: Ip> GenericOverIp<I> for AddressStatus<S> { |
| type Type = AddressStatus<S::Type>; |
| } |
| |
| /// The status of an IPv4 address. |
| pub enum Ipv4PresentAddressStatus { |
| LimitedBroadcast, |
| SubnetBroadcast, |
| Multicast, |
| Unicast, |
| } |
| |
| /// The status of an IPv6 address. |
| pub enum Ipv6PresentAddressStatus { |
| Multicast, |
| UnicastAssigned, |
| UnicastTentative, |
| } |
| |
| /// An extension trait providing IP layer properties. |
| pub trait IpLayerIpExt: IpExt { |
| type AddressStatus; |
| type State<StrongDeviceId: StrongId, BT: IpLayerBindingsTypes>: AsRef< |
| IpStateInner<Self, StrongDeviceId, BT>, |
| >; |
| type PacketIdState; |
| type PacketId; |
| type RxCounters: Default + Inspectable; |
| fn next_packet_id_from_state(state: &Self::PacketIdState) -> Self::PacketId; |
| } |
| |
| impl IpLayerIpExt for Ipv4 { |
| type AddressStatus = Ipv4PresentAddressStatus; |
| type State<StrongDeviceId: StrongId, BT: IpLayerBindingsTypes> = Ipv4State<StrongDeviceId, BT>; |
| type PacketIdState = AtomicU16; |
| type PacketId = u16; |
| type RxCounters = Ipv4RxCounters; |
| fn next_packet_id_from_state(next_packet_id: &Self::PacketIdState) -> Self::PacketId { |
| // Relaxed ordering as we only need atomicity without synchronization. See |
| // https://en.cppreference.com/w/cpp/atomic/memory_order#Relaxed_ordering |
| // for more details. |
| next_packet_id.fetch_add(1, atomic::Ordering::Relaxed) |
| } |
| } |
| |
| impl IpLayerIpExt for Ipv6 { |
| type AddressStatus = Ipv6PresentAddressStatus; |
| type State<StrongDeviceId: StrongId, BT: IpLayerBindingsTypes> = Ipv6State<StrongDeviceId, BT>; |
| type PacketIdState = (); |
| type PacketId = (); |
| type RxCounters = Ipv6RxCounters; |
| fn next_packet_id_from_state((): &Self::PacketIdState) -> Self::PacketId { |
| () |
| } |
| } |
| |
| /// The state context provided to the IP layer. |
| pub trait IpStateContext<I: IpLayerIpExt, BC>: DeviceIdContext<AnyDevice> { |
| type IpDeviceIdCtx<'a>: DeviceIdContext<AnyDevice, DeviceId = Self::DeviceId, WeakDeviceId = Self::WeakDeviceId> |
| + IpForwardingDeviceContext<I> |
| + IpDeviceStateContext<I, BC>; |
| |
| /// Calls the function with an immutable reference to IP routing table. |
| fn with_ip_routing_table< |
| O, |
| F: FnOnce(&mut Self::IpDeviceIdCtx<'_>, &ForwardingTable<I, Self::DeviceId>) -> O, |
| >( |
| &mut self, |
| cb: F, |
| ) -> O; |
| |
| /// Calls the function with a mutable reference to IP routing table. |
| fn with_ip_routing_table_mut< |
| O, |
| F: FnOnce(&mut Self::IpDeviceIdCtx<'_>, &mut ForwardingTable<I, Self::DeviceId>) -> O, |
| >( |
| &mut self, |
| cb: F, |
| ) -> O; |
| } |
| |
| /// Provices access to an IP device's state for the IP layer. |
| pub trait IpDeviceStateContext<I: IpLayerIpExt, BC>: DeviceIdContext<AnyDevice> { |
| /// Calls the callback with the next packet ID. |
| fn with_next_packet_id<O, F: FnOnce(&I::PacketIdState) -> O>(&self, cb: F) -> O; |
| |
| /// Returns the best local address for communicating with the remote. |
| fn get_local_addr_for_remote( |
| &mut self, |
| device_id: &Self::DeviceId, |
| remote: Option<SpecifiedAddr<I::Addr>>, |
| ) -> Option<IpDeviceAddr<I::Addr>>; |
| |
| /// Returns the hop limit. |
| fn get_hop_limit(&mut self, device_id: &Self::DeviceId) -> NonZeroU8; |
| |
| /// Gets the status of an address. |
| /// |
| /// Only the specified device will be checked for the address. Returns |
| /// [`AddressStatus::Unassigned`] if the address is not assigned to the |
| /// device. |
| fn address_status_for_device( |
| &mut self, |
| addr: SpecifiedAddr<I::Addr>, |
| device_id: &Self::DeviceId, |
| ) -> AddressStatus<I::AddressStatus>; |
| } |
| |
| /// The IP device context provided to the IP layer. |
| pub trait IpDeviceContext<I: IpLayerIpExt, BC>: IpDeviceStateContext<I, BC> { |
| /// Is the device enabled? |
| fn is_ip_device_enabled(&mut self, device_id: &Self::DeviceId) -> bool; |
| |
| type DeviceAndAddressStatusIter<'a, 's>: Iterator<Item = (Self::DeviceId, I::AddressStatus)> |
| where |
| Self: IpDeviceContext<I, BC> + 's; |
| |
| /// Provides access to the status of an address. |
| /// |
| /// Calls the provided callback with an iterator over the devices for which |
| /// the address is assigned and the status of the assignment for each |
| /// device. |
| fn with_address_statuses<'a, F: FnOnce(Self::DeviceAndAddressStatusIter<'_, 'a>) -> R, R>( |
| &'a mut self, |
| addr: SpecifiedAddr<I::Addr>, |
| cb: F, |
| ) -> R; |
| |
| /// Returns true iff the device has forwarding enabled. |
| fn is_device_forwarding_enabled(&mut self, device_id: &Self::DeviceId) -> bool; |
| |
| /// Returns the MTU of the device. |
| fn get_mtu(&mut self, device_id: &Self::DeviceId) -> Mtu; |
| |
| /// Confirm transport-layer forward reachability to the specified neighbor |
| /// through the specified device. |
| fn confirm_reachable( |
| &mut self, |
| bindings_ctx: &mut BC, |
| device: &Self::DeviceId, |
| neighbor: SpecifiedAddr<I::Addr>, |
| ); |
| } |
| |
| /// Events observed at the IP layer. |
| #[derive(Debug, Eq, Hash, PartialEq, GenericOverIp)] |
| #[generic_over_ip(I, Ip)] |
| pub enum IpLayerEvent<DeviceId, I: Ip> { |
| /// A route needs to be added. |
| AddRoute(types::AddableEntry<I::Addr, DeviceId>), |
| /// Routes matching these specifiers need to be removed. |
| RemoveRoutes { |
| /// Destination subnet |
| subnet: Subnet<I::Addr>, |
| /// Outgoing interface |
| device: DeviceId, |
| /// Gateway/next-hop |
| gateway: Option<SpecifiedAddr<I::Addr>>, |
| }, |
| } |
| |
| /// The bindings execution context for the IP layer. |
| pub trait IpLayerBindingsContext<I: Ip, DeviceId>: |
| InstantContext + EventContext<IpLayerEvent<DeviceId, I>> + TracingContext + FilterBindingsContext |
| { |
| } |
| impl< |
| I: Ip, |
| DeviceId, |
| BC: InstantContext |
| + EventContext<IpLayerEvent<DeviceId, I>> |
| + TracingContext |
| + FilterBindingsContext, |
| > IpLayerBindingsContext<I, DeviceId> for BC |
| { |
| } |
| |
| /// A marker trait for bindings types at the IP layer. |
| pub trait IpLayerBindingsTypes: |
| FilterBindingsTypes + IcmpBindingsTypes + IpStateBindingsTypes |
| { |
| } |
| impl<BT: FilterBindingsTypes + IcmpBindingsTypes + IpStateBindingsTypes> IpLayerBindingsTypes |
| for BT |
| { |
| } |
| |
| /// The execution context for the IP layer. |
| pub trait IpLayerContext< |
| I: IpLayerIpExt, |
| BC: IpLayerBindingsContext<I, <Self as DeviceIdContext<AnyDevice>>::DeviceId>, |
| >: IpStateContext<I, BC> + IpDeviceContext<I, BC> |
| { |
| } |
| |
| impl< |
| I: IpLayerIpExt, |
| BC: IpLayerBindingsContext<I, <CC as DeviceIdContext<AnyDevice>>::DeviceId>, |
| CC: IpStateContext<I, BC> + IpDeviceContext<I, BC>, |
| > IpLayerContext<I, BC> for CC |
| { |
| } |
| |
| fn is_unicast_assigned<I: IpLayerIpExt>(status: &I::AddressStatus) -> bool { |
| #[derive(GenericOverIp)] |
| #[generic_over_ip(I, Ip)] |
| struct WrapAddressStatus<'a, I: IpLayerIpExt>(&'a I::AddressStatus); |
| |
| I::map_ip( |
| WrapAddressStatus(status), |
| |WrapAddressStatus(status)| match status { |
| Ipv4PresentAddressStatus::Unicast => true, |
| Ipv4PresentAddressStatus::LimitedBroadcast |
| | Ipv4PresentAddressStatus::SubnetBroadcast |
| | Ipv4PresentAddressStatus::Multicast => false, |
| }, |
| |WrapAddressStatus(status)| match status { |
| Ipv6PresentAddressStatus::UnicastAssigned => true, |
| Ipv6PresentAddressStatus::Multicast | Ipv6PresentAddressStatus::UnicastTentative => { |
| false |
| } |
| }, |
| ) |
| } |
| |
| fn is_local_assigned_address< |
| I: Ip + IpLayerIpExt, |
| BC: IpLayerBindingsContext<I, CC::DeviceId>, |
| CC: IpDeviceStateContext<I, BC>, |
| >( |
| core_ctx: &mut CC, |
| device: &CC::DeviceId, |
| local_ip: SpecifiedAddr<I::Addr>, |
| ) -> bool { |
| match core_ctx.address_status_for_device(local_ip, device) { |
| AddressStatus::Present(status) => is_unicast_assigned::<I>(&status), |
| AddressStatus::Unassigned => false, |
| } |
| } |
| |
| // Returns the local IP address to use for sending packets from the |
| // given device to `addr`, restricting to `local_ip` if it is not |
| // `None`. |
| fn get_local_addr< |
| I: Ip + IpLayerIpExt, |
| BC: IpLayerBindingsContext<I, CC::DeviceId>, |
| CC: IpDeviceStateContext<I, BC>, |
| >( |
| core_ctx: &mut CC, |
| local_ip: Option<IpDeviceAddr<I::Addr>>, |
| device: &CC::DeviceId, |
| remote_addr: Option<RoutableIpAddr<I::Addr>>, |
| ) -> Result<IpDeviceAddr<I::Addr>, ResolveRouteError> { |
| if let Some(local_ip) = local_ip { |
| is_local_assigned_address(core_ctx, device, local_ip.into()) |
| .then_some(local_ip) |
| .ok_or(ResolveRouteError::NoSrcAddr) |
| } else { |
| core_ctx |
| .get_local_addr_for_remote(device, remote_addr.map(Into::into)) |
| .ok_or(ResolveRouteError::NoSrcAddr) |
| } |
| } |
| |
| /// An error occurred while resolving the route to a destination |
| #[derive(Error, Copy, Clone, Debug, Eq, GenericOverIp, PartialEq)] |
| #[generic_over_ip()] |
| pub enum ResolveRouteError { |
| /// A source address could not be selected. |
| #[error("a source address could not be selected")] |
| NoSrcAddr, |
| /// The destination in unreachable. |
| #[error("no route exists to the destination IP address")] |
| Unreachable, |
| } |
| |
| // Returns the forwarding instructions for reaching the given destination. |
| // |
| // If a `device` is specified, the resolved route is limited to those that |
| // egress over the device. |
| // |
| // If `local_ip` is specified the resolved route is limited to those that egress |
| // over a device with the address assigned. |
| pub(crate) fn resolve_route_to_destination< |
| I: Ip + IpDeviceStateIpExt + IpDeviceIpExt + IpLayerIpExt, |
| BC: IpDeviceBindingsContext<I, CC::DeviceId> + IpLayerBindingsContext<I, CC::DeviceId>, |
| CC: IpLayerContext<I, BC> + device::IpDeviceConfigurationContext<I, BC>, |
| >( |
| core_ctx: &mut CC, |
| device: Option<&CC::DeviceId>, |
| local_ip: Option<IpDeviceAddr<I::Addr>>, |
| addr: Option<RoutableIpAddr<I::Addr>>, |
| ) -> Result<ResolvedRoute<I, CC::DeviceId>, ResolveRouteError> { |
| enum LocalDelivery<A, D> { |
| WeakLoopback { addr: A, device: D }, |
| StrongForDevice(D), |
| } |
| |
| // Check if locally destined. If the destination is an address assigned |
| // on an interface, and an egress interface wasn't specifically |
| // selected, route via the loopback device. This lets us operate as a |
| // strong host when an outgoing interface is explicitly requested while |
| // still enabling local delivery via the loopback interface, which is |
| // acting as a weak host. Note that if the loopback interface is |
| // requested as an outgoing interface, route selection is still |
| // performed as a strong host! This makes the loopback interface behave |
| // more like the other interfaces on the system. |
| // |
| // TODO(https://fxbug.dev/42175703): Encode the delivery of locally- |
| // destined packets to loopback in the route table. |
| let local_delivery_instructions: Option<LocalDelivery<RoutableIpAddr<I::Addr>, CC::DeviceId>> = |
| addr.and_then(|addr| { |
| match device { |
| Some(device) => match core_ctx.address_status_for_device(addr.into(), device) { |
| AddressStatus::Present(status) => { |
| // If the destination is an address assigned to the |
| // requested egress interface, route locally via the strong |
| // host model. |
| is_unicast_assigned::<I>(&status) |
| .then_some(LocalDelivery::StrongForDevice(device.clone())) |
| } |
| AddressStatus::Unassigned => None, |
| }, |
| None => { |
| // If the destination is an address assigned on an interface, |
| // and an egress interface wasn't specifically selected, route |
| // via the loopback device operating in a weak host model, with |
| // the exception that if either the source or destination |
| // addresses needs a zone ID, then use strong host to enforce |
| // that the source and destination addresses are assigned to |
| // the same interface. |
| // |
| // TODO(https://fxbug.dev/322539434): Linux is more permissive |
| // about allowing cross-device local delivery even when |
| // SO_BINDTODEVICE or link-local addresses are involved, and this |
| // behavior may need to be emulated. |
| core_ctx |
| .with_address_statuses(addr.into(), |mut it| { |
| it.find_map(|(device, status)| { |
| is_unicast_assigned::<I>(&status).then_some(device) |
| }) |
| }) |
| .map(|device| { |
| if local_ip.is_some_and(|local_ip| { |
| crate::socket::must_have_zone(local_ip.as_ref()) |
| }) || crate::socket::must_have_zone(addr.as_ref()) |
| { |
| LocalDelivery::StrongForDevice(device) |
| } else { |
| LocalDelivery::WeakLoopback { addr, device } |
| } |
| }) |
| } |
| } |
| }); |
| |
| match local_delivery_instructions { |
| Some(local_delivery) => { |
| let loopback = match core_ctx.loopback_id() { |
| None => return Err(ResolveRouteError::Unreachable), |
| Some(loopback) => loopback, |
| }; |
| |
| let (local_ip, dest_device) = match local_delivery { |
| LocalDelivery::WeakLoopback { addr, device } => match local_ip { |
| Some(local_ip) => core_ctx |
| .with_address_statuses(local_ip.into(), |mut it| { |
| it.find_map(|(device, status)| { |
| is_unicast_assigned::<I>(&status).then_some(device) |
| }) |
| }) |
| .map(|device| (local_ip, device)) |
| .ok_or(ResolveRouteError::NoSrcAddr)?, |
| None => (addr, device), |
| }, |
| LocalDelivery::StrongForDevice(device) => { |
| (get_local_addr(core_ctx, local_ip, &device, addr)?, device) |
| } |
| }; |
| Ok(ResolvedRoute { |
| src_addr: local_ip, |
| local_delivery_device: Some(dest_device), |
| device: loopback, |
| next_hop: NextHop::RemoteAsNeighbor, |
| }) |
| } |
| None => { |
| core_ctx |
| .with_ip_routing_table(|core_ctx, table| { |
| let mut matching_with_addr = table.lookup_filter_map( |
| core_ctx, |
| device, |
| addr.map_or(I::UNSPECIFIED_ADDRESS, |a| a.addr()), |
| |core_ctx, d| Some(get_local_addr(core_ctx, local_ip, d, addr)), |
| ); |
| |
| let first_error = match matching_with_addr.next() { |
| Some((Destination { device, next_hop }, Ok(addr))) => { |
| return Ok((Destination { device: device.clone(), next_hop }, addr)) |
| } |
| Some((_, Err(e))) => e, |
| None => return Err(ResolveRouteError::Unreachable), |
| }; |
| |
| matching_with_addr |
| .filter_map(|(d, r)| { |
| // Select successful routes. We ignore later errors |
| // since we've already saved the first one. |
| r.ok_checked::<ResolveRouteError>().map(|a| (d, a)) |
| }) |
| .next() |
| .map_or(Err(first_error), |(Destination { device, next_hop }, addr)| { |
| Ok((Destination { device: device.clone(), next_hop }, addr)) |
| }) |
| }) |
| .map(|(Destination { device, next_hop }, local_ip)| ResolvedRoute { |
| src_addr: local_ip, |
| device, |
| local_delivery_device: None, |
| next_hop, |
| }) |
| } |
| } |
| } |
| |
| impl< |
| I: Ip + IpDeviceStateIpExt + IpDeviceIpExt + IpLayerIpExt, |
| BC: IpDeviceBindingsContext<I, CC::DeviceId> |
| + IpLayerBindingsContext<I, CC::DeviceId> |
| + IpSocketBindingsContext, |
| CC: IpLayerContext<I, BC> |
| + IpLayerEgressContext<I, BC> |
| + device::IpDeviceConfigurationContext<I, BC> |
| + NonTestCtxMarker, |
| > IpSocketContext<I, BC> for CC |
| { |
| fn lookup_route( |
| &mut self, |
| _bindings_ctx: &mut BC, |
| device: Option<&CC::DeviceId>, |
| local_ip: Option<IpDeviceAddr<I::Addr>>, |
| addr: RoutableIpAddr<I::Addr>, |
| ) -> Result<ResolvedRoute<I, CC::DeviceId>, ResolveRouteError> { |
| resolve_route_to_destination(self, device, local_ip, Some(addr)) |
| } |
| |
| fn send_ip_packet<S>( |
| &mut self, |
| bindings_ctx: &mut BC, |
| meta: SendIpPacketMeta< |
| I, |
| &<CC as DeviceIdContext<AnyDevice>>::DeviceId, |
| SpecifiedAddr<I::Addr>, |
| >, |
| body: S, |
| packet_metadata: IpLayerPacketMetadata<I, BC>, |
| ) -> Result<(), S> |
| where |
| S: Serializer + MaybeTransportPacket, |
| S::Buffer: BufferMut, |
| { |
| send_ip_packet_from_device(self, bindings_ctx, meta.into(), body, packet_metadata) |
| } |
| } |
| |
| impl<BC: BindingsContext, I: IpLayerIpExt, L> CounterContext<IpCounters<I>> for CoreCtx<'_, BC, L> { |
| fn with_counters<O, F: FnOnce(&IpCounters<I>) -> O>(&self, cb: F) -> O { |
| cb(self.unlocked_access::<crate::lock_ordering::IpStateCounters<I>>()) |
| } |
| } |
| |
| impl<I, BC, L> IpStateContext<I, BC> for CoreCtx<'_, BC, L> |
| where |
| I: IpLayerIpExt, |
| BC: BindingsContext, |
| L: LockBefore<crate::lock_ordering::IpStateRoutingTable<I>>, |
| |
| // These bounds ensure that we can fulfill all the traits for the associated |
| // type `IpDeviceIdCtx` below and keep the compiler happy where we don't |
| // have implementations that are generic on Ip. |
| for<'a> CoreCtx<'a, BC, crate::lock_ordering::IpStateRoutingTable<I>>: |
| DeviceIdContext<AnyDevice, DeviceId = Self::DeviceId, WeakDeviceId = Self::WeakDeviceId> |
| + IpForwardingDeviceContext<I> |
| + IpDeviceStateContext<I, BC>, |
| { |
| type IpDeviceIdCtx<'a> = CoreCtx<'a, BC, crate::lock_ordering::IpStateRoutingTable<I>>; |
| |
| fn with_ip_routing_table< |
| O, |
| F: FnOnce(&mut Self::IpDeviceIdCtx<'_>, &ForwardingTable<I, Self::DeviceId>) -> O, |
| >( |
| &mut self, |
| cb: F, |
| ) -> O { |
| let (cache, mut locked) = |
| self.read_lock_and::<crate::lock_ordering::IpStateRoutingTable<I>>(); |
| cb(&mut locked, &cache) |
| } |
| |
| fn with_ip_routing_table_mut< |
| O, |
| F: FnOnce(&mut Self::IpDeviceIdCtx<'_>, &mut ForwardingTable<I, Self::DeviceId>) -> O, |
| >( |
| &mut self, |
| cb: F, |
| ) -> O { |
| let (mut cache, mut locked) = |
| self.write_lock_and::<crate::lock_ordering::IpStateRoutingTable<I>>(); |
| cb(&mut locked, &mut cache) |
| } |
| } |
| |
| /// The IP context providing dispatch to the available transport protocols. |
| /// |
| /// This trait acts like a demux on the transport protocol for ingress IP |
| /// packets. |
| pub(crate) trait IpTransportDispatchContext<I: IpLayerIpExt, BC>: |
| DeviceIdContext<AnyDevice> |
| { |
| /// Dispatches a received incoming IP packet to the appropriate protocol. |
| fn dispatch_receive_ip_packet<B: BufferMut>( |
| &mut self, |
| bindings_ctx: &mut BC, |
| device: &Self::DeviceId, |
| src_ip: I::RecvSrcAddr, |
| dst_ip: SpecifiedAddr<I::Addr>, |
| proto: I::Proto, |
| body: B, |
| transport_override: Option<TransparentLocalDelivery<I>>, |
| ) -> Result<(), (B, TransportReceiveError)>; |
| } |
| |
| /// A marker trait for all the contexts required for IP ingress. |
| pub(crate) trait IpLayerIngressContext< |
| I: IpLayerIpExt + IcmpHandlerIpExt, |
| BC: IpLayerBindingsContext<I, Self::DeviceId>, |
| >: |
| IpTransportDispatchContext<I, BC, DeviceId = Self::DeviceId_> |
| + IpDeviceStateContext<I, BC> |
| + IpDeviceSendContext<I, BC> |
| + IcmpErrorHandler<I, BC> |
| + IpLayerContext<I, BC> |
| + FragmentHandler<I, BC> |
| + FilterHandlerProvider<I, BC> |
| { |
| // This is working around the fact that currently, where clauses are only |
| // elaborated for supertraits, and not, for example, bounds on associated types |
| // as we have here. |
| // |
| // See https://github.com/rust-lang/rust/issues/20671#issuecomment-1905186183 |
| // for more discussion. |
| type DeviceId_: crate::filter::InterfaceProperties<BC::DeviceClass> + Debug; |
| } |
| |
| impl< |
| I: IpLayerIpExt + IcmpHandlerIpExt, |
| BC: IpLayerBindingsContext<I, CC::DeviceId>, |
| CC: IpTransportDispatchContext<I, BC> |
| + IpDeviceStateContext<I, BC> |
| + IpDeviceSendContext<I, BC> |
| + IcmpErrorHandler<I, BC> |
| + IpLayerContext<I, BC> |
| + FragmentHandler<I, BC> |
| + FilterHandlerProvider<I, BC>, |
| > IpLayerIngressContext<I, BC> for CC |
| where |
| Self::DeviceId: crate::filter::InterfaceProperties<BC::DeviceClass>, |
| { |
| type DeviceId_ = Self::DeviceId; |
| } |
| |
| /// A marker trait for all the contexts required for IP egress. |
| pub(crate) trait IpLayerEgressContext<I, BC>: |
| IpDeviceSendContext<I, BC, DeviceId = Self::DeviceId_> + FilterHandlerProvider<I, BC> |
| where |
| I: IpLayerIpExt, |
| BC: FilterBindingsTypes, |
| { |
| // This is working around the fact that currently, where clauses are only |
| // elaborated for supertraits, and not, for example, bounds on associated types |
| // as we have here. |
| // |
| // See https://github.com/rust-lang/rust/issues/20671#issuecomment-1905186183 |
| // for more discussion. |
| type DeviceId_: crate::filter::InterfaceProperties<BC::DeviceClass> + StrongId + Debug; |
| } |
| |
| impl<I, BC, CC> IpLayerEgressContext<I, BC> for CC |
| where |
| I: IpLayerIpExt, |
| BC: FilterBindingsTypes, |
| CC: IpDeviceSendContext<I, BC> + FilterHandlerProvider<I, BC>, |
| Self::DeviceId: crate::filter::InterfaceProperties<BC::DeviceClass>, |
| { |
| type DeviceId_ = Self::DeviceId; |
| } |
| |
| impl<BC: BindingsContext, L: LockBefore<crate::lock_ordering::IcmpAllSocketsSet<Ipv4>>> |
| IpTransportDispatchContext<Ipv4, BC> for CoreCtx<'_, BC, L> |
| { |
| fn dispatch_receive_ip_packet<B: BufferMut>( |
| &mut self, |
| bindings_ctx: &mut BC, |
| device: &Self::DeviceId, |
| src_ip: Ipv4Addr, |
| dst_ip: SpecifiedAddr<Ipv4Addr>, |
| proto: Ipv4Proto, |
| body: B, |
| transport_override: Option<TransparentLocalDelivery<Ipv4>>, |
| ) -> Result<(), (B, TransportReceiveError)> { |
| // TODO(https://fxbug.dev/42175797): Deliver the packet to interested raw |
| // sockets. |
| |
| match proto { |
| Ipv4Proto::Icmp => { |
| <IcmpIpTransportContext as IpTransportContext<Ipv4, _, _>>::receive_ip_packet( |
| self, |
| bindings_ctx, |
| device, |
| src_ip, |
| dst_ip, |
| body, |
| transport_override, |
| ) |
| } |
| Ipv4Proto::Igmp => { |
| device::receive_igmp_packet(self, bindings_ctx, device, src_ip, dst_ip, body); |
| Ok(()) |
| } |
| Ipv4Proto::Proto(IpProto::Udp) => { |
| <UdpIpTransportContext as IpTransportContext<Ipv4, _, _>>::receive_ip_packet( |
| self, |
| bindings_ctx, |
| device, |
| src_ip, |
| dst_ip, |
| body, |
| transport_override, |
| ) |
| } |
| Ipv4Proto::Proto(IpProto::Tcp) => { |
| <TcpIpTransportContext as IpTransportContext<Ipv4, _, _>>::receive_ip_packet( |
| self, |
| bindings_ctx, |
| device, |
| src_ip, |
| dst_ip, |
| body, |
| transport_override, |
| ) |
| } |
| // TODO(joshlf): Once all IP protocol numbers are covered, remove |
| // this default case. |
| _ => Err(( |
| body, |
| TransportReceiveError { inner: TransportReceiveErrorInner::ProtocolUnsupported }, |
| )), |
| } |
| } |
| } |
| |
| impl<BC: BindingsContext, L: LockBefore<crate::lock_ordering::IcmpAllSocketsSet<Ipv6>>> |
| IpTransportDispatchContext<Ipv6, BC> for CoreCtx<'_, BC, L> |
| { |
| fn dispatch_receive_ip_packet<B: BufferMut>( |
| &mut self, |
| bindings_ctx: &mut BC, |
| device: &Self::DeviceId, |
| src_ip: Ipv6SourceAddr, |
| dst_ip: SpecifiedAddr<Ipv6Addr>, |
| proto: Ipv6Proto, |
| body: B, |
| transport_override: Option<TransparentLocalDelivery<Ipv6>>, |
| ) -> Result<(), (B, TransportReceiveError)> { |
| // TODO(https://fxbug.dev/42175797): Deliver the packet to interested raw |
| // sockets. |
| |
| match proto { |
| Ipv6Proto::Icmpv6 => { |
| <IcmpIpTransportContext as IpTransportContext<Ipv6, _, _>>::receive_ip_packet( |
| self, |
| bindings_ctx, |
| device, |
| src_ip, |
| dst_ip, |
| body, |
| transport_override, |
| ) |
| } |
| // A value of `Ipv6Proto::NoNextHeader` tells us that there is no |
| // header whatsoever following the last lower-level header so we stop |
| // processing here. |
| Ipv6Proto::NoNextHeader => Ok(()), |
| Ipv6Proto::Proto(IpProto::Tcp) => { |
| <TcpIpTransportContext as IpTransportContext<Ipv6, _, _>>::receive_ip_packet( |
| self, |
| bindings_ctx, |
| device, |
| src_ip, |
| dst_ip, |
| body, |
| transport_override, |
| ) |
| } |
| Ipv6Proto::Proto(IpProto::Udp) => { |
| <UdpIpTransportContext as IpTransportContext<Ipv6, _, _>>::receive_ip_packet( |
| self, |
| bindings_ctx, |
| device, |
| src_ip, |
| dst_ip, |
| body, |
| transport_override, |
| ) |
| } |
| // TODO(joshlf): Once all IP Next Header numbers are covered, remove |
| // this default case. |
| _ => Err(( |
| body, |
| TransportReceiveError { inner: TransportReceiveErrorInner::ProtocolUnsupported }, |
| )), |
| } |
| } |
| } |
| |
| /// A builder for IPv4 state. |
| #[derive(Copy, Clone, Default)] |
| pub(crate) struct Ipv4StateBuilder { |
| icmp: Icmpv4StateBuilder, |
| } |
| |
| impl Ipv4StateBuilder { |
| /// Get the builder for the ICMPv4 state. |
| #[cfg(test)] |
| pub(crate) fn icmpv4_builder(&mut self) -> &mut Icmpv4StateBuilder { |
| &mut self.icmp |
| } |
| |
| pub(crate) fn build< |
| CC: CoreTimerContext<IpLayerTimerId, BC>, |
| StrongDeviceId: StrongId, |
| BC: TimerContext + IpLayerBindingsTypes, |
| >( |
| self, |
| bindings_ctx: &mut BC, |
| ) -> Ipv4State<StrongDeviceId, BC> { |
| let Ipv4StateBuilder { icmp } = self; |
| |
| Ipv4State { |
| inner: IpStateInner::new::<CC>(bindings_ctx), |
| icmp: icmp.build(), |
| next_packet_id: Default::default(), |
| filter: RwLock::new(crate::filter::State::default()), |
| } |
| } |
| } |
| |
| /// A builder for IPv6 state. |
| #[derive(Copy, Clone, Default)] |
| pub(crate) struct Ipv6StateBuilder { |
| icmp: Icmpv6StateBuilder, |
| } |
| |
| impl Ipv6StateBuilder { |
| pub(crate) fn build< |
| CC: CoreTimerContext<IpLayerTimerId, BC>, |
| StrongDeviceId: StrongId, |
| BC: TimerContext + IpLayerBindingsTypes, |
| >( |
| self, |
| bindings_ctx: &mut BC, |
| ) -> Ipv6State<StrongDeviceId, BC> { |
| let Ipv6StateBuilder { icmp } = self; |
| |
| Ipv6State { |
| inner: IpStateInner::new::<CC>(bindings_ctx), |
| icmp: icmp.build(), |
| slaac_counters: Default::default(), |
| filter: RwLock::new(crate::filter::State::default()), |
| } |
| } |
| } |
| |
| pub struct Ipv4State<StrongDeviceId: StrongId, BT: IpLayerBindingsTypes> { |
| pub(super) inner: IpStateInner<Ipv4, StrongDeviceId, BT>, |
| pub(super) icmp: Icmpv4State<StrongDeviceId::Weak, BT>, |
| pub(super) next_packet_id: AtomicU16, |
| pub(super) filter: RwLock<crate::filter::State<Ipv4, BT>>, |
| } |
| |
| impl<StrongDeviceId: StrongId, BT: IpLayerBindingsTypes> Ipv4State<StrongDeviceId, BT> { |
| pub fn filter(&self) -> &RwLock<crate::filter::State<Ipv4, BT>> { |
| &self.filter |
| } |
| |
| pub(crate) fn inner(&self) -> &IpStateInner<Ipv4, StrongDeviceId, BT> { |
| &self.inner |
| } |
| |
| pub(crate) fn icmp(&self) -> &Icmpv4State<StrongDeviceId::Weak, BT> { |
| &self.icmp |
| } |
| } |
| |
| impl<StrongDeviceId: StrongId, BT: IpLayerBindingsTypes> |
| AsRef<IpStateInner<Ipv4, StrongDeviceId, BT>> for Ipv4State<StrongDeviceId, BT> |
| { |
| fn as_ref(&self) -> &IpStateInner<Ipv4, StrongDeviceId, BT> { |
| &self.inner |
| } |
| } |
| |
| pub(super) fn gen_ip_packet_id<I: IpLayerIpExt, BC, CC: IpDeviceStateContext<I, BC>>( |
| core_ctx: &mut CC, |
| ) -> I::PacketId { |
| core_ctx.with_next_packet_id(|state| I::next_packet_id_from_state(state)) |
| } |
| |
| pub struct Ipv6State<StrongDeviceId: StrongId, BT: IpLayerBindingsTypes> { |
| pub(super) inner: IpStateInner<Ipv6, StrongDeviceId, BT>, |
| pub(super) icmp: Icmpv6State<StrongDeviceId::Weak, BT>, |
| pub(super) slaac_counters: SlaacCounters, |
| pub(super) filter: RwLock<crate::filter::State<Ipv6, BT>>, |
| } |
| |
| impl<StrongDeviceId: StrongId, BT: IpLayerBindingsTypes> Ipv6State<StrongDeviceId, BT> { |
| pub(crate) fn slaac_counters(&self) -> &SlaacCounters { |
| &self.slaac_counters |
| } |
| |
| pub fn filter(&self) -> &RwLock<crate::filter::State<Ipv6, BT>> { |
| &self.filter |
| } |
| |
| pub(crate) fn inner(&self) -> &IpStateInner<Ipv6, StrongDeviceId, BT> { |
| &self.inner |
| } |
| |
| pub(crate) fn icmp(&self) -> &Icmpv6State<StrongDeviceId::Weak, BT> { |
| &self.icmp |
| } |
| } |
| |
| impl<StrongDeviceId: StrongId, BT: IpLayerBindingsTypes> |
| AsRef<IpStateInner<Ipv6, StrongDeviceId, BT>> for Ipv6State<StrongDeviceId, BT> |
| { |
| fn as_ref(&self) -> &IpStateInner<Ipv6, StrongDeviceId, BT> { |
| &self.inner |
| } |
| } |
| |
| impl<I, BT> LockFor<crate::lock_ordering::IpStateFragmentCache<I>> for StackState<BT> |
| where |
| I: IpLayerIpExt, |
| BT: BindingsTypes, |
| { |
| type Data = IpPacketFragmentCache<I, BT>; |
| type Guard<'l> = LockGuard<'l, IpPacketFragmentCache<I, BT>> where Self: 'l; |
| |
| fn lock(&self) -> Self::Guard<'_> { |
| self.inner_ip_state().fragment_cache.lock() |
| } |
| } |
| |
| impl<I, BT> LockFor<crate::lock_ordering::IpStatePmtuCache<I>> for StackState<BT> |
| where |
| I: IpLayerIpExt, |
| BT: BindingsTypes, |
| { |
| type Data = PmtuCache<I, BT>; |
| type Guard<'l> = LockGuard<'l, PmtuCache<I, BT>> where Self: 'l; |
| |
| fn lock(&self) -> Self::Guard<'_> { |
| self.inner_ip_state().pmtu_cache.lock() |
| } |
| } |
| |
| impl<I: IpLayerIpExt, BT: BindingsTypes> RwLockFor<crate::lock_ordering::IpStateRoutingTable<I>> |
| for StackState<BT> |
| { |
| type Data = ForwardingTable<I, DeviceId<BT>>; |
| type ReadGuard<'l> = RwLockReadGuard<'l, ForwardingTable<I, DeviceId<BT>>> |
| where Self: 'l; |
| type WriteGuard<'l> = RwLockWriteGuard<'l, ForwardingTable<I, DeviceId<BT>>> |
| where Self: 'l; |
| |
| fn read_lock(&self) -> Self::ReadGuard<'_> { |
| self.inner_ip_state().table.read() |
| } |
| |
| fn write_lock(&self) -> Self::WriteGuard<'_> { |
| self.inner_ip_state().table.write() |
| } |
| } |
| |
| impl<I, BT> RwLockFor<crate::lock_ordering::IcmpBoundMap<I>> for StackState<BT> |
| where |
| I: datagram::DualStackIpExt, |
| BT: BindingsTypes, |
| { |
| type Data = icmp::socket::BoundSockets<I, WeakDeviceId<BT>, BT>; |
| type ReadGuard<'l> = RwLockReadGuard<'l, Self::Data> where Self: 'l; |
| type WriteGuard<'l> = RwLockWriteGuard<'l, Self::Data> where Self: 'l; |
| |
| fn read_lock(&self) -> Self::ReadGuard<'_> { |
| self.inner_icmp_state().sockets.bound_and_id_allocator.read() |
| } |
| fn write_lock(&self) -> Self::WriteGuard<'_> { |
| self.inner_icmp_state().sockets.bound_and_id_allocator.write() |
| } |
| } |
| |
| impl<I: datagram::DualStackIpExt, D: crate::device::WeakId, BT: IcmpEchoBindingsTypes> |
| RwLockFor<crate::lock_ordering::IcmpSocketState<I>> for IcmpSocketId<I, D, BT> |
| { |
| type Data = IcmpSocketState<I, D, BT>; |
| |
| type ReadGuard<'l> = crate::sync::RwLockReadGuard<'l, Self::Data> |
| where |
| Self: 'l ; |
| type WriteGuard<'l> = crate::sync::RwLockWriteGuard<'l, Self::Data> |
| where |
| Self: 'l ; |
| |
| fn read_lock(&self) -> Self::ReadGuard<'_> { |
| self.state_for_locking().read() |
| } |
| |
| fn write_lock(&self) -> Self::WriteGuard<'_> { |
| self.state_for_locking().write() |
| } |
| } |
| |
| impl<I, BT> RwLockFor<crate::lock_ordering::IcmpAllSocketsSet<I>> for StackState<BT> |
| where |
| I: datagram::DualStackIpExt, |
| BT: BindingsTypes, |
| { |
| type Data = IcmpSocketSet<I, WeakDeviceId<BT>, BT>; |
| type ReadGuard<'l> = RwLockReadGuard<'l, Self::Data> where Self: 'l; |
| type WriteGuard<'l> = RwLockWriteGuard<'l, Self::Data> where Self: 'l; |
| |
| fn read_lock(&self) -> Self::ReadGuard<'_> { |
| self.inner_icmp_state().sockets.all_sockets.read() |
| } |
| fn write_lock(&self) -> Self::WriteGuard<'_> { |
| self.inner_icmp_state().sockets.all_sockets.write() |
| } |
| } |
| |
| impl<I, BT> LockFor<crate::lock_ordering::IcmpTokenBucket<I>> for StackState<BT> |
| where |
| I: datagram::DualStackIpExt, |
| BT: BindingsTypes, |
| { |
| type Data = TokenBucket<BT::Instant>; |
| type Guard<'l> = LockGuard<'l, TokenBucket<BT::Instant>> |
| where Self: 'l; |
| |
| fn lock(&self) -> Self::Guard<'_> { |
| self.inner_icmp_state::<I>().error_send_bucket.lock() |
| } |
| } |
| |
| impl<BC: BindingsContext> UnlockedAccess<crate::lock_ordering::Ipv4StateNextPacketId> |
| for StackState<BC> |
| { |
| type Data = AtomicU16; |
| type Guard<'l> = &'l AtomicU16 where Self: 'l; |
| |
| fn access(&self) -> Self::Guard<'_> { |
| &self.ipv4.next_packet_id |
| } |
| } |
| |
| /// Ip layer counters. |
| #[derive(Default, GenericOverIp)] |
| #[generic_over_ip(I, Ip)] |
| pub struct IpCounters<I: IpLayerIpExt> { |
| /// Count of incoming IP packets that are dispatched to the appropriate protocol. |
| pub dispatch_receive_ip_packet: Counter, |
| /// Count of incoming IP packets destined to another host. |
| pub dispatch_receive_ip_packet_other_host: Counter, |
| /// Count of incoming IP packets received by the stack. |
| pub receive_ip_packet: Counter, |
| /// Count of sent outgoing IP packets. |
| pub send_ip_packet: Counter, |
| /// Count of packets to be forwarded which are instead dropped because |
| /// forwarding is disabled. |
| pub forwarding_disabled: Counter, |
| /// Count of incoming packets forwarded to another host. |
| pub forward: Counter, |
| /// Count of incoming packets which cannot be forwarded because there is no |
| /// route to the destination host. |
| pub no_route_to_host: Counter, |
| /// Count of incoming packets which cannot be forwarded because the MTU has |
| /// been exceeded. |
| pub mtu_exceeded: Counter, |
| /// Count of incoming packets which cannot be forwarded because the TTL has |
| /// expired. |
| pub ttl_expired: Counter, |
| /// Count of ICMP error messages received. |
| pub receive_icmp_error: Counter, |
| /// Count of IP fragment reassembly errors. |
| pub fragment_reassembly_error: Counter, |
| /// Count of IP fragments that could not be reassembled because more |
| /// fragments were needed. |
| pub need_more_fragments: Counter, |
| /// Count of IP fragments that could not be reassembled because the fragment |
| /// was invalid. |
| pub invalid_fragment: Counter, |
| /// Count of IP fragments that could not be reassembled because the stack's |
| /// per-IP-protocol fragment cache was full. |
| pub fragment_cache_full: Counter, |
| /// Count of incoming IP packets not delivered because of a parameter problem. |
| pub parameter_problem: Counter, |
| /// Count of incoming IP packets with an unspecified destination address. |
| pub unspecified_destination: Counter, |
| /// Count of incoming IP packets with an unspecified source address. |
| pub unspecified_source: Counter, |
| /// Count of incoming IP packets dropped. |
| pub dropped: Counter, |
| /// Version specific rx counters. |
| pub version_rx: I::RxCounters, |
| } |
| |
| /// IPv4-specific Rx counters. |
| #[derive(Default)] |
| pub struct Ipv4RxCounters { |
| /// Count of incoming IPv4 packets delivered. |
| pub deliver: Counter, |
| } |
| |
| impl Inspectable for Ipv4RxCounters { |
| fn record<I: Inspector>(&self, inspector: &mut I) { |
| let Self { deliver } = self; |
| inspector.record_counter("Delivered", deliver); |
| } |
| } |
| |
| /// IPv6-specific Rx counters. |
| #[derive(Default)] |
| pub struct Ipv6RxCounters { |
| /// Count of incoming IPv6 multicast packets delivered. |
| pub deliver_multicast: Counter, |
| /// Count of incoming IPv6 unicast packets delivered. |
| pub deliver_unicast: Counter, |
| /// Count of incoming IPv6 packets dropped because the destination address |
| /// is only tentatively assigned to the device. |
| pub drop_for_tentative: Counter, |
| /// Count of incoming IPv6 packets dropped due to a non-unicast source address. |
| pub non_unicast_source: Counter, |
| /// Count of incoming IPv6 packets discarded while processing extension |
| /// headers. |
| pub extension_header_discard: Counter, |
| } |
| |
| impl Inspectable for Ipv6RxCounters { |
| fn record<I: Inspector>(&self, inspector: &mut I) { |
| let Self { |
| deliver_multicast, |
| deliver_unicast, |
| drop_for_tentative, |
| non_unicast_source, |
| extension_header_discard, |
| } = self; |
| inspector.record_counter("DeliveredMulticast", deliver_multicast); |
| inspector.record_counter("DeliveredUnicast", deliver_unicast); |
| inspector.record_counter("DroppedTentativeDst", drop_for_tentative); |
| inspector.record_counter("DroppedNonUnicastSrc", non_unicast_source); |
| inspector.record_counter("DroppedExtensionHeader", extension_header_discard); |
| } |
| } |
| |
| impl<BC: BindingsContext, I: IpLayerIpExt> UnlockedAccess<crate::lock_ordering::IpStateCounters<I>> |
| for StackState<BC> |
| { |
| type Data = IpCounters<I>; |
| type Guard<'l> = &'l IpCounters<I> where Self: 'l; |
| |
| fn access(&self) -> Self::Guard<'_> { |
| self.ip_counters() |
| } |
| } |
| |
| /// Marker trait for the bindings types required by the IP layer's inner state. |
| pub trait IpStateBindingsTypes: PmtuBindingsTypes + FragmentBindingsTypes {} |
| impl<BT> IpStateBindingsTypes for BT where BT: PmtuBindingsTypes + FragmentBindingsTypes {} |
| |
| #[derive(GenericOverIp)] |
| #[generic_over_ip(I, Ip)] |
| pub struct IpStateInner<I: IpLayerIpExt, DeviceId, BT: IpStateBindingsTypes> { |
| table: RwLock<ForwardingTable<I, DeviceId>>, |
| fragment_cache: Mutex<IpPacketFragmentCache<I, BT>>, |
| pmtu_cache: Mutex<PmtuCache<I, BT>>, |
| counters: IpCounters<I>, |
| } |
| |
| impl<I: IpLayerIpExt, DeviceId, BT: IpStateBindingsTypes> IpStateInner<I, DeviceId, BT> { |
| pub(crate) fn counters(&self) -> &IpCounters<I> { |
| &self.counters |
| } |
| } |
| |
| impl<I: IpLayerIpExt, DeviceId, BC: TimerContext + IpStateBindingsTypes> |
| IpStateInner<I, DeviceId, BC> |
| { |
| pub fn new<CC: CoreTimerContext<IpLayerTimerId, BC>>(bindings_ctx: &mut BC) -> Self { |
| Self { |
| table: Default::default(), |
| fragment_cache: Mutex::new( |
| IpPacketFragmentCache::new::<NestedIntoCoreTimerCtx<CC, _>>(bindings_ctx), |
| ), |
| pmtu_cache: Mutex::new(PmtuCache::new::<NestedIntoCoreTimerCtx<CC, _>>(bindings_ctx)), |
| counters: Default::default(), |
| } |
| } |
| } |
| |
| /// The identifier for timer events in the IP layer. |
| #[derive(Debug, Clone, Eq, PartialEq, Hash, GenericOverIp)] |
| #[generic_over_ip()] |
| pub enum IpLayerTimerId { |
| /// A timer event for IPv4 packet reassembly timers. |
| ReassemblyTimeoutv4(FragmentTimerId<Ipv4>), |
| /// A timer event for IPv6 packet reassembly timers. |
| ReassemblyTimeoutv6(FragmentTimerId<Ipv6>), |
| /// A timer event for IPv4 path MTU discovery. |
| PmtuTimeoutv4(PmtuTimerId<Ipv4>), |
| /// A timer event for IPv6 path MTU discovery. |
| PmtuTimeoutv6(PmtuTimerId<Ipv6>), |
| } |
| |
| impl<I: Ip> From<FragmentTimerId<I>> for IpLayerTimerId { |
| fn from(timer: FragmentTimerId<I>) -> IpLayerTimerId { |
| I::map_ip(timer, IpLayerTimerId::ReassemblyTimeoutv4, IpLayerTimerId::ReassemblyTimeoutv6) |
| } |
| } |
| |
| impl<I: Ip> From<PmtuTimerId<I>> for IpLayerTimerId { |
| fn from(timer: PmtuTimerId<I>) -> IpLayerTimerId { |
| I::map_ip(timer, IpLayerTimerId::PmtuTimeoutv4, IpLayerTimerId::PmtuTimeoutv6) |
| } |
| } |
| |
| impl<CC, BC> HandleableTimer<CC, BC> for IpLayerTimerId |
| where |
| CC: TimerHandler<BC, FragmentTimerId<Ipv4>> |
| + TimerHandler<BC, FragmentTimerId<Ipv6>> |
| + TimerHandler<BC, PmtuTimerId<Ipv4>> |
| + TimerHandler<BC, PmtuTimerId<Ipv6>>, |
| { |
| fn handle(self, core_ctx: &mut CC, bindings_ctx: &mut BC) { |
| match self { |
| IpLayerTimerId::ReassemblyTimeoutv4(id) => core_ctx.handle_timer(bindings_ctx, id), |
| IpLayerTimerId::ReassemblyTimeoutv6(id) => core_ctx.handle_timer(bindings_ctx, id), |
| IpLayerTimerId::PmtuTimeoutv4(id) => core_ctx.handle_timer(bindings_ctx, id), |
| IpLayerTimerId::PmtuTimeoutv6(id) => core_ctx.handle_timer(bindings_ctx, id), |
| } |
| } |
| } |
| |
| // TODO(joshlf): Once we support multiple extension headers in IPv6, we will |
| // need to verify that the callers of this function are still sound. In |
| // particular, they may accidentally pass a parse_metadata argument which |
| // corresponds to a single extension header rather than all of the IPv6 headers. |
| |
| /// Dispatch a received IPv4 packet to the appropriate protocol. |
| /// |
| /// `device` is the device the packet was received on. `parse_metadata` is the |
| /// parse metadata associated with parsing the IP headers. It is used to undo |
| /// that parsing. Both `device` and `parse_metadata` are required in order to |
| /// send ICMP messages in response to unrecognized protocols or ports. If either |
| /// of `device` or `parse_metadata` is `None`, the caller promises that the |
| /// protocol and port are recognized. |
| /// |
| /// # Panics |
| /// |
| /// `dispatch_receive_ipv4_packet` panics if the protocol is unrecognized and |
| /// `parse_metadata` is `None`. If an IGMP message is received but it is not |
| /// coming from a device, i.e., `device` given is `None`, |
| /// `dispatch_receive_ip_packet` will also panic. |
| fn dispatch_receive_ipv4_packet< |
| BC: IpLayerBindingsContext<Ipv4, CC::DeviceId>, |
| B: BufferMut, |
| CC: IpLayerIngressContext<Ipv4, BC> + CounterContext<IpCounters<Ipv4>>, |
| >( |
| core_ctx: &mut CC, |
| bindings_ctx: &mut BC, |
| device: &CC::DeviceId, |
| frame_dst: Option<FrameDestination>, |
| src_ip: Ipv4Addr, |
| dst_ip: SpecifiedAddr<Ipv4Addr>, |
| proto: Ipv4Proto, |
| body: B, |
| parse_metadata: Option<ParseMetadata>, |
| mut packet_metadata: IpLayerPacketMetadata<Ipv4, BC>, |
| transport_override: Option<TransparentLocalDelivery<Ipv4>>, |
| ) { |
| core_ctx.increment(|counters| &counters.dispatch_receive_ip_packet); |
| |
| match frame_dst { |
| Some(FrameDestination::Individual { local: false }) => { |
| core_ctx.increment(|counters| &counters.dispatch_receive_ip_packet_other_host); |
| } |
| Some(FrameDestination::Individual { local: true }) |
| | Some(FrameDestination::Multicast) |
| | Some(FrameDestination::Broadcast) |
| | None => (), |
| } |
| |
| match core_ctx.filter_handler().local_ingress_hook( |
| &mut crate::filter::RxPacket::new(src_ip, dst_ip.get(), proto, &body), |
| device, |
| &mut packet_metadata, |
| ) { |
| crate::filter::Verdict::Drop => { |
| packet_metadata.acknowledge_drop(); |
| return; |
| } |
| crate::filter::Verdict::Accept => {} |
| } |
| packet_metadata.acknowledge_drop(); |
| |
| let (mut body, err) = match core_ctx.dispatch_receive_ip_packet( |
| bindings_ctx, |
| device, |
| src_ip, |
| dst_ip, |
| proto, |
| body, |
| transport_override, |
| ) { |
| Ok(()) => return, |
| Err(e) => e, |
| }; |
| // All branches promise to return the buffer in the same state it was in |
| // when they were executed. Thus, all we have to do is undo the parsing |
| // of the IP packet header, and the buffer will be back to containing |
| // the entire original IP packet. |
| let meta = parse_metadata.unwrap(); |
| body.undo_parse(meta); |
| |
| if let Some(src_ip) = SpecifiedAddr::new(src_ip) { |
| match err.inner { |
| TransportReceiveErrorInner::ProtocolUnsupported => { |
| core_ctx.send_icmp_error_message( |
| bindings_ctx, |
| device, |
| frame_dst, |
| src_ip, |
| dst_ip, |
| body, |
| Icmpv4Error { |
| kind: Icmpv4ErrorKind::ProtocolUnreachable, |
| header_len: meta.header_len(), |
| }, |
| ); |
| } |
| TransportReceiveErrorInner::PortUnreachable => { |
| // TODO(joshlf): What if we're called from a loopback |
| // handler, and device and parse_metadata are None? In other |
| // words, what happens if we attempt to send to a loopback |
| // port which is unreachable? We will eventually need to |
| // restructure the control flow here to handle that case. |
| core_ctx.send_icmp_error_message( |
| bindings_ctx, |
| device, |
| frame_dst, |
| src_ip, |
| dst_ip, |
| body, |
| Icmpv4Error { |
| kind: Icmpv4ErrorKind::PortUnreachable, |
| header_len: meta.header_len(), |
| }, |
| ); |
| } |
| } |
| } else { |
| trace!("dispatch_receive_ipv4_packet: Cannot send ICMP error message in response to a packet from the unspecified address"); |
| } |
| } |
| |
| /// Dispatch a received IPv6 packet to the appropriate protocol. |
| /// |
| /// `dispatch_receive_ipv6_packet` has the same semantics as |
| /// `dispatch_receive_ipv4_packet`, but for IPv6. |
| fn dispatch_receive_ipv6_packet< |
| BC: IpLayerBindingsContext<Ipv6, CC::DeviceId>, |
| B: BufferMut, |
| CC: IpLayerIngressContext<Ipv6, BC> + CounterContext<IpCounters<Ipv6>>, |
| >( |
| core_ctx: &mut CC, |
| bindings_ctx: &mut BC, |
| device: &CC::DeviceId, |
| frame_dst: Option<FrameDestination>, |
| src_ip: Ipv6SourceAddr, |
| dst_ip: SpecifiedAddr<Ipv6Addr>, |
| proto: Ipv6Proto, |
| body: B, |
| parse_metadata: Option<ParseMetadata>, |
| mut packet_metadata: IpLayerPacketMetadata<Ipv6, BC>, |
| transport_override: Option<TransparentLocalDelivery<Ipv6>>, |
| ) { |
| // TODO(https://fxbug.dev/42095067): Once we support multiple extension |
| // headers in IPv6, we will need to verify that the callers of this |
| // function are still sound. In particular, they may accidentally pass a |
| // parse_metadata argument which corresponds to a single extension |
| // header rather than all of the IPv6 headers. |
| |
| core_ctx.increment(|counters| &counters.dispatch_receive_ip_packet); |
| |
| match frame_dst { |
| Some(FrameDestination::Individual { local: false }) => { |
| core_ctx.increment(|counters| &counters.dispatch_receive_ip_packet_other_host); |
| } |
| Some(FrameDestination::Individual { local: true }) |
| | Some(FrameDestination::Multicast) |
| | Some(FrameDestination::Broadcast) |
| | None => (), |
| } |
| |
| match core_ctx.filter_handler().local_ingress_hook( |
| &mut crate::filter::RxPacket::new(src_ip.get(), dst_ip.get(), proto, &body), |
| device, |
| &mut packet_metadata, |
| ) { |
| crate::filter::Verdict::Drop => { |
| packet_metadata.acknowledge_drop(); |
| return; |
| } |
| crate::filter::Verdict::Accept => {} |
| } |
| |
| let (mut body, err) = match core_ctx.dispatch_receive_ip_packet( |
| bindings_ctx, |
| device, |
| src_ip, |
| dst_ip, |
| proto, |
| body, |
| transport_override, |
| ) { |
| Ok(()) => { |
| packet_metadata.acknowledge_drop(); |
| return; |
| } |
| Err(e) => e, |
| }; |
| |
| // All branches promise to return the buffer in the same state it was in |
| // when they were executed. Thus, all we have to do is undo the parsing |
| // of the IP packet header, and the buffer will be back to containing |
| // the entire original IP packet. |
| let meta = parse_metadata.unwrap(); |
| body.undo_parse(meta); |
| |
| match err.inner { |
| TransportReceiveErrorInner::ProtocolUnsupported => { |
| if let Ipv6SourceAddr::Unicast(src_ip) = src_ip { |
| core_ctx.send_icmp_error_message( |
| bindings_ctx, |
| device, |
| frame_dst, |
| *src_ip, |
| dst_ip, |
| body, |
| Icmpv6ErrorKind::ProtocolUnreachable { header_len: meta.header_len() }, |
| ); |
| } |
| } |
| TransportReceiveErrorInner::PortUnreachable => { |
| // TODO(joshlf): What if we're called from a loopback handler, |
| // and device and parse_metadata are None? In other words, what |
| // happens if we attempt to send to a loopback port which is |
| // unreachable? We will eventually need to restructure the |
| // control flow here to handle that case. |
| if let Ipv6SourceAddr::Unicast(src_ip) = src_ip { |
| core_ctx.send_icmp_error_message( |
| bindings_ctx, |
| device, |
| frame_dst, |
| *src_ip, |
| dst_ip, |
| body, |
| Icmpv6ErrorKind::PortUnreachable, |
| ); |
| } |
| } |
| } |
| |
| packet_metadata.acknowledge_drop(); |
| } |
| |
| pub(crate) fn send_ip_frame<I, CC, BC, S>( |
| core_ctx: &mut CC, |
| bindings_ctx: &mut BC, |
| device: &CC::DeviceId, |
| next_hop: SpecifiedAddr<I::Addr>, |
| mut body: S, |
| broadcast: Option<I::BroadcastMarker>, |
| mut packet_metadata: IpLayerPacketMetadata<I, BC>, |
| ) -> Result<(), S> |
| where |
| I: IpLayerIpExt, |
| BC: FilterBindingsTypes, |
| CC: IpLayerEgressContext<I, BC>, |
| S: Serializer + IpPacket<I>, |
| S::Buffer: BufferMut, |
| { |
| let (verdict, proof) = |
| core_ctx.filter_handler().egress_hook(&mut body, device, &mut packet_metadata); |
| match verdict { |
| crate::filter::Verdict::Drop => { |
| packet_metadata.acknowledge_drop(); |
| return Ok(()); |
| } |
| crate::filter::Verdict::Accept => {} |
| } |
| |
| packet_metadata.acknowledge_drop(); |
| core_ctx.send_ip_frame(bindings_ctx, device, next_hop, body, broadcast, proof) |
| } |
| |
| /// Drop a packet and undo the effects of parsing it. |
| /// |
| /// `drop_packet_and_undo_parse!` takes a `$packet` and a `$buffer` which the |
| /// packet was parsed from. It saves the results of the `src_ip()`, `dst_ip()`, |
| /// `proto()`, and `parse_metadata()` methods. It drops `$packet` and uses the |
| /// result of `parse_metadata()` to undo the effects of parsing the packet. |
| /// Finally, it returns the source IP, destination IP, protocol, and parse |
| /// metadata. |
| macro_rules! drop_packet_and_undo_parse { |
| ($packet:expr, $buffer:expr) => {{ |
| let (src_ip, dst_ip, proto, meta) = $packet.into_metadata(); |
| $buffer.undo_parse(meta); |
| (src_ip, dst_ip, proto, meta) |
| }}; |
| } |
| |
| /// Process a fragment and reassemble if required. |
| /// |
| /// Attempts to process a potential fragment packet and reassemble if we are |
| /// ready to do so. If the packet isn't fragmented, or a packet was reassembled, |
| /// attempt to dispatch the packet. |
| macro_rules! process_fragment { |
| ($core_ctx:expr, $bindings_ctx:expr, $dispatch:ident, $device:ident, $frame_dst:expr, $buffer:expr, $packet:expr, $src_ip:expr, $dst_ip:expr, $ip:ident, $packet_metadata:expr) => {{ |
| match FragmentHandler::<$ip, _>::process_fragment::<&mut [u8]>( |
| $core_ctx, |
| $bindings_ctx, |
| $packet, |
| ) { |
| // Handle the packet right away since reassembly is not needed. |
| FragmentProcessingState::NotNeeded(packet) => { |
| trace!("receive_ip_packet: not fragmented"); |
| // TODO(joshlf): |
| // - Check for already-expired TTL? |
| let (_, _, proto, meta) = packet.into_metadata(); |
| $dispatch( |
| $core_ctx, |
| $bindings_ctx, |
| $device, |
| $frame_dst, |
| $src_ip, |
| $dst_ip, |
| proto, |
| $buffer, |
| Some(meta), |
| $packet_metadata, |
| None, |
| ); |
| } |
| // Ready to reassemble a packet. |
| FragmentProcessingState::Ready { key, packet_len } => { |
| trace!("receive_ip_packet: fragmented, ready for reassembly"); |
| // Allocate a buffer of `packet_len` bytes. |
| let mut buffer = Buf::new(alloc::vec![0; packet_len], ..); |
| // The packet metadata associated with this last fragment will |
| // be dropped and a new metadata struct created for the |
| // reassembled packet. |
| $packet_metadata.acknowledge_drop(); |
| |
| // Attempt to reassemble the packet. |
| match FragmentHandler::<$ip, _>::reassemble_packet( |
| $core_ctx, |
| $bindings_ctx, |
| &key, |
| buffer.buffer_view_mut(), |
| ) { |
| // Successfully reassembled the packet, handle it. |
| Ok(packet) => { |
| trace!("receive_ip_packet: fragmented, reassembled packet: {:?}", packet); |
| // TODO(joshlf): |
| // - Check for already-expired TTL? |
| let (_, _, proto, meta) = packet.into_metadata(); |
| // Since each fragment had its own packet metadata, it's |
| // not clear what metadata to use for the reassembled |
| // packet. Resetting the metadata is the safest bet, |
| // though it means downstream consumers must be aware of |
| // this case. |
| let packet_metadata = IpLayerPacketMetadata::default(); |
| $dispatch::<_, Buf<Vec<u8>>, _,>( |
| $core_ctx, |
| $bindings_ctx, |
| $device, |
| $frame_dst, |
| $src_ip, |
| $dst_ip, |
| proto, |
| buffer, |
| Some(meta), |
| packet_metadata, |
| None, |
| ); |
| } |
| // TODO(ghanan): Handle reassembly errors, remove |
| // `allow(unreachable_patterns)` when complete. |
| _ => { |
| $packet_metadata.acknowledge_drop(); |
| return; |
| }, |
| #[allow(unreachable_patterns)] |
| Err(e) => { |
| $core_ctx.increment(|counters: &IpCounters<$ip>| { |
| &counters.fragment_reassembly_error |
| }); |
| $packet_metadata.acknowledge_drop(); |
| trace!("receive_ip_packet: fragmented, failed to reassemble: {:?}", e); |
| } |
| } |
| } |
| // Cannot proceed since we need more fragments before we |
| // can reassemble a packet. |
| FragmentProcessingState::NeedMoreFragments => { |
| $packet_metadata.acknowledge_drop(); |
| $core_ctx.increment(|counters: &IpCounters<$ip>| { |
| &counters.need_more_fragments |
| }); |
| trace!("receive_ip_packet: fragmented, need more before reassembly") |
| } |
| // TODO(ghanan): Handle invalid fragments. |
| FragmentProcessingState::InvalidFragment => { |
| $packet_metadata.acknowledge_drop(); |
| $core_ctx.increment(|counters: &IpCounters<$ip>| { |
| &counters.invalid_fragment |
| }); |
| trace!("receive_ip_packet: fragmented, invalid") |
| } |
| FragmentProcessingState::OutOfMemory => { |
| $packet_metadata.acknowledge_drop(); |
| $core_ctx.increment(|counters: &IpCounters<$ip>| { |
| &counters.fragment_cache_full |
| }); |
| trace!("receive_ip_packet: fragmented, dropped because OOM") |
| } |
| }; |
| }}; |
| } |
| |
| // TODO(joshlf): Can we turn `try_parse_ip_packet` into a function? So far, I've |
| // been unable to get the borrow checker to accept it. |
| |
| /// Try to parse an IP packet from a buffer. |
| /// |
| /// If parsing fails, return the buffer to its original state so that its |
| /// contents can be used to send an ICMP error message. When invoked, the macro |
| /// expands to an expression whose type is `Result<P, P::Error>`, where `P` is |
| /// the parsed packet type. |
| macro_rules! try_parse_ip_packet { |
| ($buffer:expr) => {{ |
| let p_len = $buffer.prefix_len(); |
| let s_len = $buffer.suffix_len(); |
| |
| let result = $buffer.parse_mut(); |
| |
| if let Err(err) = result { |
| // Revert `buffer` to it's original state. |
| let n_p_len = $buffer.prefix_len(); |
| let n_s_len = $buffer.suffix_len(); |
| |
| if p_len > n_p_len { |
| $buffer.grow_front(p_len - n_p_len); |
| } |
| |
| if s_len > n_s_len { |
| $buffer.grow_back(s_len - n_s_len); |
| } |
| |
| Err(err) |
| } else { |
| result |
| } |
| }}; |
| } |
| |
| /// Receive an IPv4 packet from a device. |
| /// |
| /// `frame_dst` specifies how this packet was received; see [`FrameDestination`] |
| /// for options. |
| pub(crate) fn receive_ipv4_packet< |
| BC: IpLayerBindingsContext<Ipv4, CC::DeviceId>, |
| B: BufferMut, |
| CC: IpLayerIngressContext<Ipv4, BC> + CounterContext<IpCounters<Ipv4>>, |
| >( |
| core_ctx: &mut CC, |
| bindings_ctx: &mut BC, |
| device: &CC::DeviceId, |
| frame_dst: Option<FrameDestination>, |
| mut buffer: B, |
| ) { |
| if !core_ctx.is_ip_device_enabled(&device) { |
| return; |
| } |
| |
| core_ctx.increment(|counters: &IpCounters<Ipv4>| &counters.receive_ip_packet); |
| trace!("receive_ip_packet({device:?})"); |
| |
| let mut packet: Ipv4Packet<_> = match try_parse_ip_packet!(buffer) { |
| Ok(packet) => packet, |
| // Conditionally send an ICMP response if we encountered a parameter |
| // problem error when parsing an IPv4 packet. Note, we do not always |
| // send back an ICMP response as it can be used as an attack vector for |
| // DDoS attacks. We only send back an ICMP response if the RFC requires |
| // that we MUST send one, as noted by `must_send_icmp` and `action`. |
| // TODO(https://fxbug.dev/42157630): test this code path once |
| // `Ipv4Packet::parse` can return an `IpParseError::ParameterProblem` |
| // error. |
| Err(IpParseError::ParameterProblem { |
| src_ip, |
| dst_ip, |
| code, |
| pointer, |
| must_send_icmp, |
| header_len, |
| action, |
| }) if must_send_icmp && action.should_send_icmp(&dst_ip) => { |
| core_ctx.increment(|counters: &IpCounters<Ipv4>| &counters.parameter_problem); |
| // `should_send_icmp_to_multicast` should never return `true` for IPv4. |
| assert!(!action.should_send_icmp_to_multicast()); |
| let dst_ip = match SpecifiedAddr::new(dst_ip) { |
| Some(ip) => ip, |
| None => { |
| core_ctx |
| .increment(|counters: &IpCounters<Ipv4>| &counters.unspecified_destination); |
| debug!("receive_ipv4_packet: Received packet with unspecified destination IP address; dropping"); |
| return; |
| } |
| }; |
| let src_ip = match SpecifiedAddr::new(src_ip) { |
| Some(ip) => ip, |
| None => { |
| core_ctx.increment(|counters: &IpCounters<Ipv4>| &counters.unspecified_source); |
| trace!("receive_ipv4_packet: Cannot send ICMP error in response to packet with unspecified source IP address"); |
| return; |
| } |
| }; |
| IcmpErrorHandler::<Ipv4, _>::send_icmp_error_message( |
| core_ctx, |
| bindings_ctx, |
| device, |
| frame_dst, |
| src_ip, |
| dst_ip, |
| buffer, |
| Icmpv4Error { |
| kind: Icmpv4ErrorKind::ParameterProblem { |
| code, |
| pointer, |
| // When the call to `action.should_send_icmp` returns true, it always means that |
| // the IPv4 packet that failed parsing is an initial fragment. |
| fragment_type: Ipv4FragmentType::InitialFragment, |
| }, |
| header_len, |
| }, |
| ); |
| return; |
| } |
| _ => return, // TODO(joshlf): Do something with ICMP here? |
| }; |
| |
| let dst_ip = match SpecifiedAddr::new(packet.dst_ip()) { |
| Some(ip) => ip, |
| None => { |
| core_ctx.increment(|counters: &IpCounters<Ipv4>| &counters.unspecified_destination); |
| debug!("receive_ipv4_packet: Received packet with unspecified destination IP address; dropping"); |
| return; |
| } |
| }; |
| |
| // TODO(ghanan): Act upon options. |
| |
| let mut packet_metadata = IpLayerPacketMetadata::default(); |
| let mut filter = core_ctx.filter_handler(); |
| match filter.ingress_hook(&mut packet, device, &mut packet_metadata) { |
| IngressVerdict::Verdict(crate::filter::Verdict::Accept) => {} |
| IngressVerdict::Verdict(crate::filter::Verdict::Drop) => { |
| packet_metadata.acknowledge_drop(); |
| return; |
| } |
| IngressVerdict::TransparentLocalDelivery { addr, port } => { |
| // Drop the filter handler since it holds a mutable borrow of `core_ctx`, which |
| // we need to provide to the packet dispatch function. |
| drop(filter); |
| |
| let Some(addr) = SpecifiedAddr::new(addr) else { |
| core_ctx.increment(|counters: &IpCounters<Ipv4>| &counters.unspecified_destination); |
| debug!("receive_ipv4_packet: Received packet with unspecified destination IP address; dropping"); |
| return; |
| }; |
| |
| // Short-circuit the routing process and override local demux, providing a local |
| // address and port to which the packet should be transparently delivered at the |
| // transport layer. |
| let src_ip = packet.src_ip(); |
| let (_, _, proto, meta) = packet.into_metadata(); |
| dispatch_receive_ipv4_packet( |
| core_ctx, |
| bindings_ctx, |
| device, |
| frame_dst, |
| src_ip, |
| dst_ip, |
| proto, |
| buffer, |
| Some(meta), |
| packet_metadata, |
| Some(TransparentLocalDelivery { addr, port }), |
| ); |
| return; |
| } |
| } |
| // Drop the filter handler since it holds a mutable borrow of `core_ctx`, which |
| // we need below. |
| drop(filter); |
| |
| match receive_ipv4_packet_action(core_ctx, bindings_ctx, device, dst_ip) { |
| ReceivePacketAction::Deliver => { |
| trace!("receive_ipv4_packet: delivering locally"); |
| let src_ip = packet.src_ip(); |
| |
| // Process a potential IPv4 fragment if the destination is this |
| // host. |
| // |
| // We process IPv4 packet reassembly here because, for IPv4, the |
| // fragment data is in the header itself so we can handle it right |
| // away. |
| // |
| // Note, the `process_fragment` function (which is called by the |
| // `process_fragment!` macro) could panic if the packet does not |
| // have fragment data. However, we are guaranteed that it will not |
| // panic because the fragment data is in the fixed header so it is |
| // always present (even if the fragment data has values that implies |
| // that the packet is not fragmented). |
| process_fragment!( |
| core_ctx, |
| bindings_ctx, |
| dispatch_receive_ipv4_packet, |
| device, |
| frame_dst, |
| buffer, |
| packet, |
| src_ip, |
| dst_ip, |
| Ipv4, |
| packet_metadata |
| ); |
| } |
| ReceivePacketAction::Forward { dst: Destination { device: dst_device, next_hop } } => { |
| let (next_hop, broadcast) = match next_hop { |
| NextHop::RemoteAsNeighbor => (dst_ip, None), |
| NextHop::Gateway(gateway) => (gateway, None), |
| NextHop::Broadcast(marker) => (dst_ip, Some(marker)), |
| }; |
| let ttl = packet.ttl(); |
| if ttl > 1 { |
| trace!("receive_ipv4_packet: forwarding"); |
| |
| match core_ctx.filter_handler().forwarding_hook( |
| &mut packet, |
| device, |
| &dst_device, |
| &mut packet_metadata, |
| ) { |
| crate::filter::Verdict::Drop => { |
| packet_metadata.acknowledge_drop(); |
| return; |
| } |
| crate::filter::Verdict::Accept => {} |
| } |
| |
| packet.set_ttl(ttl - 1); |
| let (src, dst, proto, meta) = packet.into_metadata(); |
| let packet = ForwardedPacket::new(src, dst, proto, meta, buffer); |
| |
| match send_ip_frame( |
| core_ctx, |
| bindings_ctx, |
| &dst_device, |
| next_hop, |
| packet, |
| broadcast, |
| packet_metadata, |
| ) { |
| Ok(()) => (), |
| Err(p) => { |
| let _: ForwardedPacket<_, B> = p; |
| core_ctx.increment(|counters: &IpCounters<Ipv4>| &counters.mtu_exceeded); |
| // TODO(https://fxbug.dev/42167236): Encode the MTU error |
| // more obviously in the type system. |
| debug!("failed to forward IPv4 packet: MTU exceeded"); |
| } |
| } |
| } else { |
| // TTL is 0 or would become 0 after decrement; see "TTL" |
| // section, https://tools.ietf.org/html/rfc791#page-14 |
| use packet_formats::ipv4::Ipv4Header as _; |
| core_ctx.increment(|counters: &IpCounters<Ipv4>| &counters.ttl_expired); |
| debug!("received IPv4 packet dropped due to expired TTL"); |
| let fragment_type = packet.fragment_type(); |
| let (src_ip, _, proto, meta): (_, Ipv4Addr, _, _) = |
| drop_packet_and_undo_parse!(packet, buffer); |
| packet_metadata.acknowledge_drop(); |
| let src_ip = match SpecifiedAddr::new(src_ip) { |
| Some(ip) => ip, |
| None => { |
| core_ctx |
| .increment(|counters: &IpCounters<Ipv4>| &counters.unspecified_source); |
| trace!("receive_ipv4_packet: Cannot send ICMP error in response to packet with unspecified source IP address"); |
| return; |
| } |
| }; |
| IcmpErrorHandler::<Ipv4, _>::send_icmp_error_message( |
| core_ctx, |
| bindings_ctx, |
| device, |
| frame_dst, |
| src_ip, |
| dst_ip, |
| buffer, |
| Icmpv4Error { |
| kind: Icmpv4ErrorKind::TtlExpired { proto, fragment_type }, |
| header_len: meta.header_len(), |
| }, |
| ); |
| } |
| } |
| ReceivePacketAction::SendNoRouteToDest => { |
| use packet_formats::ipv4::Ipv4Header as _; |
| core_ctx.increment(|counters: &IpCounters<Ipv4>| &counters.no_route_to_host); |
| debug!("received IPv4 packet with no known route to destination {}", dst_ip); |
| let fragment_type = packet.fragment_type(); |
| let (src_ip, _, proto, meta): (_, Ipv4Addr, _, _) = |
| drop_packet_and_undo_parse!(packet, buffer); |
| packet_metadata.acknowledge_drop(); |
| let src_ip = match SpecifiedAddr::new(src_ip) { |
| Some(ip) => ip, |
| None => { |
| core_ctx.increment(|counters: &IpCounters<Ipv4>| &counters.unspecified_source); |
| trace!("receive_ipv4_packet: Cannot send ICMP error in response to packet with unspecified source IP address"); |
| return; |
| } |
| }; |
| IcmpErrorHandler::<Ipv4, _>::send_icmp_error_message( |
| core_ctx, |
| bindings_ctx, |
| device, |
| frame_dst, |
| src_ip, |
| dst_ip, |
| buffer, |
| Icmpv4Error { |
| kind: Icmpv4ErrorKind::NetUnreachable { proto, fragment_type }, |
| header_len: meta.header_len(), |
| }, |
| ); |
| } |
| ReceivePacketAction::Drop { reason } => { |
| let src_ip = packet.src_ip(); |
| packet_metadata.acknowledge_drop(); |
| core_ctx.increment(|counters: &IpCounters<Ipv4>| &counters.dropped); |
| debug!( |
| "receive_ipv4_packet: dropping packet from {src_ip} to {dst_ip} received on \ |
| {device:?}: {reason:?}", |
| ); |
| } |
| } |
| } |
| |
| /// Receive an IPv6 packet from a device. |
| /// |
| /// `frame_dst` specifies how this packet was received; see [`FrameDestination`] |
| /// for options. |
| pub(crate) fn receive_ipv6_packet< |
| BC: IpLayerBindingsContext<Ipv6, CC::DeviceId>, |
| B: BufferMut, |
| CC: IpLayerIngressContext<Ipv6, BC> + CounterContext<IpCounters<Ipv6>>, |
| >( |
| core_ctx: &mut CC, |
| bindings_ctx: &mut BC, |
| device: &CC::DeviceId, |
| frame_dst: Option<FrameDestination>, |
| mut buffer: B, |
| ) { |
| if !core_ctx.is_ip_device_enabled(&device) { |
| return; |
| } |
| |
| core_ctx.increment(|counters: &IpCounters<Ipv6>| &counters.receive_ip_packet); |
| trace!("receive_ipv6_packet({:?})", device); |
| |
| let mut packet: Ipv6Packet<_> = match try_parse_ip_packet!(buffer) { |
| Ok(packet) => packet, |
| // Conditionally send an ICMP response if we encountered a parameter |
| // problem error when parsing an IPv4 packet. Note, we do not always |
| // send back an ICMP response as it can be used as an attack vector for |
| // DDoS attacks. We only send back an ICMP response if the RFC requires |
| // that we MUST send one, as noted by `must_send_icmp` and `action`. |
| Err(IpParseError::ParameterProblem { |
| src_ip, |
| dst_ip, |
| code, |
| pointer, |
| must_send_icmp, |
| header_len: _, |
| action, |
| }) if must_send_icmp && action.should_send_icmp(&dst_ip) => { |
| core_ctx.increment(|counters| &counters.parameter_problem); |
| let dst_ip = match SpecifiedAddr::new(dst_ip) { |
| Some(ip) => ip, |
| None => { |
| core_ctx.increment(|counters| &counters.unspecified_destination); |
| debug!("receive_ipv6_packet: Received packet with unspecified destination IP address; dropping"); |
| return; |
| } |
| }; |
| let src_ip = match UnicastAddr::new(src_ip) { |
| Some(ip) => ip, |
| None => { |
| core_ctx.increment(|counters| &counters.version_rx.non_unicast_source); |
| trace!("receive_ipv6_packet: Cannot send ICMP error in response to packet with non unicast source IP address"); |
| return; |
| } |
| }; |
| IcmpErrorHandler::<Ipv6, _>::send_icmp_error_message( |
| core_ctx, |
| bindings_ctx, |
| device, |
| frame_dst, |
| src_ip, |
| dst_ip, |
| buffer, |
| Icmpv6ErrorKind::ParameterProblem { |
| code, |
| pointer, |
| allow_dst_multicast: action.should_send_icmp_to_multicast(), |
| }, |
| ); |
| return; |
| } |
| _ => return, // TODO(joshlf): Do something with ICMP here? |
| }; |
| |
| trace!("receive_ipv6_packet: parsed packet: {:?}", packet); |
| |
| // TODO(ghanan): Act upon extension headers. |
| |
| let src_ip = match packet.src_ipv6() { |
| Some(ip) => ip, |
| None => { |
| debug!( |
| "receive_ipv6_packet: received packet from non-unicast source {}; dropping", |
| packet.src_ip() |
| ); |
| core_ctx |
| .increment(|counters: &IpCounters<Ipv6>| &counters.version_rx.non_unicast_source); |
| return; |
| } |
| }; |
| let dst_ip = match SpecifiedAddr::new(packet.dst_ip()) { |
| Some(ip) => ip, |
| None => { |
| core_ctx.increment(|counters: &IpCounters<Ipv6>| &counters.unspecified_destination); |
| debug!("receive_ipv6_packet: Received packet with unspecified destination IP address; dropping"); |
| return; |
| } |
| }; |
| |
| let mut packet_metadata = IpLayerPacketMetadata::default(); |
| let mut filter = core_ctx.filter_handler(); |
| match filter.ingress_hook(&mut packet, device, &mut packet_metadata) { |
| IngressVerdict::Verdict(crate::filter::Verdict::Accept) => {} |
| IngressVerdict::Verdict(crate::filter::Verdict::Drop) => { |
| packet_metadata.acknowledge_drop(); |
| return; |
| } |
| IngressVerdict::TransparentLocalDelivery { addr, port } => { |
| // Drop the filter handler since it holds a mutable borrow of `core_ctx`, which |
| // we need to provide to the packet dispatch function. |
| drop(filter); |
| |
| let Some(addr) = SpecifiedAddr::new(addr) else { |
| core_ctx.increment(|counters: &IpCounters<Ipv6>| &counters.unspecified_destination); |
| debug!("receive_ipv6_packet: Received packet with unspecified destination IP address; dropping"); |
| return; |
| }; |
| |
| // Short-circuit the routing process and override local demux, providing a local |
| // address and port to which the packet should be transparently delivered at the |
| // transport layer. |
| let (_, _, proto, meta) = packet.into_metadata(); |
| dispatch_receive_ipv6_packet( |
| core_ctx, |
| bindings_ctx, |
| device, |
| frame_dst, |
| src_ip, |
| dst_ip, |
| proto, |
| buffer, |
| Some(meta), |
| packet_metadata, |
| Some(TransparentLocalDelivery { addr, port }), |
| ); |
| return; |
| } |
| } |
| // Drop the filter handler since it holds a mutable borrow of `core_ctx`, which |
| // we need below. |
| drop(filter); |
| |
| match receive_ipv6_packet_action(core_ctx, bindings_ctx, device, dst_ip) { |
| ReceivePacketAction::Deliver => { |
| trace!("receive_ipv6_packet: delivering locally"); |
| |
| // Process a potential IPv6 fragment if the destination is this |
| // host. |
| // |
| // We need to process extension headers in the order they appear in |
| // the header. With some extension headers, we do not proceed to the |
| // next header, and do some action immediately. For example, say we |
| // have an IPv6 packet with two extension headers (routing extension |
| // header before a fragment extension header). Until we get to the |
| // final destination node in the routing header, we would need to |
| // reroute the packet to the next destination without reassembling. |
| // Once the packet gets to the last destination in the routing |
| // header, that node will process the fragment extension header and |
| // handle reassembly. |
| match ipv6::handle_extension_headers(core_ctx, device, frame_dst, &packet, true) { |
| Ipv6PacketAction::_Discard => { |
| core_ctx.increment(|counters: &IpCounters<Ipv6>| { |
| &counters.version_rx.extension_header_discard |
| }); |
| trace!( |
| "receive_ipv6_packet: handled IPv6 extension headers: discarding packet" |
| ); |
| packet_metadata.acknowledge_drop(); |
| } |
| Ipv6PacketAction::Continue => { |
| trace!( |
| "receive_ipv6_packet: handled IPv6 extension headers: dispatching packet" |
| ); |
| |
| // TODO(joshlf): |
| // - Do something with ICMP if we don't have a handler for |
| // that protocol? |
| // - Check for already-expired TTL? |
| let (_, _, proto, meta): (Ipv6Addr, Ipv6Addr, _, _) = packet.into_metadata(); |
| dispatch_receive_ipv6_packet( |
| core_ctx, |
| bindings_ctx, |
| device, |
| frame_dst, |
| src_ip, |
| dst_ip, |
| proto, |
| buffer, |
| Some(meta), |
| packet_metadata, |
| None, |
| ); |
| } |
| Ipv6PacketAction::ProcessFragment => { |
| trace!( |
| "receive_ipv6_packet: handled IPv6 extension headers: handling fragmented packet" |
| ); |
| |
| // Note, the `IpPacketFragmentCache::process_fragment` |
| // method (which is called by the `process_fragment!` macro) |
| // could panic if the packet does not have fragment data. |
| // However, we are guaranteed that it will not panic for an |
| // IPv6 packet because the fragment data is in an (optional) |
| // fragment extension header which we attempt to handle by |
| // calling `ipv6::handle_extension_headers`. We will only |
| // end up here if its return value is |
| // `Ipv6PacketAction::ProcessFragment` which is only |
| // possible when the packet has the fragment extension |
| // header (even if the fragment data has values that implies |
| // that the packet is not fragmented). |
| // |
| // TODO(ghanan): Handle extension headers again since there |
| // could be some more in a reassembled packet |
| // (after the fragment header). |
| process_fragment!( |
| core_ctx, |
| bindings_ctx, |
| dispatch_receive_ipv6_packet, |
| device, |
| frame_dst, |
| buffer, |
| packet, |
| src_ip, |
| dst_ip, |
| Ipv6, |
| packet_metadata |
| ); |
| } |
| } |
| } |
| ReceivePacketAction::Forward { dst: Destination { device: dst_device, next_hop } } => { |
| let ttl = packet.ttl(); |
| if ttl > 1 { |
| trace!("receive_ipv6_packet: forwarding"); |
| |
| // Handle extension headers first. |
| match ipv6::handle_extension_headers(core_ctx, device, frame_dst, &packet, false) { |
| Ipv6PacketAction::_Discard => { |
| core_ctx.increment(|counters: &IpCounters<Ipv6>| { |
| &counters.version_rx.extension_header_discard |
| }); |
| trace!("receive_ipv6_packet: handled IPv6 extension headers: discarding packet"); |
| return; |
| } |
| Ipv6PacketAction::Continue => { |
| trace!("receive_ipv6_packet: handled IPv6 extension headers: forwarding packet"); |
| } |
| Ipv6PacketAction::ProcessFragment => unreachable!("When forwarding packets, we should only ever look at the hop by hop options extension header (if present)"), |
| } |
| |
| match core_ctx.filter_handler().forwarding_hook( |
| &mut packet, |
| device, |
| &dst_device, |
| &mut packet_metadata, |
| ) { |
| crate::filter::Verdict::Drop => { |
| packet_metadata.acknowledge_drop(); |
| return; |
| } |
| crate::filter::Verdict::Accept => {} |
| } |
| |
| let next_hop = match next_hop { |
| NextHop::RemoteAsNeighbor => dst_ip, |
| NextHop::Gateway(gateway) => gateway, |
| NextHop::Broadcast(never) => match never {}, |
| }; |
| packet.set_ttl(ttl - 1); |
| let (src, dst, proto, meta) = packet.into_metadata(); |
| let packet = ForwardedPacket::new(src, dst, proto, meta, buffer); |
| |
| if let Err(packet) = send_ip_frame( |
| core_ctx, |
| bindings_ctx, |
| &dst_device, |
| next_hop, |
| packet, |
| None, |
| packet_metadata, |
| ) { |
| // TODO(https://fxbug.dev/42167236): Encode the MTU error more |
| // obviously in the type system. |
| core_ctx.increment(|counters: &IpCounters<Ipv6>| &counters.mtu_exceeded); |
| debug!("failed to forward IPv6 packet: MTU exceeded"); |
| if let Ipv6SourceAddr::Unicast(src_ip) = src_ip { |
| trace!("receive_ipv6_packet: Sending ICMPv6 Packet Too Big"); |
| // TODO(joshlf): Increment the TTL since we just |
| // decremented it. The fact that we don't do this is |
| // technically a violation of the ICMP spec (we're not |
| // encapsulating the original packet that caused the |
| // issue, but a slightly modified version of it), but |
| // it's not that big of a deal because it won't affect |
| // the sender's ability to figure out the minimum path |
| // MTU. This may break other logic, though, so we should |
| // still fix it eventually. |
| let mtu = core_ctx.get_mtu(&device); |
| IcmpErrorHandler::<Ipv6, _>::send_icmp_error_message( |
| core_ctx, |
| bindings_ctx, |
| device, |
| frame_dst, |
| *src_ip, |
| dst_ip, |
| packet.into_buffer(), |
| Icmpv6ErrorKind::PacketTooBig { |
| proto, |
| header_len: meta.header_len(), |
| mtu, |
| }, |
| ); |
| } |
| } |
| } else { |
| // Hop Limit is 0 or would become 0 after decrement; see RFC |
| // 2460 Section 3. |
| core_ctx.increment(|counters: &IpCounters<Ipv6>| &counters.ttl_expired); |
| debug!("received IPv6 packet dropped due to expired Hop Limit"); |
| packet_metadata.acknowledge_drop(); |
| |
| if let Ipv6SourceAddr::Unicast(src_ip) = src_ip { |
| let (_, _, proto, meta): (Ipv6Addr, Ipv6Addr, _, _) = |
| drop_packet_and_undo_parse!(packet, buffer); |
| IcmpErrorHandler::<Ipv6, _>::send_icmp_error_message( |
| core_ctx, |
| bindings_ctx, |
| device, |
| frame_dst, |
| *src_ip, |
| dst_ip, |
| buffer, |
| Icmpv6ErrorKind::TtlExpired { proto, header_len: meta.header_len() }, |
| ); |
| } |
| } |
| } |
| ReceivePacketAction::SendNoRouteToDest => { |
| core_ctx.increment(|counters: &IpCounters<Ipv6>| &counters.no_route_to_host); |
| let (_, _, proto, meta): (Ipv6Addr, Ipv6Addr, _, _) = |
| drop_packet_and_undo_parse!(packet, buffer); |
| debug!("received IPv6 packet with no known route to destination {}", dst_ip); |
| packet_metadata.acknowledge_drop(); |
| |
| if let Ipv6SourceAddr::Unicast(src_ip) = src_ip { |
| IcmpErrorHandler::<Ipv6, _>::send_icmp_error_message( |
| core_ctx, |
| bindings_ctx, |
| device, |
| frame_dst, |
| *src_ip, |
| dst_ip, |
| buffer, |
| Icmpv6ErrorKind::NetUnreachable { proto, header_len: meta.header_len() }, |
| ); |
| } |
| } |
| ReceivePacketAction::Drop { reason } => { |
| core_ctx.increment(|counters: &IpCounters<Ipv6>| &counters.dropped); |
| let src_ip = packet.src_ip(); |
| packet_metadata.acknowledge_drop(); |
| debug!( |
| "receive_ipv6_packet: dropping packet from {src_ip} to {dst_ip} received on \ |
| {device:?}: {reason:?}", |
| ); |
| } |
| } |
| } |
| |
| /// The action to take in order to process a received IP packet. |
| #[cfg_attr(test, derive(Debug, PartialEq))] |
| enum ReceivePacketAction<A: IpAddress, DeviceId> |
| where |
| A::Version: IpTypesIpExt, |
| { |
| /// Deliver the packet locally. |
| Deliver, |
| /// Forward the packet to the given destination. |
| Forward { dst: Destination<A, DeviceId> }, |
| /// Send a Destination Unreachable ICMP error message to the packet's sender |
| /// and drop the packet. |
| /// |
| /// For ICMPv4, use the code "net unreachable". For ICMPv6, use the code "no |
| /// route to destination". |
| SendNoRouteToDest, |
| /// Silently drop the packet. |
| /// |
| /// `reason` describes why the packet was dropped. |
| Drop { reason: DropReason }, |
| } |
| |
| #[cfg_attr(test, derive(PartialEq))] |
| #[derive(Debug)] |
| enum DropReason { |
| /// Remote packet destined to tentative address. |
| Tentative, |
| /// Packet should be forwarded but packet's inbound interface has forwarding |
| /// disabled. |
| ForwardingDisabledInboundIface, |
| } |
| |
| /// Computes the action to take in order to process a received IPv4 packet. |
| fn receive_ipv4_packet_action< |
| BC: IpLayerBindingsContext<Ipv4, CC::DeviceId>, |
| CC: IpLayerContext<Ipv4, BC> + CounterContext<IpCounters<Ipv4>>, |
| >( |
| core_ctx: &mut CC, |
| bindings_ctx: &mut BC, |
| device: &CC::DeviceId, |
| dst_ip: SpecifiedAddr<Ipv4Addr>, |
| ) -> ReceivePacketAction<Ipv4Addr, CC::DeviceId> { |
| // If the packet arrived at the loopback interface, check if any local |
| // interface has the destination address assigned. This effectively lets |
| // the loopback interface operate as a weak host for incoming packets. |
| // |
| // Note that (as of writing) the stack sends all locally destined traffic to |
| // the loopback interface so we need this hack to allow the stack to accept |
| // packets that arrive at the loopback interface (after being looped back) |
| // but destined to an address that is assigned to another local interface. |
| // |
| // TODO(https://fxbug.dev/42175703): This should instead be controlled by the |
| // routing table. |
| |
| // Since we treat all addresses identically, it doesn't matter whether one |
| // or more than one device has the address assigned. That means we can just |
| // take the first status and ignore the rest. |
| let first_status = if device.is_loopback() { |
| core_ctx.with_address_statuses(dst_ip, |it| it.map(|(_device, status)| status).next()) |
| } else { |
| core_ctx.address_status_for_device(dst_ip, device).into_present() |
| }; |
| match first_status { |
| Some( |
| Ipv4PresentAddressStatus::LimitedBroadcast |
| | Ipv4PresentAddressStatus::SubnetBroadcast |
| | Ipv4PresentAddressStatus::Multicast |
| | Ipv4PresentAddressStatus::Unicast, |
| ) => { |
| core_ctx.increment(|counters| &counters.version_rx.deliver); |
| ReceivePacketAction::Deliver |
| } |
| None => { |
| receive_ip_packet_action_common::<Ipv4, _, _>(core_ctx, bindings_ctx, dst_ip, device) |
| } |
| } |
| } |
| |
| /// Computes the action to take in order to process a received IPv6 packet. |
| fn receive_ipv6_packet_action< |
| BC: IpLayerBindingsContext<Ipv6, CC::DeviceId>, |
| CC: IpLayerContext<Ipv6, BC> + CounterContext<IpCounters<Ipv6>>, |
| >( |
| core_ctx: &mut CC, |
| bindings_ctx: &mut BC, |
| device: &CC::DeviceId, |
| dst_ip: SpecifiedAddr<Ipv6Addr>, |
| ) -> ReceivePacketAction<Ipv6Addr, CC::DeviceId> { |
| // If the packet arrived at the loopback interface, check if any local |
| // interface has the destination address assigned. This effectively lets |
| // the loopback interface operate as a weak host for incoming packets. |
| // |
| // Note that (as of writing) the stack sends all locally destined traffic to |
| // the loopback interface so we need this hack to allow the stack to accept |
| // packets that arrive at the loopback interface (after being looped back) |
| // but destined to an address that is assigned to another local interface. |
| // |
| // TODO(https://fxbug.dev/42175703): This should instead be controlled by the |
| // routing table. |
| |
| // It's possible that there is more than one device with the address |
| // assigned. Since IPv6 addresses are either multicast or unicast, we |
| // don't expect to see one device with `UnicastAssigned` or |
| // `UnicastTentative` and another with `Multicast`. We might see one |
| // assigned and one tentative status, though, in which case we should |
| // prefer the former. |
| fn choose_highest_priority( |
| address_statuses: impl Iterator<Item = Ipv6PresentAddressStatus>, |
| dst_ip: SpecifiedAddr<Ipv6Addr>, |
| ) -> Option<Ipv6PresentAddressStatus> { |
| address_statuses.max_by(|lhs, rhs| { |
| use Ipv6PresentAddressStatus::*; |
| match (lhs, rhs) { |
| (UnicastAssigned | UnicastTentative, Multicast) |
| | (Multicast, UnicastAssigned | UnicastTentative) => { |
| unreachable!("the IPv6 address {:?} is not both unicast and multicast", dst_ip) |
| } |
| (UnicastAssigned, UnicastTentative) => Ordering::Greater, |
| (UnicastTentative, UnicastAssigned) => Ordering::Less, |
| (UnicastTentative, UnicastTentative) |
| | (UnicastAssigned, UnicastAssigned) |
| | (Multicast, Multicast) => Ordering::Equal, |
| } |
| }) |
| } |
| |
| let highest_priority = if device.is_loopback() { |
| core_ctx.with_address_statuses(dst_ip, |it| { |
| let it = it.map(|(_device, status)| status); |
| choose_highest_priority(it, dst_ip) |
| }) |
| } else { |
| core_ctx.address_status_for_device(dst_ip, device).into_present() |
| }; |
| match highest_priority { |
| Some(Ipv6PresentAddressStatus::Multicast) => { |
| core_ctx.increment(|counters| &counters.version_rx.deliver_multicast); |
| ReceivePacketAction::Deliver |
| } |
| Some(Ipv6PresentAddressStatus::UnicastAssigned) => { |
| core_ctx.increment(|counters| &counters.version_rx.deliver_unicast); |
| ReceivePacketAction::Deliver |
| } |
| Some(Ipv6PresentAddressStatus::UnicastTentative) => { |
| // If the destination address is tentative (which implies that |
| // we are still performing NDP's Duplicate Address Detection on |
| // it), then we don't consider the address "assigned to an |
| // interface", and so we drop packets instead of delivering them |
| // locally. |
| // |
| // As per RFC 4862 section 5.4: |
| // |
| // An address on which the Duplicate Address Detection |
| // procedure is applied is said to be tentative until the |
| // procedure has completed successfully. A tentative address |
| // is not considered "assigned to an interface" in the |
| // traditional sense. That is, the interface must accept |
| // Neighbor Solicitation and Advertisement messages containing |
| // the tentative address in the Target Address field, but |
| // processes such packets differently from those whose Target |
| // Address matches an address assigned to the interface. Other |
| // packets addressed to the tentative address should be |
| // silently discarded. Note that the "other packets" include |
| // Neighbor Solicitation and Advertisement messages that have |
| // the tentative (i.e., unicast) address as the IP destination |
| // address and contain the tentative address in the Target |
| // Address field. Such a case should not happen in normal |
| // operation, though, since these messages are multicasted in |
| // the Duplicate Address Detection procedure. |
| // |
| // That is, we accept no packets destined to a tentative |
| // address. NS and NA packets should be addressed to a multicast |
| // address that we would have joined during DAD so that we can |
| // receive those packets. |
| core_ctx.increment(|counters| &counters.version_rx.drop_for_tentative); |
| ReceivePacketAction::Drop { reason: DropReason::Tentative } |
| } |
| None => { |
| receive_ip_packet_action_common::<Ipv6, _, _>(core_ctx, bindings_ctx, dst_ip, device) |
| } |
| } |
| } |
| |
| /// Computes the remaining protocol-agnostic actions on behalf of |
| /// [`receive_ipv4_packet_action`] and [`receive_ipv6_packet_action`]. |
| fn receive_ip_packet_action_common< |
| I: IpLayerIpExt, |
| BC: IpLayerBindingsContext<I, CC::DeviceId>, |
| CC: IpLayerContext<I, BC> + CounterContext<IpCounters<I>>, |
| >( |
| core_ctx: &mut CC, |
| bindings_ctx: &mut BC, |
| dst_ip: SpecifiedAddr<I::Addr>, |
| device_id: &CC::DeviceId, |
| ) -> ReceivePacketAction<I::Addr, CC::DeviceId> { |
| // The packet is not destined locally, so we attempt to forward it. |
| if !core_ctx.is_device_forwarding_enabled(device_id) { |
| // Forwarding is disabled; we are operating only as a host. |
| // |
| // For IPv4, per RFC 1122 Section 3.2.1.3, "A host MUST silently discard |
| // an incoming datagram that is not destined for the host." |
| // |
| // For IPv6, per RFC 4443 Section 3.1, the only instance in which a host |
| // sends an ICMPv6 Destination Unreachable message is when a packet is |
| // destined to that host but on an unreachable port (Code 4 - "Port |
| // unreachable"). Since the only sensible error message to send in this |
| // case is a Destination Unreachable message, we interpret the RFC text |
| // to mean that, consistent with IPv4's behavior, we should silently |
| // discard the packet in this case. |
| core_ctx.increment(|counters| &counters.forwarding_disabled); |
| ReceivePacketAction::Drop { reason: DropReason::ForwardingDisabledInboundIface } |
| } else { |
| match lookup_route_table(core_ctx, bindings_ctx, None, *dst_ip) { |
| Some(dst) => { |
| core_ctx.increment(|counters| &counters.forward); |
| ReceivePacketAction::Forward { dst } |
| } |
| None => { |
| core_ctx.increment(|counters| &counters.no_route_to_host); |
| ReceivePacketAction::SendNoRouteToDest |
| } |
| } |
| } |
| } |
| |
| // Look up the route to a host. |
| fn lookup_route_table< |
| I: IpLayerIpExt, |
| BC: IpLayerBindingsContext<I, CC::DeviceId>, |
| CC: IpLayerContext<I, BC>, |
| >( |
| core_ctx: &mut CC, |
| _bindings_ctx: &mut BC, |
| device: Option<&CC::DeviceId>, |
| dst_ip: I::Addr, |
| ) -> Option<Destination<I::Addr, CC::DeviceId>> { |
| core_ctx.with_ip_routing_table(|core_ctx, table| table.lookup(core_ctx, device, dst_ip)) |
| } |
| |
| /// The metadata associated with an outgoing IP packet. |
| #[cfg_attr(test, derive(Debug))] |
| pub struct SendIpPacketMeta<I: packet_formats::ip::IpExt + IpTypesIpExt, D, Src> { |
| /// The outgoing device. |
| pub(crate) device: D, |
| |
| /// The source address of the packet. |
| pub(crate) src_ip: Src, |
| |
| /// The destination address of the packet. |
| pub(crate) dst_ip: SpecifiedAddr<I::Addr>, |
| |
| /// The next-hop node that the packet should be sent to. |
| pub(crate) next_hop: SpecifiedAddr<I::Addr>, |
| |
| /// Whether the destination is a broadcast address. |
| pub(crate) broadcast: Option<I::BroadcastMarker>, |
| |
| /// The upper-layer protocol held in the packet's payload. |
| pub(crate) proto: I::Proto, |
| |
| /// The time-to-live (IPv4) or hop limit (IPv6) for the packet. |
| /// |
| /// If not set, a default TTL may be used. |
| pub(crate) ttl: Option<NonZeroU8>, |
| |
| /// An MTU to artificially impose on the whole IP packet. |
| /// |
| /// Note that the device's MTU will still be imposed on the packet. |
| pub(crate) mtu: Option<u32>, |
| } |
| |
| impl<I: packet_formats::ip::IpExt + IpTypesIpExt, D> |
| From<SendIpPacketMeta<I, D, SpecifiedAddr<I::Addr>>> |
| for SendIpPacketMeta<I, D, Option<SpecifiedAddr<I::Addr>>> |
| { |
| fn from( |
| SendIpPacketMeta { device, src_ip, dst_ip, broadcast, next_hop, proto, ttl, mtu }: SendIpPacketMeta< |
| I, |
| D, |
| SpecifiedAddr<I::Addr>, |
| >, |
| ) -> SendIpPacketMeta<I, D, Option<SpecifiedAddr<I::Addr>>> { |
| SendIpPacketMeta { |
| device, |
| src_ip: Some(src_ip), |
| dst_ip, |
| broadcast, |
| next_hop, |
| proto, |
| ttl, |
| mtu, |
| } |
| } |
| } |
| |
| /// Trait for abstracting the IP layer for locally-generated traffic. That is, |
| /// traffic generated by the netstack itself (e.g. ICMP, IGMP, or MLD). |
| /// |
| /// NOTE: Due to filtering rules, it is possible that the device provided in |
| /// `meta` will not be the device that final IP packet is actually sent from. |
| pub(crate) trait IpLayerHandler<I: IpExt, BC>: DeviceIdContext<AnyDevice> { |
| /// Encapsulate and send the provided transport packet and from the device |
| /// provided in `meta`. |
| fn send_ip_packet_from_device<S>( |
| &mut self, |
| bindings_ctx: &mut BC, |
| meta: SendIpPacketMeta<I, &Self::DeviceId, Option<SpecifiedAddr<I::Addr>>>, |
| body: S, |
| ) -> Result<(), S> |
| where |
| S: Serializer + MaybeTransportPacket, |
| S::Buffer: BufferMut; |
| |
| /// Send an IP packet that doesn't require the encapsulation and other |
| /// processing of [`send_ip_packet_from_device`] from the device specified |
| /// in `meta`. |
| // TODO(https://fxbug.dev/333908066): The packets going through this |
| // function only hit the EGRESS filter hook, bypassing LOCAL_EGRESS. |
| // Refactor callers and other functions to prevent this. |
| fn send_ip_frame<S>( |
| &mut self, |
| bindings_ctx: &mut BC, |
| device: &Self::DeviceId, |
| next_hop: SpecifiedAddr<I::Addr>, |
| body: S, |
| broadcast: Option<I::BroadcastMarker>, |
| ) -> Result<(), S> |
| where |
| S: Serializer + IpPacket<I>, |
| S::Buffer: BufferMut; |
| } |
| |
| impl< |
| I: IpLayerIpExt, |
| BC: IpLayerBindingsContext<I, <CC as DeviceIdContext<AnyDevice>>::DeviceId>, |
| CC: IpLayerEgressContext<I, BC> + IpDeviceStateContext<I, BC>, |
| > IpLayerHandler<I, BC> for CC |
| { |
| fn send_ip_packet_from_device<S>( |
| &mut self, |
| bindings_ctx: &mut BC, |
| meta: SendIpPacketMeta<I, &CC::DeviceId, Option<SpecifiedAddr<I::Addr>>>, |
| body: S, |
| ) -> Result<(), S> |
| where |
| S: Serializer + MaybeTransportPacket, |
| S::Buffer: BufferMut, |
| { |
| send_ip_packet_from_device(self, bindings_ctx, meta, body, IpLayerPacketMetadata::default()) |
| } |
| |
| fn send_ip_frame<S>( |
| &mut self, |
| bindings_ctx: &mut BC, |
| device: &Self::DeviceId, |
| next_hop: SpecifiedAddr<I::Addr>, |
| body: S, |
| broadcast: Option<I::BroadcastMarker>, |
| ) -> Result<(), S> |
| where |
| S: Serializer + IpPacket<I>, |
| S::Buffer: BufferMut, |
| { |
| send_ip_frame( |
| self, |
| bindings_ctx, |
| device, |
| next_hop, |
| body, |
| broadcast, |
| IpLayerPacketMetadata::default(), |
| ) |
| } |
| } |
| |
| /// Sends an Ip packet with the specified metadata. |
| /// |
| /// # Panics |
| /// |
| /// Panics if either the source or destination address is the loopback address |
| /// and the device is a non-loopback device. |
| pub(crate) fn send_ip_packet_from_device<I, BC, CC, S>( |
| core_ctx: &mut CC, |
| bindings_ctx: &mut BC, |
| meta: SendIpPacketMeta< |
| I, |
| &<CC as DeviceIdContext<AnyDevice>>::DeviceId, |
| Option<SpecifiedAddr<I::Addr>>, |
| >, |
| body: S, |
| packet_metadata: IpLayerPacketMetadata<I, BC>, |
| ) -> Result<(), S> |
| where |
| I: IpLayerIpExt, |
| BC: IpLayerBindingsContext<I, <CC as DeviceIdContext<AnyDevice>>::DeviceId>, |
| CC: IpLayerEgressContext<I, BC> + IpDeviceStateContext<I, BC>, |
| S: Serializer + MaybeTransportPacket, |
| S::Buffer: BufferMut, |
| { |
| let SendIpPacketMeta { device, src_ip, dst_ip, broadcast, next_hop, proto, ttl, mtu } = meta; |
| let next_packet_id = gen_ip_packet_id(core_ctx); |
| let ttl = ttl.unwrap_or_else(|| core_ctx.get_hop_limit(device)).get(); |
| let src_ip = src_ip.map_or(I::UNSPECIFIED_ADDRESS, |a| a.get()); |
| assert!( |
| (!I::LOOPBACK_SUBNET.contains(&src_ip) && !I::LOOPBACK_SUBNET.contains(&dst_ip)) |
| || device.is_loopback() |
| ); |
| let mut builder = I::PacketBuilder::new(src_ip, dst_ip.get(), ttl, proto); |
| |
| #[derive(GenericOverIp)] |
| #[generic_over_ip(I, Ip)] |
| struct Wrap<'a, I: IpLayerIpExt> { |
| builder: &'a mut I::PacketBuilder, |
| next_packet_id: I::PacketId, |
| } |
| |
| I::map_ip::<_, ()>( |
| Wrap { builder: &mut builder, next_packet_id }, |
| |Wrap { builder, next_packet_id }| { |
| builder.id(next_packet_id); |
| }, |
| |Wrap { builder: _, next_packet_id: () }| { |
| // IPv6 doesn't have packet IDs. |
| }, |
| ); |
| |
| let body = body.encapsulate(builder); |
| |
| if let Some(mtu) = mtu { |
| let body = NestedWithInnerIpPacket::new(body.with_size_limit(mtu as usize)); |
| send_ip_frame(core_ctx, bindings_ctx, device, next_hop, body, broadcast, packet_metadata) |
| .map_err(|ser| ser.into_inner().into_inner()) |
| } else { |
| send_ip_frame(core_ctx, bindings_ctx, device, next_hop, body, broadcast, packet_metadata) |
| .map_err(|ser| ser.into_inner()) |
| } |
| } |
| |
| impl< |
| BC: BindingsContext, |
| L: LockBefore<crate::lock_ordering::IcmpBoundMap<Ipv4>> |
| + LockBefore<crate::lock_ordering::TcpAllSocketsSet<Ipv4>> |
| + LockBefore<crate::lock_ordering::UdpAllSocketsSet<Ipv4>>, |
| > InnerIcmpContext<Ipv4, BC> for CoreCtx<'_, BC, L> |
| { |
| type IpSocketsCtx<'a> = CoreCtx<'a, BC, crate::lock_ordering::IcmpBoundMap<Ipv4>>; |
| type DualStackContext = UninstantiableWrapper<Self>; |
| |
| fn receive_icmp_error( |
| &mut self, |
| bindings_ctx: &mut BC, |
| device: &DeviceId<BC>, |
| original_src_ip: Option<SpecifiedAddr<Ipv4Addr>>, |
| original_dst_ip: SpecifiedAddr<Ipv4Addr>, |
| original_proto: Ipv4Proto, |
| original_body: &[u8], |
| err: Icmpv4ErrorCode, |
| ) { |
| self.increment(|counters: &IpCounters<Ipv4>| &counters.receive_icmp_error); |
| trace!("InnerIcmpContext<Ipv4>::receive_icmp_error({:?})", err); |
| |
| match original_proto { |
| Ipv4Proto::Icmp => { |
| <IcmpIpTransportContext as IpTransportContext<Ipv4, _, _>>::receive_icmp_error( |
| self, |
| bindings_ctx, |
| device, |
| original_src_ip, |
| original_dst_ip, |
| original_body, |
| err, |
| ) |
| } |
| Ipv4Proto::Proto(IpProto::Tcp) => { |
| <TcpIpTransportContext as IpTransportContext<Ipv4, _, _>>::receive_icmp_error( |
| self, |
| bindings_ctx, |
| device, |
| original_src_ip, |
| original_dst_ip, |
| original_body, |
| err, |
| ) |
| } |
| Ipv4Proto::Proto(IpProto::Udp) => { |
| <UdpIpTransportContext as IpTransportContext<Ipv4, _, _>>::receive_icmp_error( |
| self, |
| bindings_ctx, |
| device, |
| original_src_ip, |
| original_dst_ip, |
| original_body, |
| err, |
| ) |
| } |
| // TODO(joshlf): Once all IP protocol numbers are covered, |
| // remove this default case. |
| _ => <() as IpTransportContext<Ipv4, _, _>>::receive_icmp_error( |
| self, |
| bindings_ctx, |
| device, |
| original_src_ip, |
| original_dst_ip, |
| original_body, |
| err, |
| ), |
| } |
| } |
| |
| fn with_icmp_ctx_and_sockets_mut< |
| O, |
| F: FnOnce( |
| &mut Self::IpSocketsCtx<'_>, |
| &mut icmp::socket::BoundSockets<Ipv4, Self::WeakDeviceId, BC>, |
| ) -> O, |
| >( |
| &mut self, |
| cb: F, |
| ) -> O { |
| let (mut sockets, mut core_ctx) = |
| self.write_lock_and::<crate::lock_ordering::IcmpBoundMap<Ipv4>>(); |
| cb(&mut core_ctx, &mut sockets) |
| } |
| |
| fn with_error_send_bucket_mut<O, F: FnOnce(&mut TokenBucket<BC::Instant>) -> O>( |
| &mut self, |
| cb: F, |
| ) -> O { |
| cb(&mut self.lock::<crate::lock_ordering::IcmpTokenBucket<Ipv4>>()) |
| } |
| } |
| |
| impl< |
| BC: BindingsContext, |
| L: LockBefore<crate::lock_ordering::IcmpBoundMap<Ipv6>> |
| + LockBefore<crate::lock_ordering::TcpAllSocketsSet<Ipv6>> |
| + LockBefore<crate::lock_ordering::UdpAllSocketsSet<Ipv6>>, |
| > InnerIcmpContext<Ipv6, BC> for CoreCtx<'_, BC, L> |
| { |
| type IpSocketsCtx<'a> = CoreCtx<'a, BC, crate::lock_ordering::IcmpBoundMap<Ipv6>>; |
| type DualStackContext = UninstantiableWrapper<Self>; |
| fn receive_icmp_error( |
| &mut self, |
| bindings_ctx: &mut BC, |
| device: &DeviceId<BC>, |
| original_src_ip: Option<SpecifiedAddr<Ipv6Addr>>, |
| original_dst_ip: SpecifiedAddr<Ipv6Addr>, |
| original_next_header: Ipv6Proto, |
| original_body: &[u8], |
| err: Icmpv6ErrorCode, |
| ) { |
| self.increment(|counters: &IpCounters<Ipv6>| &counters.receive_icmp_error); |
| trace!("InnerIcmpContext<Ipv6>::receive_icmp_error({:?})", err); |
| |
| match original_next_header { |
| Ipv6Proto::Icmpv6 => { |
| <IcmpIpTransportContext as IpTransportContext<Ipv6, _, _>>::receive_icmp_error( |
| self, |
| bindings_ctx, |
| device, |
| original_src_ip, |
| original_dst_ip, |
| original_body, |
| err, |
| ) |
| } |
| Ipv6Proto::Proto(IpProto::Tcp) => { |
| <TcpIpTransportContext as IpTransportContext<Ipv6, _, _>>::receive_icmp_error( |
| self, |
| bindings_ctx, |
| device, |
| original_src_ip, |
| original_dst_ip, |
| original_body, |
| err, |
| ) |
| } |
| Ipv6Proto::Proto(IpProto::Udp) => { |
| <UdpIpTransportContext as IpTransportContext<Ipv6, _, _>>::receive_icmp_error( |
| self, |
| bindings_ctx, |
| device, |
| original_src_ip, |
| original_dst_ip, |
| original_body, |
| err, |
| ) |
| } |
| // TODO(joshlf): Once all IP protocol numbers are covered, |
| // remove this default case. |
| _ => <() as IpTransportContext<Ipv6, _, _>>::receive_icmp_error( |
| self, |
| bindings_ctx, |
| device, |
| original_src_ip, |
| original_dst_ip, |
| original_body, |
| err, |
| ), |
| } |
| } |
| |
| fn with_icmp_ctx_and_sockets_mut< |
| O, |
| F: FnOnce( |
| &mut Self::IpSocketsCtx<'_>, |
| &mut icmp::socket::BoundSockets<Ipv6, Self::WeakDeviceId, BC>, |
| ) -> O, |
| >( |
| &mut self, |
| cb: F, |
| ) -> O { |
| let (mut sockets, mut core_ctx) = |
| self.write_lock_and::<crate::lock_ordering::IcmpBoundMap<Ipv6>>(); |
| cb(&mut core_ctx, &mut sockets) |
| } |
| |
| fn with_error_send_bucket_mut<O, F: FnOnce(&mut TokenBucket<BC::Instant>) -> O>( |
| &mut self, |
| cb: F, |
| ) -> O { |
| cb(&mut self.lock::<crate::lock_ordering::IcmpTokenBucket<Ipv6>>()) |
| } |
| } |
| |
| impl<L, BC: BindingsContext> icmp::IcmpStateContext for CoreCtx<'_, BC, L> {} |
| |
| #[netstack3_macros::instantiate_ip_impl_block(I)] |
| impl< |
| I, |
| BC: BindingsContext, |
| L: LockBefore<crate::lock_ordering::IcmpAllSocketsSet<I>> |
| + LockBefore<crate::lock_ordering::TcpDemux<I>> |
| + LockBefore<crate::lock_ordering::UdpBoundMap<I>>, |
| > icmp::socket::StateContext<I, BC> for CoreCtx<'_, BC, L> |
| { |
| type SocketStateCtx<'a> = CoreCtx<'a, BC, crate::lock_ordering::IcmpSocketState<I>>; |
| |
| fn with_all_sockets_mut<O, F: FnOnce(&mut IcmpSocketSet<I, Self::WeakDeviceId, BC>) -> O>( |
| &mut self, |
| cb: F, |
| ) -> O { |
| cb(&mut self.write_lock::<crate::lock_ordering::IcmpAllSocketsSet<I>>()) |
| } |
| |
| fn with_all_sockets<O, F: FnOnce(&IcmpSocketSet<I, Self::WeakDeviceId, BC>) -> O>( |
| &mut self, |
| cb: F, |
| ) -> O { |
| cb(&self.read_lock::<crate::lock_ordering::IcmpAllSocketsSet<I>>()) |
| } |
| |
| fn with_socket_state< |
| O, |
| F: FnOnce(&mut Self::SocketStateCtx<'_>, &IcmpSocketState<I, Self::WeakDeviceId, BC>) -> O, |
| >( |
| &mut self, |
| id: &IcmpSocketId<I, Self::WeakDeviceId, BC>, |
| cb: F, |
| ) -> O { |
| let mut locked = self.adopt(id); |
| let (socket_state, mut restricted) = |
| locked.read_lock_with_and::<crate::lock_ordering::IcmpSocketState<I>, _>(|c| c.right()); |
| let mut restricted = restricted.cast_core_ctx(); |
| cb(&mut restricted, &socket_state) |
| } |
| |
| fn with_socket_state_mut< |
| O, |
| F: FnOnce(&mut Self::SocketStateCtx<'_>, &mut IcmpSocketState<I, Self::WeakDeviceId, BC>) -> O, |
| >( |
| &mut self, |
| id: &IcmpSocketId<I, Self::WeakDeviceId, BC>, |
| cb: F, |
| ) -> O { |
| let mut locked = self.adopt(id); |
| let (mut socket_state, mut restricted) = locked |
| .write_lock_with_and::<crate::lock_ordering::IcmpSocketState<I>, _>(|c| c.right()); |
| let mut restricted = restricted.cast_core_ctx(); |
| cb(&mut restricted, &mut socket_state) |
| } |
| |
| fn with_bound_state_context<O, F: FnOnce(&mut Self::SocketStateCtx<'_>) -> O>( |
| &mut self, |
| cb: F, |
| ) -> O { |
| cb(&mut self.cast_locked()) |
| } |
| |
| fn for_each_socket< |
| F: FnMut( |
| &mut Self::SocketStateCtx<'_>, |
| &IcmpSocketId<I, Self::WeakDeviceId, BC>, |
| &IcmpSocketState<I, Self::WeakDeviceId, BC>, |
| ), |
| >( |
| &mut self, |
| mut cb: F, |
| ) { |
| let (all_sockets, mut locked) = |
| self.read_lock_and::<crate::lock_ordering::IcmpAllSocketsSet<I>>(); |
| all_sockets.keys().for_each(|id| { |
| let id = IcmpSocketId::from(id.clone()); |
| let mut locked = locked.adopt(&id); |
| let (socket_state, mut restricted) = locked |
| .read_lock_with_and::<crate::lock_ordering::IcmpSocketState<I>, _>(|c| c.right()); |
| let mut restricted = restricted.cast_core_ctx(); |
| cb(&mut restricted, &id, &socket_state); |
| }); |
| } |
| } |
| |
| #[cfg(test)] |
| pub(crate) mod testutil { |
| use super::*; |
| |
| use core::marker::PhantomData; |
| |
| use derivative::Derivative; |
| use net_types::ip::IpInvariant; |
| |
| use crate::{ |
| context::{testutil::FakeInstant, RngContext}, |
| device::testutil::{FakeStrongDeviceId, FakeWeakDeviceId}, |
| }; |
| |
| impl<S, Meta, D: StrongId + 'static> DeviceIdContext<AnyDevice> |
| for crate::context::testutil::FakeCoreCtx<S, Meta, D> |
| where |
| FakeIpDeviceIdCtx<D>: DeviceIdContext<AnyDevice, DeviceId = D, WeakDeviceId = D::Weak>, |
| { |
| type DeviceId = D; |
| type WeakDeviceId = D::Weak; |
| } |
| |
| impl<Outer, Inner: DeviceIdContext<AnyDevice>> DeviceIdContext<AnyDevice> |
| for crate::context::testutil::Wrapped<Outer, Inner> |
| { |
| type DeviceId = Inner::DeviceId; |
| type WeakDeviceId = Inner::WeakDeviceId; |
| } |
| |
| #[derive(Debug, GenericOverIp)] |
| #[generic_over_ip()] |
| pub(crate) enum DualStackSendIpPacketMeta<D> { |
| V4(SendIpPacketMeta<Ipv4, D, SpecifiedAddr<Ipv4Addr>>), |
| V6(SendIpPacketMeta<Ipv6, D, SpecifiedAddr<Ipv6Addr>>), |
| } |
| |
| impl<I: packet_formats::ip::IpExt + IpTypesIpExt, D> |
| From<SendIpPacketMeta<I, D, SpecifiedAddr<I::Addr>>> for DualStackSendIpPacketMeta<D> |
| { |
| fn from(value: SendIpPacketMeta<I, D, SpecifiedAddr<I::Addr>>) -> Self { |
| #[derive(GenericOverIp)] |
| #[generic_over_ip(I, Ip)] |
| struct Wrap<I: packet_formats::ip::IpExt + IpTypesIpExt, D>( |
| SendIpPacketMeta<I, D, SpecifiedAddr<I::Addr>>, |
| ); |
| use DualStackSendIpPacketMeta::*; |
| let IpInvariant(dual_stack) = I::map_ip( |
| Wrap(value), |
| |Wrap(value)| IpInvariant(V4(value)), |
| |Wrap(value)| IpInvariant(V6(value)), |
| ); |
| dual_stack |
| } |
| } |
| |
| #[cfg(test)] |
| impl<I: packet_formats::ip::IpExt + IpTypesIpExt, S, DeviceId, BC> |
| crate::context::SendFrameContext<BC, SendIpPacketMeta<I, DeviceId, SpecifiedAddr<I::Addr>>> |
| for crate::context::testutil::FakeCoreCtx<S, DualStackSendIpPacketMeta<DeviceId>, DeviceId> |
| { |
| fn send_frame<SS>( |
| &mut self, |
| bindings_ctx: &mut BC, |
| metadata: SendIpPacketMeta<I, DeviceId, SpecifiedAddr<I::Addr>>, |
| frame: SS, |
| ) -> Result<(), SS> |
| where |
| SS: Serializer, |
| SS::Buffer: BufferMut, |
| { |
| self.frames.send_frame(bindings_ctx, DualStackSendIpPacketMeta::from(metadata), frame) |
| } |
| } |
| |
| #[derive(Debug)] |
| pub(crate) struct WrongIpVersion; |
| |
| impl<D> DualStackSendIpPacketMeta<D> { |
| pub(crate) fn try_as<I: packet_formats::ip::IpExt + IpTypesIpExt>( |
| &self, |
| ) -> Result<&SendIpPacketMeta<I, D, SpecifiedAddr<I::Addr>>, WrongIpVersion> { |
| #[derive(GenericOverIp)] |
| #[generic_over_ip(I, Ip)] |
| struct Wrap<'a, I: packet_formats::ip::IpExt + IpTypesIpExt, D>( |
| Option<&'a SendIpPacketMeta<I, D, SpecifiedAddr<I::Addr>>>, |
| ); |
| use DualStackSendIpPacketMeta::*; |
| let Wrap(dual_stack) = I::map_ip( |
| self, |
| |value| { |
| Wrap(match value { |
| V4(meta) => Some(meta), |
| V6(_) => None, |
| }) |
| }, |
| |value| { |
| Wrap(match value { |
| V4(_) => None, |
| V6(meta) => Some(meta), |
| }) |
| }, |
| ); |
| dual_stack.ok_or(WrongIpVersion) |
| } |
| } |
| |
| #[derive(Derivative)] |
| #[derivative(Default(bound = ""))] |
| pub(crate) struct FakeIpDeviceIdCtx<D>(PhantomData<D>); |
| |
| impl<DeviceId: FakeStrongDeviceId> DeviceIdContext<AnyDevice> for FakeIpDeviceIdCtx<DeviceId> { |
| type DeviceId = DeviceId; |
| type WeakDeviceId = FakeWeakDeviceId<DeviceId>; |
| } |
| |
| impl< |
| I: Ip, |
| BC: RngContext + InstantContext<Instant = FakeInstant>, |
| D: FakeStrongDeviceId, |
| State: MulticastMembershipHandler<I, BC, DeviceId = D>, |
| Meta, |
| > MulticastMembershipHandler<I, BC> |
| for crate::context::testutil::FakeCoreCtx<State, Meta, D> |
| where |
| Self: DeviceIdContext<AnyDevice, DeviceId = D>, |
| { |
| fn join_multicast_group( |
| &mut self, |
| bindings_ctx: &mut BC, |
| device: &Self::DeviceId, |
| addr: MulticastAddr<<I as Ip>::Addr>, |
| ) { |
| self.get_mut().join_multicast_group(bindings_ctx, device, addr) |
| } |
| |
| fn leave_multicast_group( |
| &mut self, |
| bindings_ctx: &mut BC, |
| device: &Self::DeviceId, |
| addr: MulticastAddr<<I as Ip>::Addr>, |
| ) { |
| self.get_mut().leave_multicast_group(bindings_ctx, device, addr) |
| } |
| |
| fn select_device_for_multicast_group( |
| &mut self, |
| addr: MulticastAddr<<I as Ip>::Addr>, |
| ) -> Result<Self::DeviceId, ResolveRouteError> { |
| self.get_mut().select_device_for_multicast_group(addr) |
| } |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use alloc::vec; |
| use assert_matches::assert_matches; |
| use core::time::Duration; |
| |
| use ip_test_macro::ip_test; |
| use net_types::{ |
| ethernet::Mac, |
| ip::{AddrSubnet, IpAddr}, |
| }; |
| use packet::ParseBuffer; |
| use packet_formats::{ |
| ethernet::{ |
| EthernetFrame, EthernetFrameBuilder, EthernetFrameLengthCheck, |
| ETHERNET_MIN_BODY_LEN_NO_TAG, |
| }, |
| icmp::{ |
| IcmpDestUnreachable, IcmpEchoRequest, IcmpPacketBuilder, IcmpParseArgs, IcmpUnusedCode, |
| Icmpv4DestUnreachableCode, Icmpv6Packet, Icmpv6PacketTooBig, |
| Icmpv6ParameterProblemCode, MessageBody, |
| }, |
| ip::{IpPacketBuilder, Ipv6ExtHdrType}, |
| ipv4::Ipv4PacketBuilder, |
| ipv6::{ext_hdrs::ExtensionHeaderOptionAction, Ipv6PacketBuilder}, |
| testutil::parse_icmp_packet_in_ip_packet_in_ethernet_frame, |
| }; |
| use rand::Rng; |
| use test_case::test_case; |
| |
| use super::*; |
| use crate::{ |
| context::testutil::FakeInstant, |
| device::{ |
| ethernet::{EthernetCreationProperties, EthernetLinkDevice, RecvEthernetFrameMeta}, |
| loopback::{LoopbackCreationProperties, LoopbackDevice}, |
| testutil::set_forwarding_enabled, |
| }, |
| ip::{ |
| device::{ |
| config::{ |
| IpDeviceConfigurationUpdate, Ipv4DeviceConfigurationUpdate, |
| Ipv6DeviceConfigurationUpdate, |
| }, |
| slaac::SlaacConfiguration, |
| }, |
| types::{AddableEntryEither, AddableMetric, RawMetric}, |
| }, |
| testutil::{ |
| new_rng, set_logger_for_test, Ctx, FakeBindingsCtx, FakeCtx, |
| FakeEventDispatcherBuilder, TestIpExt, DEFAULT_INTERFACE_METRIC, FAKE_CONFIG_V4, |
| FAKE_CONFIG_V6, IPV6_MIN_IMPLIED_MAX_FRAME_SIZE, |
| }, |
| UnlockedCoreCtx, |
| }; |
| |
| // Some helper functions |
| |
| /// Verify that an ICMP Parameter Problem packet was actually sent in |
| /// response to a packet with an unrecognized IPv6 extension header option. |
| /// |
| /// `verify_icmp_for_unrecognized_ext_hdr_option` verifies that the next |
| /// frame in `net` is an ICMP packet with code set to `code`, and pointer |
| /// set to `pointer`. |
| fn verify_icmp_for_unrecognized_ext_hdr_option( |
| code: Icmpv6ParameterProblemCode, |
| pointer: u32, |
| packet: &[u8], |
| ) { |
| // Check the ICMP that bob attempted to send to alice |
| let mut buffer = Buf::new(packet, ..); |
| let _frame = |
| buffer.parse_with::<_, EthernetFrame<_>>(EthernetFrameLengthCheck::Check).unwrap(); |
| let packet = buffer.parse::<<Ipv6 as packet_formats::ip::IpExt>::Packet<_>>().unwrap(); |
| let (src_ip, dst_ip, proto, _): (_, _, _, ParseMetadata) = packet.into_metadata(); |
| assert_eq!(dst_ip, FAKE_CONFIG_V6.remote_ip.get()); |
| assert_eq!(src_ip, FAKE_CONFIG_V6.local_ip.get()); |
| assert_eq!(proto, Ipv6Proto::Icmpv6); |
| let icmp = |
| buffer.parse_with::<_, Icmpv6Packet<_>>(IcmpParseArgs::new(src_ip, dst_ip)).unwrap(); |
| if let Icmpv6Packet::ParameterProblem(icmp) = icmp { |
| assert_eq!(icmp.code(), code); |
| assert_eq!(icmp.message().pointer(), pointer); |
| } else { |
| panic!("Expected ICMPv6 Parameter Problem: {:?}", icmp); |
| } |
| } |
| |
| /// Populate a buffer `bytes` with data required to test unrecognized |
| /// options. |
| /// |
| /// The unrecognized option type will be located at index 48. `bytes` must |
| /// be at least 64 bytes long. If `to_multicast` is `true`, the destination |
| /// address of the packet will be a multicast address. |
| fn buf_for_unrecognized_ext_hdr_option_test( |
| bytes: &mut [u8], |
| action: ExtensionHeaderOptionAction, |
| to_multicast: bool, |
| ) -> Buf<&mut [u8]> { |
| assert!(bytes.len() >= 64); |
| |
| let action: u8 = action.into(); |
| |
| // Unrecognized Option type. |
| let oty = 63 | (action << 6); |
| |
| #[rustfmt::skip] |
| bytes[40..64].copy_from_slice(&[ |
| // Destination Options Extension Header |
| IpProto::Udp.into(), // Next Header |
| 1, // Hdr Ext Len (In 8-octet units, not including first 8 octets) |
| 0, // Pad1 |
| 1, 0, // Pad2 |
| 1, 1, 0, // Pad3 |
| oty, 6, 0, 0, 0, 0, 0, 0, // Unrecognized type w/ action = discard |
| |
| // Body |
| 1, 2, 3, 4, 5, 6, 7, 8 |
| ][..]); |
| bytes[..4].copy_from_slice(&[0x60, 0x20, 0x00, 0x77][..]); |
| |
| let payload_len = u16::try_from(bytes.len() - 40).unwrap(); |
| bytes[4..6].copy_from_slice(&payload_len.to_be_bytes()); |
| |
| bytes[6] = Ipv6ExtHdrType::DestinationOptions.into(); |
| bytes[7] = 64; |
| bytes[8..24].copy_from_slice(FAKE_CONFIG_V6.remote_ip.bytes()); |
| |
| if to_multicast { |
| bytes[24..40].copy_from_slice( |
| &[255, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32][..], |
| ); |
| } else { |
| bytes[24..40].copy_from_slice(FAKE_CONFIG_V6.local_ip.bytes()); |
| } |
| |
| Buf::new(bytes, ..) |
| } |
| |
| /// Create an IPv4 packet builder. |
| fn get_ipv4_builder() -> Ipv4PacketBuilder { |
| Ipv4PacketBuilder::new( |
| FAKE_CONFIG_V4.remote_ip, |
| FAKE_CONFIG_V4.local_ip, |
| 10, |
| IpProto::Udp.into(), |
| ) |
| } |
| |
| /// Process an IP fragment depending on the `Ip` `process_ip_fragment` is |
| /// specialized with. |
| fn process_ip_fragment<I: Ip>( |
| ctx: &mut FakeCtx, |
| device: &DeviceId<FakeBindingsCtx>, |
| fragment_id: u16, |
| fragment_offset: u8, |
| fragment_count: u8, |
| ) { |
| match I::VERSION { |
| IpVersion::V4 => { |
| process_ipv4_fragment(ctx, device, fragment_id, fragment_offset, fragment_count) |
| } |
| IpVersion::V6 => { |
| process_ipv6_fragment(ctx, device, fragment_id, fragment_offset, fragment_count) |
| } |
| } |
| } |
| |
| /// Generate and 'receive' an IPv4 fragment packet. |
| /// |
| /// `fragment_offset` is the fragment offset. `fragment_count` is the number |
| /// of fragments for a packet. The generated packet will have a body of size |
| /// 8 bytes. |
| fn process_ipv4_fragment( |
| ctx: &mut FakeCtx, |
| device: &DeviceId<FakeBindingsCtx>, |
| fragment_id: u16, |
| fragment_offset: u8, |
| fragment_count: u8, |
| ) { |
| assert!(fragment_offset < fragment_count); |
| |
| let m_flag = fragment_offset < (fragment_count - 1); |
| |
| let mut builder = get_ipv4_builder(); |
| builder.id(fragment_id); |
| builder.fragment_offset(fragment_offset as u16); |
| builder.mf_flag(m_flag); |
| let mut body: Vec<u8> = Vec::new(); |
| body.extend(fragment_offset * 8..fragment_offset * 8 + 8); |
| let buffer = |
| Buf::new(body, ..).encapsulate(builder).serialize_vec_outer().unwrap().into_inner(); |
| ctx.test_api().receive_ip_packet::<Ipv4, _>( |
| device, |
| Some(FrameDestination::Individual { local: true }), |
| buffer, |
| ); |
| } |
| |
| /// Generate and 'receive' an IPv6 fragment packet. |
| /// |
| /// `fragment_offset` is the fragment offset. `fragment_count` is the number |
| /// of fragments for a packet. The generated packet will have a body of size |
| /// 8 bytes. |
| fn process_ipv6_fragment( |
| ctx: &mut FakeCtx, |
| device: &DeviceId<FakeBindingsCtx>, |
| fragment_id: u16, |
| fragment_offset: u8, |
| fragment_count: u8, |
| ) { |
| assert!(fragment_offset < fragment_count); |
| |
| let m_flag = fragment_offset < (fragment_count - 1); |
| |
| let mut bytes = vec![0; 48]; |
| bytes[..4].copy_from_slice(&[0x60, 0x20, 0x00, 0x77][..]); |
| bytes[6] = Ipv6ExtHdrType::Fragment.into(); // Next Header |
| bytes[7] = 64; |
| bytes[8..24].copy_from_slice(FAKE_CONFIG_V6.remote_ip.bytes()); |
| bytes[24..40].copy_from_slice(FAKE_CONFIG_V6.local_ip.bytes()); |
| bytes[40] = IpProto::Udp.into(); |
| bytes[42] = fragment_offset >> 5; |
| bytes[43] = ((fragment_offset & 0x1F) << 3) | if m_flag { 1 } else { 0 }; |
| bytes[44..48].copy_from_slice(&(u32::try_from(fragment_id).unwrap().to_be_bytes())); |
| bytes.extend(fragment_offset * 8..fragment_offset * 8 + 8); |
| let payload_len = u16::try_from(bytes.len() - 40).unwrap(); |
| bytes[4..6].copy_from_slice(&payload_len.to_be_bytes()); |
| let buffer = Buf::new(bytes, ..); |
| ctx.test_api().receive_ip_packet::<Ipv6, _>( |
| device, |
| Some(FrameDestination::Individual { local: true }), |
| buffer, |
| ); |
| } |
| |
| #[test] |
| fn test_ipv6_icmp_parameter_problem_non_must() { |
| let (mut ctx, device_ids) = FakeEventDispatcherBuilder::from_config(FAKE_CONFIG_V6).build(); |
| let device: DeviceId<_> = device_ids[0].clone().into(); |
| |
| // Test parsing an IPv6 packet with invalid next header value which |
| // we SHOULD send an ICMP response for (but we don't since its not a |
| // MUST). |
| |
| #[rustfmt::skip] |
| let bytes: &mut [u8] = &mut [ |
| // FixedHeader (will be replaced later) |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| |
| // Body |
| 1, 2, 3, 4, 5, |
| ][..]; |
| bytes[..4].copy_from_slice(&[0x60, 0x20, 0x00, 0x77][..]); |
| let payload_len = u16::try_from(bytes.len() - 40).unwrap(); |
| bytes[4..6].copy_from_slice(&payload_len.to_be_bytes()); |
| bytes[6] = 255; // Invalid Next Header |
| bytes[7] = 64; |
| bytes[8..24].copy_from_slice(FAKE_CONFIG_V6.remote_ip.bytes()); |
| bytes[24..40].copy_from_slice(FAKE_CONFIG_V6.local_ip.bytes()); |
| let buf = Buf::new(bytes, ..); |
| |
| ctx.test_api().receive_ip_packet::<Ipv6, _>( |
| &device, |
| Some(FrameDestination::Individual { local: true }), |
| buf, |
| ); |
| |
| assert_eq!(ctx.core_ctx.ipv4.icmp.inner.tx_counters.parameter_problem.get(), 0); |
| assert_eq!(ctx.core_ctx.ipv6.icmp.inner.tx_counters.parameter_problem.get(), 0); |
| assert_eq!(ctx.core_ctx.ipv4.inner.counters.dispatch_receive_ip_packet.get(), 0); |
| assert_eq!(ctx.core_ctx.ipv6.inner.counters.dispatch_receive_ip_packet.get(), 0); |
| } |
| |
| #[test] |
| fn test_ipv6_icmp_parameter_problem_must() { |
| let (mut ctx, device_ids) = FakeEventDispatcherBuilder::from_config(FAKE_CONFIG_V6).build(); |
| let device: DeviceId<_> = device_ids[0].clone().into(); |
| |
| // Test parsing an IPv6 packet where we MUST send an ICMP parameter problem |
| // response (invalid routing type for a routing extension header). |
| |
| #[rustfmt::skip] |
| let bytes: &mut [u8] = &mut [ |
| // FixedHeader (will be replaced later) |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| |
| // Routing Extension Header |
| IpProto::Udp.into(), // Next Header |
| 4, // Hdr Ext Len (In 8-octet units, not including first 8 octets) |
| 255, // Routing Type (Invalid) |
| 1, // Segments Left |
| 0, 0, 0, 0, // Reserved |
| // Addresses for Routing Header w/ Type 0 |
| 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, |
| 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, |
| |
| // Body |
| 1, 2, 3, 4, 5, |
| ][..]; |
| bytes[..4].copy_from_slice(&[0x60, 0x20, 0x00, 0x77][..]); |
| let payload_len = u16::try_from(bytes.len() - 40).unwrap(); |
| bytes[4..6].copy_from_slice(&payload_len.to_be_bytes()); |
| bytes[6] = Ipv6ExtHdrType::Routing.into(); |
| bytes[7] = 64; |
| bytes[8..24].copy_from_slice(FAKE_CONFIG_V6.remote_ip.bytes()); |
| bytes[24..40].copy_from_slice(FAKE_CONFIG_V6.local_ip.bytes()); |
| let buf = Buf::new(bytes, ..); |
| ctx.test_api().receive_ip_packet::<Ipv6, _>( |
| &device, |
| Some(FrameDestination::Individual { local: true }), |
| buf, |
| ); |
| let frames = ctx.bindings_ctx.take_ethernet_frames(); |
| let (_dev, frame) = assert_matches!(&frames[..], [frame] => frame); |
| verify_icmp_for_unrecognized_ext_hdr_option( |
| Icmpv6ParameterProblemCode::ErroneousHeaderField, |
| 42, |
| &frame[..], |
| ); |
| } |
| |
| #[test] |
| fn test_ipv6_unrecognized_ext_hdr_option() { |
| let (mut ctx, device_ids) = FakeEventDispatcherBuilder::from_config(FAKE_CONFIG_V6).build(); |
| let device: DeviceId<_> = device_ids[0].clone().into(); |
| let mut expected_icmps = 0; |
| let mut bytes = [0; 64]; |
| let frame_dst = FrameDestination::Individual { local: true }; |
| |
| // Test parsing an IPv6 packet where we MUST send an ICMP parameter |
| // problem due to an unrecognized extension header option. |
| |
| // Test with unrecognized option type set with action = skip & continue. |
| |
| let buf = buf_for_unrecognized_ext_hdr_option_test( |
| &mut bytes, |
| ExtensionHeaderOptionAction::SkipAndContinue, |
| false, |
| ); |
| ctx.test_api().receive_ip_packet::<Ipv6, _>(&device, Some(frame_dst), buf); |
| assert_eq!( |
| ctx.core_ctx.inner_icmp_state::<Ipv6>().tx_counters.parameter_problem.get(), |
| expected_icmps |
| ); |
| assert_eq!(ctx.core_ctx.ipv6.inner.counters.dispatch_receive_ip_packet.get(), 1); |
| assert_matches!(ctx.bindings_ctx.take_ethernet_frames()[..], []); |
| |
| // Test with unrecognized option type set with |
| // action = discard. |
| |
| let buf = buf_for_unrecognized_ext_hdr_option_test( |
| &mut bytes, |
| ExtensionHeaderOptionAction::DiscardPacket, |
| false, |
| ); |
| ctx.test_api().receive_ip_packet::<Ipv6, _>(&device, Some(frame_dst), buf); |
| assert_eq!( |
| ctx.core_ctx.inner_icmp_state::<Ipv6>().tx_counters.parameter_problem.get(), |
| expected_icmps |
| ); |
| assert_matches!(ctx.bindings_ctx.take_ethernet_frames()[..], []); |
| |
| // Test with unrecognized option type set with |
| // action = discard & send icmp |
| // where dest addr is a unicast addr. |
| |
| let buf = buf_for_unrecognized_ext_hdr_option_test( |
| &mut bytes, |
| ExtensionHeaderOptionAction::DiscardPacketSendIcmp, |
| false, |
| ); |
| ctx.test_api().receive_ip_packet::<Ipv6, _>(&device, Some(frame_dst), buf); |
| expected_icmps += 1; |
| assert_eq!( |
| ctx.core_ctx.inner_icmp_state::<Ipv6>().tx_counters.parameter_problem.get(), |
| expected_icmps |
| ); |
| let frames = ctx.bindings_ctx.take_ethernet_frames(); |
| let (_dev, frame) = assert_matches!(&frames[..], [frame] => frame); |
| verify_icmp_for_unrecognized_ext_hdr_option( |
| Icmpv6ParameterProblemCode::UnrecognizedIpv6Option, |
| 48, |
| &frame[..], |
| ); |
| |
| // Test with unrecognized option type set with |
| // action = discard & send icmp |
| // where dest addr is a multicast addr. |
| |
| let buf = buf_for_unrecognized_ext_hdr_option_test( |
| &mut bytes, |
| ExtensionHeaderOptionAction::DiscardPacketSendIcmp, |
| true, |
| ); |
| ctx.test_api().receive_ip_packet::<Ipv6, _>(&device, Some(frame_dst), buf); |
| expected_icmps += 1; |
| assert_eq!( |
| ctx.core_ctx.inner_icmp_state::<Ipv6>().tx_counters.parameter_problem.get(), |
| expected_icmps |
| ); |
| |
| let frames = ctx.bindings_ctx.take_ethernet_frames(); |
| let (_dev, frame) = assert_matches!(&frames[..], [frame] => frame); |
| verify_icmp_for_unrecognized_ext_hdr_option( |
| Icmpv6ParameterProblemCode::UnrecognizedIpv6Option, |
| 48, |
| &frame[..], |
| ); |
| |
| // Test with unrecognized option type set with |
| // action = discard & send icmp if not multicast addr |
| // where dest addr is a unicast addr. |
| |
| let buf = buf_for_unrecognized_ext_hdr_option_test( |
| &mut bytes, |
| ExtensionHeaderOptionAction::DiscardPacketSendIcmpNoMulticast, |
| false, |
| ); |
| ctx.test_api().receive_ip_packet::<Ipv6, _>(&device, Some(frame_dst), buf); |
| expected_icmps += 1; |
| assert_eq!( |
| ctx.core_ctx.inner_icmp_state::<Ipv6>().tx_counters.parameter_problem.get(), |
| expected_icmps |
| ); |
| |
| let frames = ctx.bindings_ctx.take_ethernet_frames(); |
| let (_dev, frame) = assert_matches!(&frames[..], [frame] => frame); |
| verify_icmp_for_unrecognized_ext_hdr_option( |
| Icmpv6ParameterProblemCode::UnrecognizedIpv6Option, |
| 48, |
| &frame[..], |
| ); |
| |
| // Test with unrecognized option type set with |
| // action = discard & send icmp if not multicast addr |
| // but dest addr is a multicast addr. |
| |
| let buf = buf_for_unrecognized_ext_hdr_option_test( |
| &mut bytes, |
| ExtensionHeaderOptionAction::DiscardPacketSendIcmpNoMulticast, |
| true, |
| ); |
| // Do not expect an ICMP response for this packet |
| ctx.test_api().receive_ip_packet::<Ipv6, _>(&device, Some(frame_dst), buf); |
| assert_eq!( |
| ctx.core_ctx.inner_icmp_state::<Ipv6>().tx_counters.parameter_problem.get(), |
| expected_icmps |
| ); |
| assert_matches!(ctx.bindings_ctx.take_ethernet_frames()[..], []); |
| |
| // None of our tests should have sent an icmpv4 packet, or dispatched an |
| // IP packet after the first. |
| |
| assert_eq!(ctx.core_ctx.inner_icmp_state::<Ipv4>().tx_counters.parameter_problem.get(), 0); |
| assert_eq!(ctx.core_ctx.ipv6.inner.counters.dispatch_receive_ip_packet.get(), 1); |
| } |
| |
| #[ip_test] |
| fn test_ip_packet_reassembly_not_needed<I: Ip + TestIpExt>() { |
| let (mut ctx, device_ids) = FakeEventDispatcherBuilder::from_config(I::FAKE_CONFIG).build(); |
| let device: DeviceId<_> = device_ids[0].clone().into(); |
| let fragment_id = 5; |
| |
| assert_eq!(ctx.core_ctx.ip_counters::<I>().dispatch_receive_ip_packet.get(), 0); |
| |
| // Test that a non fragmented packet gets dispatched right away. |
| |
| process_ip_fragment::<I>(&mut ctx, &device, fragment_id, 0, 1); |
| |
| // Make sure the packet got dispatched. |
| assert_eq!(ctx.core_ctx.ip_counters::<I>().dispatch_receive_ip_packet.get(), 1); |
| } |
| |
| #[ip_test] |
| fn test_ip_packet_reassembly<I: Ip + TestIpExt>() { |
| let (mut ctx, device_ids) = FakeEventDispatcherBuilder::from_config(I::FAKE_CONFIG).build(); |
| let device: DeviceId<_> = device_ids[0].clone().into(); |
| let fragment_id = 5; |
| |
| // Test that the received packet gets dispatched only after receiving |
| // all the fragments. |
| |
| // Process fragment #0 |
| process_ip_fragment::<I>(&mut ctx, &device, fragment_id, 0, 3); |
| |
| // Process fragment #1 |
| process_ip_fragment::<I>(&mut ctx, &device, fragment_id, 1, 3); |
| |
| // Make sure no packets got dispatched yet. |
| assert_eq!(ctx.core_ctx.ip_counters::<I>().dispatch_receive_ip_packet.get(), 0); |
| |
| // Process fragment #2 |
| process_ip_fragment::<I>(&mut ctx, &device, fragment_id, 2, 3); |
| |
| // Make sure the packet finally got dispatched now that the final |
| // fragment has been 'received'. |
| assert_eq!(ctx.core_ctx.ip_counters::<I>().dispatch_receive_ip_packet.get(), 1); |
| } |
| |
| #[ip_test] |
| fn test_ip_packet_reassembly_with_packets_arriving_out_of_order<I: Ip + TestIpExt>() { |
| let (mut ctx, device_ids) = FakeEventDispatcherBuilder::from_config(I::FAKE_CONFIG).build(); |
| let device: DeviceId<_> = device_ids[0].clone().into(); |
| let fragment_id_0 = 5; |
| let fragment_id_1 = 10; |
| let fragment_id_2 = 15; |
| |
| // Test that received packets gets dispatched only after receiving all |
| // the fragments with out of order arrival of fragments. |
| |
| // Process packet #0, fragment #1 |
| process_ip_fragment::<I>(&mut ctx, &device, fragment_id_0, 1, 3); |
| |
| // Process packet #1, fragment #2 |
| process_ip_fragment::<I>(&mut ctx, &device, fragment_id_1, 2, 3); |
| |
| // Process packet #1, fragment #0 |
| process_ip_fragment::<I>(&mut ctx, &device, fragment_id_1, 0, 3); |
| |
| // Make sure no packets got dispatched yet. |
| assert_eq!(ctx.core_ctx.ip_counters::<I>().dispatch_receive_ip_packet.get(), 0); |
| |
| // Process a packet that does not require reassembly (packet #2, fragment #0). |
| process_ip_fragment::<I>(&mut ctx, &device, fragment_id_2, 0, 1); |
| |
| // Make packet #1 got dispatched since it didn't need reassembly. |
| assert_eq!(ctx.core_ctx.ip_counters::<I>().dispatch_receive_ip_packet.get(), 1); |
| |
| // Process packet #0, fragment #2 |
| process_ip_fragment::<I>(&mut ctx, &device, fragment_id_0, 2, 3); |
| |
| // Make sure no other packets got dispatched yet. |
| assert_eq!(ctx.core_ctx.ip_counters::<I>().dispatch_receive_ip_packet.get(), 1); |
| |
| // Process packet #0, fragment #0 |
| process_ip_fragment::<I>(&mut ctx, &device, fragment_id_0, 0, 3); |
| |
| // Make sure that packet #0 finally got dispatched now that the final |
| // fragment has been 'received'. |
| assert_eq!(ctx.core_ctx.ip_counters::<I>().dispatch_receive_ip_packet.get(), 2); |
| |
| // Process packet #1, fragment #1 |
| process_ip_fragment::<I>(&mut ctx, &device, fragment_id_1, 1, 3); |
| |
| // Make sure the packet finally got dispatched now that the final |
| // fragment has been 'received'. |
| assert_eq!(ctx.core_ctx.ip_counters::<I>().dispatch_receive_ip_packet.get(), 3); |
| } |
| |
| #[ip_test] |
| fn test_ip_packet_reassembly_timer<I: Ip + TestIpExt>() |
| where |
| IpLayerTimerId: From<FragmentTimerId<I>>, |
| { |
| let (mut ctx, device_ids) = FakeEventDispatcherBuilder::from_config(I::FAKE_CONFIG).build(); |
| let device: DeviceId<_> = device_ids[0].clone().into(); |
| let fragment_id = 5; |
| |
| // Test to make sure that packets must arrive within the reassembly |
| // timer. |
| |
| // Process fragment #0 |
| process_ip_fragment::<I>(&mut ctx, &device, fragment_id, 0, 3); |
| |
| // Make sure a timer got added. |
| ctx.bindings_ctx.timer_ctx().assert_timers_installed_range([( |
| IpLayerTimerId::from(FragmentTimerId::<I>::default()).into(), |
| .., |
| )]); |
| |
| // Process fragment #1 |
| process_ip_fragment::<I>(&mut ctx, &device, fragment_id, 1, 3); |
| |
| assert_eq!( |
| ctx.trigger_next_timer().unwrap(), |
| IpLayerTimerId::from(FragmentTimerId::<I>::default()).into(), |
| ); |
| |
| // Make sure no other timers exist. |
| ctx.bindings_ctx.timer_ctx().assert_no_timers_installed(); |
| |
| // Process fragment #2 |
| process_ip_fragment::<I>(&mut ctx, &device, fragment_id, 2, 3); |
| |
| // Make sure no packets got dispatched yet since even though we |
| // technically received all the fragments, this fragment (#2) arrived |
| // too late and the reassembly timer was triggered, causing the prior |
| // fragment data to be discarded. |
| assert_eq!(ctx.core_ctx.ip_counters::<I>().dispatch_receive_ip_packet.get(), 0); |
| } |
| |
| #[ip_test] |
| #[netstack3_macros::context_ip_bounds(I, FakeBindingsCtx, crate)] |
| fn test_ip_reassembly_only_at_destination_host<I: Ip + TestIpExt + crate::IpExt>() { |
| // Create a new network with two parties (alice & bob) and enable IP |
| // packet routing for alice. |
| let a = "alice"; |
| let b = "bob"; |
| let fake_config = I::FAKE_CONFIG; |
| let (mut alice, alice_device_ids) = |
| FakeEventDispatcherBuilder::from_config(fake_config.swap()).build(); |
| { |
| set_forwarding_enabled::<_, I>(&mut alice, &alice_device_ids[0].clone().into(), true); |
| } |
| let (bob, bob_device_ids) = FakeEventDispatcherBuilder::from_config(fake_config).build(); |
| let mut net = crate::context::testutil::new_simple_fake_network( |
| a, |
| alice, |
| alice_device_ids[0].downgrade(), |
| b, |
| bob, |
| bob_device_ids[0].downgrade(), |
| ); |
| // Make sure the (strongly referenced) device IDs are dropped before |
| // `net`. |
| let alice_device_id: DeviceId<_> = alice_device_ids[0].clone().into(); |
| core::mem::drop((alice_device_ids, bob_device_ids)); |
| |
| let fragment_id = 5; |
| |
| // Test that packets only get reassembled and dispatched at the |
| // destination. In this test, Alice is receiving packets from some |
| // source that is actually destined for Bob. Alice should simply forward |
| // the packets without attempting to process or reassemble the |
| // fragments. |
| |
| // Process fragment #0 |
| net.with_context("alice", |ctx| { |
| process_ip_fragment::<I>(ctx, &alice_device_id, fragment_id, 0, 3); |
| }); |
| // Make sure the packet got sent from alice to bob |
| assert!(!net.step().is_idle()); |
| |
| // Process fragment #1 |
| net.with_context("alice", |ctx| { |
| process_ip_fragment::<I>(ctx, &alice_device_id, fragment_id, 1, 3); |
| }); |
| assert!(!net.step().is_idle()); |
| |
| // Make sure no packets got dispatched yet. |
| assert_eq!( |
| net.context("alice").core_ctx.ip_counters::<I>().dispatch_receive_ip_packet.get(), |
| 0 |
| ); |
| assert_eq!( |
| net.context("bob").core_ctx.ip_counters::<I>().dispatch_receive_ip_packet.get(), |
| 0 |
| ); |
| |
| // Process fragment #2 |
| net.with_context("alice", |ctx| { |
| process_ip_fragment::<I>(ctx, &alice_device_id, fragment_id, 2, 3); |
| }); |
| assert!(!net.step().is_idle()); |
| |
| // Make sure the packet finally got dispatched now that the final |
| // fragment has been received by bob. |
| assert_eq!( |
| net.context("alice").core_ctx.ip_counters::<I>().dispatch_receive_ip_packet.get(), |
| 0 |
| ); |
| assert_eq!( |
| net.context("bob").core_ctx.ip_counters::<I>().dispatch_receive_ip_packet.get(), |
| 1 |
| ); |
| |
| // Make sure there are no more events. |
| assert!(net.step().is_idle()); |
| } |
| |
| #[test] |
| fn test_ipv6_packet_too_big() { |
| // Test sending an IPv6 Packet Too Big Error when receiving a packet |
| // that is too big to be forwarded when it isn't destined for the node |
| // it arrived at. |
| |
| let fake_config = Ipv6::FAKE_CONFIG; |
| let mut dispatcher_builder = FakeEventDispatcherBuilder::from_config(fake_config.clone()); |
| let extra_ip = UnicastAddr::new(Ipv6Addr::from_bytes([ |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 168, 0, 100, |
| ])) |
| .unwrap(); |
| let extra_mac = UnicastAddr::new(Mac::new([12, 13, 14, 15, 16, 17])).unwrap(); |
| dispatcher_builder.add_ndp_table_entry(0, extra_ip, extra_mac); |
| dispatcher_builder.add_ndp_table_entry( |
| 0, |
| extra_mac.to_ipv6_link_local().addr().get(), |
| extra_mac, |
| ); |
| let (mut ctx, device_ids) = dispatcher_builder.build(); |
| |
| let device: DeviceId<_> = device_ids[0].clone().into(); |
| set_forwarding_enabled::<_, Ipv6>(&mut ctx, &device, true); |
| let frame_dst = FrameDestination::Individual { local: true }; |
| |
| // Construct an IPv6 packet that is too big for our MTU (MTU = 1280; |
| // body itself is 5000). Note, the final packet will be larger because |
| // of IP header data. |
| let mut rng = new_rng(70812476915813); |
| let body: Vec<u8> = core::iter::repeat_with(|| rng.gen()).take(5000).collect(); |
| |
| // Ip packet from some node destined to a remote on this network, |
| // arriving locally. |
| let mut ipv6_packet_buf = Buf::new(body, ..) |
| .encapsulate(Ipv6PacketBuilder::new( |
| extra_ip, |
| fake_config.remote_ip, |
| 64, |
| IpProto::Udp.into(), |
| )) |
| .serialize_vec_outer() |
| .unwrap(); |
| // Receive the IP packet. |
| ctx.test_api().receive_ip_packet::<Ipv6, _>( |
| &device, |
| Some(frame_dst), |
| ipv6_packet_buf.clone(), |
| ); |
| |
| let Ctx { core_ctx, bindings_ctx } = &mut ctx; |
| // Should not have dispatched the packet. |
| assert_eq!(core_ctx.ipv6.inner.counters.dispatch_receive_ip_packet.get(), 0); |
| assert_eq!(core_ctx.inner_icmp_state::<Ipv6>().tx_counters.packet_too_big.get(), 1); |
| |
| // Should have sent out one frame, and the received packet should be a |
| // Packet Too Big ICMP error message. |
| let frames = bindings_ctx.take_ethernet_frames(); |
| let (_dev, frame) = assert_matches!(&frames[..], [frame] => frame); |
| // The original packet's TTL gets decremented so we decrement here |
| // to validate the rest of the icmp message body. |
| let ipv6_packet_buf_mut: &mut [u8] = ipv6_packet_buf.as_mut(); |
| ipv6_packet_buf_mut[7] -= 1; |
| let (_, _, _, _, _, message, code) = |
| parse_icmp_packet_in_ip_packet_in_ethernet_frame::<Ipv6, _, Icmpv6PacketTooBig, _>( |
| &frame[..], |
| EthernetFrameLengthCheck::NoCheck, |
| move |packet| { |
| // Size of the ICMP message body should be size of the |
| // MTU without IP and ICMP headers. |
| let expected_len = 1280 - 48; |
| let actual_body: &[u8] = ipv6_packet_buf.as_ref(); |
| let actual_body = &actual_body[..expected_len]; |
| assert_eq!(packet.body().len(), expected_len); |
| assert_eq!(packet.body().bytes(), actual_body); |
| }, |
| ) |
| .unwrap(); |
| assert_eq!(code, IcmpUnusedCode); |
| // MTU should match the MTU for the link. |
| assert_eq!(message, Icmpv6PacketTooBig::new(1280)); |
| } |
| |
| fn create_packet_too_big_buf<A: IpAddress>( |
| src_ip: A, |
| dst_ip: A, |
| mtu: u16, |
| body: Option<Buf<Vec<u8>>>, |
| ) -> Buf<Vec<u8>> { |
| let body = body.unwrap_or_else(|| Buf::new(Vec::new(), ..)); |
| |
| match [src_ip, dst_ip].into() { |
| IpAddr::V4([src_ip, dst_ip]) => body |
| .encapsulate(IcmpPacketBuilder::<Ipv4, IcmpDestUnreachable>::new( |
| dst_ip, |
| src_ip, |
| Icmpv4DestUnreachableCode::FragmentationRequired, |
| NonZeroU16::new(mtu) |
| .map(IcmpDestUnreachable::new_for_frag_req) |
| .unwrap_or_else(Default::default), |
| )) |
| .encapsulate(Ipv4PacketBuilder::new(src_ip, dst_ip, 64, Ipv4Proto::Icmp)) |
| .serialize_vec_outer() |
| .unwrap(), |
| IpAddr::V6([src_ip, dst_ip]) => body |
| .encapsulate(IcmpPacketBuilder::<Ipv6, Icmpv6PacketTooBig>::new( |
| dst_ip, |
| src_ip, |
| IcmpUnusedCode, |
| Icmpv6PacketTooBig::new(u32::from(mtu)), |
| )) |
| .encapsulate(Ipv6PacketBuilder::new(src_ip, dst_ip, 64, Ipv6Proto::Icmpv6)) |
| .serialize_vec_outer() |
| .unwrap(), |
| } |
| .into_inner() |
| } |
| |
| trait GetPmtuIpExt: Ip { |
| fn get_pmtu<BC: BindingsContext>( |
| state: &StackState<BC>, |
| local_ip: Self::Addr, |
| remote_ip: Self::Addr, |
| ) -> Option<Mtu>; |
| } |
| |
| impl GetPmtuIpExt for Ipv4 { |
| fn get_pmtu<BC: BindingsContext>( |
| state: &StackState<BC>, |
| local_ip: Ipv4Addr, |
| remote_ip: Ipv4Addr, |
| ) -> Option<Mtu> { |
| state.ipv4.inner.pmtu_cache.lock().get_pmtu(local_ip, remote_ip) |
| } |
| } |
| |
| impl GetPmtuIpExt for Ipv6 { |
| fn get_pmtu<BC: BindingsContext>( |
| state: &StackState<BC>, |
| local_ip: Ipv6Addr, |
| remote_ip: Ipv6Addr, |
| ) -> Option<Mtu> { |
| state.ipv6.inner.pmtu_cache.lock().get_pmtu(local_ip, remote_ip) |
| } |
| } |
| |
| #[ip_test] |
| fn test_ip_update_pmtu<I: Ip + TestIpExt + GetPmtuIpExt>() { |
| // Test receiving a Packet Too Big (IPv6) or Dest Unreachable |
| // Fragmentation Required (IPv4) which should update the PMTU if it is |
| // less than the current value. |
| |
| let fake_config = I::FAKE_CONFIG; |
| let (mut ctx, device_ids) = |
| FakeEventDispatcherBuilder::from_config(fake_config.clone()).build(); |
| let device: DeviceId<_> = device_ids[0].clone().into(); |
| let frame_dst = FrameDestination::Individual { local: true }; |
| |
| // Update PMTU from None. |
| |
| let new_mtu1 = Mtu::new(u32::from(I::MINIMUM_LINK_MTU) + 100); |
| |
| // Create ICMP IP buf |
| let packet_buf = create_packet_too_big_buf( |
| fake_config.remote_ip.get(), |
| fake_config.local_ip.get(), |
| u16::try_from(u32::from(new_mtu1)).unwrap(), |
| None, |
| ); |
| |
| // Receive the IP packet. |
| ctx.test_api().receive_ip_packet::<I, _>(&device, Some(frame_dst), packet_buf); |
| |
| // Should have dispatched the packet. |
| assert_eq!(ctx.core_ctx.ip_counters::<I>().dispatch_receive_ip_packet.get(), 1); |
| |
| assert_eq!( |
| I::get_pmtu(&ctx.core_ctx, fake_config.local_ip.get(), fake_config.remote_ip.get()) |
| .unwrap(), |
| new_mtu1 |
| ); |
| |
| // Don't update PMTU when current PMTU is less than reported MTU. |
| |
| let new_mtu2 = Mtu::new(u32::from(I::MINIMUM_LINK_MTU) + 200); |
| |
| // Create IPv6 ICMPv6 packet too big packet with MTU larger than current |
| // PMTU. |
| let packet_buf = create_packet_too_big_buf( |
| fake_config.remote_ip.get(), |
| fake_config.local_ip.get(), |
| u16::try_from(u32::from(new_mtu2)).unwrap(), |
| None, |
| ); |
| |
| // Receive the IP packet. |
| ctx.test_api().receive_ip_packet::<I, _>(&device, Some(frame_dst), packet_buf); |
| |
| // Should have dispatched the packet. |
| assert_eq!(ctx.core_ctx.ip_counters::<I>().dispatch_receive_ip_packet.get(), 2); |
| |
| // The PMTU should not have updated to `new_mtu2` |
| assert_eq!( |
| I::get_pmtu(&ctx.core_ctx, fake_config.local_ip.get(), fake_config.remote_ip.get()) |
| .unwrap(), |
| new_mtu1 |
| ); |
| |
| // Update PMTU when current PMTU is greater than the reported MTU. |
| |
| let new_mtu3 = Mtu::new(u32::from(I::MINIMUM_LINK_MTU) + 50); |
| |
| // Create IPv6 ICMPv6 packet too big packet with MTU smaller than |
| // current PMTU. |
| let packet_buf = create_packet_too_big_buf( |
| fake_config.remote_ip.get(), |
| fake_config.local_ip.get(), |
| u16::try_from(u32::from(new_mtu3)).unwrap(), |
| None, |
| ); |
| |
| // Receive the IP packet. |
| ctx.test_api().receive_ip_packet::<I, _>(&device, Some(frame_dst), packet_buf); |
| |
| // Should have dispatched the packet. |
| assert_eq!(ctx.core_ctx.ip_counters::<I>().dispatch_receive_ip_packet.get(), 3); |
| |
| // The PMTU should have updated to 1900. |
| assert_eq!( |
| I::get_pmtu(&ctx.core_ctx, fake_config.local_ip.get(), fake_config.remote_ip.get()) |
| .unwrap(), |
| new_mtu3 |
| ); |
| } |
| |
| #[ip_test] |
| fn test_ip_update_pmtu_too_low<I: Ip + TestIpExt + GetPmtuIpExt>() { |
| // Test receiving a Packet Too Big (IPv6) or Dest Unreachable |
| // Fragmentation Required (IPv4) which should not update the PMTU if it |
| // is less than the min MTU. |
| |
| let fake_config = I::FAKE_CONFIG; |
| let (mut ctx, device_ids) = |
| FakeEventDispatcherBuilder::from_config(fake_config.clone()).build(); |
| let device: DeviceId<_> = device_ids[0].clone().into(); |
| let frame_dst = FrameDestination::Individual { local: true }; |
| |
| // Update PMTU from None but with an MTU too low. |
| |
| let new_mtu1 = u32::from(I::MINIMUM_LINK_MTU) - 1; |
| |
| // Create ICMP IP buf |
| let packet_buf = create_packet_too_big_buf( |
| fake_config.remote_ip.get(), |
| fake_config.local_ip.get(), |
| u16::try_from(new_mtu1).unwrap(), |
| None, |
| ); |
| |
| // Receive the IP packet. |
| ctx.test_api().receive_ip_packet::<I, _>(&device, Some(frame_dst), packet_buf); |
| |
| // Should have dispatched the packet. |
| assert_eq!(ctx.core_ctx.ip_counters::<I>().dispatch_receive_ip_packet.get(), 1); |
| |
| assert_eq!( |
| I::get_pmtu(&ctx.core_ctx, fake_config.local_ip.get(), fake_config.remote_ip.get()), |
| None |
| ); |
| } |
| |
| /// Create buffer to be used as the ICMPv4 message body |
| /// where the original packet's body length is `body_len`. |
| fn create_orig_packet_buf(src_ip: Ipv4Addr, dst_ip: Ipv4Addr, body_len: usize) -> Buf<Vec<u8>> { |
| Buf::new(vec![0; body_len], ..) |
| .encapsulate(Ipv4PacketBuilder::new(src_ip, dst_ip, 64, IpProto::Udp.into())) |
| .serialize_vec_outer() |
| .unwrap() |
| .into_inner() |
| } |
| |
| #[test] |
| fn test_ipv4_remote_no_rfc1191() { |
| // Test receiving an IPv4 Dest Unreachable Fragmentation |
| // Required from a node that does not implement RFC 1191. |
| |
| let fake_config = Ipv4::FAKE_CONFIG; |
| let (mut ctx, device_ids) = |
| FakeEventDispatcherBuilder::from_config(fake_config.clone()).build(); |
| let device: DeviceId<_> = device_ids[0].clone().into(); |
| let frame_dst = FrameDestination::Individual { local: true }; |
| |
| // Update from None. |
| |
| // Create ICMP IP buf w/ orig packet body len = 500; orig packet len = |
| // 520 |
| let packet_buf = create_packet_too_big_buf( |
| fake_config.remote_ip.get(), |
| fake_config.local_ip.get(), |
| 0, // A 0 value indicates that the source of the |
| // ICMP message does not implement RFC 1191. |
| create_orig_packet_buf(fake_config.local_ip.get(), fake_config.remote_ip.get(), 500) |
| .into(), |
| ); |
| |
| // Receive the IP packet. |
| ctx.test_api().receive_ip_packet::<Ipv4, _>(&device, Some(frame_dst), packet_buf); |
| |
| // Should have dispatched the packet. |
| assert_eq!(ctx.core_ctx.ipv4.inner.counters.dispatch_receive_ip_packet.get(), 1); |
| |
| // Should have decreased PMTU value to the next lower PMTU |
| // plateau from `crate::ip::path_mtu::PMTU_PLATEAUS`. |
| assert_eq!( |
| Ipv4::get_pmtu(&ctx.core_ctx, fake_config.local_ip.get(), fake_config.remote_ip.get()) |
| .unwrap(), |
| Mtu::new(508), |
| ); |
| |
| // Don't Update when packet size is too small. |
| |
| // Create ICMP IP buf w/ orig packet body len = 1; orig packet len = 21 |
| let packet_buf = create_packet_too_big_buf( |
| fake_config.remote_ip.get(), |
| fake_config.local_ip.get(), |
| 0, |
| create_orig_packet_buf(fake_config.local_ip.get(), fake_config.remote_ip.get(), 1) |
| .into(), |
| ); |
| |
| // Receive the IP packet. |
| ctx.test_api().receive_ip_packet::<Ipv4, _>(&device, Some(frame_dst), packet_buf); |
| |
| // Should have dispatched the packet. |
| assert_eq!(ctx.core_ctx.ipv4.inner.counters.dispatch_receive_ip_packet.get(), 2); |
| |
| // Should not have updated PMTU as there is no other valid |
| // lower PMTU value. |
| assert_eq!( |
| Ipv4::get_pmtu(&ctx.core_ctx, fake_config.local_ip.get(), fake_config.remote_ip.get()) |
| .unwrap(), |
| Mtu::new(508), |
| ); |
| |
| // Update to lower PMTU estimate based on original packet size. |
| |
| // Create ICMP IP buf w/ orig packet body len = 60; orig packet len = 80 |
| let packet_buf = create_packet_too_big_buf( |
| fake_config.remote_ip.get(), |
| fake_config.local_ip.get(), |
| 0, |
| create_orig_packet_buf(fake_config.local_ip.get(), fake_config.remote_ip.get(), 60) |
| .into(), |
| ); |
| |
| // Receive the IP packet. |
| ctx.test_api().receive_ip_packet::<Ipv4, _>(&device, Some(frame_dst), packet_buf); |
| |
| // Should have dispatched the packet. |
| assert_eq!(ctx.core_ctx.ipv4.inner.counters.dispatch_receive_ip_packet.get(), 3); |
| |
| // Should have decreased PMTU value to the next lower PMTU |
| // plateau from `crate::ip::path_mtu::PMTU_PLATEAUS`. |
| assert_eq!( |
| Ipv4::get_pmtu(&ctx.core_ctx, fake_config.local_ip.get(), fake_config.remote_ip.get()) |
| .unwrap(), |
| Mtu::new(68), |
| ); |
| |
| // Should not update PMTU because the next low PMTU from this original |
| // packet size is higher than current PMTU. |
| |
| // Create ICMP IP buf w/ orig packet body len = 290; orig packet len = |
| // 310 |
| let packet_buf = create_packet_too_big_buf( |
| fake_config.remote_ip.get(), |
| fake_config.local_ip.get(), |
| 0, // A 0 value indicates that the source of the |
| // ICMP message does not implement RFC 1191. |
| create_orig_packet_buf(fake_config.local_ip.get(), fake_config.remote_ip.get(), 290) |
| .into(), |
| ); |
| |
| // Receive the IP packet. |
| ctx.test_api().receive_ip_packet::<Ipv4, _>(&device, Some(frame_dst), packet_buf); |
| |
| // Should have dispatched the packet. |
| assert_eq!(ctx.core_ctx.ipv4.inner.counters.dispatch_receive_ip_packet.get(), 4); |
| |
| // Should not have updated the PMTU as the current PMTU is lower. |
| assert_eq!( |
| Ipv4::get_pmtu(&ctx.core_ctx, fake_config.local_ip.get(), fake_config.remote_ip.get()) |
| .unwrap(), |
| Mtu::new(68), |
| ); |
| } |
| |
| #[test] |
| fn test_invalid_icmpv4_in_ipv6() { |
| let ip_config = Ipv6::FAKE_CONFIG; |
| let (mut ctx, device_ids) = |
| FakeEventDispatcherBuilder::from_config(ip_config.clone()).build(); |
| let device: DeviceId<_> = device_ids[0].clone().into(); |
| let frame_dst = FrameDestination::Individual { local: true }; |
| |
| let ic_config = Ipv4::FAKE_CONFIG; |
| let icmp_builder = IcmpPacketBuilder::<Ipv4, _>::new( |
| ic_config.remote_ip, |
| ic_config.local_ip, |
| IcmpUnusedCode, |
| IcmpEchoRequest::new(0, 0), |
| ); |
| |
| let ip_builder = Ipv6PacketBuilder::new( |
| ip_config.remote_ip, |
| ip_config.local_ip, |
| 64, |
| Ipv6Proto::Other(Ipv4Proto::Icmp.into()), |
| ); |
| |
| let buf = Buf::new(Vec::new(), ..) |
| .encapsulate(icmp_builder) |
| .encapsulate(ip_builder) |
| .serialize_vec_outer() |
| .unwrap(); |
| |
| crate::device::testutil::enable_device(&mut ctx, &device); |
| ctx.test_api().receive_ip_packet::<Ipv6, _>(&device, Some(frame_dst), buf); |
| |
| let Ctx { core_ctx, bindings_ctx } = &mut ctx; |
| // Should not have dispatched the packet. |
| assert_eq!(core_ctx.ipv6.inner.counters.receive_ip_packet.get(), 1); |
| assert_eq!(core_ctx.ipv6.inner.counters.dispatch_receive_ip_packet.get(), 0); |
| |
| // In IPv6, the next header value (ICMP(v4)) would have been considered |
| // unrecognized so an ICMP parameter problem response SHOULD be sent, |
| // but the netstack chooses to just drop the packet since we are not |
| // required to send the ICMP response. |
| assert_matches!(bindings_ctx.take_ethernet_frames()[..], []); |
| } |
| |
| #[test] |
| fn test_invalid_icmpv6_in_ipv4() { |
| let ip_config = Ipv4::FAKE_CONFIG; |
| let (mut ctx, device_ids) = |
| FakeEventDispatcherBuilder::from_config(ip_config.clone()).build(); |
| // First possible device id. |
| let device: DeviceId<_> = device_ids[0].clone().into(); |
| let frame_dst = FrameDestination::Individual { local: true }; |
| |
| let ic_config = Ipv6::FAKE_CONFIG; |
| let icmp_builder = IcmpPacketBuilder::<Ipv6, _>::new( |
| ic_config.remote_ip, |
| ic_config.local_ip, |
| IcmpUnusedCode, |
| IcmpEchoRequest::new(0, 0), |
| ); |
| |
| let ip_builder = Ipv4PacketBuilder::new( |
| ip_config.remote_ip, |
| ip_config.local_ip, |
| 64, |
| Ipv4Proto::Other(Ipv6Proto::Icmpv6.into()), |
| ); |
| |
| let buf = Buf::new(Vec::new(), ..) |
| .encapsulate(icmp_builder) |
| .encapsulate(ip_builder) |
| .serialize_vec_outer() |
| .unwrap(); |
| |
| ctx.test_api().receive_ip_packet::<Ipv4, _>(&device, Some(frame_dst), buf); |
| |
| // Should have dispatched the packet but resulted in an ICMP error. |
| assert_eq!(ctx.core_ctx.ipv4.inner.counters.dispatch_receive_ip_packet.get(), 1); |
| assert_eq!(ctx.core_ctx.inner_icmp_state::<Ipv4>().tx_counters.dest_unreachable.get(), 1); |
| let frames = ctx.bindings_ctx.take_ethernet_frames(); |
| let (_dev, frame) = assert_matches!(&frames[..], [frame] => frame); |
| let (_, _, _, _, _, _, code) = |
| parse_icmp_packet_in_ip_packet_in_ethernet_frame::<Ipv4, _, IcmpDestUnreachable, _>( |
| &frame[..], |
| EthernetFrameLengthCheck::NoCheck, |
| |_| {}, |
| ) |
| .unwrap(); |
| assert_eq!(code, Icmpv4DestUnreachableCode::DestProtocolUnreachable); |
| } |
| |
| #[ip_test] |
| #[netstack3_macros::context_ip_bounds(I, FakeBindingsCtx, crate)] |
| fn test_joining_leaving_ip_multicast_group<I: Ip + TestIpExt + crate::IpExt>() { |
| // Test receiving a packet destined to a multicast IP (and corresponding |
| // multicast MAC). |
| |
| let config = I::FAKE_CONFIG; |
| let (mut ctx, device_ids) = FakeEventDispatcherBuilder::from_config(config.clone()).build(); |
| let eth_device = &device_ids[0]; |
| let device: DeviceId<_> = eth_device.clone().into(); |
| let multi_addr = I::get_multicast_addr(3).get(); |
| let dst_mac = Mac::from(&MulticastAddr::new(multi_addr).unwrap()); |
| let buf = Buf::new(vec![0; 10], ..) |
| .encapsulate(I::PacketBuilder::new( |
| config.remote_ip.get(), |
| multi_addr, |
| 64, |
| IpProto::Udp.into(), |
| )) |
| .encapsulate(EthernetFrameBuilder::new( |
| config.remote_mac.get(), |
| dst_mac, |
| I::ETHER_TYPE, |
| ETHERNET_MIN_BODY_LEN_NO_TAG, |
| )) |
| .serialize_vec_outer() |
| .ok() |
| .unwrap() |
| .into_inner(); |
| |
| let multi_addr = MulticastAddr::new(multi_addr).unwrap(); |
| // Should not have dispatched the packet since we are not in the |
| // multicast group `multi_addr`. |
| |
| assert!(!ctx.test_api().is_in_ip_multicast(&device, multi_addr)); |
| ctx.core_api() |
| .device::<EthernetLinkDevice>() |
| .receive_frame(RecvEthernetFrameMeta { device_id: eth_device.clone() }, buf.clone()); |
| |
| let Ctx { core_ctx, bindings_ctx } = &mut ctx; |
| assert_eq!(core_ctx.ip_counters::<I>().dispatch_receive_ip_packet.get(), 0); |
| |
| // Join the multicast group and receive the packet, we should dispatch |
| // it. |
| match multi_addr.into() { |
| IpAddr::V4(multicast_addr) => crate::ip::device::join_ip_multicast::<Ipv4, _, _>( |
| &mut core_ctx.context(), |
| bindings_ctx, |
| &device, |
| multicast_addr, |
| ), |
| IpAddr::V6(multicast_addr) => crate::ip::device::join_ip_multicast::<Ipv6, _, _>( |
| &mut core_ctx.context(), |
| bindings_ctx, |
| &device, |
| multicast_addr, |
| ), |
| } |
| assert!(ctx.test_api().is_in_ip_multicast(&device, multi_addr)); |
| ctx.core_api() |
| .device::<EthernetLinkDevice>() |
| .receive_frame(RecvEthernetFrameMeta { device_id: eth_device.clone() }, buf.clone()); |
| |
| let Ctx { core_ctx, bindings_ctx } = &mut ctx; |
| assert_eq!(core_ctx.ip_counters::<I>().dispatch_receive_ip_packet.get(), 1); |
| |
| // Leave the multicast group and receive the packet, we should not |
| // dispatch it. |
| match multi_addr.into() { |
| IpAddr::V4(multicast_addr) => crate::ip::device::leave_ip_multicast::<Ipv4, _, _>( |
| &mut core_ctx.context(), |
| bindings_ctx, |
| &device, |
| multicast_addr, |
| ), |
| IpAddr::V6(multicast_addr) => crate::ip::device::leave_ip_multicast::<Ipv6, _, _>( |
| &mut core_ctx.context(), |
| bindings_ctx, |
| &device, |
| multicast_addr, |
| ), |
| } |
| assert!(!ctx.test_api().is_in_ip_multicast(&device, multi_addr)); |
| ctx.core_api() |
| .device::<EthernetLinkDevice>() |
| .receive_frame(RecvEthernetFrameMeta { device_id: eth_device.clone() }, buf); |
| assert_eq!(ctx.core_ctx.ip_counters::<I>().dispatch_receive_ip_packet.get(), 1); |
| } |
| |
| #[test] |
| fn test_no_dispatch_non_ndp_packets_during_ndp_dad() { |
| // Here we make sure we are not dispatching packets destined to a |
| // tentative address (that is performing NDP's Duplicate Address |
| // Detection (DAD)) -- IPv6 only. |
| |
| let config = Ipv6::FAKE_CONFIG; |
| let mut ctx = crate::testutil::FakeCtx::default(); |
| let device = ctx |
| .core_api() |
| .device::<EthernetLinkDevice>() |
| .add_device_with_default_state( |
| EthernetCreationProperties { |
| mac: config.local_mac, |
| max_frame_size: IPV6_MIN_IMPLIED_MAX_FRAME_SIZE, |
| }, |
| DEFAULT_INTERFACE_METRIC, |
| ) |
| .into(); |
| |
| let _: Ipv6DeviceConfigurationUpdate = ctx |
| .core_api() |
| .device_ip::<Ipv6>() |
| .update_configuration( |
| &device, |
| Ipv6DeviceConfigurationUpdate { |
| // Doesn't matter as long as DAD is enabled. |
| dad_transmits: Some(NonZeroU16::new(1)), |
| // Auto-generate a link-local address. |
| slaac_config: Some(SlaacConfiguration { |
| enable_stable_addresses: true, |
| ..Default::default() |
| }), |
| ip_config: IpDeviceConfigurationUpdate { |
| ip_enabled: Some(true), |
| ..Default::default() |
| }, |
| ..Default::default() |
| }, |
| ) |
| .unwrap(); |
| |
| let frame_dst = FrameDestination::Individual { local: true }; |
| |
| let ip: Ipv6Addr = config.local_mac.to_ipv6_link_local().addr().get(); |
| |
| let buf = Buf::new(vec![0; 10], ..) |
| .encapsulate(Ipv6PacketBuilder::new(config.remote_ip, ip, 64, IpProto::Udp.into())) |
| .serialize_vec_outer() |
| .unwrap() |
| .into_inner(); |
| |
| // Received packet should not have been dispatched. |
| ctx.test_api().receive_ip_packet::<Ipv6, _>(&device, Some(frame_dst), buf.clone()); |
| |
| assert_eq!(ctx.core_ctx.ipv6.inner.counters.dispatch_receive_ip_packet.get(), 0); |
| |
| // Wait until DAD is complete. Arbitrarily choose a year in the future |
| // as a time after which we're confident DAD will be complete. We can't |
| // run until there are no timers because some timers will always exist |
| // for background tasks. |
| // |
| // TODO(https://fxbug.dev/42125450): Once this test is contextified, use a |
| // more precise condition to ensure that DAD is complete. |
| let now = ctx.bindings_ctx.now(); |
| let _: Vec<_> = |
| ctx.trigger_timers_until_instant(now + Duration::from_secs(60 * 60 * 24 * 365)); |
| |
| // Received packet should have been dispatched. |
| ctx.test_api().receive_ip_packet::<Ipv6, _>(&device, Some(frame_dst), buf); |
| assert_eq!(ctx.core_ctx.ipv6.inner.counters.dispatch_receive_ip_packet.get(), 1); |
| |
| // Set the new IP (this should trigger DAD). |
| let ip = config.local_ip.get(); |
| ctx.core_api() |
| .device_ip::<Ipv6>() |
| .add_ip_addr_subnet(&device, AddrSubnet::new(ip, 128).unwrap()) |
| .unwrap(); |
| |
| let buf = Buf::new(vec![0; 10], ..) |
| .encapsulate(Ipv6PacketBuilder::new(config.remote_ip, ip, 64, IpProto::Udp.into())) |
| .serialize_vec_outer() |
| .unwrap() |
| .into_inner(); |
| |
| // Received packet should not have been dispatched. |
| ctx.test_api().receive_ip_packet::<Ipv6, _>(&device, Some(frame_dst), buf.clone()); |
| assert_eq!(ctx.core_ctx.ipv6.inner.counters.dispatch_receive_ip_packet.get(), 1); |
| |
| // Make sure all timers are done (DAD to complete on the interface due |
| // to new IP). |
| // |
| // TODO(https://fxbug.dev/42125450): Once this test is contextified, use a |
| // more precise condition to ensure that DAD is complete. |
| let _: Vec<_> = ctx.trigger_timers_until_instant(FakeInstant::LATEST); |
| |
| // Received packet should have been dispatched. |
| ctx.test_api().receive_ip_packet::<Ipv6, _>(&device, Some(frame_dst), buf); |
| assert_eq!(ctx.core_ctx.ipv6.inner.counters.dispatch_receive_ip_packet.get(), 2); |
| } |
| |
| #[test] |
| fn test_drop_non_unicast_ipv6_source() { |
| // Test that an inbound IPv6 packet with a non-unicast source address is |
| // dropped. |
| let cfg = FAKE_CONFIG_V6; |
| let (mut ctx, _device_ids) = FakeEventDispatcherBuilder::from_config(cfg.clone()).build(); |
| let device = ctx |
| .core_api() |
| .device::<EthernetLinkDevice>() |
| .add_device_with_default_state( |
| EthernetCreationProperties { |
| mac: cfg.local_mac, |
| max_frame_size: IPV6_MIN_IMPLIED_MAX_FRAME_SIZE, |
| }, |
| DEFAULT_INTERFACE_METRIC, |
| ) |
| .into(); |
| crate::device::testutil::enable_device(&mut ctx, &device); |
| |
| let ip: Ipv6Addr = cfg.local_mac.to_ipv6_link_local().addr().get(); |
| let buf = Buf::new(vec![0; 10], ..) |
| .encapsulate(Ipv6PacketBuilder::new( |
| Ipv6::MULTICAST_SUBNET.network(), |
| ip, |
| 64, |
| IpProto::Udp.into(), |
| )) |
| .serialize_vec_outer() |
| .unwrap() |
| .into_inner(); |
| |
| ctx.test_api().receive_ip_packet::<Ipv6, _>( |
| &device, |
| Some(FrameDestination::Individual { local: true }), |
| buf, |
| ); |
| assert_eq!(ctx.core_ctx.ipv6.inner.counters.version_rx.non_unicast_source.get(), 1); |
| } |
| |
| #[test] |
| fn test_receive_ip_packet_action() { |
| let v4_config = Ipv4::FAKE_CONFIG; |
| let v6_config = Ipv6::FAKE_CONFIG; |
| |
| let mut builder = FakeEventDispatcherBuilder::default(); |
| // Both devices have the same MAC address, which is a bit weird, but not |
| // a problem for this test. |
| let v4_subnet = AddrSubnet::from_witness(v4_config.local_ip, 16).unwrap().subnet(); |
| let dev_idx0 = |
| builder.add_device_with_ip(v4_config.local_mac, v4_config.local_ip.get(), v4_subnet); |
| let dev_idx1 = builder.add_device_with_ip_and_config( |
| v6_config.local_mac, |
| v6_config.local_ip.get(), |
| AddrSubnet::from_witness(v6_config.local_ip, 64).unwrap().subnet(), |
| Ipv4DeviceConfigurationUpdate::default(), |
| Ipv6DeviceConfigurationUpdate { |
| // Auto-generate a link-local address. |
| slaac_config: Some(SlaacConfiguration { |
| enable_stable_addresses: true, |
| ..Default::default() |
| }), |
| ..Default::default() |
| }, |
| ); |
| let (mut ctx, device_ids) = builder.clone().build(); |
| let v4_dev: DeviceId<_> = device_ids[dev_idx0].clone().into(); |
| let v6_dev: DeviceId<_> = device_ids[dev_idx1].clone().into(); |
| |
| let Ctx { core_ctx, bindings_ctx } = &mut ctx; |
| |
| // Receive packet addressed to us. |
| assert_eq!( |
| receive_ipv4_packet_action( |
| &mut core_ctx.context(), |
| bindings_ctx, |
| &v4_dev, |
| v4_config.local_ip |
| ), |
| ReceivePacketAction::Deliver |
| ); |
| assert_eq!( |
| receive_ipv6_packet_action( |
| &mut core_ctx.context(), |
| bindings_ctx, |
| &v6_dev, |
| v6_config.local_ip |
| ), |
| ReceivePacketAction::Deliver |
| ); |
| |
| // Receive packet addressed to the IPv4 subnet broadcast address. |
| assert_eq!( |
| receive_ipv4_packet_action( |
| &mut core_ctx.context(), |
| bindings_ctx, |
| &v4_dev, |
| SpecifiedAddr::new(v4_subnet.broadcast()).unwrap() |
| ), |
| ReceivePacketAction::Deliver |
| ); |
| |
| // Receive packet addressed to the IPv4 limited broadcast address. |
| assert_eq!( |
| receive_ipv4_packet_action( |
| &mut core_ctx.context(), |
| bindings_ctx, |
| &v4_dev, |
| Ipv4::LIMITED_BROADCAST_ADDRESS |
| ), |
| ReceivePacketAction::Deliver |
| ); |
| |
| // Receive packet addressed to a multicast address we're subscribed to. |
| crate::ip::device::join_ip_multicast::<Ipv4, _, _>( |
| &mut core_ctx.context(), |
| bindings_ctx, |
| &v4_dev, |
| Ipv4::ALL_ROUTERS_MULTICAST_ADDRESS, |
| ); |
| assert_eq!( |
| receive_ipv4_packet_action( |
| &mut core_ctx.context(), |
| bindings_ctx, |
| &v4_dev, |
| Ipv4::ALL_ROUTERS_MULTICAST_ADDRESS.into_specified() |
| ), |
| ReceivePacketAction::Deliver |
| ); |
| |
| // Receive packet addressed to the all-nodes multicast address. |
| assert_eq!( |
| receive_ipv6_packet_action( |
| &mut core_ctx.context(), |
| bindings_ctx, |
| &v6_dev, |
| Ipv6::ALL_NODES_LINK_LOCAL_MULTICAST_ADDRESS.into_specified() |
| ), |
| ReceivePacketAction::Deliver |
| ); |
| |
| // Receive packet addressed to a multicast address we're subscribed to. |
| assert_eq!( |
| receive_ipv6_packet_action( |
| &mut core_ctx.context(), |
| bindings_ctx, |
| &v6_dev, |
| v6_config.local_ip.to_solicited_node_address().into_specified(), |
| ), |
| ReceivePacketAction::Deliver |
| ); |
| |
| // Receive packet addressed to a tentative address. |
| { |
| // Construct a one-off context that has DAD enabled. The context |
| // built above has DAD disabled, and so addresses start off in the |
| // assigned state rather than the tentative state. |
| let mut ctx = FakeCtx::default(); |
| let local_mac = v6_config.local_mac; |
| let eth_device = |
| ctx.core_api().device::<EthernetLinkDevice>().add_device_with_default_state( |
| EthernetCreationProperties { |
| mac: local_mac, |
| max_frame_size: IPV6_MIN_IMPLIED_MAX_FRAME_SIZE, |
| }, |
| DEFAULT_INTERFACE_METRIC, |
| ); |
| let device = eth_device.clone().into(); |
| let _: Ipv6DeviceConfigurationUpdate = ctx |
| .core_api() |
| .device_ip::<Ipv6>() |
| .update_configuration( |
| &device, |
| Ipv6DeviceConfigurationUpdate { |
| // Doesn't matter as long as DAD is enabled. |
| dad_transmits: Some(NonZeroU16::new(1)), |
| // Auto-generate a link-local address. |
| slaac_config: Some(SlaacConfiguration { |
| enable_stable_addresses: true, |
| ..Default::default() |
| }), |
| ip_config: IpDeviceConfigurationUpdate { |
| ip_enabled: Some(true), |
| ..Default::default() |
| }, |
| ..Default::default() |
| }, |
| ) |
| .unwrap(); |
| let Ctx { core_ctx, bindings_ctx } = &mut ctx; |
| let tentative: UnicastAddr<Ipv6Addr> = local_mac.to_ipv6_link_local().addr().get(); |
| assert_eq!( |
| receive_ipv6_packet_action( |
| &mut core_ctx.context(), |
| bindings_ctx, |
| &device, |
| tentative.into_specified() |
| ), |
| ReceivePacketAction::Drop { reason: DropReason::Tentative } |
| ); |
| // Clean up secondary context. |
| core::mem::drop(device); |
| ctx.core_api().device().remove_device(eth_device).into_removed(); |
| } |
| |
| // Receive packet destined to a remote address when forwarding is |
| // disabled on the inbound interface. |
| assert_eq!( |
| receive_ipv4_packet_action( |
| &mut core_ctx.context(), |
| bindings_ctx, |
| &v4_dev, |
| v4_config.remote_ip |
| ), |
| ReceivePacketAction::Drop { reason: DropReason::ForwardingDisabledInboundIface } |
| ); |
| assert_eq!( |
| receive_ipv6_packet_action( |
| &mut core_ctx.context(), |
| bindings_ctx, |
| &v6_dev, |
| v6_config.remote_ip |
| ), |
| ReceivePacketAction::Drop { reason: DropReason::ForwardingDisabledInboundIface } |
| ); |
| |
| // Receive packet destined to a remote address when forwarding is |
| // enabled both globally and on the inbound device. |
| set_forwarding_enabled::<_, Ipv4>(&mut ctx, &v4_dev, true); |
| set_forwarding_enabled::<_, Ipv6>(&mut ctx, &v6_dev, true); |
| let Ctx { core_ctx, bindings_ctx } = &mut ctx; |
| assert_eq!( |
| receive_ipv4_packet_action( |
| &mut core_ctx.context(), |
| bindings_ctx, |
| &v4_dev, |
| v4_config.remote_ip |
| ), |
| ReceivePacketAction::Forward { |
| dst: Destination { next_hop: NextHop::RemoteAsNeighbor, device: v4_dev.clone() } |
| } |
| ); |
| assert_eq!( |
| receive_ipv6_packet_action( |
| &mut core_ctx.context(), |
| bindings_ctx, |
| &v6_dev, |
| v6_config.remote_ip |
| ), |
| ReceivePacketAction::Forward { |
| dst: Destination { next_hop: NextHop::RemoteAsNeighbor, device: v6_dev.clone() } |
| } |
| ); |
| |
| // Receive packet destined to a host with no route when forwarding is |
| // enabled both globally and on the inbound device. |
| *core_ctx.ipv4.inner.table.write() = Default::default(); |
| *core_ctx.ipv6.inner.table.write() = Default::default(); |
| assert_eq!( |
| receive_ipv4_packet_action( |
| &mut core_ctx.context(), |
| bindings_ctx, |
| &v4_dev, |
| v4_config.remote_ip |
| ), |
| ReceivePacketAction::SendNoRouteToDest |
| ); |
| assert_eq!( |
| receive_ipv6_packet_action( |
| &mut core_ctx.context(), |
| bindings_ctx, |
| &v6_dev, |
| v6_config.remote_ip |
| ), |
| ReceivePacketAction::SendNoRouteToDest |
| ); |
| |
| // Cleanup all device references. |
| core::mem::drop((v4_dev, v6_dev)); |
| for device in device_ids { |
| ctx.core_api().device().remove_device(device).into_removed(); |
| } |
| } |
| |
| #[derive(Copy, Clone, Debug, Eq, PartialEq)] |
| enum Device { |
| First, |
| Second, |
| Loopback, |
| } |
| |
| impl Device { |
| fn index(self) -> usize { |
| match self { |
| Self::First => 0, |
| Self::Second => 1, |
| Self::Loopback => 2, |
| } |
| } |
| |
| fn from_index(index: usize) -> Self { |
| match index { |
| 0 => Self::First, |
| 1 => Self::Second, |
| 2 => Self::Loopback, |
| x => panic!("index out of bounds: {x}"), |
| } |
| } |
| |
| fn ip_address<A: IpAddress>(self) -> IpDeviceAddr<A> |
| where |
| A::Version: TestIpExt, |
| { |
| match self { |
| Self::First | Self::Second => <A::Version as TestIpExt>::get_other_ip_address( |
| (self.index() + 1).try_into().unwrap(), |
| ) |
| .try_into() |
| .unwrap(), |
| Self::Loopback => <A::Version as Ip>::LOOPBACK_ADDRESS.try_into().unwrap(), |
| } |
| } |
| |
| fn mac(self) -> UnicastAddr<Mac> { |
| UnicastAddr::new(Mac::new([0, 1, 2, 3, 4, self.index().try_into().unwrap()])).unwrap() |
| } |
| |
| fn link_local_addr(self) -> IpDeviceAddr<Ipv6Addr> { |
| match self { |
| Self::First | Self::Second => SpecifiedAddr::new(Ipv6Addr::new([ |
| 0xfe80, |
| 0, |
| 0, |
| 0, |
| 0, |
| 0, |
| 0, |
| self.index().try_into().unwrap(), |
| ])) |
| .unwrap() |
| .try_into() |
| .unwrap(), |
| Self::Loopback => panic!("should not generate link local addresses for loopback"), |
| } |
| } |
| } |
| |
| fn remote_ip<I: TestIpExt>() -> SpecifiedAddr<I::Addr> { |
| I::get_other_ip_address(27) |
| } |
| |
| #[netstack3_macros::context_ip_bounds(I, FakeBindingsCtx, crate)] |
| fn make_test_ctx<I: Ip + TestIpExt + crate::IpExt>( |
| ) -> (Ctx<FakeBindingsCtx>, Vec<DeviceId<FakeBindingsCtx>>) { |
| let mut builder = FakeEventDispatcherBuilder::default(); |
| for device in [Device::First, Device::Second] { |
| let ip: SpecifiedAddr<I::Addr> = device.ip_address().into(); |
| let subnet = |
| AddrSubnet::from_witness(ip, <I::Addr as IpAddress>::BYTES * 8).unwrap().subnet(); |
| let index = builder.add_device_with_ip(device.mac(), ip.get(), subnet); |
| assert_eq!(index, device.index()); |
| } |
| let (mut ctx, device_ids) = builder.build(); |
| let mut device_ids = device_ids.into_iter().map(Into::into).collect::<Vec<_>>(); |
| |
| if I::VERSION.is_v6() { |
| for device in [Device::First, Device::Second] { |
| ctx.core_api() |
| .device_ip::<Ipv6>() |
| .add_ip_addr_subnet( |
| &device_ids[device.index()], |
| AddrSubnet::new(device.link_local_addr().addr(), 64).unwrap(), |
| ) |
| .unwrap(); |
| } |
| } |
| |
| let loopback_id = ctx |
| .core_api() |
| .device::<LoopbackDevice>() |
| .add_device_with_default_state( |
| LoopbackCreationProperties { mtu: Ipv6::MINIMUM_LINK_MTU }, |
| DEFAULT_INTERFACE_METRIC, |
| ) |
| .into(); |
| crate::device::testutil::enable_device(&mut ctx, &loopback_id); |
| ctx.core_api() |
| .device_ip::<I>() |
| .add_ip_addr_subnet( |
| &loopback_id, |
| AddrSubnet::from_witness(I::LOOPBACK_ADDRESS, I::LOOPBACK_SUBNET.prefix()).unwrap(), |
| ) |
| .unwrap(); |
| assert_eq!(device_ids.len(), Device::Loopback.index()); |
| device_ids.push(loopback_id); |
| (ctx, device_ids) |
| } |
| |
| fn do_route_lookup<I: Ip + TestIpExt + IpDeviceStateIpExt>( |
| ctx: &mut FakeCtx, |
| device_ids: Vec<DeviceId<FakeBindingsCtx>>, |
| egress_device: Option<Device>, |
| local_ip: Option<IpDeviceAddr<I::Addr>>, |
| dest_ip: RoutableIpAddr<I::Addr>, |
| ) -> Result<ResolvedRoute<I, Device>, ResolveRouteError> |
| where |
| for<'a> UnlockedCoreCtx<'a, FakeBindingsCtx>: |
| IpSocketContext<I, FakeBindingsCtx, DeviceId = DeviceId<FakeBindingsCtx>>, |
| { |
| let egress_device = egress_device.map(|d| &device_ids[d.index()]); |
| |
| let (mut core_ctx, bindings_ctx) = ctx.contexts(); |
| IpSocketContext::<I, _>::lookup_route( |
| &mut core_ctx, |
| bindings_ctx, |
| egress_device, |
| local_ip, |
| dest_ip, |
| ) |
| // Convert device IDs in any route so it's easier to compare. |
| .map(|ResolvedRoute { src_addr, device, local_delivery_device, next_hop }| { |
| let device = Device::from_index(device_ids.iter().position(|d| d == &device).unwrap()); |
| let local_delivery_device = local_delivery_device.map(|device| { |
| Device::from_index(device_ids.iter().position(|d| d == &device).unwrap()) |
| }); |
| ResolvedRoute { src_addr, device, local_delivery_device, next_hop } |
| }) |
| } |
| |
| #[ip_test] |
| #[test_case(None, |
| None, |
| Device::First.ip_address(), |
| Ok(ResolvedRoute { src_addr: Device::First.ip_address(), device: Device::Loopback, |
| local_delivery_device: Some(Device::First), next_hop: NextHop::RemoteAsNeighbor |
| }); "local delivery")] |
| #[test_case(Some(Device::First.ip_address()), |
| None, |
| Device::First.ip_address(), |
| Ok(ResolvedRoute { src_addr: Device::First.ip_address(), device: Device::Loopback, |
| local_delivery_device: Some(Device::First), next_hop: NextHop::RemoteAsNeighbor |
| }); "local delivery specified local addr")] |
| #[test_case(Some(Device::First.ip_address()), |
| Some(Device::First), |
| Device::First.ip_address(), |
| Ok(ResolvedRoute { src_addr: Device::First.ip_address(), device: Device::Loopback, |
| local_delivery_device: Some(Device::First), next_hop: NextHop::RemoteAsNeighbor |
| }); "local delivery specified device and addr")] |
| #[test_case(None, |
| Some(Device::Loopback), |
| Device::First.ip_address(), |
| Err(ResolveRouteError::Unreachable); |
| "local delivery specified loopback device no addr")] |
| #[test_case(None, |
| Some(Device::Loopback), |
| Device::Loopback.ip_address(), |
| Ok(ResolvedRoute { src_addr: Device::Loopback.ip_address(), |
| device: Device::Loopback, local_delivery_device: Some(Device::Loopback), |
| next_hop: NextHop::RemoteAsNeighbor, |
| }); "local delivery to loopback addr via specified loopback device no addr")] |
| #[test_case(None, |
| Some(Device::Second), |
| Device::First.ip_address(), |
| Err(ResolveRouteError::Unreachable); |
| "local delivery specified mismatched device no addr")] |
| #[test_case(Some(Device::First.ip_address()), |
| Some(Device::Loopback), |
| Device::First.ip_address(), |
| Err(ResolveRouteError::Unreachable); "local delivery specified loopback device")] |
| #[test_case(Some(Device::First.ip_address()), |
| Some(Device::Second), |
| Device::First.ip_address(), |
| Err(ResolveRouteError::Unreachable); "local delivery specified mismatched device")] |
| #[test_case(None, |
| None, |
| remote_ip::<I>().try_into().unwrap(), |
| Ok(ResolvedRoute { src_addr: Device::First.ip_address(), device: Device::First, |
| local_delivery_device: None, next_hop: NextHop::RemoteAsNeighbor }); |
| "remote delivery")] |
| #[test_case(Some(Device::First.ip_address()), |
| None, |
| remote_ip::<I>().try_into().unwrap(), |
| Ok(ResolvedRoute { src_addr: Device::First.ip_address(), device: Device::First, |
| local_delivery_device: None, next_hop: NextHop::RemoteAsNeighbor }); |
| "remote delivery specified addr")] |
| #[test_case(Some(Device::Second.ip_address()), None, remote_ip::<I>().try_into().unwrap(), |
| Err(ResolveRouteError::NoSrcAddr); "remote delivery specified addr no route")] |
| #[test_case(None, |
| Some(Device::First), |
| remote_ip::<I>().try_into().unwrap(), |
| Ok(ResolvedRoute { src_addr: Device::First.ip_address(), device: Device::First, |
| local_delivery_device: None, next_hop: NextHop::RemoteAsNeighbor }); |
| "remote delivery specified device")] |
| #[test_case(None, Some(Device::Second), remote_ip::<I>().try_into().unwrap(), |
| Err(ResolveRouteError::Unreachable); "remote delivery specified device no route")] |
| #[test_case(Some(Device::Second.ip_address()), |
| None, |
| Device::First.ip_address(), |
| Ok(ResolvedRoute {src_addr: Device::Second.ip_address(), device: Device::Loopback, |
| local_delivery_device: Some(Device::Second), |
| next_hop: NextHop::RemoteAsNeighbor }); |
| "local delivery cross device")] |
| #[netstack3_macros::context_ip_bounds(I, FakeBindingsCtx, crate)] |
| fn lookup_route<I: Ip + TestIpExt + crate::IpExt>( |
| local_ip: Option<IpDeviceAddr<I::Addr>>, |
| egress_device: Option<Device>, |
| dest_ip: RoutableIpAddr<I::Addr>, |
| expected_result: Result<ResolvedRoute<I, Device>, ResolveRouteError>, |
| ) { |
| set_logger_for_test(); |
| |
| let (mut ctx, device_ids) = make_test_ctx::<I>(); |
| |
| // Add a route to the remote address only for Device::First. |
| ctx.test_api() |
| .add_route(AddableEntryEither::without_gateway( |
| Subnet::new(*remote_ip::<I>(), <I::Addr as IpAddress>::BYTES * 8).unwrap().into(), |
| device_ids[Device::First.index()].clone(), |
| AddableMetric::ExplicitMetric(RawMetric(0)), |
| )) |
| .unwrap(); |
| |
| let result = do_route_lookup(&mut ctx, device_ids, egress_device, local_ip, dest_ip); |
| assert_eq!(result, expected_result); |
| } |
| |
| #[ip_test] |
| #[test_case(None, |
| None, |
| Ok(ResolvedRoute { src_addr: Device::First.ip_address(), device: Device::First, |
| local_delivery_device: None, next_hop: NextHop::RemoteAsNeighbor }); |
| "no constraints")] |
| #[test_case(Some(Device::First.ip_address()), |
| None, |
| Ok(ResolvedRoute { src_addr: Device::First.ip_address(), device: Device::First, |
| local_delivery_device: None, next_hop: NextHop::RemoteAsNeighbor }); |
| "constrain local addr")] |
| #[test_case(Some(Device::Second.ip_address()), None, |
| Ok(ResolvedRoute { src_addr: Device::Second.ip_address(), device: Device::Second, |
| local_delivery_device: None, next_hop: NextHop::RemoteAsNeighbor }); |
| "constrain local addr to second device")] |
| #[test_case(None, |
| Some(Device::First), |
| Ok(ResolvedRoute { src_addr: Device::First.ip_address(), device: Device::First, |
| local_delivery_device: None, next_hop: NextHop::RemoteAsNeighbor }); |
| "constrain device")] |
| #[test_case(None, Some(Device::Second), |
| Ok(ResolvedRoute { src_addr: Device::Second.ip_address(), device: Device::Second, |
| local_delivery_device: None, next_hop: NextHop::RemoteAsNeighbor }); |
| "constrain to second device")] |
| #[netstack3_macros::context_ip_bounds(I, FakeBindingsCtx, crate)] |
| fn lookup_route_multiple_devices_with_route<I: Ip + TestIpExt + crate::IpExt>( |
| local_ip: Option<IpDeviceAddr<I::Addr>>, |
| egress_device: Option<Device>, |
| expected_result: Result<ResolvedRoute<I, Device>, ResolveRouteError>, |
| ) { |
| set_logger_for_test(); |
| |
| let (mut ctx, device_ids) = make_test_ctx::<I>(); |
| |
| // Add a route to the remote address for both devices, with preference |
| // for the first. |
| for device in [Device::First, Device::Second] { |
| ctx.test_api() |
| .add_route(AddableEntryEither::without_gateway( |
| Subnet::new(*remote_ip::<I>(), <I::Addr as IpAddress>::BYTES * 8) |
| .unwrap() |
| .into(), |
| device_ids[device.index()].clone(), |
| AddableMetric::ExplicitMetric(RawMetric(device.index().try_into().unwrap())), |
| )) |
| .unwrap(); |
| } |
| |
| let result = do_route_lookup( |
| &mut ctx, |
| device_ids, |
| egress_device, |
| local_ip, |
| remote_ip::<I>().try_into().unwrap(), |
| ); |
| assert_eq!(result, expected_result); |
| } |
| |
| #[test_case(None, None, Device::Second.link_local_addr(), |
| Ok(ResolvedRoute { src_addr: Device::Second.link_local_addr(), |
| device: Device::Loopback, local_delivery_device: Some(Device::Second), |
| next_hop: NextHop::RemoteAsNeighbor }); |
| "local delivery no local address to link-local")] |
| #[test_case(Some(Device::Second.ip_address()), None, Device::Second.link_local_addr(), |
| Ok(ResolvedRoute { src_addr: Device::Second.ip_address(), device: Device::Loopback, |
| local_delivery_device: Some(Device::Second), |
| next_hop: NextHop::RemoteAsNeighbor }); |
| "local delivery same device to link-local")] |
| #[test_case(Some(Device::Second.link_local_addr()), None, Device::Second.ip_address(), |
| Ok(ResolvedRoute { src_addr: Device::Second.link_local_addr(), |
| device: Device::Loopback, local_delivery_device: Some(Device::Second), |
| next_hop: NextHop::RemoteAsNeighbor }); |
| "local delivery same device from link-local")] |
| #[test_case(Some(Device::First.ip_address()), None, Device::Second.link_local_addr(), |
| Err(ResolveRouteError::NoSrcAddr); |
| "local delivery cross device to link-local")] |
| #[test_case(Some(Device::First.link_local_addr()), None, Device::Second.ip_address(), |
| Err(ResolveRouteError::NoSrcAddr); |
| "local delivery cross device from link-local")] |
| fn lookup_route_v6only( |
| local_ip: Option<IpDeviceAddr<Ipv6Addr>>, |
| egress_device: Option<Device>, |
| dest_ip: RoutableIpAddr<Ipv6Addr>, |
| expected_result: Result<ResolvedRoute<Ipv6, Device>, ResolveRouteError>, |
| ) { |
| set_logger_for_test(); |
| |
| let (mut ctx, device_ids) = make_test_ctx::<Ipv6>(); |
| |
| let result = do_route_lookup(&mut ctx, device_ids, egress_device, local_ip, dest_ip); |
| assert_eq!(result, expected_result); |
| } |
| } |