| // Copyright 2019 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| //! IPv6 specific functionality. |
| |
| use core::iter::Iterator; |
| |
| use packet_formats::ipv6::ext_hdrs::{ |
| DestinationOptionData, ExtensionHeaderOption, FragmentData, HopByHopOptionData, |
| Ipv6ExtensionHeaderData, RoutingData, |
| }; |
| use packet_formats::ipv6::Ipv6Packet; |
| use zerocopy::ByteSlice; |
| |
| use crate::device::{DeviceId, FrameDestination}; |
| use crate::{Context, EventDispatcher}; |
| |
| /// What to do with an IPv6 packet after parsing an extension header. |
| #[derive(Debug, PartialEq, Eq)] |
| pub(crate) enum Ipv6PacketAction { |
| /// Discard the packet. |
| _Discard, |
| |
| /// Continue processing the next extension header (if any are |
| /// available and the processing node is the destination node) |
| /// or continue processing the packet (if the extension headers |
| /// have been exhausted or the processing node is not the |
| /// destination node). |
| Continue, |
| |
| /// Stop processing extension headers and consider the |
| /// packet fragmented. The node must attempt to handle |
| /// the fragmented packet (attempt reassembly). |
| ProcessFragment, |
| } |
| |
| /// Handle IPv6 extension headers. |
| /// |
| /// What this function does depends on whether or not the `at_destination` flag |
| /// is set. If it is `true`, then we will attempt to process all the extension |
| /// headers in `packet`. Otherwise, we will only attempt to process the hop-by-hop |
| /// extension header (which MUST be the first extension header if present) as |
| /// per RFC 8200 section 4. |
| pub(crate) fn handle_extension_headers<D: EventDispatcher, B: ByteSlice>( |
| ctx: &mut Context<D>, |
| device: DeviceId, |
| frame_dst: FrameDestination, |
| packet: &Ipv6Packet<B>, |
| at_destination: bool, |
| ) -> Ipv6PacketAction { |
| // The next action we need to do after processing an extension header. |
| // |
| // Initialized to `Ipv6PacketAction::Continue` so we start off processing |
| // extension headers. |
| let mut action = Ipv6PacketAction::Continue; |
| let mut iter = packet.iter_extension_hdrs(); |
| |
| if at_destination { |
| // Keep looping while we are okay to just continue parsing extension headers. |
| while action == Ipv6PacketAction::Continue { |
| let ext_hdr = match iter.next() { |
| None => break, |
| Some(x) => x, |
| }; |
| |
| match ext_hdr.data() { |
| Ipv6ExtensionHeaderData::HopByHopOptions { options } => { |
| action = handle_hop_by_hop_options_ext_hdr( |
| ctx, |
| device, |
| frame_dst, |
| packet, |
| options.iter(), |
| ); |
| } |
| Ipv6ExtensionHeaderData::Fragment { fragment_data } => { |
| action = handle_fragment_ext_hdr(ctx, device, frame_dst, packet, fragment_data); |
| } |
| Ipv6ExtensionHeaderData::DestinationOptions { options } => { |
| action = handle_destination_options_ext_hdr( |
| ctx, |
| device, |
| frame_dst, |
| packet, |
| options.iter(), |
| ); |
| } |
| } |
| } |
| } else { |
| // Packet is not yet at the destination, so only process the hop-by-hop |
| // options extension header (which MUST be the first extension header if |
| // it is present) as per RFC 8200 section 4. |
| if let Some(ext_hdr) = iter.next() { |
| if let Ipv6ExtensionHeaderData::HopByHopOptions { options } = ext_hdr.data() { |
| action = handle_hop_by_hop_options_ext_hdr( |
| ctx, |
| device, |
| frame_dst, |
| packet, |
| options.iter(), |
| ); |
| } |
| } |
| } |
| |
| action |
| } |
| |
| /// Handles a Hop By Hop extension header for a `packet`. |
| // For now, we do not support any options. If parsing succeeds we are |
| // guaranteed that the options present are safely skippable. If they |
| // aren't safely skippable, we must have resulted in a parsing error |
| // when parsing the packet, and so this function will never be called. |
| fn handle_hop_by_hop_options_ext_hdr< |
| 'a, |
| D: EventDispatcher, |
| B: ByteSlice, |
| I: Iterator<Item = ExtensionHeaderOption<HopByHopOptionData<'a>>>, |
| >( |
| _ctx: &mut Context<D>, |
| _device: DeviceId, |
| _frame_dst: FrameDestination, |
| _packet: &Ipv6Packet<B>, |
| options: I, |
| ) -> Ipv6PacketAction { |
| for option in options { |
| match option.data { |
| // Safely skip and continue, as we know that if we parsed an unrecognized |
| // option, the option's action was set to skip and continue. |
| HopByHopOptionData::Unrecognized { .. } => {} |
| // Also skip RouterAlert because router part of MLD is not implemented. |
| HopByHopOptionData::RouterAlert { .. } => {} |
| } |
| } |
| |
| Ipv6PacketAction::Continue |
| } |
| |
| /// Handles a routing extension header for a `packet`. |
| // TODO(rheacock): Remove `_` prefix when this is used |
| fn _handle_routing_ext_hdr<'a, D: EventDispatcher, B: ByteSlice>( |
| _ctx: &mut Context<D>, |
| _device: DeviceId, |
| _frame_dst: FrameDestination, |
| _packet: &Ipv6Packet<B>, |
| _routing_data: &RoutingData<'a>, |
| ) -> Ipv6PacketAction { |
| // We should never end up here because we do not support parsing any routing header |
| // type yet. We should have errored out while parsing the extension header if |
| // there is a routing header we would normally have to act on. |
| unreachable!("We should not end up here because no routing type is supported yet"); |
| } |
| |
| /// Handles a fragment extension header for a `packet`. |
| fn handle_fragment_ext_hdr<'a, D: EventDispatcher, B: ByteSlice>( |
| _ctx: &mut Context<D>, |
| _device: DeviceId, |
| _frame_dst: FrameDestination, |
| _packet: &Ipv6Packet<B>, |
| _fragment_data: &FragmentData<'a>, |
| ) -> Ipv6PacketAction { |
| Ipv6PacketAction::ProcessFragment |
| } |
| |
| /// Handles a destination extension header for a `packet`. |
| // For now, we do not support any options. If parsing succeeds we are |
| // guaranteed that the options present are safely skippable. If they |
| // aren't safely skippable, we must have resulted in a parsing error |
| // when parsing the packet, and so this function will never be called. |
| fn handle_destination_options_ext_hdr< |
| 'a, |
| D: EventDispatcher, |
| B: ByteSlice, |
| I: Iterator<Item = ExtensionHeaderOption<DestinationOptionData<'a>>>, |
| >( |
| _ctx: &mut Context<D>, |
| _device: DeviceId, |
| _frame_dst: FrameDestination, |
| _packet: &Ipv6Packet<B>, |
| options: I, |
| ) -> Ipv6PacketAction { |
| for option in options { |
| match option.data { |
| // Safely skip and continue, as we know that if we parsed an unrecognized |
| // option, the option's action was set to skip and continue. |
| DestinationOptionData::Unrecognized { .. } => {} |
| } |
| } |
| |
| Ipv6PacketAction::Continue |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| |
| use packet::serialize::{Buf, Serializer}; |
| use packet::ParseBuffer; |
| use packet_formats::ip::IpProto; |
| use packet_formats::ipv6::{Ipv6Packet, Ipv6PacketBuilder}; |
| |
| use crate::testutil::{DummyEventDispatcher, DummyEventDispatcherBuilder, DUMMY_CONFIG_V6}; |
| |
| #[test] |
| fn test_no_extension_headers() { |
| // |
| // Test that if we have no extension headers, we continue |
| // |
| |
| let mut ctx = DummyEventDispatcherBuilder::from_config(DUMMY_CONFIG_V6) |
| .build::<DummyEventDispatcher>(); |
| let builder = Ipv6PacketBuilder::new( |
| DUMMY_CONFIG_V6.remote_ip, |
| DUMMY_CONFIG_V6.local_ip, |
| 10, |
| IpProto::Tcp, |
| ); |
| let device_id = DeviceId::new_ethernet(0); |
| let frame_dst = FrameDestination::Unicast; |
| let mut buffer = |
| Buf::new(vec![1, 2, 3, 4, 5], ..).encapsulate(builder).serialize_vec_outer().unwrap(); |
| let packet = buffer.parse::<Ipv6Packet<_>>().unwrap(); |
| |
| assert_eq!( |
| handle_extension_headers(&mut ctx, device_id, frame_dst, &packet, false), |
| Ipv6PacketAction::Continue |
| ); |
| } |
| } |