From 53bdf4359be250b40f4dd5acd01e8f53aa58564d Mon Sep 17 00:00:00 2001 From: Thibaut Vandervelden Date: Mon, 7 Aug 2023 11:08:12 +0200 Subject: [PATCH] refactor: move IPv6 HBH in own module The Ipv6HopByHopRepr was previously the same as an Ipv6ExtHeader. However, the RFC says that hop-by-hop options might change, which is not possible with the current implementation. I added a representatation for the Hop-by-Hop header, which has an heapless Vec of parsed options. This way, we can modify the options when needed. The function of the Ipv6ExtHeader struct is now purely for parsing the ext header type and the length. It also returns a pointer to the data it holds, which must be parsed by the correct extension header representations. --- Cargo.toml | 8 ++ README.md | 2 + build.rs | 1 + gen_config.py | 1 + src/iface/interface/ipv6.rs | 14 +-- src/lib.rs | 1 + src/wire/ipv6hbh.rs | 176 ++++++++++++++++++++++++++++++++++++ src/wire/mod.rs | 10 +- 8 files changed, 200 insertions(+), 13 deletions(-) create mode 100644 src/wire/ipv6hbh.rs diff --git a/Cargo.toml b/Cargo.toml index a1745066b..faca1e60f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -200,6 +200,14 @@ reassembly-buffer-count-8 = [] reassembly-buffer-count-16 = [] reassembly-buffer-count-32 = [] +ipv6-hbh-max-options-1 = [] # Default +ipv6-hbh-max-options-2 = [] +ipv6-hbh-max-options-3 = [] +ipv6-hbh-max-options-4 = [] +ipv6-hbh-max-options-8 = [] +ipv6-hbh-max-options-16 = [] +ipv6-hbh-max-options-32 = [] + dns-max-result-count-1 = [] # Default dns-max-result-count-2 = [] dns-max-result-count-3 = [] diff --git a/README.md b/README.md index 65f7a29eb..9405dd843 100644 --- a/README.md +++ b/README.md @@ -276,7 +276,9 @@ Maximum amount of DNS servers that can be configured in one DNS socket. Default: Maximum length of DNS names that can be queried. Default: 255. +### IPV6_HBH_MAX_OPTIONS +The maximum amount of parsed options the IPv6 Hop-by-Hop header can hold. Default: 1. ## Hosted usage examples diff --git a/build.rs b/build.rs index 568713925..54662ed52 100644 --- a/build.rs +++ b/build.rs @@ -15,6 +15,7 @@ static CONFIGS: &[(&str, usize)] = &[ ("ASSEMBLER_MAX_SEGMENT_COUNT", 4), ("REASSEMBLY_BUFFER_SIZE", 1500), ("REASSEMBLY_BUFFER_COUNT", 1), + ("IPV6_HBH_MAX_OPTIONS", 1), ("DNS_MAX_RESULT_COUNT", 1), ("DNS_MAX_SERVER_COUNT", 1), ("DNS_MAX_NAME_SIZE", 255), diff --git a/gen_config.py b/gen_config.py index 5d327a6db..25691929e 100644 --- a/gen_config.py +++ b/gen_config.py @@ -36,6 +36,7 @@ def feature(name, default, min, max, pow2=None): feature("assembler_max_segment_count", default=4, min=1, max=32, pow2=4) feature("reassembly_buffer_size", default=1500, min=256, max=65536, pow2=True) feature("reassembly_buffer_count", default=1, min=1, max=32, pow2=4) +feature("ipv6_hbh_max_options", default=1, min=1, max=32, pow2=4) feature("dns_max_result_count", default=1, min=1, max=32, pow2=4) feature("dns_max_server_count", default=1, min=1, max=32, pow2=4) feature("dns_max_name_size", default=255, min=64, max=255, pow2=True) diff --git a/src/iface/interface/ipv6.rs b/src/iface/interface/ipv6.rs index c443aa3c7..de4867530 100644 --- a/src/iface/interface/ipv6.rs +++ b/src/iface/interface/ipv6.rs @@ -250,19 +250,19 @@ impl InterfaceInner { handled_by_raw_socket: bool, ip_payload: &'frame [u8], ) -> Option> { - let hbh_hdr = check!(Ipv6HopByHopHeader::new_checked(ip_payload)); + let ext_hdr = check!(Ipv6ExtHeader::new_checked(ip_payload)); + let ext_repr = check!(Ipv6ExtHeaderRepr::parse(&ext_hdr)); + let hbh_hdr = check!(Ipv6HopByHopHeader::new_checked(ext_repr.data)); let hbh_repr = check!(Ipv6HopByHopRepr::parse(&hbh_hdr)); - let hbh_options = Ipv6OptionsIterator::new(hbh_repr.data); - for opt_repr in hbh_options { - let opt_repr = check!(opt_repr); + for opt_repr in &hbh_repr.options { match opt_repr { Ipv6OptionRepr::Pad1 | Ipv6OptionRepr::PadN(_) => (), #[cfg(feature = "proto-rpl")] Ipv6OptionRepr::Rpl(_) => {} Ipv6OptionRepr::Unknown { type_, .. } => { - match Ipv6OptionFailureType::from(type_) { + match Ipv6OptionFailureType::from(*type_) { Ipv6OptionFailureType::Skip => (), Ipv6OptionFailureType::Discard => { return None; @@ -280,9 +280,9 @@ impl InterfaceInner { sockets, meta, ipv6_repr, - hbh_repr.next_header, + ext_repr.next_header, handled_by_raw_socket, - &ip_payload[hbh_repr.header_len() + hbh_repr.data.len()..], + &ip_payload[ext_repr.header_len() + ext_repr.data.len()..], ) } diff --git a/src/lib.rs b/src/lib.rs index 6128c2314..fc6a20ea5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -146,6 +146,7 @@ mod config { pub const REASSEMBLY_BUFFER_SIZE: usize = 1500; pub const RPL_RELATIONS_BUFFER_COUNT: usize = 16; pub const RPL_PARENTS_BUFFER_COUNT: usize = 8; + pub const IPV6_HBH_MAX_OPTIONS: usize = 2; } #[cfg(not(test))] diff --git a/src/wire/ipv6hbh.rs b/src/wire/ipv6hbh.rs new file mode 100644 index 000000000..4ac965758 --- /dev/null +++ b/src/wire/ipv6hbh.rs @@ -0,0 +1,176 @@ +use super::{Error, Ipv6Option, Ipv6OptionRepr, Ipv6OptionsIterator, Result}; + +use heapless::Vec; + +/// A read/write wrapper around an IPv6 Hop-by-Hop Header buffer. +pub struct Header> { + buffer: T, +} + +impl> Header { + /// Create a raw octet buffer with an IPv6 Hop-by-Hop Header structure. + pub const fn new_unchecked(buffer: T) -> Self { + Header { buffer } + } + + /// Shorthand for a combination of [new_unchecked] and [check_len]. + /// + /// [new_unchecked]: #method.new_unchecked + /// [check_len]: #method.check_len + pub fn new_checked(buffer: T) -> Result { + let header = Self::new_unchecked(buffer); + header.check_len()?; + Ok(header) + } + + /// Ensure that no accessor method will panic if called. + /// Returns `Err(Error)` if the buffer is too short. + /// + /// The result of this check is invalidated by calling [set_header_len]. + /// + /// [set_header_len]: #method.set_header_len + pub fn check_len(&self) -> Result<()> { + if self.buffer.as_ref().is_empty() { + return Err(Error); + } + + Ok(()) + } + + /// Consume the header, returning the underlying buffer. + pub fn into_inner(self) -> T { + self.buffer + } +} + +impl<'a, T: AsRef<[u8]> + ?Sized> Header<&'a T> { + /// Return the options of the IPv6 Hop-by-Hop header. + pub fn options(&self) -> &'a [u8] { + self.buffer.as_ref() + } +} + +impl<'a, T: AsRef<[u8]> + AsMut<[u8]> + ?Sized> Header<&'a mut T> { + /// Return a mutable pointer to the options of the IPv6 Hop-by-Hop header. + pub fn options_mut(&mut self) -> &mut [u8] { + self.buffer.as_mut() + } +} + +/// A high-level representation of an IPv6 Hop-by-Hop Header. +#[derive(Debug, PartialEq, Eq, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Repr<'a> { + pub options: heapless::Vec, { crate::config::IPV6_HBH_MAX_OPTIONS }>, +} + +impl<'a> Repr<'a> { + /// Parse an IPv6 Hop-by-Hop Header and return a high-level representation. + pub fn parse(header: &'a Header<&'a T>) -> Result> + where + T: AsRef<[u8]> + ?Sized, + { + let mut options = Vec::new(); + + let iter = Ipv6OptionsIterator::new(header.options()); + + for option in iter { + let option = option?; + + if let Err(e) = options.push(option) { + net_trace!("eror when parsing hop-by-hop options: {}", e); + break; + } + } + + Ok(Self { options }) + } + + /// Return the length, in bytes, of a header that will be emitted from this high-level + /// representation. + pub fn buffer_len(&self) -> usize { + self.options.iter().map(|o| o.buffer_len()).sum() + } + + /// Emit a high-level representation into an IPv6 Hop-by-Hop Header. + pub fn emit + AsMut<[u8]> + ?Sized>(&self, header: &mut Header<&mut T>) { + let mut buffer = header.options_mut(); + + for opt in &self.options { + opt.emit(&mut Ipv6Option::new_unchecked( + &mut buffer[..opt.buffer_len()], + )); + buffer = &mut buffer[opt.buffer_len()..]; + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::wire::Error; + + // A Hop-by-Hop Option header with a PadN option of option data length 4. + static REPR_PACKET_PAD4: [u8; 6] = [0x1, 0x4, 0x0, 0x0, 0x0, 0x0]; + + // A Hop-by-Hop Option header with a PadN option of option data length 12. + static REPR_PACKET_PAD12: [u8; 14] = [ + 0x1, 0x0C, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + ]; + + #[test] + fn test_check_len() { + // zero byte buffer + assert_eq!( + Err(Error), + Header::new_unchecked(&REPR_PACKET_PAD4[..0]).check_len() + ); + // valid + assert_eq!(Ok(()), Header::new_unchecked(&REPR_PACKET_PAD4).check_len()); + // valid + assert_eq!( + Ok(()), + Header::new_unchecked(&REPR_PACKET_PAD12).check_len() + ); + } + + #[test] + fn test_repr_parse_valid() { + let header = Header::new_unchecked(&REPR_PACKET_PAD4); + let repr = Repr::parse(&header).unwrap(); + + let mut options = Vec::new(); + options.push(Ipv6OptionRepr::PadN(4)).unwrap(); + assert_eq!(repr, Repr { options }); + + let header = Header::new_unchecked(&REPR_PACKET_PAD12); + let repr = Repr::parse(&header).unwrap(); + + let mut options = Vec::new(); + options.push(Ipv6OptionRepr::PadN(12)).unwrap(); + assert_eq!(repr, Repr { options }); + } + + #[test] + fn test_repr_emit() { + let mut options = Vec::new(); + options.push(Ipv6OptionRepr::PadN(4)).unwrap(); + let repr = Repr { options }; + + let mut bytes = [0u8; 6]; + let mut header = Header::new_unchecked(&mut bytes); + repr.emit(&mut header); + + assert_eq!(header.into_inner(), &REPR_PACKET_PAD4[..]); + + let mut options = Vec::new(); + options.push(Ipv6OptionRepr::PadN(12)).unwrap(); + let repr = Repr { options }; + + let mut bytes = [0u8; 14]; + let mut header = Header::new_unchecked(&mut bytes); + repr.emit(&mut header); + + assert_eq!(header.into_inner(), &REPR_PACKET_PAD12[..]); + } +} diff --git a/src/wire/mod.rs b/src/wire/mod.rs index 85f7d8b34..0370956cd 100644 --- a/src/wire/mod.rs +++ b/src/wire/mod.rs @@ -105,6 +105,8 @@ mod ipv6ext_header; #[cfg(feature = "proto-ipv6")] mod ipv6fragment; #[cfg(feature = "proto-ipv6")] +mod ipv6hbh; +#[cfg(feature = "proto-ipv6")] mod ipv6option; #[cfg(feature = "proto-ipv6")] mod ipv6routing; @@ -198,14 +200,10 @@ pub use self::ipv6option::{ pub use self::ipv6ext_header::{Header as Ipv6ExtHeader, Repr as Ipv6ExtHeaderRepr}; #[cfg(feature = "proto-ipv6")] -/// A read/write wrapper around an IPv6 Hop-By-Hop header. -pub type Ipv6HopByHopHeader = Ipv6ExtHeader; -#[cfg(feature = "proto-ipv6")] -/// A high-level representation of an IPv6 Hop-By-Hop heade. -pub type Ipv6HopByHopRepr<'a> = Ipv6ExtHeaderRepr<'a>; +pub use self::ipv6fragment::{Header as Ipv6FragmentHeader, Repr as Ipv6FragmentRepr}; #[cfg(feature = "proto-ipv6")] -pub use self::ipv6fragment::{Header as Ipv6FragmentHeader, Repr as Ipv6FragmentRepr}; +pub use self::ipv6hbh::{Header as Ipv6HopByHopHeader, Repr as Ipv6HopByHopRepr}; #[cfg(feature = "proto-ipv6")] pub use self::ipv6routing::{