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::{