瀏覽代碼

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.
Thibaut Vandervelden 1 年之前
父節點
當前提交
53bdf4359b
共有 8 個文件被更改,包括 200 次插入13 次删除
  1. 8 0
      Cargo.toml
  2. 2 0
      README.md
  3. 1 0
      build.rs
  4. 1 0
      gen_config.py
  5. 7 7
      src/iface/interface/ipv6.rs
  6. 1 0
      src/lib.rs
  7. 176 0
      src/wire/ipv6hbh.rs
  8. 4 6
      src/wire/mod.rs

+ 8 - 0
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 = []

+ 2 - 0
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
 

+ 1 - 0
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),

+ 1 - 0
gen_config.py

@@ -36,6 +36,7 @@ feature("fragmentation_buffer_size", default=1500, min=256, max=65536, pow2=True
 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)

+ 7 - 7
src/iface/interface/ipv6.rs

@@ -250,19 +250,19 @@ impl InterfaceInner {
         handled_by_raw_socket: bool,
         ip_payload: &'frame [u8],
     ) -> Option<IpPacket<'frame>> {
-        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()..],
         )
     }
 

+ 1 - 0
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))]

+ 176 - 0
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<T: AsRef<[u8]>> {
+    buffer: T,
+}
+
+impl<T: AsRef<[u8]>> Header<T> {
+    /// 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<Self> {
+        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<Ipv6OptionRepr<'a>, { 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<T>(header: &'a Header<&'a T>) -> Result<Repr<'a>>
+    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<T: AsRef<[u8]> + 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[..]);
+    }
+}

+ 4 - 6
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<T> = Ipv6ExtHeader<T>;
-#[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::{