| // 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. |
| |
| //! The Internet Protocol, versions 4 and 6. |
| |
| mod forwarding; |
| mod gmp; |
| pub mod icmp; |
| mod igmp; |
| mod ipv6; |
| mod mld; |
| pub(crate) mod path_mtu; |
| pub(crate) mod reassembly; |
| mod types; |
| |
| // It's ok to `pub use` rather `pub(crate) use` here because the items in |
| // `types` which are themselves `pub(crate)` will still not be allowed to be |
| // re-exported from the root. |
| pub use self::types::*; |
| |
| use std::fmt::{Debug, Display}; |
| use std::ops::Deref; |
| |
| use log::{debug, trace}; |
| use net_types::ip::{AddrSubnet, Ip, IpAddress, IpVersion, Ipv4, Ipv4Addr, Ipv6, Ipv6Addr, Subnet}; |
| use net_types::{LinkLocalAddr, MulticastAddr, SpecifiedAddr, Witness}; |
| use packet::{Buf, BufferMut, Either, EmptyBuf, ParseMetadata, Serializer}; |
| use specialize_ip_macro::{specialize_ip, specialize_ip_address}; |
| |
| use crate::context::{CounterContext, FrameContext, StateContext, TimerContext}; |
| use crate::data_structures::IdMap; |
| use crate::device::{DeviceId, FrameDestination}; |
| use crate::error::{ExistsError, IpParseError, NotFoundError}; |
| use crate::ip::forwarding::{Destination, ForwardingTable}; |
| use crate::ip::icmp::{ |
| send_icmpv4_parameter_problem, send_icmpv6_parameter_problem, IcmpContext, IcmpEventDispatcher, |
| Icmpv4State, Icmpv4StateBuilder, Icmpv6State, |
| }; |
| use crate::ip::igmp::{IgmpContext, IgmpInterface, IgmpPacketMetadata, IgmpTimerId}; |
| use crate::ip::ipv6::Ipv6PacketAction; |
| use crate::ip::mld::{MldContext, MldFrameMetadata, MldInterface, MldReportDelay}; |
| use crate::ip::path_mtu::{handle_pmtu_timer, IpLayerPathMtuCache, PmtuTimerId}; |
| use crate::ip::reassembly::{ |
| handle_reassembly_timer, process_fragment, reassemble_packet, FragmentCacheKey, |
| FragmentProcessingState, IpLayerFragmentCache, |
| }; |
| use crate::wire::icmp::{IcmpIpExt, Icmpv4ParameterProblem, Icmpv6ParameterProblem}; |
| use crate::wire::ipv4::{Ipv4Packet, Ipv4PacketBuilder, Ipv4PacketBuilderWithOptions}; |
| use crate::wire::ipv6::Ipv6Packet; |
| use crate::{BufferDispatcher, Context, EventDispatcher, StackState, TimerId, TimerIdInner}; |
| |
| /// Default IPv4 TTL. |
| const DEFAULT_TTL: u8 = 64; |
| |
| /// Minimum MTU required by all IPv6 devices. |
| pub(crate) const IPV6_MIN_MTU: u32 = 1280; |
| |
| // The ipv4 multicast address for all routers. |
| const IPV4_ALL_ROUTERS: Ipv4Addr = Ipv4Addr::new([224, 0, 0, 2]); |
| // The ipv6 multicast address for all routers. |
| const IPV6_ALL_ROUTERS: Ipv6Addr = |
| Ipv6Addr::new([0xff, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2]); |
| |
| /// The metadata for sending an IP packet from a particular source address. |
| /// |
| /// `IpPacketFromArgs` is used as the metadata for the [`FrameContext`] bound |
| /// required by [`BufferTransportIpContext`]. It allows sending an IP packet |
| /// from a particular source address. |
| pub struct IpPacketFromArgs<A: IpAddress> { |
| pub(crate) src_ip: SpecifiedAddr<A>, |
| pub(crate) dst_ip: SpecifiedAddr<A>, |
| pub(crate) proto: IpProto, |
| } |
| |
| impl<A: IpAddress> IpPacketFromArgs<A> { |
| /// Constructs a new `IpPacketFromArgs`. |
| pub(crate) fn new( |
| src_ip: SpecifiedAddr<A>, |
| dst_ip: SpecifiedAddr<A>, |
| proto: IpProto, |
| ) -> IpPacketFromArgs<A> { |
| IpPacketFromArgs { src_ip, dst_ip, proto } |
| } |
| } |
| |
| /// The execution context provided by the IP layer to transport layer protocols. |
| pub trait TransportIpContext<I: Ip> { |
| /// Is this one of our local addresses? |
| /// |
| /// `is_local_addr` returns whether `addr` is the address associated with |
| /// one of our local interfaces. |
| fn is_local_addr(&self, addr: I::Addr) -> bool; |
| |
| /// Get the local address of the interface that will be used to route to a |
| /// remote address. |
| /// |
| /// `local_address_for_remote` looks up the route to `remote`. If one is |
| /// found, it returns the IP address of the interface specified by the |
| /// route, or `None` if the interface has no IP address. |
| fn local_address_for_remote( |
| &self, |
| remote: SpecifiedAddr<I::Addr>, |
| ) -> Option<SpecifiedAddr<I::Addr>>; |
| } |
| |
| /// The execution context provided by the IP layer to transport layer protocols |
| /// when a buffer is provided. |
| /// |
| /// `BufferTransportIpContext` is like [`TransportIpContext`], except that it |
| /// also requires that the context be capable of receiving frames in buffers of |
| /// type `B`. This is used when a buffer of type `B` is provided to IP (in |
| /// particular, in the [`FrameContext`] implementation), and allows any |
| /// generated link-layer frames to reuse that buffer rather than needing to |
| /// always allocate a new one. |
| pub trait BufferTransportIpContext<I: Ip, B: BufferMut>: |
| TransportIpContext<I> + FrameContext<B, IpPacketFromArgs<I::Addr>> |
| { |
| } |
| |
| impl< |
| I: Ip, |
| B: BufferMut, |
| C: TransportIpContext<I> + FrameContext<B, IpPacketFromArgs<I::Addr>>, |
| > BufferTransportIpContext<I, B> for C |
| { |
| } |
| |
| impl<A: IpAddress, B: BufferMut, D: BufferDispatcher<B>> FrameContext<B, IpPacketFromArgs<A>> |
| for Context<D> |
| { |
| fn send_frame<S: Serializer<Buffer = B>>( |
| &mut self, |
| _meta: IpPacketFromArgs<A>, |
| _body: S, |
| ) -> Result<(), S> { |
| log_unimplemented!(Ok(()), "FrameContext<IpPacketFromArgs>::send_frame: not implemented") |
| } |
| } |
| |
| impl<I: Ip, D: EventDispatcher> TransportIpContext<I> for Context<D> { |
| fn is_local_addr(&self, addr: I::Addr) -> bool { |
| is_local_addr(self, addr) |
| } |
| |
| fn local_address_for_remote( |
| &self, |
| remote: SpecifiedAddr<I::Addr>, |
| ) -> Option<SpecifiedAddr<I::Addr>> { |
| local_address_for_remote(self, remote) |
| } |
| } |
| |
| /// An execution context which provides a `DeviceId` type for various IP |
| /// internals to share. |
| /// |
| /// This trait provides the associated `DeviceId` type, and is used by |
| /// [`IgmpContext`], [`MldContext`], and [`IcmpContext`]. It allows them to use |
| /// the same `DeviceId` type rather than each providing their own, which would |
| /// require lots of verbose type bounds when they need to be interoperable (such |
| /// as when ICMP delivers an MLD packet to the `mld` module for processing). |
| pub(crate) trait IpDeviceIdContext { |
| type DeviceId: Copy + Display + Debug + Send + Sync + 'static; |
| } |
| |
| // Temporary blanket impl until we switch over entirely to the traits defined in |
| // the `context` module. |
| impl<D: EventDispatcher> IpDeviceIdContext for Context<D> { |
| type DeviceId = DeviceId; |
| } |
| |
| /// A builder for IPv4 state. |
| #[derive(Copy, Clone)] |
| pub struct Ipv4StateBuilder { |
| forward: bool, |
| icmp: Icmpv4StateBuilder, |
| } |
| |
| impl Default for Ipv4StateBuilder { |
| fn default() -> Ipv4StateBuilder { |
| // NOTE: We implement `Default` manually even though this implementation |
| // is equivalent to what `#[derive(Default)]` would produce in order to |
| // make the default values explicit. |
| Ipv4StateBuilder { forward: false, icmp: Icmpv4StateBuilder::default() } |
| } |
| } |
| |
| impl Ipv4StateBuilder { |
| /// Enable or disable IP packet forwarding (default: disabled). |
| /// |
| /// If `forward` is true, then an incoming IP packet whose destination |
| /// address identifies a remote host will be forwarded to that host. |
| pub fn forward(&mut self, forward: bool) -> &mut Self { |
| self.forward = forward; |
| self |
| } |
| |
| /// Get the builder for the ICMPv4 state. |
| pub fn icmp_builder(&mut self) -> &mut Icmpv4StateBuilder { |
| &mut self.icmp |
| } |
| |
| pub(crate) fn build<Instant: crate::Instant>(self) -> Ipv4State<Instant> { |
| Ipv4State { |
| inner: IpStateInner { |
| forward: self.forward, |
| table: ForwardingTable::default(), |
| fragment_cache: IpLayerFragmentCache::new(), |
| path_mtu: IpLayerPathMtuCache::new(), |
| }, |
| icmp: self.icmp.build(), |
| igmp: IdMap::default(), |
| } |
| } |
| } |
| |
| /// A builder for IPv6 state. |
| #[derive(Copy, Clone)] |
| pub struct Ipv6StateBuilder { |
| forward: bool, |
| } |
| |
| impl Default for Ipv6StateBuilder { |
| fn default() -> Ipv6StateBuilder { |
| // NOTE: We implement `Default` manually even though this implementation |
| // is equivalent to what `#[derive(Default)]` would produce in order to |
| // make the default values explicit. |
| Ipv6StateBuilder { forward: false } |
| } |
| } |
| |
| impl Ipv6StateBuilder { |
| /// Enable or disable IPv6 packet forwarding (default: disabled). |
| /// |
| /// If `forward` is true, then an incoming IPv6 packet whose destination |
| /// address identifies a remote host will be forwarded to that host. |
| pub fn forward(&mut self, forward: bool) -> &mut Self { |
| self.forward = forward; |
| self |
| } |
| |
| pub(crate) fn build<Instant: crate::Instant>(self) -> Ipv6State<Instant> { |
| Ipv6State { |
| inner: IpStateInner { |
| forward: self.forward, |
| table: ForwardingTable::default(), |
| fragment_cache: IpLayerFragmentCache::new(), |
| path_mtu: IpLayerPathMtuCache::new(), |
| }, |
| icmp: Icmpv6State::default(), |
| mld: IdMap::default(), |
| } |
| } |
| } |
| |
| pub(crate) struct Ipv4State<Instant: crate::Instant> { |
| inner: IpStateInner<Ipv4, Instant>, |
| icmp: Icmpv4State, |
| igmp: IdMap<IgmpInterface<Instant>>, |
| } |
| |
| impl<Instant: crate::Instant> Ipv4State<Instant> { |
| /// Get the IGMP state associated with the device immutably. |
| pub(crate) fn get_igmp_state(&self, device_id: usize) -> &IgmpInterface<Instant> { |
| self.igmp.get(device_id).unwrap() |
| } |
| |
| /// Get the IGMP state associated with the device mutably. |
| pub(crate) fn get_igmp_state_mut(&mut self, device_id: usize) -> &mut IgmpInterface<Instant> { |
| self.igmp.entry(device_id).or_insert(IgmpInterface::default()) |
| } |
| } |
| |
| pub(crate) struct Ipv6State<Instant: crate::Instant> { |
| inner: IpStateInner<Ipv6, Instant>, |
| icmp: Icmpv6State, |
| mld: IdMap<MldInterface<Instant>>, |
| } |
| |
| impl<Instant: crate::Instant> Ipv6State<Instant> { |
| /// Get the MLD state associated with the device immutably. |
| fn get_mld_state(&self, device_id: usize) -> &MldInterface<Instant> { |
| self.mld.get(device_id).unwrap() |
| } |
| |
| /// Get the MLD state associated with the device mutably. |
| fn get_mld_state_mut(&mut self, device_id: usize) -> &mut MldInterface<Instant> { |
| self.mld.entry(device_id).or_insert(MldInterface::default()) |
| |
| // if self.mld.get(device_id).is_none() { |
| // self.mld.insert(device_id, MldInterface::default()); |
| // } |
| // self.mld.get_mut(device_id).unwrap() |
| } |
| } |
| |
| struct IpStateInner<I: Ip, Instant: crate::Instant> { |
| forward: bool, |
| table: ForwardingTable<I, DeviceId>, |
| fragment_cache: IpLayerFragmentCache<I>, |
| path_mtu: IpLayerPathMtuCache<I, Instant>, |
| } |
| |
| #[specialize_ip] |
| fn get_state_inner<I: Ip, D: EventDispatcher>( |
| state: &StackState<D>, |
| ) -> &IpStateInner<I, D::Instant> { |
| #[ipv4] |
| return &state.ipv4.inner; |
| #[ipv6] |
| return &state.ipv6.inner; |
| } |
| |
| #[specialize_ip] |
| fn get_state_inner_mut<I: Ip, D: EventDispatcher>( |
| state: &mut StackState<D>, |
| ) -> &mut IpStateInner<I, D::Instant> { |
| #[ipv4] |
| return &mut state.ipv4.inner; |
| #[ipv6] |
| return &mut state.ipv6.inner; |
| } |
| |
| impl<D: EventDispatcher> StateContext<DeviceId, IgmpInterface<D::Instant>> for Context<D> { |
| fn get_state(&self, device: DeviceId) -> &IgmpInterface<D::Instant> { |
| self.state().ipv4.get_igmp_state(device.id()) |
| } |
| |
| fn get_state_mut(&mut self, device: DeviceId) -> &mut IgmpInterface<D::Instant> { |
| self.state_mut().ipv4.get_igmp_state_mut(device.id()) |
| } |
| } |
| |
| impl<D: EventDispatcher> StateContext<DeviceId, MldInterface<D::Instant>> for Context<D> { |
| fn get_state(&self, device: DeviceId) -> &MldInterface<D::Instant> { |
| self.state().ipv6.get_mld_state(device.id()) |
| } |
| |
| fn get_state_mut(&mut self, device: DeviceId) -> &mut MldInterface<D::Instant> { |
| self.state_mut().ipv6.get_mld_state_mut(device.id()) |
| } |
| } |
| |
| impl<I: Ip, D: EventDispatcher> StateContext<(), IpLayerFragmentCache<I>> for Context<D> { |
| fn get_state(&self, _id: ()) -> &IpLayerFragmentCache<I> { |
| &get_state_inner(self.state()).fragment_cache |
| } |
| |
| fn get_state_mut(&mut self, _id: ()) -> &mut IpLayerFragmentCache<I> { |
| &mut get_state_inner_mut(self.state_mut()).fragment_cache |
| } |
| } |
| |
| impl<I: Ip, D: EventDispatcher> StateContext<(), IpLayerPathMtuCache<I, D::Instant>> |
| for Context<D> |
| { |
| fn get_state(&self, _id: ()) -> &IpLayerPathMtuCache<I, D::Instant> { |
| &get_state_inner(self.state()).path_mtu |
| } |
| |
| fn get_state_mut(&mut self, _id: ()) -> &mut IpLayerPathMtuCache<I, D::Instant> { |
| &mut get_state_inner_mut(self.state_mut()).path_mtu |
| } |
| } |
| |
| /// An event dispatcher for the IP layer. |
| /// |
| /// See the `EventDispatcher` trait in the crate root for more details. |
| pub trait IpLayerEventDispatcher<B: BufferMut>: IcmpEventDispatcher<B> {} |
| |
| /// The identifier for timer events in the IP layer. |
| #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] |
| pub(crate) enum IpLayerTimerId { |
| /// A timer event for IPv4 packet reassembly timeouts. |
| ReassemblyTimeoutv4(FragmentCacheKey<Ipv4Addr>), |
| /// A timer event for IPv6 packet reassembly timeouts. |
| ReassemblyTimeoutv6(FragmentCacheKey<Ipv6Addr>), |
| PmtuTimeout(IpVersion), |
| /// Timer for IGMP protocol |
| IgmpTimer(IgmpTimerId<DeviceId>), |
| MldTimer(MldReportDelay<DeviceId>), |
| } |
| |
| impl IpLayerTimerId { |
| fn new_igmp_timer_id(id: IgmpTimerId<DeviceId>) -> TimerId { |
| TimerId(TimerIdInner::IpLayer(IpLayerTimerId::IgmpTimer(id))) |
| } |
| |
| fn new_mld_timer_id(id: MldReportDelay<DeviceId>) -> TimerId { |
| TimerId(TimerIdInner::IpLayer(IpLayerTimerId::MldTimer(id))) |
| } |
| |
| #[specialize_ip_address] |
| fn new_reassembly_timeout_timer_id<A: IpAddress>(key: FragmentCacheKey<A>) -> TimerId { |
| #[ipv4addr] |
| let id = IpLayerTimerId::ReassemblyTimeoutv4(key); |
| #[ipv6addr] |
| let id = IpLayerTimerId::ReassemblyTimeoutv6(key); |
| |
| TimerId(TimerIdInner::IpLayer(id)) |
| } |
| |
| fn new_pmtu_timeout_timer_id<I: Ip>() -> TimerId { |
| TimerId(TimerIdInner::IpLayer(IpLayerTimerId::PmtuTimeout(I::VERSION))) |
| } |
| } |
| |
| /// Handle a timer event firing in the IP layer. |
| pub(crate) fn handle_timeout<D: EventDispatcher>(ctx: &mut Context<D>, id: IpLayerTimerId) { |
| match id { |
| IpLayerTimerId::ReassemblyTimeoutv4(key) => handle_reassembly_timer::<Ipv4, _>(ctx, key), |
| IpLayerTimerId::ReassemblyTimeoutv6(key) => handle_reassembly_timer::<Ipv6, _>(ctx, key), |
| IpLayerTimerId::PmtuTimeout(IpVersion::V4) => handle_pmtu_timer::<Ipv4, _>(ctx), |
| IpLayerTimerId::PmtuTimeout(IpVersion::V6) => handle_pmtu_timer::<Ipv6, _>(ctx), |
| IpLayerTimerId::IgmpTimer(timer) => crate::ip::igmp::handle_timeout(ctx, timer), |
| IpLayerTimerId::MldTimer(timer) => crate::ip::mld::handle_timeout(ctx, timer), |
| } |
| } |
| |
| impl<D: EventDispatcher> TimerContext<IgmpTimerId<DeviceId>> for Context<D> { |
| fn schedule_timer_instant( |
| &mut self, |
| time: Self::Instant, |
| id: IgmpTimerId<DeviceId>, |
| ) -> Option<Self::Instant> { |
| self.dispatcher_mut().schedule_timeout_instant(time, IpLayerTimerId::new_igmp_timer_id(id)) |
| } |
| |
| fn cancel_timer(&mut self, id: IgmpTimerId<DeviceId>) -> Option<Self::Instant> { |
| self.dispatcher_mut().cancel_timeout(IpLayerTimerId::new_igmp_timer_id(id)) |
| } |
| |
| fn cancel_timers_with<F: FnMut(&IgmpTimerId<DeviceId>) -> bool>(&mut self, mut f: F) { |
| self.dispatcher_mut().cancel_timeouts_with(|id| match id { |
| TimerId(TimerIdInner::IpLayer(IpLayerTimerId::IgmpTimer(id))) => f(id), |
| _ => false, |
| }) |
| } |
| |
| fn scheduled_instant(&self, id: IgmpTimerId<DeviceId>) -> Option<Self::Instant> { |
| self.dispatcher().scheduled_instant(IpLayerTimerId::new_igmp_timer_id(id)) |
| } |
| } |
| |
| impl<D: EventDispatcher> TimerContext<MldReportDelay<DeviceId>> for Context<D> { |
| fn schedule_timer_instant( |
| &mut self, |
| time: Self::Instant, |
| id: MldReportDelay<DeviceId>, |
| ) -> Option<Self::Instant> { |
| self.dispatcher_mut().schedule_timeout_instant(time, IpLayerTimerId::new_mld_timer_id(id)) |
| } |
| |
| fn cancel_timer(&mut self, id: MldReportDelay<DeviceId>) -> Option<Self::Instant> { |
| self.dispatcher_mut().cancel_timeout(IpLayerTimerId::new_mld_timer_id(id)) |
| } |
| |
| fn cancel_timers_with<F: FnMut(&MldReportDelay<DeviceId>) -> bool>(&mut self, mut f: F) { |
| self.dispatcher_mut().cancel_timeouts_with(|id| match id { |
| TimerId(TimerIdInner::IpLayer(IpLayerTimerId::MldTimer(id))) => f(id), |
| _ => false, |
| }) |
| } |
| |
| fn scheduled_instant(&self, id: MldReportDelay<DeviceId>) -> Option<Self::Instant> { |
| self.dispatcher().scheduled_instant(IpLayerTimerId::new_mld_timer_id(id)) |
| } |
| } |
| |
| impl<A: IpAddress, D: EventDispatcher> TimerContext<FragmentCacheKey<A>> for Context<D> { |
| fn schedule_timer_instant( |
| &mut self, |
| time: Self::Instant, |
| key: FragmentCacheKey<A>, |
| ) -> Option<Self::Instant> { |
| self.dispatcher_mut() |
| .schedule_timeout_instant(time, IpLayerTimerId::new_reassembly_timeout_timer_id(key)) |
| } |
| |
| fn cancel_timer(&mut self, key: FragmentCacheKey<A>) -> Option<Self::Instant> { |
| self.dispatcher_mut().cancel_timeout(IpLayerTimerId::new_reassembly_timeout_timer_id(key)) |
| } |
| |
| fn cancel_timers_with<F: FnMut(&FragmentCacheKey<A>) -> bool>(&mut self, f: F) { |
| #[specialize_ip_address] |
| fn cancel_timers_with_inner< |
| A: IpAddress, |
| D: EventDispatcher, |
| F: FnMut(&FragmentCacheKey<A>) -> bool, |
| >( |
| ctx: &mut Context<D>, |
| mut f: F, |
| ) { |
| ctx.dispatcher_mut().cancel_timeouts_with(|id| match id { |
| #[ipv4addr] |
| TimerId(TimerIdInner::IpLayer(IpLayerTimerId::ReassemblyTimeoutv4(key))) => f(key), |
| #[ipv6addr] |
| TimerId(TimerIdInner::IpLayer(IpLayerTimerId::ReassemblyTimeoutv6(key))) => f(key), |
| _ => false, |
| }); |
| } |
| |
| cancel_timers_with_inner(self, f); |
| } |
| |
| fn scheduled_instant(&self, key: FragmentCacheKey<A>) -> Option<Self::Instant> { |
| self.dispatcher().scheduled_instant(IpLayerTimerId::new_reassembly_timeout_timer_id(key)) |
| } |
| } |
| |
| impl<I: Ip, D: EventDispatcher> TimerContext<PmtuTimerId<I>> for Context<D> { |
| fn schedule_timer_instant( |
| &mut self, |
| time: Self::Instant, |
| _id: PmtuTimerId<I>, |
| ) -> Option<Self::Instant> { |
| self.dispatcher_mut() |
| .schedule_timeout_instant(time, IpLayerTimerId::new_pmtu_timeout_timer_id::<I>()) |
| } |
| |
| fn cancel_timer(&mut self, _id: PmtuTimerId<I>) -> Option<Self::Instant> { |
| self.dispatcher_mut().cancel_timeout(IpLayerTimerId::new_pmtu_timeout_timer_id::<I>()) |
| } |
| |
| fn cancel_timers_with<F: FnMut(&PmtuTimerId<I>) -> bool>(&mut self, f: F) { |
| self.dispatcher_mut() |
| .cancel_timeouts_with(|id| id == &IpLayerTimerId::new_pmtu_timeout_timer_id::<I>()); |
| } |
| |
| fn scheduled_instant(&self, _id: PmtuTimerId<I>) -> Option<Self::Instant> { |
| self.dispatcher().scheduled_instant(IpLayerTimerId::new_pmtu_timeout_timer_id::<I>()) |
| } |
| } |
| |
| // 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<B: BufferMut, D: BufferDispatcher<B>>( |
| ctx: &mut Context<D>, |
| device: Option<DeviceId>, |
| frame_dst: FrameDestination, |
| src_ip: Ipv4Addr, |
| dst_ip: SpecifiedAddr<Ipv4Addr>, |
| proto: IpProto, |
| mut buffer: B, |
| parse_metadata: Option<ParseMetadata>, |
| ) { |
| increment_counter!(ctx, "dispatch_receive_ipv4_packet"); |
| |
| let res = match proto { |
| IpProto::Icmp => { |
| icmp::receive_icmpv4_packet(ctx, device, src_ip, dst_ip, buffer); |
| Ok(()) |
| } |
| IpProto::Igmp => { |
| igmp::receive_igmp_packet( |
| ctx, |
| device.expect("IGMP messages should come from a device"), |
| src_ip, |
| dst_ip, |
| buffer, |
| ); |
| Ok(()) |
| } |
| IpProto::Udp => crate::transport::udp::receive_ip_packet(ctx, src_ip, dst_ip, buffer), |
| |
| _ => { |
| // Undo the parsing of the IP packet header so that the buffer |
| // contains the entire original IP packet. |
| let meta = parse_metadata.unwrap(); |
| buffer.undo_parse(meta); |
| icmp::send_icmpv4_protocol_unreachable( |
| ctx, |
| device.unwrap(), |
| frame_dst, |
| src_ip, |
| dst_ip, |
| proto, |
| buffer, |
| meta.header_len(), |
| ); |
| Ok(()) |
| } |
| }; |
| |
| if let Err(mut buffer) = res { |
| // 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. |
| |
| // udp::receive_ip_packet promises to return the buffer in the same |
| // state it was in when they were called. 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(); |
| buffer.undo_parse(meta); |
| icmp::send_icmpv4_port_unreachable( |
| ctx, |
| device.unwrap(), |
| frame_dst, |
| src_ip, |
| dst_ip, |
| buffer, |
| meta.header_len(), |
| ); |
| } |
| } |
| |
| /// 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<B: BufferMut, D: BufferDispatcher<B>>( |
| ctx: &mut Context<D>, |
| device: Option<DeviceId>, |
| frame_dst: FrameDestination, |
| src_ip: Ipv6Addr, |
| dst_ip: SpecifiedAddr<Ipv6Addr>, |
| proto: IpProto, |
| mut buffer: B, |
| parse_metadata: Option<ParseMetadata>, |
| ) { |
| increment_counter!(ctx, "dispatch_receive_ipv6_packet"); |
| |
| let res = match proto { |
| IpProto::Icmpv6 => { |
| icmp::receive_icmpv6_packet(ctx, device, src_ip, dst_ip, buffer); |
| Ok(()) |
| } |
| // A value of `IpProto::NoNextHeader` tells us that there is no header whatsoever |
| // following the last lower-level header so we stop processing here. |
| IpProto::NoNextHeader => Ok(()), |
| IpProto::Udp => crate::transport::udp::receive_ip_packet(ctx, src_ip, dst_ip, buffer), |
| _ => { |
| // Undo the parsing of the IP packet header so that the buffer |
| // contains the entire original IP packet. |
| let meta = parse_metadata.unwrap(); |
| buffer.undo_parse(meta); |
| icmp::send_icmpv6_protocol_unreachable( |
| ctx, |
| device.unwrap(), |
| frame_dst, |
| src_ip, |
| dst_ip, |
| proto, |
| buffer, |
| meta.header_len(), |
| ); |
| Ok(()) |
| } |
| }; |
| |
| if let Err(mut buffer) = res { |
| // 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. |
| |
| // udp::receive_ip_packet promises to return the buffer in the same |
| // state it was in when they were called. 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(); |
| buffer.undo_parse(meta); |
| icmp::send_icmpv6_port_unreachable(ctx, device.unwrap(), frame_dst, src_ip, dst_ip, buffer); |
| } |
| } |
| |
| /// 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 { |
| ($ctx:expr, $dispatch:ident, $device:expr, $frame_dst:expr, $buffer:expr, $packet:expr, $dst_ip:expr, $ip:ident) => {{ |
| match process_fragment::<$ip, _, &mut [u8]>($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 (src_ip, _, proto, meta) = packet.into_metadata(); |
| $dispatch( |
| $ctx, |
| Some($device), |
| $frame_dst, |
| src_ip, |
| $dst_ip, |
| proto, |
| $buffer, |
| Some(meta), |
| ); |
| } |
| // 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(vec![0; packet_len], ..); |
| |
| // Attempt to reassemble the packet. |
| match reassemble_packet::<$ip, _, _, _>($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 (src_ip, _, proto, meta) = packet.into_metadata(); |
| $dispatch::<Buf<Vec<u8>>, _>( |
| $ctx, |
| Some($device), |
| $frame_dst, |
| src_ip, |
| $dst_ip, |
| proto, |
| buffer, |
| Some(meta), |
| ); |
| } |
| // TODO(ghanan): Handle reassembly errors. |
| _ => return, |
| Err(e) => { |
| trace!("receive_ip_packet: fragmented, failed to reassemble: {:?}", e); |
| } |
| } |
| } |
| // Cannot proceed since we need more fragments before we |
| // can reassemble a packet. |
| FragmentProcessingState::NeedMoreFragments => { |
| trace!("receive_ip_packet: fragmented, need more before reassembly") |
| } |
| // TODO(ghanan): Handle invalid fragments. |
| FragmentProcessingState::InvalidFragment => { |
| trace!("receive_ip_packet: fragmented, invalid") |
| } |
| }; |
| }}; |
| } |
| |
| // 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 IP packet from a device. |
| /// |
| /// `receive_ip_packet` calls [`receive_ipv4_packet`] or [`receive_ipv6_packet`] |
| /// depending on the type parameter, `I`. It is used only in testing. |
| #[cfg(test)] |
| #[specialize_ip] |
| pub(crate) fn receive_ip_packet<B: BufferMut, D: BufferDispatcher<B>, I: Ip>( |
| ctx: &mut Context<D>, |
| device: DeviceId, |
| frame_dst: FrameDestination, |
| buffer: B, |
| ) { |
| #[ipv4] |
| receive_ipv4_packet(ctx, device, frame_dst, buffer); |
| #[ipv6] |
| receive_ipv6_packet(ctx, device, frame_dst, buffer); |
| } |
| |
| /// Receive an IPv4 packet from a device. |
| /// |
| /// `frame_dst` specifies whether this packet was received in a broadcast or |
| /// unicast link-layer frame. |
| pub(crate) fn receive_ipv4_packet<B: BufferMut, D: BufferDispatcher<B>>( |
| ctx: &mut Context<D>, |
| device: DeviceId, |
| frame_dst: FrameDestination, |
| mut buffer: B, |
| ) { |
| increment_counter!(ctx, "receive_ipv4_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`. |
| Err(IpParseError::ParameterProblem { |
| src_ip, |
| dst_ip, |
| code, |
| pointer, |
| must_send_icmp, |
| header_len, |
| action, |
| }) if action.should_send_icmp(&dst_ip) && must_send_icmp => { |
| // This should never return `true` for IPv4. |
| assert!(!action.should_send_icmp_to_multicast()); |
| send_icmpv4_parameter_problem( |
| ctx, |
| device, |
| frame_dst, |
| src_ip, |
| dst_ip, |
| code, |
| Icmpv4ParameterProblem::new(pointer), |
| buffer, |
| header_len, |
| ); |
| return; |
| } |
| _ => return, // TODO(joshlf): Do something with ICMP here? |
| }; |
| |
| // TODO(ghanan): Act upon options. |
| |
| if Ipv4::LOOPBACK_SUBNET.contains(&packet.dst_ip()) { |
| // A packet from outside this host was sent with the destination IP of |
| // the loopback address, which is illegal. Loopback traffic is handled |
| // explicitly in send_ip_packet. |
| // |
| // TODO(joshlf): Do something with ICMP here? |
| debug!("got packet from remote host for loopback address {}", packet.dst_ip()); |
| } else if let Some((dst_ip, true)) = SpecifiedAddr::new(packet.dst_ip()) |
| .map(|dst_ip| (dst_ip, deliver_ipv4(ctx, device, dst_ip))) |
| { |
| trace!("receive_ipv4_packet: delivering locally"); |
| |
| // 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!( |
| ctx, |
| dispatch_receive_ipv4_packet, |
| device, |
| frame_dst, |
| buffer, |
| packet, |
| dst_ip, |
| Ipv4 |
| ); |
| } else if let Some(dest) = forward(ctx, device, packet.dst_ip()) { |
| let ttl = packet.ttl(); |
| if ttl > 1 { |
| trace!("receive_ipv4_packet: forwarding"); |
| |
| packet.set_ttl(ttl - 1); |
| let (src_ip, dst_ip, proto, meta) = drop_packet_and_undo_parse!(packet, buffer); |
| if crate::device::send_ip_frame(ctx, dest.device, dest.next_hop, buffer).is_err() { |
| debug!("failed to forward IPv4 packet: MTU exceeded"); |
| } |
| } else { |
| debug!("received IPv4 packet dropped due to expired TTL"); |
| |
| // TTL is 0 or would become 0 after decrement; see "TTL" section, |
| // https://tools.ietf.org/html/rfc791#page-14 |
| let (src_ip, dst_ip, proto, meta) = drop_packet_and_undo_parse!(packet, buffer); |
| icmp::send_icmpv4_ttl_expired( |
| ctx, |
| device, |
| frame_dst, |
| src_ip, |
| dst_ip, |
| proto, |
| buffer, |
| meta.header_len(), |
| ); |
| } |
| } else { |
| let (src_ip, dst_ip, proto, meta) = drop_packet_and_undo_parse!(packet, buffer); |
| debug!("received IPv4 packet with no known route to destination {}", dst_ip); |
| |
| icmp::send_icmpv4_net_unreachable( |
| ctx, |
| device, |
| frame_dst, |
| src_ip, |
| dst_ip, |
| proto, |
| buffer, |
| meta.header_len(), |
| ); |
| } |
| } |
| |
| /// Receive an IPv6 packet from a device. |
| /// |
| /// `frame_dst` specifies whether this packet was received in a broadcast or |
| /// unicast link-layer frame. |
| pub(crate) fn receive_ipv6_packet<B: BufferMut, D: BufferDispatcher<B>>( |
| ctx: &mut Context<D>, |
| device: DeviceId, |
| frame_dst: FrameDestination, |
| mut buffer: B, |
| ) { |
| increment_counter!(ctx, "receive_ipv6_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 action.should_send_icmp(&dst_ip) && must_send_icmp => { |
| send_icmpv6_parameter_problem( |
| ctx, |
| device, |
| frame_dst, |
| src_ip, |
| dst_ip, |
| code, |
| Icmpv6ParameterProblem::new(pointer), |
| buffer, |
| header_len, |
| 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. |
| |
| if Ipv6::LOOPBACK_SUBNET.contains(&packet.dst_ip()) { |
| // A packet from outside this host was sent with the destination IP of |
| // the loopback address, which is illegal. Loopback traffic is handled |
| // explicitly in send_ip_packet. |
| // |
| // TODO(joshlf): Do something with ICMP here? |
| debug!("got packet from remote host for loopback address {}", packet.dst_ip()); |
| } else if let Some((dst_ip, true)) = SpecifiedAddr::new(packet.dst_ip()) |
| .map(|dst_ip| (dst_ip, deliver_ipv6(ctx, device, dst_ip))) |
| { |
| 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. |
| // |
| // We also need to make sure the destination address is not actually a |
| // tentative address we are performing NDP's Duplicate Address Detection |
| // on. |
| // |
| // 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. |
| // |
| // Here we check to see if the packet's destination address is the IPv6 |
| // address assigned to the device. If it is, we check to see if it is |
| // tentative. If the destination address is not the address assigned to |
| // the device, or it is not tentative, we are okay to proceed; else, we |
| // drop the packet. |
| if crate::device::is_addr_tentative_on_device(ctx.state(), dst_ip, device) { |
| // Silently drop as per RFC 4862 section 5.4 |
| trace!( |
| "receive_ipv6_packet: Dropping packet as it is destined for a tentative address" |
| ); |
| return; |
| } |
| |
| // Handle extension headers first. |
| match ipv6::handle_extension_headers(ctx, device, frame_dst, &packet, true) { |
| Ipv6PacketAction::Discard => { |
| trace!("receive_ipv6_packet: handled IPv6 extension headers: discarding packet"); |
| } |
| 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 (src_ip, _, proto, meta) = packet.into_metadata(); |
| dispatch_receive_ipv6_packet( |
| ctx, |
| Some(device), |
| frame_dst, |
| src_ip, |
| dst_ip, |
| proto, |
| buffer, |
| Some(meta), |
| ); |
| } |
| Ipv6PacketAction::ProcessFragment => { |
| trace!( |
| "receive_ipv6_packet: handled IPv6 extension headers: handling fragmented packet" |
| ); |
| |
| // 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 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 posisble |
| // 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!( |
| ctx, |
| dispatch_receive_ipv6_packet, |
| device, |
| frame_dst, |
| buffer, |
| packet, |
| dst_ip, |
| Ipv6 |
| ); |
| } |
| } |
| } else if let Some(dest) = forward(ctx, device, packet.dst_ip()) { |
| let ttl = packet.ttl(); |
| if ttl > 1 { |
| trace!("receive_ipv6_packet: forwarding"); |
| |
| // Handle extension headers first. |
| match ipv6::handle_extension_headers(ctx, device, frame_dst, &packet, false) { |
| Ipv6PacketAction::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)"), |
| } |
| |
| packet.set_ttl(ttl - 1); |
| let (src_ip, dst_ip, proto, meta) = drop_packet_and_undo_parse!(packet, buffer); |
| if let Err(buffer) = |
| crate::device::send_ip_frame(ctx, dest.device, dest.next_hop, buffer) |
| { |
| debug!("failed to forward IPv6 packet: MTU exceeded"); |
| 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 = crate::device::get_mtu(ctx.state(), device); |
| crate::ip::icmp::send_icmpv6_packet_too_big( |
| ctx, |
| device, |
| frame_dst, |
| src_ip, |
| dst_ip, |
| proto, |
| mtu, |
| buffer, |
| meta.header_len(), |
| ); |
| } |
| } else { |
| debug!("received IPv6 packet dropped due to expired Hop Limit"); |
| |
| // Hop Limit is 0 or would become 0 after decrement; see RFC 2460 |
| // Section 3. |
| let (src_ip, dst_ip, proto, meta) = drop_packet_and_undo_parse!(packet, buffer); |
| icmp::send_icmpv6_ttl_expired( |
| ctx, |
| device, |
| frame_dst, |
| src_ip, |
| dst_ip, |
| proto, |
| buffer, |
| meta.header_len(), |
| ); |
| } |
| } else { |
| let (src_ip, dst_ip, proto, meta) = drop_packet_and_undo_parse!(packet, buffer); |
| debug!("received IPv6 packet with no known route to destination {}", dst_ip); |
| |
| icmp::send_icmpv6_net_unreachable( |
| ctx, |
| device, |
| frame_dst, |
| src_ip, |
| dst_ip, |
| proto, |
| buffer, |
| meta.header_len(), |
| ); |
| } |
| } |
| |
| /// Get the local address of the interface that will be used to route to a |
| /// remote address. |
| /// |
| /// `local_address_for_remote` looks up the route to `remote`. If one is found, |
| /// it returns the IP address of the interface specified by the route, or `None` |
| /// if the interface has no IP address. |
| pub(crate) fn local_address_for_remote<D: EventDispatcher, A: IpAddress>( |
| ctx: &Context<D>, |
| remote: SpecifiedAddr<A>, |
| ) -> Option<SpecifiedAddr<A>> { |
| let route = lookup_route(ctx, remote)?; |
| crate::device::get_ip_addr_subnet(ctx.state(), route.device).map(|a| a.addr()) |
| } |
| |
| // Should we deliver this IPv4 packet locally? |
| // deliver returns true if: |
| // - dst_ip is equal to the address set on the device |
| // - dst_ip is equal to the broadcast address of the subnet set on the device |
| // - dst_ip is equal to the global broadcast address |
| // - dst_ip is equal to a mutlicast group that `device` joined |
| fn deliver_ipv4<D: EventDispatcher>( |
| ctx: &mut Context<D>, |
| device: DeviceId, |
| dst_ip: SpecifiedAddr<Ipv4Addr>, |
| ) -> bool { |
| // TODO(joshlf): |
| // - This implements a strict host model (in which we only accept packets |
| // which are addressed to the device over which they were received). This |
| // is the easiest to implement for the time being, but we should actually |
| // put real thought into what our host model should be (NET-1011). |
| let dst_ip = dst_ip.get(); |
| |
| crate::device::get_ip_addr_subnets::<_, Ipv4Addr>(ctx.state(), device) |
| .map(AddrSubnet::into_addr_subnet) |
| .any(|(addr, subnet)| dst_ip == addr.get() || dst_ip == subnet.broadcast()) |
| || dst_ip.is_global_broadcast() |
| || MulticastAddr::new(dst_ip) |
| .map_or(false, |a| crate::device::is_in_ip_multicast(ctx, device, a)) |
| } |
| |
| // Should we deliver this IPv6 packet locally? |
| // deliver returns true if: |
| // - dst_ip is equal to the address set on the device |
| // - dst_ip is equal to the broadcast address of the subnet set on the device |
| // - dst_ip is equal to a mutlicast group that `device` joined |
| fn deliver_ipv6<D: EventDispatcher>( |
| ctx: &mut Context<D>, |
| device: DeviceId, |
| dst_ip: SpecifiedAddr<Ipv6Addr>, |
| ) -> bool { |
| // TODO(brunodalbo): |
| // Along with the host model described above, we need to be able to have |
| // multiple IPs per interface, it becomes imperative for IPv6. |
| crate::device::get_ip_addr_subnets(ctx.state(), device) |
| .map(AddrSubnet::into_addr_subnet) |
| .any(|(addr, _)| dst_ip == addr) |
| || crate::device::get_ipv6_link_local_addr(ctx, device) |
| .map_or(false, |x| x.get() == dst_ip.get()) |
| || dst_ip.deref() == &Ipv6::ALL_NODES_LINK_LOCAL_ADDRESS |
| || MulticastAddr::new(dst_ip.get()) |
| .map_or(false, |a| crate::device::is_in_ip_multicast(ctx, device, a)) |
| } |
| |
| // Should we forward this packet, and if so, to whom? |
| fn forward<D: EventDispatcher, A: IpAddress>( |
| ctx: &mut Context<D>, |
| device_id: DeviceId, |
| dst_ip: A, |
| ) -> Option<Destination<A, DeviceId>> { |
| trace!("ip::forward: destination ip = {:?}", dst_ip); |
| |
| // Is this netstack configured to foward packets not destined for it? |
| if !crate::ip::is_routing_enabled::<_, A::Version>(ctx) { |
| trace!("ip::forward: can't forward because netstack not configured to do so"); |
| return None; |
| } |
| |
| // Does the interface the packet arrived on have routing enabled? |
| if !crate::device::is_routing_enabled::<_, A::Version>(ctx, device_id) { |
| trace!("ip::forward: can't forward because packet arrived on an interface without routing enabled; device = {:?}", device_id); |
| return None; |
| } |
| |
| match SpecifiedAddr::new(dst_ip).map_or(None, |dst_ip| lookup_route(ctx, dst_ip)) { |
| Some(dest) => { |
| trace!("ip::forward: found a valid route to {:?} -> {:?}", dst_ip, dest); |
| Some(dest) |
| } |
| None => { |
| trace!("ip::forward: can't forward because no valid route exists to {:?}", dst_ip); |
| None |
| } |
| } |
| } |
| |
| // Look up the route to a host. |
| pub(crate) fn lookup_route<A: IpAddress, D: EventDispatcher>( |
| ctx: &Context<D>, |
| dst_ip: SpecifiedAddr<A>, |
| ) -> Option<Destination<A, DeviceId>> { |
| get_state_inner::<A::Version, _>(ctx.state()).table.lookup(dst_ip) |
| } |
| |
| /// Add a route to the forwarding table, returning `Err` if the subnet |
| /// is already in the table. |
| pub(crate) fn add_route<D: EventDispatcher, A: IpAddress>( |
| ctx: &mut Context<D>, |
| subnet: Subnet<A>, |
| next_hop: SpecifiedAddr<A>, |
| ) -> Result<(), ExistsError> { |
| get_state_inner_mut::<A::Version, _>(ctx.state_mut()).table.add_route(subnet, next_hop) |
| } |
| |
| /// Add a device route to the forwarding table, returning `Err` if the |
| /// subnet is already in the table. |
| pub(crate) fn add_device_route<D: EventDispatcher, A: IpAddress>( |
| ctx: &mut Context<D>, |
| subnet: Subnet<A>, |
| device: DeviceId, |
| ) -> Result<(), ExistsError> { |
| get_state_inner_mut::<A::Version, _>(ctx.state_mut()).table.add_device_route(subnet, device) |
| } |
| |
| /// Delete a route from the forwarding table, returning `Err` if no |
| /// route was found to be deleted. |
| pub(crate) fn del_device_route<D: EventDispatcher, A: IpAddress>( |
| ctx: &mut Context<D>, |
| subnet: Subnet<A>, |
| ) -> Result<(), NotFoundError> { |
| get_state_inner_mut::<A::Version, _>(ctx.state_mut()).table.del_route(subnet) |
| } |
| |
| /// Return all the routes for the provided `IpAddress` type |
| pub(crate) fn iter_all_routes<D: EventDispatcher, A: IpAddress>( |
| ctx: &Context<D>, |
| ) -> std::slice::Iter<Entry<A, DeviceId>> { |
| get_state_inner::<A::Version, _>(ctx.state()).table.iter_installed() |
| } |
| |
| /// Is this one of our local addresses? |
| /// |
| /// `is_local_addr` returns whether `addr` is the address associated with one of |
| /// our local interfaces. |
| pub(crate) fn is_local_addr<D: EventDispatcher, A: IpAddress>(ctx: &Context<D>, addr: A) -> bool { |
| log_unimplemented!(false, "ip::is_local_addr: not implemented") |
| } |
| |
| /// Send an IPv4 packet to a remote host. |
| /// |
| /// `send_ipv4_packet` accepts a destination IP address, a protocol, and a |
| /// callback. It computes the routing information, and invokes the callback with |
| /// the computed destination address. The callback returns a |
| /// `SerializationRequest`, which is serialized in a new IP packet and sent. |
| pub(crate) fn send_ipv4_packet< |
| B: BufferMut, |
| D: BufferDispatcher<B>, |
| S: Serializer<Buffer = B>, |
| F: FnOnce(SpecifiedAddr<Ipv4Addr>) -> S, |
| >( |
| ctx: &mut Context<D>, |
| dst_ip: SpecifiedAddr<Ipv4Addr>, |
| proto: IpProto, |
| get_body: F, |
| ) -> Result<(), S> { |
| trace!("send_ipv4_packet({}, {})", dst_ip, proto); |
| increment_counter!(ctx, "send_ipv4_packet"); |
| if Ipv4::LOOPBACK_SUBNET.contains(&dst_ip) { |
| increment_counter!(ctx, "send_ipv4_packet::loopback"); |
| |
| // TODO(joshlf): Currently, we have no way of representing the loopback |
| // device as a DeviceId. We will need to fix that eventually. |
| |
| // TODO(joshlf): Currently, we serialize using the normal Serializer |
| // functionality. I wonder if, in the case of delivering to loopback, we |
| // can do something more efficient? |
| let mut buffer = |
| get_body(Ipv4::LOOPBACK_ADDRESS).serialize_vec_outer().map_err(|(_, ser)| ser)?; |
| // TODO(joshlf): Respond with some kind of error if we don't have a |
| // handler for that protocol? Maybe simulate what would have happened |
| // (w.r.t ICMP) if this were a remote host? |
| |
| // NOTE(joshlf): By passing a DeviceId and ParseMetadata of None here, |
| // we are promising that the protocol will be recognized (and the call |
| // will panic if it's not). This is OK because the fact that we're |
| // sending a packet with this protocol means we are able to process that |
| // protocol. |
| // |
| // NOTE(joshlf): By doing that, we are also promising that the port will |
| // be recognized. That is NOT OK, and the call will panic in that case. |
| // TODO(joshlf): Fix this. |
| |
| // The reason for this match is so that, in each case, we are guaranteed |
| // that the buffer type is a supported buffer type for our event |
| // dispatcher. If we were to just pass the original buffer itself, then |
| // in addition to supporting `B`, the event dispatcher would need to |
| // support `Either<B, Vec<u8>>`, but then `Either<B, Vec<u8>>` would |
| // become `B` from the perspective of `dispatch_receive_ip_packet`, so |
| // it would need to support `Either<Either<B, Vec<u8>>, Vec<u8>>`, and |
| // so on. This match allows us to break that type recursion. |
| match buffer { |
| Either::A(buffer) => dispatch_receive_ipv4_packet( |
| ctx, |
| None, |
| FrameDestination::Unicast, |
| Ipv4::LOOPBACK_ADDRESS.into_addr(), |
| dst_ip, |
| proto, |
| buffer, |
| None, |
| ), |
| Either::B(buffer) => dispatch_receive_ipv4_packet::<Buf<Vec<u8>>, _>( |
| ctx, |
| None, |
| FrameDestination::Unicast, |
| Ipv4::LOOPBACK_ADDRESS.into_addr(), |
| dst_ip, |
| proto, |
| buffer, |
| None, |
| ), |
| } |
| } else if let Some(dest) = lookup_route(ctx, dst_ip) { |
| // TODO(joshlf): Are we sure that a device route can never be set for a |
| // device without an IP address? At the least, this is not currently |
| // enforced anywhere, and is a DoS vector. |
| let src_ip: SpecifiedAddr<Ipv4Addr> = |
| crate::device::get_ip_addr_subnet(ctx.state(), dest.device) |
| .expect("IP device route set for device without IP address") |
| .addr(); |
| send_ip_packet_from_device( |
| ctx, |
| dest.device, |
| src_ip.into_addr(), |
| dst_ip.into_addr(), |
| dest.next_hop, |
| proto, |
| get_body(src_ip), |
| None, |
| )?; |
| } else { |
| debug!("No route to host"); |
| // TODO(joshlf): No route to host |
| } |
| |
| Ok(()) |
| } |
| |
| /// Send an IPv6 packet to a remote host. |
| /// |
| /// `send_ipv6_packet` accepts a destination IP address, a protocol, and a |
| /// callback. It computes the routing information, and invokes the callback with |
| /// the computed destination address. The callback returns a |
| /// `SerializationRequest`, which is serialized in a new IP packet and sent. |
| pub(crate) fn send_ipv6_packet< |
| B: BufferMut, |
| D: BufferDispatcher<B>, |
| S: Serializer<Buffer = B>, |
| F: FnOnce(SpecifiedAddr<Ipv6Addr>) -> S, |
| >( |
| ctx: &mut Context<D>, |
| dst_ip: SpecifiedAddr<Ipv6Addr>, |
| proto: IpProto, |
| get_body: F, |
| ) -> Result<(), S> { |
| trace!("send_ipv6_packet({}, {})", dst_ip, proto); |
| increment_counter!(ctx, "send_ipv6_packet"); |
| if Ipv6::LOOPBACK_SUBNET.contains(&dst_ip) { |
| increment_counter!(ctx, "send_ipv6_packet::loopback"); |
| |
| // TODO(joshlf): Currently, we have no way of representing the loopback |
| // device as a DeviceId. We will need to fix that eventually. |
| |
| // TODO(joshlf): Currently, we serialize using the normal Serializer |
| // functionality. I wonder if, in the case of delivering to loopback, we |
| // can do something more efficient? |
| let mut buffer = |
| get_body(Ipv6::LOOPBACK_ADDRESS).serialize_vec_outer().map_err(|(_, ser)| ser)?; |
| // TODO(joshlf): Respond with some kind of error if we don't have a |
| // handler for that protocol? Maybe simulate what would have happened |
| // (w.r.t ICMP) if this were a remote host? |
| |
| // NOTE(joshlf): By passing a DeviceId and ParseMetadata of None here, |
| // we are promising that the protocol will be recognized (and the call |
| // will panic if it's not). This is OK because the fact that we're |
| // sending a packet with this protocol means we are able to process that |
| // protocol. |
| // |
| // NOTE(joshlf): By doing that, we are also promising that the port will |
| // be recognized. That is NOT OK, and the call will panic in that case. |
| // TODO(joshlf): Fix this. |
| |
| // The reason for this match is so that, in each case, we are guaranteed |
| // that the buffer type is a supported buffer type for our event |
| // dispatcher. If we were to just pass the original buffer itself, then |
| // in addition to supporting `B`, the event dispatcher would need to |
| // support `Either<B, Vec<u8>>`, but then `Either<B, Vec<u8>>` would |
| // become `B` from the perspective of `dispatch_receive_ip_packet`, so |
| // it would need to support `Either<Either<B, Vec<u8>>, Vec<u8>>`, and |
| // so on. This match allows us to break that type recursion. |
| match buffer { |
| Either::A(buffer) => dispatch_receive_ipv6_packet( |
| ctx, |
| None, |
| FrameDestination::Unicast, |
| Ipv6::LOOPBACK_ADDRESS.into_addr(), |
| dst_ip, |
| proto, |
| buffer, |
| None, |
| ), |
| Either::B(buffer) => dispatch_receive_ipv6_packet::<Buf<Vec<u8>>, _>( |
| ctx, |
| None, |
| FrameDestination::Unicast, |
| Ipv6::LOOPBACK_ADDRESS.into_addr(), |
| dst_ip, |
| proto, |
| buffer, |
| None, |
| ), |
| } |
| } else if let Some(dest) = lookup_route(ctx, dst_ip) { |
| // TODO(joshlf): Are we sure that a device route can never be set for a |
| // device without an IP address? At the least, this is not currently |
| // enforced anywhere, and is a DoS vector. |
| let src_ip: SpecifiedAddr<Ipv6Addr> = |
| crate::device::get_ip_addr_subnet(ctx.state(), dest.device) |
| .expect("IP device route set for device without IP address") |
| .addr(); |
| send_ip_packet_from_device( |
| ctx, |
| dest.device, |
| src_ip.into_addr(), |
| dst_ip.into_addr(), |
| dest.next_hop, |
| proto, |
| get_body(src_ip), |
| None, |
| )?; |
| } else { |
| debug!("No route to host"); |
| // TODO(joshlf): No route to host |
| } |
| |
| Ok(()) |
| } |
| |
| /// Send an IP packet to a remote host over a specific device. |
| /// |
| /// `send_ip_packet_from_device` accepts a device, a source and destination IP |
| /// address, a next hop IP address, and a `SerializationRequest`. It computes |
| /// the routing information and serializes the request in a new IP packet and |
| /// sends it. `mtu` will optionally impose an MTU constraint on the whole IP |
| /// packet. This is useful for cases where some packets are being sent out |
| /// which must not exceed some size (ICMPv6 Error Responses). |
| /// |
| /// # Panics |
| /// |
| /// Since `send_ip_packet_from_device` specifies a physical device, it cannot |
| /// send to or from a loopback IP address. If either `src_ip` or `dst_ip` are in |
| /// the loopback subnet, `send_ip_packet_from_device` will panic. |
| pub(crate) fn send_ip_packet_from_device<B: BufferMut, D: BufferDispatcher<B>, A, S>( |
| ctx: &mut Context<D>, |
| device: DeviceId, |
| src_ip: A, |
| dst_ip: A, |
| next_hop: SpecifiedAddr<A>, |
| proto: IpProto, |
| body: S, |
| mtu: Option<u32>, |
| ) -> Result<(), S> |
| where |
| A: IpAddress, |
| S: Serializer<Buffer = B>, |
| { |
| assert!(!A::Version::LOOPBACK_SUBNET.contains(&src_ip)); |
| assert!(!A::Version::LOOPBACK_SUBNET.contains(&dst_ip)); |
| |
| // Tentative addresses are not considered bound to an interface in the traditional sense, |
| // therefore, no packet should have a source IP set to a tentative address. |
| debug_assert!(!SpecifiedAddr::new(src_ip).map_or(false, |src_ip| { |
| crate::device::is_addr_tentative_on_device(ctx.state(), src_ip, device) |
| })); |
| |
| let builder = <A::Version as IpExt>::PacketBuilder::new( |
| src_ip, |
| dst_ip, |
| get_hop_limit::<_, A::Version>(ctx, device), |
| proto, |
| ); |
| let body = body.encapsulate(builder); |
| |
| if let Some(mtu) = mtu { |
| let body = body.with_mtu(mtu as usize); |
| crate::device::send_ip_frame(ctx, device, next_hop, body) |
| .map_err(|ser| ser.into_inner().into_inner()) |
| } else { |
| crate::device::send_ip_frame(ctx, device, next_hop, body).map_err(|ser| ser.into_inner()) |
| } |
| } |
| |
| impl<D: EventDispatcher> FrameContext<EmptyBuf, IgmpPacketMetadata<DeviceId>> for Context<D> { |
| fn send_frame<S: Serializer<Buffer = EmptyBuf>>( |
| &mut self, |
| meta: IgmpPacketMetadata<DeviceId>, |
| body: S, |
| ) -> Result<(), S> { |
| send_igmp_packet(self, meta.device, meta.src_ip, meta.dst_ip, body) |
| } |
| } |
| |
| impl<D: EventDispatcher> FrameContext<EmptyBuf, MldFrameMetadata<DeviceId>> for Context<D> { |
| fn send_frame<S: Serializer<Buffer = EmptyBuf>>( |
| &mut self, |
| meta: MldFrameMetadata<DeviceId>, |
| body: S, |
| ) -> Result<(), S> { |
| crate::device::send_ip_frame(self, meta.device, meta.local_ip.into_specified(), body) |
| } |
| } |
| |
| impl<D: EventDispatcher> IgmpContext for Context<D> { |
| fn get_ip_addr_subnet(&self, device: DeviceId) -> Option<AddrSubnet<Ipv4Addr>> { |
| crate::device::get_ip_addr_subnet(self.state(), device) |
| } |
| } |
| |
| impl<D: EventDispatcher> MldContext for Context<D> { |
| fn get_ipv6_link_local_addr(&self, device: DeviceId) -> Option<LinkLocalAddr<Ipv6Addr>> { |
| crate::device::get_ipv6_link_local_addr(self, device) |
| } |
| } |
| |
| /// Send an IGMP packet. |
| /// |
| /// IGMP packet must have a TTL of 1 and have RouterAlert set. |
| fn send_igmp_packet<B: BufferMut, D: BufferDispatcher<B>, S>( |
| ctx: &mut Context<D>, |
| device: DeviceId, |
| src_ip: SpecifiedAddr<Ipv4Addr>, |
| dst_ip: MulticastAddr<Ipv4Addr>, |
| body: S, |
| ) -> Result<(), S> |
| where |
| S: Serializer<Buffer = B>, |
| { |
| let ipv4 = Ipv4PacketBuilder::new(src_ip, dst_ip, 1, IpProto::Igmp); |
| let with_options = match Ipv4PacketBuilderWithOptions::new( |
| ipv4, |
| &[Ipv4Option { copied: true, data: crate::ip::Ipv4OptionData::RouterAlert { data: 0 } }], |
| ) { |
| None => return Err(body), |
| Some(builder) => builder, |
| }; |
| |
| let body = body.encapsulate(with_options); |
| crate::device::send_ip_frame(ctx, device, dst_ip.into_specified(), body) |
| .map_err(|ser| ser.into_inner()) |
| } |
| |
| impl<D: EventDispatcher> StateContext<(), Icmpv4State> for Context<D> { |
| fn get_state(&self, _id: ()) -> &Icmpv4State { |
| &self.state().ipv4.icmp |
| } |
| |
| fn get_state_mut(&mut self, _id: ()) -> &mut Icmpv4State { |
| &mut self.state_mut().ipv4.icmp |
| } |
| } |
| |
| impl<D: EventDispatcher> StateContext<(), Icmpv6State> for Context<D> { |
| fn get_state(&self, _id: ()) -> &Icmpv6State { |
| &self.state().ipv6.icmp |
| } |
| |
| fn get_state_mut(&mut self, _id: ()) -> &mut Icmpv6State { |
| &mut self.state_mut().ipv6.icmp |
| } |
| } |
| |
| impl<I: IcmpIpExt, B: BufferMut, D: BufferDispatcher<B>> IcmpContext<I, B> for Context<D> { |
| fn send_icmp_reply<S: Serializer<Buffer = B>, F: FnOnce(SpecifiedAddr<I::Addr>) -> S>( |
| &mut self, |
| device: Option<DeviceId>, |
| src_ip: SpecifiedAddr<I::Addr>, |
| dst_ip: SpecifiedAddr<I::Addr>, |
| get_body: F, |
| ) -> Result<(), S> { |
| trace!("send_icmp_reply({:?}, {}, {})", device, src_ip, dst_ip); |
| self.increment_counter("send_icmp_reply"); |
| |
| #[specialize_ip_address] |
| fn send_ip_packet< |
| B: BufferMut, |
| D: BufferDispatcher<B>, |
| A: IpAddress, |
| S: Serializer<Buffer = B>, |
| F: FnOnce(SpecifiedAddr<A>) -> S, |
| >( |
| ctx: &mut Context<D>, |
| dst_ip: SpecifiedAddr<A>, |
| proto: IpProto, |
| get_body: F, |
| ) -> Result<(), S> { |
| #[ipv4addr] |
| return send_ipv4_packet(ctx, dst_ip, proto, get_body); |
| #[ipv6addr] |
| return send_ipv6_packet(ctx, dst_ip, proto, get_body); |
| } |
| |
| // TODO(joshlf): Use `dst_ip` for anything? |
| send_ip_packet(self, src_ip, I::IP_PROTO, get_body) |
| } |
| |
| fn send_icmp_error_message< |
| S: Serializer<Buffer = B>, |
| F: FnOnce(SpecifiedAddr<I::Addr>) -> S, |
| >( |
| &mut self, |
| device: DeviceId, |
| frame_dst: FrameDestination, |
| src_ip: SpecifiedAddr<I::Addr>, |
| dst_ip: I::Addr, |
| get_body: F, |
| ip_mtu: Option<u32>, |
| allow_dst_multicast: bool, |
| ) -> Result<(), S> { |
| trace!("send_icmp_error_message({}, {}, {}, {:?})", device, src_ip, dst_ip, ip_mtu); |
| self.increment_counter("send_icmp_error_message"); |
| |
| // TODO(joshlf): Come up with rules for when to send ICMP responses. |
| // E.g., should we send a response over a different device than the |
| // device that the original packet ingressed over? We'll probably want |
| // to consult BCP 38 (aka RFC 2827) and RFC 3704. |
| |
| #[specialize_ip_address] |
| fn should_send_icmp_error<A: IpAddress>( |
| frame_dst: FrameDestination, |
| src_ip: SpecifiedAddr<A>, |
| dst_ip: A, |
| allow_dst_multicast: bool, |
| ) -> bool { |
| #[ipv4addr] |
| return crate::ip::icmp::should_send_icmpv4_error(frame_dst, src_ip, dst_ip); |
| #[ipv6addr] |
| return crate::ip::icmp::should_send_icmpv6_error( |
| frame_dst, |
| src_ip, |
| dst_ip, |
| allow_dst_multicast, |
| ); |
| } |
| |
| if !should_send_icmp_error(frame_dst, src_ip, dst_ip, allow_dst_multicast) { |
| return Ok(()); |
| } |
| |
| if let Some(route) = lookup_route(self, src_ip) { |
| if let Some(local_ip_subnet) = |
| crate::device::get_ip_addr_subnet(self.state(), route.device) |
| { |
| let local_ip: SpecifiedAddr<I::Addr> = local_ip_subnet.into_addr(); |
| |
| send_ip_packet_from_device( |
| self, |
| route.device, |
| local_ip.into_addr(), |
| src_ip.into_addr(), |
| route.next_hop, |
| I::IP_PROTO, |
| get_body(local_ip), |
| ip_mtu, |
| )?; |
| } else { |
| log_unimplemented!( |
| (), |
| "Sending ICMP over unnumbered device {} is unimplemented", |
| route.device |
| ); |
| |
| // TODO(joshlf): We need a general-purpose mechanism for |
| // choosing a source address in cases where we're a) acting as a |
| // router (and thus sending packets with our own source address, |
| // but not as a result of any local application behavior) and, |
| // b) sending over an unnumbered device (one without any |
| // configured IP address). ICMP is the notable use case. Most |
| // likely, we will want to pick the IP address of a different |
| // local device. See for an explanation of why we might have |
| // this setup: |
| // https://www.cisco.com/c/en/us/support/docs/ip/hot-standby-router-protocol-hsrp/13786-20.html#unnumbered_iface |
| } |
| } else { |
| debug!("Can't send ICMP response to {}: no route to host", src_ip); |
| } |
| |
| Ok(()) |
| } |
| } |
| |
| /// Is `ctx` configured to route packets? |
| #[specialize_ip] |
| pub(crate) fn is_routing_enabled<D: EventDispatcher, I: Ip>(ctx: &Context<D>) -> bool { |
| get_state_inner::<I, _>(ctx.state()).forward |
| } |
| |
| /// Get the hop limit for new IP packets that will be sent out from `device`. |
| #[specialize_ip] |
| fn get_hop_limit<D: EventDispatcher, I: Ip>(ctx: &Context<D>, device: DeviceId) -> u8 { |
| // TODO(ghanan): Should IPv4 packets use the same TTL value |
| // as IPv6 packets? Currently for the IPv6 case, |
| // we get the default hop limit from the device |
| // state which can be updated by NDP's Router |
| // Advertisement. |
| |
| #[ipv4] |
| return DEFAULT_TTL; |
| |
| // This value can be updated by NDP's Router Advertisements. |
| #[ipv6] |
| return crate::device::get_ipv6_hop_limit(ctx, device).get(); |
| } |
| |
| // Used in testing in other modules. |
| #[cfg(test)] |
| #[specialize_ip] |
| pub(crate) fn dispatch_receive_ip_packet_name<I: Ip>() -> &'static str { |
| #[ipv4] |
| return "dispatch_receive_ipv4_packet"; |
| #[ipv6] |
| return "dispatch_receive_ipv6_packet"; |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| |
| use std::convert::TryFrom; |
| use std::num::NonZeroU16; |
| |
| use byteorder::{ByteOrder, NetworkEndian}; |
| use net_types::ip::{Ipv4Addr, Ipv6Addr}; |
| use packet::{Buf, ParseBuffer}; |
| use rand::Rng; |
| |
| use crate::device::ethernet::EthernetIpExt; |
| use crate::device::{receive_frame, set_routing_enabled, FrameDestination}; |
| use crate::ip::path_mtu::{get_pmtu, min_mtu}; |
| use crate::testutil::*; |
| use crate::wire::ethernet::{EthernetFrame, EthernetFrameBuilder}; |
| use crate::wire::icmp::{ |
| IcmpDestUnreachable, IcmpEchoRequest, IcmpPacketBuilder, IcmpParseArgs, IcmpUnusedCode, |
| Icmpv4DestUnreachableCode, Icmpv6Packet, Icmpv6PacketTooBig, Icmpv6ParameterProblemCode, |
| MessageBody, |
| }; |
| use crate::wire::ipv4::Ipv4PacketBuilder; |
| use crate::wire::ipv6::ext_hdrs::ExtensionHeaderOptionAction; |
| use crate::wire::ipv6::Ipv6PacketBuilder; |
| use crate::{DeviceId, Mac, StackStateBuilder}; |
| |
| // |
| // 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( |
| ctx: &mut Context<DummyEventDispatcher>, |
| code: Icmpv6ParameterProblemCode, |
| pointer: u32, |
| offset: usize, |
| ) { |
| // Check the ICMP that bob attempted to send to alice |
| let device_frames = ctx.dispatcher.frames_sent().clone(); |
| assert!(!device_frames.is_empty()); |
| let mut buffer = Buf::new(device_frames[offset].1.as_slice(), ..); |
| let frame = buffer.parse::<EthernetFrame<_>>().unwrap(); |
| let packet = buffer.parse::<<Ipv6 as IpExtByteSlice<&[u8]>>::Packet>().unwrap(); |
| let (src_ip, dst_ip, proto, _) = packet.into_metadata(); |
| assert_eq!(dst_ip, DUMMY_CONFIG_V6.remote_ip.get()); |
| assert_eq!(src_ip, DUMMY_CONFIG_V6.local_ip.get()); |
| assert_eq!(proto, IpProto::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 = (bytes.len() - 40) as u16; |
| NetworkEndian::write_u16(&mut bytes[4..6], payload_len); |
| |
| bytes[6] = Ipv6ExtHdrType::DestinationOptions.into(); |
| bytes[7] = 64; |
| bytes[8..24].copy_from_slice(DUMMY_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(DUMMY_CONFIG_V6.local_ip.bytes()); |
| } |
| |
| Buf::new(bytes, ..) |
| } |
| |
| /// Create an IPv4 packet builder. |
| fn get_ipv4_builder() -> Ipv4PacketBuilder { |
| Ipv4PacketBuilder::new( |
| Ipv4Addr::new([192, 168, 0, 100]), |
| DUMMY_CONFIG_V4.local_ip, |
| 10, |
| IpProto::Udp, |
| ) |
| } |
| |
| /// Process an IP fragment depending on the `Ip` `process_ip_fragment` is |
| /// specialized with. |
| #[specialize_ip] |
| fn process_ip_fragment<I: Ip, D: EventDispatcher>( |
| ctx: &mut Context<D>, |
| device: DeviceId, |
| fragment_id: u16, |
| fragment_offset: u8, |
| fragment_count: u8, |
| ) { |
| #[ipv4] |
| process_ipv4_fragment(ctx, device, fragment_id, fragment_offset, fragment_count); |
| |
| #[ipv6] |
| 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<D: EventDispatcher>( |
| ctx: &mut Context<D>, |
| device: DeviceId, |
| 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 mut buffer = |
| Buf::new(body, ..).encapsulate(builder).serialize_vec_outer().unwrap().into_inner(); |
| receive_ipv4_packet(ctx, device, FrameDestination::Unicast, 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<D: EventDispatcher>( |
| ctx: &mut Context<D>, |
| device: DeviceId, |
| 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(&[16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31]); |
| bytes[24..40].copy_from_slice(DUMMY_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 }; |
| NetworkEndian::write_u32(&mut bytes[44..48], fragment_id as u32); |
| bytes.extend(fragment_offset * 8..fragment_offset * 8 + 8); |
| let payload_len = (bytes.len() - 40) as u16; |
| NetworkEndian::write_u16(&mut bytes[4..6], payload_len); |
| let mut buffer = Buf::new(bytes, ..); |
| receive_ipv6_packet(ctx, device, FrameDestination::Unicast, buffer); |
| } |
| |
| #[test] |
| fn test_ipv6_icmp_parameter_problem_non_must() { |
| let mut ctx = DummyEventDispatcherBuilder::from_config(DUMMY_CONFIG_V6) |
| .build::<DummyEventDispatcher>(); |
| let device = DeviceId::new_ethernet(0); |
| |
| // |
| // 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 = (bytes.len() - 40) as u16; |
| NetworkEndian::write_u16(&mut bytes[4..6], payload_len); |
| bytes[6] = 255; // Invalid Next Header |
| bytes[7] = 64; |
| bytes[8..24].copy_from_slice(DUMMY_CONFIG_V6.remote_ip.bytes()); |
| bytes[24..40].copy_from_slice(DUMMY_CONFIG_V6.local_ip.bytes()); |
| let mut buf = Buf::new(bytes, ..); |
| |
| receive_ipv6_packet(&mut ctx, device, FrameDestination::Unicast, buf); |
| |
| assert_eq!(get_counter_val(&mut ctx, "send_icmpv4_parameter_problem"), 0); |
| assert_eq!(get_counter_val(&mut ctx, "send_icmpv6_parameter_problem"), 0); |
| assert_eq!(get_counter_val(&mut ctx, "dispatch_receive_ipv4_packet"), 0); |
| assert_eq!(get_counter_val(&mut ctx, "dispatch_receive_ipv6_packet"), 0); |
| } |
| |
| #[test] |
| fn test_ipv6_icmp_parameter_problem_must() { |
| let mut ctx = DummyEventDispatcherBuilder::from_config(DUMMY_CONFIG_V6) |
| .build::<DummyEventDispatcher>(); |
| let device = DeviceId::new_ethernet(0); |
| |
| // |
| // 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 = (bytes.len() - 40) as u16; |
| NetworkEndian::write_u16(&mut bytes[4..6], payload_len); |
| bytes[6] = Ipv6ExtHdrType::Routing.into(); |
| bytes[7] = 64; |
| bytes[8..24].copy_from_slice(DUMMY_CONFIG_V6.remote_ip.bytes()); |
| bytes[24..40].copy_from_slice(DUMMY_CONFIG_V6.local_ip.bytes()); |
| let mut buf = Buf::new(bytes, ..); |
| receive_ipv6_packet(&mut ctx, device, FrameDestination::Unicast, buf); |
| assert_eq!(ctx.dispatcher().frames_sent().len(), 1); |
| verify_icmp_for_unrecognized_ext_hdr_option( |
| &mut ctx, |
| Icmpv6ParameterProblemCode::ErroneousHeaderField, |
| 42, |
| 0, |
| ); |
| } |
| |
| #[test] |
| fn test_ipv6_unrecognized_ext_hdr_option() { |
| let mut ctx = DummyEventDispatcherBuilder::from_config(DUMMY_CONFIG_V6) |
| .build::<DummyEventDispatcher>(); |
| let device = DeviceId::new_ethernet(0); |
| let mut expected_icmps = 0; |
| let mut bytes = [0; 64]; |
| let frame_dst = FrameDestination::Unicast; |
| |
| // |
| // 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 mut buf = buf_for_unrecognized_ext_hdr_option_test( |
| &mut bytes, |
| ExtensionHeaderOptionAction::SkipAndContinue, |
| false, |
| ); |
| receive_ipv6_packet(&mut ctx, device, frame_dst, buf); |
| assert_eq!(get_counter_val(&mut ctx, "send_icmpv6_parameter_problem"), expected_icmps); |
| assert_eq!(get_counter_val(&mut ctx, "dispatch_receive_ipv6_packet"), 1); |
| assert_eq!(ctx.dispatcher().frames_sent().len(), expected_icmps); |
| |
| // |
| // Test with unrecognized option type set with |
| // action = discard. |
| // |
| |
| let mut buf = buf_for_unrecognized_ext_hdr_option_test( |
| &mut bytes, |
| ExtensionHeaderOptionAction::DiscardPacket, |
| false, |
| ); |
| receive_ipv6_packet(&mut ctx, device, frame_dst, buf); |
| assert_eq!(get_counter_val(&mut ctx, "send_icmpv6_parameter_problem"), expected_icmps); |
| assert_eq!(ctx.dispatcher().frames_sent().len(), expected_icmps); |
| |
| // |
| // Test with unrecognized option type set with |
| // action = discard & send icmp |
| // where dest addr is a unicast addr. |
| // |
| |
| let mut buf = buf_for_unrecognized_ext_hdr_option_test( |
| &mut bytes, |
| ExtensionHeaderOptionAction::DiscardPacketSendICMP, |
| false, |
| ); |
| receive_ipv6_packet(&mut ctx, device, frame_dst, buf); |
| expected_icmps += 1; |
| assert_eq!(get_counter_val(&mut ctx, "send_icmpv6_parameter_problem"), expected_icmps); |
| assert_eq!(ctx.dispatcher().frames_sent().len(), expected_icmps); |
| verify_icmp_for_unrecognized_ext_hdr_option( |
| &mut ctx, |
| Icmpv6ParameterProblemCode::UnrecognizedIpv6Option, |
| 48, |
| expected_icmps - 1, |
| ); |
| |
| // |
| // Test with unrecognized option type set with |
| // action = discard & send icmp |
| // where dest addr is a multicast addr. |
| // |
| |
| let mut buf = buf_for_unrecognized_ext_hdr_option_test( |
| &mut bytes, |
| ExtensionHeaderOptionAction::DiscardPacketSendICMP, |
| true, |
| ); |
| receive_ipv6_packet(&mut ctx, device, frame_dst, buf); |
| expected_icmps += 1; |
| assert_eq!(get_counter_val(&mut ctx, "send_icmpv6_parameter_problem"), expected_icmps); |
| assert_eq!(ctx.dispatcher().frames_sent().len(), expected_icmps); |
| verify_icmp_for_unrecognized_ext_hdr_option( |
| &mut ctx, |
| Icmpv6ParameterProblemCode::UnrecognizedIpv6Option, |
| 48, |
| expected_icmps - 1, |
| ); |
| |
| // |
| // Test with unrecognized option type set with |
| // action = discard & send icmp if not multicast addr |
| // where dest addr is a unicast addr. |
| // |
| |
| let mut buf = buf_for_unrecognized_ext_hdr_option_test( |
| &mut bytes, |
| ExtensionHeaderOptionAction::DiscardPacketSendICMPNoMulticast, |
| false, |
| ); |
| receive_ipv6_packet(&mut ctx, device, frame_dst, buf); |
| expected_icmps += 1; |
| assert_eq!(get_counter_val(&mut ctx, "send_icmpv6_parameter_problem"), expected_icmps); |
| assert_eq!(ctx.dispatcher().frames_sent().len(), expected_icmps); |
| verify_icmp_for_unrecognized_ext_hdr_option( |
| &mut ctx, |
| Icmpv6ParameterProblemCode::UnrecognizedIpv6Option, |
| 48, |
| expected_icmps - 1, |
| ); |
| |
| // |
| // Test with unrecognized option type set with |
| // action = discard & send icmp if not multicast addr |
| // but dest addr is a multicast addr. |
| // |
| |
| let mut buf = buf_for_unrecognized_ext_hdr_option_test( |
| &mut bytes, |
| ExtensionHeaderOptionAction::DiscardPacketSendICMPNoMulticast, |
| true, |
| ); |
| // Do not expect an ICMP response for this packet |
| receive_ipv6_packet(&mut ctx, device, frame_dst, buf); |
| assert_eq!(get_counter_val(&mut ctx, "send_icmpv6_parameter_problem"), expected_icmps); |
| assert_eq!(ctx.dispatcher().frames_sent().len(), expected_icmps); |
| |
| // |
| // None of our tests should have sent an icmpv4 packet, or dispatched an ip packet |
| // after the first. |
| // |
| |
| assert_eq!(get_counter_val(&mut ctx, "send_icmpv4_parameter_problem"), 0); |
| assert_eq!(get_counter_val(&mut ctx, "dispatch_receive_ipv6_packet"), 1); |
| } |
| |
| fn test_ip_packet_reassembly_not_needed<I: Ip>() { |
| let mut ctx = DummyEventDispatcherBuilder::from_config(get_dummy_config::<I::Addr>()) |
| .build::<DummyEventDispatcher>(); |
| let device = DeviceId::new_ethernet(0); |
| let fragment_id = 5; |
| |
| assert_eq!(get_counter_val(&mut ctx, dispatch_receive_ip_packet_name::<I>()), 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!(get_counter_val(&mut ctx, dispatch_receive_ip_packet_name::<I>()), 1); |
| } |
| |
| #[test] |
| fn test_ipv4_packet_reassembly_not_needed() { |
| test_ip_packet_reassembly_not_needed::<Ipv4>(); |
| } |
| |
| #[test] |
| fn test_ipv6_packet_reassembly_not_needed() { |
| test_ip_packet_reassembly_not_needed::<Ipv6>(); |
| } |
| |
| fn test_ip_packet_reassembly<I: Ip>() { |
| let mut ctx = DummyEventDispatcherBuilder::from_config(get_dummy_config::<I::Addr>()) |
| .build::<DummyEventDispatcher>(); |
| let device = DeviceId::new_ethernet(0); |
| 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!(get_counter_val(&mut ctx, dispatch_receive_ip_packet_name::<I>()), 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!(get_counter_val(&mut ctx, dispatch_receive_ip_packet_name::<I>()), 1); |
| } |
| |
| #[test] |
| fn test_ipv4_packet_reassembly() { |
| test_ip_packet_reassembly::<Ipv4>(); |
| } |
| |
| #[test] |
| fn test_ipv6_packet_reassembly() { |
| test_ip_packet_reassembly::<Ipv6>(); |
| } |
| |
| fn test_ip_packet_reassembly_with_packets_arriving_out_of_order<I: Ip>() { |
| let mut ctx = DummyEventDispatcherBuilder::from_config(get_dummy_config::<I::Addr>()) |
| .build::<DummyEventDispatcher>(); |
| let device = DeviceId::new_ethernet(0); |
| 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!(get_counter_val(&mut ctx, dispatch_receive_ip_packet_name::<I>()), 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!(get_counter_val(&mut ctx, dispatch_receive_ip_packet_name::<I>()), 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!(get_counter_val(&mut ctx, dispatch_receive_ip_packet_name::<I>()), 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!(get_counter_val(&mut ctx, dispatch_receive_ip_packet_name::<I>()), 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!(get_counter_val(&mut ctx, dispatch_receive_ip_packet_name::<I>()), 3); |
| } |
| |
| #[test] |
| fn test_ipv4_packet_reassembly_with_packets_arriving_out_of_order() { |
| test_ip_packet_reassembly_with_packets_arriving_out_of_order::<Ipv4>(); |
| } |
| |
| #[test] |
| fn test_ipv6_packet_reassembly_with_packets_arriving_out_of_order() { |
| test_ip_packet_reassembly_with_packets_arriving_out_of_order::<Ipv6>(); |
| } |
| |
| fn test_ip_packet_reassembly_timeout<I: Ip>() { |
| let mut ctx = DummyEventDispatcherBuilder::from_config(get_dummy_config::<I::Addr>()) |
| .build::<DummyEventDispatcher>(); |
| let device = DeviceId::new_ethernet(0); |
| let fragment_id = 5; |
| |
| // |
| // Test to make sure that packets must arrive within the reassembly |
| // timeout. |
| // |
| |
| // Process fragment #0 |
| process_ip_fragment::<I, _>(&mut ctx, device, fragment_id, 0, 3); |
| |
| // Make sure a timer got added. |
| assert_eq!(ctx.dispatcher.timer_events().count(), 1); |
| |
| // Process fragment #1 |
| process_ip_fragment::<I, _>(&mut ctx, device, fragment_id, 1, 3); |
| |
| // Trigger the timer (simulate a timeout for the fragmented packet) |
| assert!(trigger_next_timer(&mut ctx)); |
| |
| // Make sure no other times exist.. |
| assert_eq!(ctx.dispatcher.timer_events().count(), 0); |
| |
| // 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 timeout was triggered, |
| // causing the prior fragment data to be discarded. |
| assert_eq!(get_counter_val(&mut ctx, dispatch_receive_ip_packet_name::<I>()), 0); |
| } |
| |
| #[test] |
| fn test_ipv4_packet_reassembly_timeout() { |
| test_ip_packet_reassembly_timeout::<Ipv4>(); |
| } |
| |
| #[test] |
| fn test_ipv6_packet_reassembly_timeout() { |
| test_ip_packet_reassembly_timeout::<Ipv6>(); |
| } |
| |
| fn test_ip_reassembly_only_at_destination_host<I: Ip>() { |
| // Create a new network with two parties (alice & bob) and |
| // enable IP packet routing for alice. |
| let a = "alice"; |
| let b = "bob"; |
| let dummy_config = get_dummy_config::<I::Addr>(); |
| let mut state_builder = StackStateBuilder::default(); |
| state_builder.ipv4_builder().forward(true); |
| state_builder.ipv6_builder().forward(true); |
| let mut ndp_configs = crate::device::ndp::NdpConfigurations::default(); |
| ndp_configs.set_dup_addr_detect_transmits(None); |
| ndp_configs.set_max_router_solicitations(None); |
| state_builder.device_builder().set_default_ndp_configs(ndp_configs); |
| let device = DeviceId::new_ethernet(0); |
| let mut alice = DummyEventDispatcherBuilder::from_config(dummy_config.swap()) |
| .build_with(state_builder, DummyEventDispatcher::default()); |
| set_routing_enabled::<_, I>(&mut alice, device, true); |
| let bob = DummyEventDispatcherBuilder::from_config(dummy_config).build(); |
| let contexts = vec![(a.clone(), alice), (b.clone(), bob)].into_iter(); |
| let mut net = DummyNetwork::new(contexts, move |net, device_id| { |
| if *net == a { |
| (b.clone(), device, None) |
| } else { |
| (a.clone(), device, None) |
| } |
| }); |
| 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 |
| process_ip_fragment::<I, _>(&mut net.context("alice"), device, fragment_id, 0, 3); |
| // Make sure the packet got sent from alice to bob |
| assert!(!net.step().is_idle()); |
| |
| // Process fragment #1 |
| process_ip_fragment::<I, _>(&mut net.context("alice"), device, fragment_id, 1, 3); |
| assert!(!net.step().is_idle()); |
| |
| // Make sure no packets got dispatched yet. |
| assert_eq!( |
| get_counter_val(&mut net.context("alice"), dispatch_receive_ip_packet_name::<I>()), |
| 0 |
| ); |
| assert_eq!( |
| get_counter_val(&mut net.context("bob"), dispatch_receive_ip_packet_name::<I>()), |
| 0 |
| ); |
| |
| // Process fragment #2 |
| process_ip_fragment::<I, _>(&mut net.context("alice"), device, 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!( |
| get_counter_val(&mut net.context("alice"), dispatch_receive_ip_packet_name::<I>()), |
| 0 |
| ); |
| assert_eq!( |
| get_counter_val(&mut net.context("bob"), dispatch_receive_ip_packet_name::<I>()), |
| 1 |
| ); |
| |
| // Make sure there are no more events. |
| assert!(net.step().is_idle()); |
| } |
| |
| #[test] |
| fn test_ipv4_reassembly_only_at_destination_host() { |
| test_ip_reassembly_only_at_destination_host::<Ipv4>(); |
| } |
| |
| #[test] |
| fn test_ipv6_reassembly_only_at_destination_host() { |
| test_ip_reassembly_only_at_destination_host::<Ipv6>(); |
| } |
| |
| #[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 dummy_config = get_dummy_config::<Ipv6Addr>(); |
| let mut state_builder = StackStateBuilder::default(); |
| state_builder.ipv6_builder().forward(true); |
| let mut ndp_configs = crate::device::ndp::NdpConfigurations::default(); |
| ndp_configs.set_dup_addr_detect_transmits(None); |
| ndp_configs.set_max_router_solicitations(None); |
| state_builder.device_builder().set_default_ndp_configs(ndp_configs); |
| let mut dispatcher_builder = DummyEventDispatcherBuilder::from_config(dummy_config.clone()); |
| let extra_ip = Ipv6Addr::new([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 168, 0, 100]); |
| let extra_mac = Mac::new([13, 14, 15, 16, 17, 18]); |
| dispatcher_builder.add_ndp_table_entry(0, extra_ip, extra_mac); |
| dispatcher_builder.add_ndp_table_entry(0, extra_mac.to_ipv6_link_local().get(), extra_mac); |
| let mut ctx = dispatcher_builder.build_with(state_builder, DummyEventDispatcher::default()); |
| let device = DeviceId::new_ethernet(0); |
| set_routing_enabled::<_, Ipv6>(&mut ctx, device, true); |
| let frame_dst = FrameDestination::Unicast; |
| |
| // 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 mut body: Vec<u8> = std::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.clone(), ..) |
| .encapsulate(Ipv6PacketBuilder::new(extra_ip, dummy_config.remote_ip, 64, IpProto::Udp)) |
| .serialize_vec_outer() |
| .unwrap(); |
| // Receive the IP packet. |
| receive_ipv6_packet(&mut ctx, device, frame_dst, ipv6_packet_buf.clone()); |
| |
| // Should not have dispatched the packet. |
| assert_eq!(get_counter_val(&mut ctx, "dispatch_receive_ipv6_packet"), 0); |
| assert_eq!(get_counter_val(&mut ctx, "send_icmpv6_packet_too_big"), 1); |
| |
| // Should have sent out one frame though. |
| assert_eq!(ctx.dispatcher.frames_sent().len(), 1); |
| |
| // Received packet should be a Packet Too Big ICMP error message. |
| let mut buf = &ctx.dispatcher.frames_sent()[0].1[..]; |
| // 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, _>( |
| buf, |
| 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)); |
| } |
| |
| #[specialize_ip_address] |
| 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(), ..)); |
| |
| #[ipv4addr] |
| let ret = { |
| let msg = match NonZeroU16::new(mtu) { |
| Some(mtu) => IcmpDestUnreachable::new_for_frag_req(mtu), |
| None => IcmpDestUnreachable::default(), |
| }; |
| |
| body.encapsulate(IcmpPacketBuilder::<Ipv4, &mut [u8], IcmpDestUnreachable>::new( |
| dst_ip, |
| src_ip, |
| Icmpv4DestUnreachableCode::FragmentationRequired, |
| msg, |
| )) |
| .encapsulate(Ipv4PacketBuilder::new(src_ip, dst_ip, 64, IpProto::Icmp)) |
| .serialize_vec_outer() |
| .unwrap() |
| }; |
| |
| #[ipv6addr] |
| let ret = body |
| .encapsulate(IcmpPacketBuilder::<Ipv6, &mut [u8], Icmpv6PacketTooBig>::new( |
| dst_ip, |
| src_ip, |
| IcmpUnusedCode, |
| Icmpv6PacketTooBig::new(u32::from(mtu)), |
| )) |
| .encapsulate(Ipv6PacketBuilder::new(src_ip, dst_ip, 64, IpProto::Icmpv6)) |
| .serialize_vec_outer() |
| .unwrap(); |
| |
| ret.into_inner() |
| } |
| |
| fn test_ip_update_pmtu<I: Ip>() { |
| // |
| // 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 dummy_config = get_dummy_config::<I::Addr>(); |
| let mut ctx = DummyEventDispatcherBuilder::from_config(dummy_config.clone()) |
| .build::<DummyEventDispatcher>(); |
| let device = DeviceId::new_ethernet(0); |
| let frame_dst = FrameDestination::Unicast; |
| |
| // |
| // Update PMTU from None |
| // |
| |
| let new_mtu1 = min_mtu::<I>() + 100; |
| |
| // Create ICMP IP buf |
| let packet_buf = create_packet_too_big_buf( |
| dummy_config.remote_ip.get(), |
| dummy_config.local_ip.get(), |
| u16::try_from(new_mtu1).unwrap(), |
| None, |
| ); |
| |
| // Receive the IP packet. |
| receive_ip_packet::<_, _, I>(&mut ctx, device, frame_dst, packet_buf); |
| |
| // Should have dispatched the packet. |
| assert_eq!(get_counter_val(&mut ctx, dispatch_receive_ip_packet_name::<I>()), 1); |
| |
| assert_eq!( |
| get_pmtu(&mut ctx, dummy_config.local_ip.get(), dummy_config.remote_ip.get()).unwrap(), |
| new_mtu1 |
| ); |
| |
| // |
| // Don't update PMTU when current PMTU is less than reported MTU |
| // |
| |
| let new_mtu2 = min_mtu::<I>() + 200; |
| |
| // Create IPv6 ICMPv6 packet too big packet with MTU larger than current PMTU. |
| let packet_buf = create_packet_too_big_buf( |
| dummy_config.remote_ip.get(), |
| dummy_config.local_ip.get(), |
| u16::try_from(new_mtu2).unwrap(), |
| None, |
| ); |
| |
| // Receive the IP packet. |
| receive_ip_packet::<_, _, I>(&mut ctx, device, frame_dst, packet_buf); |
| |
| // Should have dispatched the packet. |
| assert_eq!(get_counter_val(&mut ctx, dispatch_receive_ip_packet_name::<I>()), 2); |
| |
| // The PMTU should not have updated to `new_mtu2` |
| assert_eq!( |
| get_pmtu(&mut ctx, dummy_config.local_ip.get(), dummy_config.remote_ip.get()).unwrap(), |
| new_mtu1 |
| ); |
| |
| // |
| // Update PMTU when current PMTU is greater than the reported MTU |
| // |
| |
| let new_mtu3 = min_mtu::<I>() + 50; |
| |
| // Create IPv6 ICMPv6 packet too big packet with MTU smaller than current PMTU. |
| let packet_buf = create_packet_too_big_buf( |
| dummy_config.remote_ip.get(), |
| dummy_config.local_ip.get(), |
| u16::try_from(new_mtu3).unwrap(), |
| None, |
| ); |
| |
| // Receive the IP packet. |
| receive_ip_packet::<_, _, I>(&mut ctx, device, frame_dst, packet_buf); |
| |
| // Should have dispatched the packet. |
| assert_eq!(get_counter_val(&mut ctx, dispatch_receive_ip_packet_name::<I>()), 3); |
| |
| // The PMTU should have updated to 1900. |
| assert_eq!( |
| get_pmtu(&mut ctx, dummy_config.local_ip.get(), dummy_config.remote_ip.get()).unwrap(), |
| new_mtu3 |
| ); |
| } |
| |
| #[test] |
| fn test_ipv4_update_pmtu() { |
| test_ip_update_pmtu::<Ipv4>(); |
| } |
| |
| #[test] |
| fn test_ipv6_update_pmtu() { |
| test_ip_update_pmtu::<Ipv6>(); |
| } |
| |
| fn test_ip_update_pmtu_too_low<I: Ip>() { |
| // |
| // 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 dummy_config = get_dummy_config::<I::Addr>(); |
| let mut ctx = DummyEventDispatcherBuilder::from_config(dummy_config.clone()) |
| .build::<DummyEventDispatcher>(); |
| let device = DeviceId::new_ethernet(0); |
| let frame_dst = FrameDestination::Unicast; |
| |
| // |
| // Update PMTU from None but with a mtu too low. |
| // |
| |
| let new_mtu1 = min_mtu::<I>() - 1; |
| |
| // Create ICMP IP buf |
| let packet_buf = create_packet_too_big_buf( |
| dummy_config.remote_ip.get(), |
| dummy_config.local_ip.get(), |
| u16::try_from(new_mtu1).unwrap(), |
| None, |
| ); |
| |
| // Receive the IP packet. |
| receive_ip_packet::<_, _, I>(&mut ctx, device, frame_dst, packet_buf); |
| |
| // Should have dispatched the packet. |
| assert_eq!(get_counter_val(&mut ctx, dispatch_receive_ip_packet_name::<I>()), 1); |
| |
| assert!( |
| get_pmtu(&mut ctx, dummy_config.local_ip.get(), dummy_config.remote_ip.get()).is_none(), |
| ); |
| } |
| |
| #[test] |
| fn test_ipv4_update_pmtu_too_low() { |
| test_ip_update_pmtu_too_low::<Ipv4>(); |
| } |
| |
| #[test] |
| fn test_ipv6_update_pmtu_too_low() { |
| test_ip_update_pmtu_too_low::<Ipv6>(); |
| } |
| |
| /// 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)) |
| .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 dummy_config = get_dummy_config::<Ipv4Addr>(); |
| let mut ctx = DummyEventDispatcherBuilder::from_config(dummy_config.clone()) |
| .build::<DummyEventDispatcher>(); |
| let device = DeviceId::new_ethernet(0); |
| let frame_dst = FrameDestination::Unicast; |
| |
| // |
| // 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( |
| dummy_config.remote_ip.get(), |
| dummy_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(dummy_config.local_ip.get(), dummy_config.remote_ip.get(), 500) |
| .into(), |
| ); |
| |
| // Receive the IP packet. |
| receive_ipv4_packet(&mut ctx, device, frame_dst, packet_buf); |
| |
| // Should have dispatched the packet. |
| assert_eq!(get_counter_val(&mut ctx, "dispatch_receive_ipv4_packet"), 1); |
| |
| // Should have decreased PMTU value to the next lower PMTU |
| // plateau from `crate::ip::path_mtu::PMTU_PLATEAUS`. |
| assert_eq!( |
| get_pmtu(&mut ctx, dummy_config.local_ip.get(), dummy_config.remote_ip.get()).unwrap(), |
| 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( |
| dummy_config.remote_ip.get(), |
| dummy_config.local_ip.get(), |
| 0, |
| create_orig_packet_buf(dummy_config.local_ip.get(), dummy_config.remote_ip.get(), 1) |
| .into(), |
| ); |
| |
| // Receive the IP packet. |
| receive_ipv4_packet(&mut ctx, device, frame_dst, packet_buf); |
| |
| // Should have dispatched the packet. |
| assert_eq!(get_counter_val(&mut ctx, "dispatch_receive_ipv4_packet"), 2); |
| |
| // Should not have updated PMTU as there is no other valid |
| // lower PMTU value. |
| assert_eq!( |
| get_pmtu(&mut ctx, dummy_config.local_ip.get(), dummy_config.remote_ip.get()).unwrap(), |
| 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( |
| dummy_config.remote_ip.get(), |
| dummy_config.local_ip.get(), |
| 0, |
| create_orig_packet_buf(dummy_config.local_ip.get(), dummy_config.remote_ip.get(), 60) |
| .into(), |
| ); |
| |
| // Receive the IP packet. |
| receive_ipv4_packet(&mut ctx, device, frame_dst, packet_buf); |
| |
| // Should have dispatched the packet. |
| assert_eq!(get_counter_val(&mut ctx, "dispatch_receive_ipv4_packet"), 3); |
| |
| // Should have decreased PMTU value to the next lower PMTU |
| // plateau from `crate::ip::path_mtu::PMTU_PLATEAUS`. |
| assert_eq!( |
| get_pmtu(&mut ctx, dummy_config.local_ip.get(), dummy_config.remote_ip.get()).unwrap(), |
| 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( |
| dummy_config.remote_ip.get(), |
| dummy_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(dummy_config.local_ip.get(), dummy_config.remote_ip.get(), 290) |
| .into(), |
| ); |
| |
| // Receive the IP packet. |
| receive_ipv4_packet(&mut ctx, device, frame_dst, packet_buf); |
| |
| // Should have dispatched the packet. |
| assert_eq!(get_counter_val(&mut ctx, "dispatch_receive_ipv4_packet"), 4); |
| |
| // Should not have updated the PMTU as the current PMTU is lower. |
| assert_eq!( |
| get_pmtu(&mut ctx, dummy_config.local_ip.get(), dummy_config.remote_ip.get()).unwrap(), |
| 68 |
| ); |
| } |
| |
| #[test] |
| fn test_invalid_icmpv4_in_ipv6() { |
| let ip_config = get_dummy_config::<Ipv6Addr>(); |
| let mut ctx = DummyEventDispatcherBuilder::from_config(ip_config.clone()) |
| .build::<DummyEventDispatcher>(); |
| let device = DeviceId::new_ethernet(1); |
| let frame_dst = FrameDestination::Unicast; |
| |
| let ic_config = get_dummy_config::<Ipv4Addr>(); |
| let icmp_builder = IcmpPacketBuilder::<Ipv4, &[u8], _>::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, IpProto::Icmp); |
| |
| let mut buf = Buf::new(Vec::new(), ..) |
| .encapsulate(icmp_builder) |
| .encapsulate(ip_builder) |
| .serialize_vec_outer() |
| .unwrap(); |
| |
| receive_ipv6_packet(&mut ctx, device, frame_dst, buf); |
| |
| // Should not have dispatched the packet. |
| assert_eq!(get_counter_val(&mut ctx, "dispatch_receive_ipv6_packet"), 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_eq!(ctx.dispatcher.frames_sent().len(), 0); |
| } |
| |
| #[test] |
| fn test_invalid_icmpv6_in_ipv4() { |
| let ip_config = get_dummy_config::<Ipv4Addr>(); |
| let mut ctx = DummyEventDispatcherBuilder::from_config(ip_config.clone()) |
| .build::<DummyEventDispatcher>(); |
| // First possible device id. |
| let device = DeviceId::new_ethernet(0); |
| let frame_dst = FrameDestination::Unicast; |
| |
| let ic_config = get_dummy_config::<Ipv6Addr>(); |
| let icmp_builder = IcmpPacketBuilder::<Ipv6, &[u8], _>::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, IpProto::Icmpv6); |
| |
| let mut buf = Buf::new(Vec::new(), ..) |
| .encapsulate(icmp_builder) |
| .encapsulate(ip_builder) |
| .serialize_vec_outer() |
| .unwrap(); |
| |
| receive_ipv4_packet(&mut ctx, device, frame_dst, buf); |
| |
| // Should have dispatched the packet but resulted in an ICMP error. |
| assert_eq!(get_counter_val(&mut ctx, "dispatch_receive_ipv4_packet"), 1); |
| assert_eq!(get_counter_val(&mut ctx, "send_icmpv4_dest_unreachable"), 1); |
| assert_eq!(ctx.dispatcher.frames_sent().len(), 1); |
| let buf = &ctx.dispatcher.frames_sent()[0].1[..]; |
| let (_, _, _, _, msg, code) = |
| parse_icmp_packet_in_ip_packet_in_ethernet_frame::<Ipv4, _, IcmpDestUnreachable, _>( |
| buf, |
| |_| {}, |
| ) |
| .unwrap(); |
| assert_eq!(code, Icmpv4DestUnreachableCode::DestProtocolUnreachable); |
| } |
| |
| fn test_joining_leaving_ip_multicast_group<I: Ip>() { |
| #[specialize_ip_address] |
| fn get_multicast_addr<A: IpAddress>() -> A { |
| #[ipv4addr] |
| return Ipv4Addr::new([224, 0, 0, 1]); |
| |
| #[ipv6addr] |
| return Ipv6Addr::new([255, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]); |
| } |
| |
| // |
| // Test receiving a packet destined to a multicast IP (and corresponding multicast MAC). |
| // |
| |
| let config = get_dummy_config::<I::Addr>(); |
| let mut ctx = DummyEventDispatcherBuilder::from_config(config.clone()) |
| .build::<DummyEventDispatcher>(); |
| let device = DeviceId::new_ethernet(0); |
| let frame_dst = FrameDestination::Unicast; |
| let multi_addr = get_multicast_addr::<I::Addr>(); |
| let dst_mac = Mac::from(&MulticastAddr::new(multi_addr).unwrap()); |
| let buf = Buf::new(vec![0; 10], ..) |
| .encapsulate(<I as IpExt>::PacketBuilder::new( |
| config.remote_ip.get(), |
| multi_addr, |
| 64, |
| IpProto::Udp, |
| )) |
| .encapsulate(EthernetFrameBuilder::new(config.remote_mac, dst_mac, I::ETHER_TYPE)) |
| .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!(!crate::device::is_in_ip_multicast(&ctx, device, multi_addr)); |
| receive_frame(&mut ctx, device, buf.clone()); |
| assert_eq!(get_counter_val(&mut ctx, dispatch_receive_ip_packet_name::<I>()), 0); |
| |
| // Join the multicast group and receive the packet, we should dispatch it. |
| crate::device::join_ip_multicast(&mut ctx, device, multi_addr); |
| assert!(crate::device::is_in_ip_multicast(&ctx, device, multi_addr)); |
| receive_frame(&mut ctx, device, buf.clone()); |
| assert_eq!(get_counter_val(&mut ctx, dispatch_receive_ip_packet_name::<I>()), 1); |
| |
| // Leave the multicast group and receive the packet, we should not dispatch it. |
| crate::device::leave_ip_multicast(&mut ctx, device, multi_addr); |
| assert!(!crate::device::is_in_ip_multicast(&ctx, device, multi_addr)); |
| receive_frame(&mut ctx, device, buf.clone()); |
| assert_eq!(get_counter_val(&mut ctx, dispatch_receive_ip_packet_name::<I>()), 1); |
| } |
| |
| #[test] |
| fn test_joining_leaving_ipv4_multicast_groups() { |
| test_joining_leaving_ip_multicast_group::<Ipv4>(); |
| } |
| |
| #[test] |
| fn test_joining_leaving_ipv6_multicast_groups() { |
| test_joining_leaving_ip_multicast_group::<Ipv6>(); |
| } |
| |
| #[test] |
| #[should_panic] |
| fn test_lookup_table_ipv4_loopback_panic() { |
| test_lookup_table_address(get_dummy_config::<Ipv4Addr>(), Ipv4::LOOPBACK_ADDRESS); |
| } |
| |
| #[test] |
| #[should_panic] |
| fn test_lookup_table_ipv6_loopback_panic() { |
| test_lookup_table_address(get_dummy_config::<Ipv6Addr>(), Ipv6::LOOPBACK_ADDRESS); |
| } |
| |
| fn test_lookup_table_address<A: IpAddress>( |
| cfg: DummyEventDispatcherConfig<A>, |
| ip_address: SpecifiedAddr<A>, |
| ) -> Option<Destination<A, DeviceId>> { |
| let mut ctx = |
| DummyEventDispatcherBuilder::from_config(cfg.clone()).build::<DummyEventDispatcher>(); |
| lookup_route(&ctx, ip_address) |
| } |
| |
| #[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. |
| |
| // We explicitly call `build_with` when building our context below because `build` will |
| // set the default NDP parameter DUP_ADDR_DETECT_TRANSMITS to 0 (effectively disabling |
| // DAD) so we use our own custom `StackStateBuilder` to set it to the default value |
| // of `1` (see `DUP_ADDR_DETECT_TRANSMITS`). |
| let config = get_dummy_config::<Ipv6Addr>(); |
| let mut ctx = DummyEventDispatcherBuilder::from_config(config.clone()) |
| .build_with(StackStateBuilder::default(), DummyEventDispatcher::default()); |
| let device = DeviceId::new_ethernet(0); |
| let frame_dst = FrameDestination::Unicast; |
| |
| let buf = Buf::new(vec![0; 10], ..) |
| .encapsulate(Ipv6PacketBuilder::new( |
| config.remote_ip, |
| config.local_ip, |
| 64, |
| IpProto::Udp, |
| )) |
| .serialize_vec_outer() |
| .unwrap() |
| .into_inner(); |
| |
| // Make sure all timers are done (initial DAD to complete on the interface). |
| trigger_timers_until(&mut ctx, |_| false); |
| |
| // Received packet should have been dispatched. |
| receive_ipv6_packet(&mut ctx, device, frame_dst, buf); |
| assert_eq!(get_counter_val(&mut ctx, "dispatch_receive_ipv6_packet"), 1); |
| |
| // Set the new IP (this should trigger DAD). |
| let ip = Ipv6Addr::new([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 168, 0, 10]); |
| crate::device::add_ip_addr_subnet(&mut ctx, device, AddrSubnet::new(ip, 128).unwrap()); |
| |
| let buf = Buf::new(vec![0; 10], ..) |
| .encapsulate(Ipv6PacketBuilder::new(config.remote_ip, ip, 64, IpProto::Udp)) |
| .serialize_vec_outer() |
| .unwrap() |
| .into_inner(); |
| |
| // Received packet should not have been dispatched. |
| receive_ipv6_packet(&mut ctx, device, frame_dst, buf.clone()); |
| assert_eq!(get_counter_val(&mut ctx, "dispatch_receive_ipv6_packet"), 1); |
| |
| // Make sure all timers are done (DAD to complete on the interface due to new IP). |
| trigger_timers_until(&mut ctx, |_| false); |
| |
| // Received packet should have been dispatched. |
| receive_ipv6_packet(&mut ctx, device, frame_dst, buf); |
| assert_eq!(get_counter_val(&mut ctx, "dispatch_receive_ipv6_packet"), 2); |
| } |
| } |