From 5785ed7af6bc23e9d8f9e6662c98e057cc19e384 Mon Sep 17 00:00:00 2001 From: Thibaut Vandervelden Date: Wed, 9 Aug 2023 11:35:19 +0200 Subject: [PATCH 1/2] refactor(sixlowpan): add 6LoWPAN extension headers First, I rewrote how 6LoWPAN does the compression and decompression, should be clearer to follow how it works. After that I added the compression and decompression of extension headers. --- src/iface/interface/sixlowpan.rs | 778 ++++++++++++++++++------- src/iface/interface/tests/sixlowpan.rs | 16 +- src/iface/ip_packet.rs | 32 +- src/wire/mod.rs | 6 +- src/wire/sixlowpan/nhc.rs | 61 +- 5 files changed, 658 insertions(+), 235 deletions(-) diff --git a/src/iface/interface/sixlowpan.rs b/src/iface/interface/sixlowpan.rs index e479bad75..bd079bd0e 100644 --- a/src/iface/interface/sixlowpan.rs +++ b/src/iface/interface/sixlowpan.rs @@ -1,5 +1,6 @@ use super::*; +use crate::iface::ip_packet::Ipv6Packet; use crate::phy::ChecksumCapabilities; use crate::wire::{Ipv6Packet as Ipv6PacketWire, *}; @@ -33,9 +34,10 @@ impl InterfaceInner { } } SixlowpanPacket::IphcHeader => { - match self.decompress_sixlowpan( + match Self::sixlowpan_to_ipv6( + &self.sixlowpan_address_context, ieee802154_repr, - payload.as_ref(), + payload, None, &mut f.decompress_buf, ) { @@ -101,8 +103,14 @@ impl InterfaceInner { // Decompress headers+payload into the assembler. if let Err(e) = frag_slot.add_with(0, |buffer| { - self.decompress_sixlowpan(ieee802154_repr, frag.payload(), Some(total_size), buffer) - .map_err(|_| AssemblerError) + Self::sixlowpan_to_ipv6( + &self.sixlowpan_address_context, + ieee802154_repr, + frag.payload(), + Some(total_size), + buffer, + ) + .map_err(|_| AssemblerError) }) { net_debug!("fragmentation error: {:?}", e); return None; @@ -124,8 +132,8 @@ impl InterfaceInner { } } - fn decompress_sixlowpan( - &self, + fn sixlowpan_to_ipv6( + address_context: &[SixlowpanAddressContext], ieee802154_repr: &Ieee802154Repr, iphc_payload: &[u8], total_size: Option, @@ -136,20 +144,40 @@ impl InterfaceInner { &iphc, ieee802154_repr.src_addr, ieee802154_repr.dst_addr, - &self.sixlowpan_address_context, + address_context, )?; - let mut decompressed_size = 40 + iphc.payload().len(); - - let next_header = match iphc_repr.next_header { + let first_next_header = match iphc_repr.next_header { SixlowpanNextHeader::Compressed => { match SixlowpanNhcPacket::dispatch(iphc.payload())? { SixlowpanNhcPacket::ExtHeader => { - net_debug!("Extension headers are currently not supported for 6LoWPAN"); - IpProtocol::Unknown(0) + SixlowpanExtHeaderPacket::new_checked(iphc.payload())? + .extension_header_id() + .into() + } + SixlowpanNhcPacket::UdpHeader => IpProtocol::Udp, + } + } + SixlowpanNextHeader::Uncompressed(proto) => proto, + }; + + let mut decompressed_size = 40 + iphc.payload().len(); + let mut next_header = Some(iphc_repr.next_header); + let mut data = iphc.payload(); + + while let Some(nh) = next_header { + match nh { + SixlowpanNextHeader::Compressed => match SixlowpanNhcPacket::dispatch(data)? { + SixlowpanNhcPacket::ExtHeader => { + let ext_hdr = SixlowpanExtHeaderPacket::new_checked(data)?; + let ext_repr = SixlowpanExtHeaderRepr::parse(&ext_hdr)?; + decompressed_size += 2; + decompressed_size -= ext_repr.buffer_len(); + next_header = Some(ext_repr.next_header); + data = &data[ext_repr.buffer_len() + ext_repr.length as usize..]; } SixlowpanNhcPacket::UdpHeader => { - let udp_packet = SixlowpanUdpNhcPacket::new_checked(iphc.payload())?; + let udp_packet = SixlowpanUdpNhcPacket::new_checked(data)?; let udp_repr = SixlowpanUdpNhcRepr::parse( &udp_packet, &iphc_repr.src_addr, @@ -159,12 +187,20 @@ impl InterfaceInner { decompressed_size += 8; decompressed_size -= udp_repr.header_len(); - IpProtocol::Udp + break; } - } + }, + SixlowpanNextHeader::Uncompressed(proto) => match proto { + IpProtocol::Tcp => break, + IpProtocol::Udp => break, + IpProtocol::Icmpv6 => break, + proto => { + net_debug!("unable to decompress Uncompressed({})", proto); + return Err(Error); + } + }, } - SixlowpanNextHeader::Uncompressed(proto) => proto, - }; + } if buffer.len() < decompressed_size { net_debug!("sixlowpan decompress: buffer too short"); @@ -178,27 +214,68 @@ impl InterfaceInner { decompressed_size }; + let mut rest_size = total_size; + let ipv6_repr = Ipv6Repr { src_addr: iphc_repr.src_addr, dst_addr: iphc_repr.dst_addr, - next_header, + next_header: first_next_header, payload_len: total_size - 40, hop_limit: iphc_repr.hop_limit, }; + rest_size -= 40; // Emit the decompressed IPHC header (decompressed to an IPv6 header). let mut ipv6_packet = Ipv6PacketWire::new_unchecked(&mut buffer[..ipv6_repr.buffer_len()]); ipv6_repr.emit(&mut ipv6_packet); - let buffer = &mut buffer[ipv6_repr.buffer_len()..]; + let mut buffer = &mut buffer[ipv6_repr.buffer_len()..]; - match iphc_repr.next_header { - SixlowpanNextHeader::Compressed => { - match SixlowpanNhcPacket::dispatch(iphc.payload())? { - SixlowpanNhcPacket::ExtHeader => todo!(), + let mut next_header = Some(iphc_repr.next_header); + let mut data = iphc.payload(); + + while let Some(nh) = next_header { + match nh { + SixlowpanNextHeader::Compressed => match SixlowpanNhcPacket::dispatch(data)? { + SixlowpanNhcPacket::ExtHeader => { + let ext_hdr = SixlowpanExtHeaderPacket::new_checked(data)?; + let ext_repr = SixlowpanExtHeaderRepr::parse(&ext_hdr)?; + + let nh = match ext_repr.next_header { + SixlowpanNextHeader::Compressed => { + let d = &data[ext_repr.length as usize + ext_repr.buffer_len()..]; + match SixlowpanNhcPacket::dispatch(d)? { + SixlowpanNhcPacket::ExtHeader => { + SixlowpanExtHeaderPacket::new_checked(d)? + .extension_header_id() + .into() + } + SixlowpanNhcPacket::UdpHeader => IpProtocol::Udp, + } + } + SixlowpanNextHeader::Uncompressed(proto) => proto, + }; + next_header = Some(ext_repr.next_header); + + let ipv6_ext_hdr = Ipv6ExtHeaderRepr { + next_header: nh, + length: ext_repr.length / 8, + data: &ext_hdr.payload()[..ext_repr.length as usize], + }; + + ipv6_ext_hdr.emit(&mut Ipv6ExtHeader::new_unchecked( + &mut buffer[..ipv6_ext_hdr.header_len()], + )); + buffer[ipv6_ext_hdr.header_len()..][..ipv6_ext_hdr.data.len()] + .copy_from_slice(ipv6_ext_hdr.data); + + buffer = &mut buffer[ipv6_ext_hdr.header_len() + ipv6_ext_hdr.data.len()..]; + + rest_size -= ipv6_ext_hdr.header_len() + ipv6_ext_hdr.data.len(); + data = &data[ext_repr.buffer_len() + ext_repr.length as usize..]; + } SixlowpanNhcPacket::UdpHeader => { - // We need to uncompress the UDP packet and emit it to the - // buffer. - let udp_packet = SixlowpanUdpNhcPacket::new_checked(iphc.payload())?; + let udp_packet = SixlowpanUdpNhcPacket::new_checked(data)?; + let payload = udp_packet.payload(); let udp_repr = SixlowpanUdpNhcRepr::parse( &udp_packet, &iphc_repr.src_addr, @@ -206,22 +283,33 @@ impl InterfaceInner { &ChecksumCapabilities::ignored(), )?; - let mut udp = UdpPacket::new_unchecked( - &mut buffer[..udp_repr.0.header_len() + iphc.payload().len() - - udp_repr.header_len()], - ); - udp_repr.0.emit_header(&mut udp, ipv6_repr.payload_len - 8); + let mut udp = UdpPacket::new_unchecked(&mut buffer[..payload.len() + 8]); + udp_repr + .0 + .emit_header(&mut udp, rest_size - udp_repr.0.header_len()); + buffer[8..][..payload.len()].copy_from_slice(payload); - buffer[8..].copy_from_slice(&iphc.payload()[udp_repr.header_len()..]); + break; } - } - } - SixlowpanNextHeader::Uncompressed(_) => { - // For uncompressed headers we just copy the slice. - let len = iphc.payload().len(); - buffer[..len].copy_from_slice(iphc.payload()); + }, + SixlowpanNextHeader::Uncompressed(proto) => match proto { + IpProtocol::HopByHop => unreachable!(), + IpProtocol::Tcp => { + buffer.copy_from_slice(data); + break; + } + IpProtocol::Udp => { + buffer.copy_from_slice(data); + break; + } + IpProtocol::Icmpv6 => { + buffer.copy_from_slice(data); + break; + } + _ => unreachable!(), + }, } - }; + } Ok(decompressed_size) } @@ -234,69 +322,20 @@ impl InterfaceInner { ieee_repr: Ieee802154Repr, frag: &mut Fragmenter, ) { - let ip_repr = packet.ip_repr(); - - let (src_addr, dst_addr) = match (ip_repr.src_addr(), ip_repr.dst_addr()) { - (IpAddress::Ipv6(src_addr), IpAddress::Ipv6(dst_addr)) => (src_addr, dst_addr), - #[allow(unreachable_patterns)] - _ => { - unreachable!() - } + let packet = match packet { + #[cfg(feature = "proto-ipv4")] + IpPacket::Ipv4(_) => unreachable!(), + IpPacket::Ipv6(packet) => packet, }; - // Create the 6LoWPAN IPHC header. - let iphc_repr = SixlowpanIphcRepr { - src_addr, - ll_src_addr: ieee_repr.src_addr, - dst_addr, - ll_dst_addr: ieee_repr.dst_addr, - next_header: match &packet.payload() { - IpPayload::Icmpv6(..) => SixlowpanNextHeader::Uncompressed(IpProtocol::Icmpv6), - #[cfg(feature = "socket-tcp")] - IpPayload::Tcp(..) => SixlowpanNextHeader::Uncompressed(IpProtocol::Tcp), - #[cfg(feature = "socket-udp")] - IpPayload::Udp(..) => SixlowpanNextHeader::Compressed, - #[allow(unreachable_patterns)] - _ => { - net_debug!("dispatch_ieee802154: dropping, unhandled protocol."); - return; - } - }, - hop_limit: ip_repr.hop_limit(), - ecn: None, - dscp: None, - flow_label: None, - }; - - // Now we calculate the total size of the packet. - // We need to know this, such that we know when to do the fragmentation. - let mut total_size = 0; - total_size += iphc_repr.buffer_len(); - let mut _compressed_headers_len = iphc_repr.buffer_len(); - let mut _uncompressed_headers_len = ip_repr.header_len(); - - match packet.payload() { - #[cfg(feature = "socket-udp")] - IpPayload::Udp(udpv6_repr, payload) => { - let udp_repr = SixlowpanUdpNhcRepr(*udpv6_repr); - _compressed_headers_len += udp_repr.header_len(); - _uncompressed_headers_len += udpv6_repr.header_len(); - total_size += udp_repr.header_len() + payload.len(); - } - #[cfg(feature = "socket-tcp")] - IpPayload::Tcp(tcp_repr) => { - total_size += tcp_repr.buffer_len(); - } - #[cfg(feature = "proto-ipv6")] - IpPayload::Icmpv6(icmp_repr) => { - total_size += icmp_repr.buffer_len(); - } - #[allow(unreachable_patterns)] - _ => unreachable!(), - } + // First we calculate the size we are going to need. If the size is bigger than the MTU, + // then we use fragmentation. + let (total_size, compressed_size, uncompressed_size) = + Self::compressed_packet_size(&packet, &ieee_repr); let ieee_len = ieee_repr.buffer_len(); + // TODO(thvdveld): use the MTU of the device. if total_size + ieee_len > 125 { #[cfg(feature = "proto-sixlowpan-fragmentation")] { @@ -308,8 +347,8 @@ impl InterfaceInner { // `dispatch_ieee802154_frag` requires some information about the total packet size, // the link local source and destination address... - let pkt = frag; + let pkt = frag; if pkt.buffer.len() < total_size { net_debug!( "dispatch_ieee802154: dropping, \ @@ -319,63 +358,23 @@ impl InterfaceInner { return; } - pkt.sixlowpan.ll_dst_addr = ieee_repr.dst_addr.unwrap(); - pkt.sixlowpan.ll_src_addr = ieee_repr.src_addr.unwrap(); + let payload_length = packet.header.payload_len; - let mut iphc_packet = - SixlowpanIphcPacket::new_unchecked(&mut pkt.buffer[..iphc_repr.buffer_len()]); - iphc_repr.emit(&mut iphc_packet); - - let b = &mut pkt.buffer[iphc_repr.buffer_len()..]; - - match packet.payload() { - #[cfg(feature = "socket-udp")] - IpPayload::Udp(udpv6_repr, payload) => { - let udp_repr = SixlowpanUdpNhcRepr(*udpv6_repr); - let mut udp_packet = SixlowpanUdpNhcPacket::new_unchecked( - &mut b[..udp_repr.header_len() + payload.len()], - ); - udp_repr.emit( - &mut udp_packet, - &iphc_repr.src_addr, - &iphc_repr.dst_addr, - payload.len(), - |buf| buf.copy_from_slice(payload), - ); - } - #[cfg(feature = "socket-tcp")] - IpPayload::Tcp(tcp_repr) => { - let mut tcp_packet = - TcpPacket::new_unchecked(&mut b[..tcp_repr.buffer_len()]); - tcp_repr.emit( - &mut tcp_packet, - &iphc_repr.src_addr.into(), - &iphc_repr.dst_addr.into(), - &self.caps.checksum, - ); - } - #[cfg(feature = "proto-ipv6")] - IpPayload::Icmpv6(icmp_repr) => { - let mut icmp_packet = - Icmpv6Packet::new_unchecked(&mut b[..icmp_repr.buffer_len()]); - icmp_repr.emit( - &iphc_repr.src_addr.into(), - &iphc_repr.dst_addr.into(), - &mut icmp_packet, - &self.caps.checksum, - ); - } - #[allow(unreachable_patterns)] - _ => unreachable!(), - } + Self::ipv6_to_sixlowpan( + &self.checksum_caps(), + packet, + &ieee_repr, + &mut pkt.buffer[..], + ); + pkt.sixlowpan.ll_dst_addr = ieee_repr.dst_addr.unwrap(); + pkt.sixlowpan.ll_src_addr = ieee_repr.src_addr.unwrap(); pkt.packet_len = total_size; // The datagram size that we need to set in the first fragment header is equal to the // IPv6 payload length + 40. - pkt.sixlowpan.datagram_size = (packet.ip_repr().payload_len() + 40) as u16; + pkt.sixlowpan.datagram_size = (payload_length + 40) as u16; - // We generate a random tag. let tag = self.get_sixlowpan_fragment_tag(); // We save the tag for the other fragments that will be created when calling `poll` // multiple times. @@ -398,15 +397,15 @@ impl InterfaceInner { // // [RFC 4944 § 5.3]: https://datatracker.ietf.org/doc/html/rfc4944#section-5.3 - let header_diff = _uncompressed_headers_len - _compressed_headers_len; + let header_diff = uncompressed_size - compressed_size; let frag1_size = - (125 - ieee_len - frag1.buffer_len() + header_diff) / 8 * 8 - (header_diff); + (125 - ieee_len - frag1.buffer_len() + header_diff) / 8 * 8 - header_diff; pkt.sixlowpan.fragn_size = (125 - ieee_len - fragn.buffer_len()) / 8 * 8; - pkt.sent_bytes = frag1_size; pkt.sixlowpan.datagram_offset = frag1_size + header_diff; + tx_token.set_meta(meta); tx_token.consume(ieee_len + frag1.buffer_len() + frag1_size, |mut tx_buf| { // Add the IEEE header. let mut ieee_packet = Ieee802154Frame::new_unchecked(&mut tx_buf[..ieee_len]); @@ -439,55 +438,241 @@ impl InterfaceInner { ieee_repr.emit(&mut ieee_packet); tx_buf = &mut tx_buf[ieee_len..]; - let mut iphc_packet = - SixlowpanIphcPacket::new_unchecked(&mut tx_buf[..iphc_repr.buffer_len()]); - iphc_repr.emit(&mut iphc_packet); - tx_buf = &mut tx_buf[iphc_repr.buffer_len()..]; - - match packet.payload() { - #[cfg(feature = "socket-udp")] - IpPayload::Udp(udpv6_repr, payload) => { - let udp_repr = SixlowpanUdpNhcRepr(*udpv6_repr); - let mut udp_packet = SixlowpanUdpNhcPacket::new_unchecked( - &mut tx_buf[..udp_repr.header_len() + payload.len()], - ); - udp_repr.emit( - &mut udp_packet, - &iphc_repr.src_addr, - &iphc_repr.dst_addr, - payload.len(), - |buf| buf.copy_from_slice(payload), - ); - } - #[cfg(feature = "socket-tcp")] - IpPayload::Tcp(tcp_repr) => { - let mut tcp_packet = - TcpPacket::new_unchecked(&mut tx_buf[..tcp_repr.buffer_len()]); - tcp_repr.emit( - &mut tcp_packet, - &iphc_repr.src_addr.into(), - &iphc_repr.dst_addr.into(), - &self.caps.checksum, - ); - } - #[cfg(feature = "proto-ipv6")] - IpPayload::Icmpv6(icmp_repr) => { - let mut icmp_packet = - Icmpv6Packet::new_unchecked(&mut tx_buf[..icmp_repr.buffer_len()]); - icmp_repr.emit( - &iphc_repr.src_addr.into(), - &iphc_repr.dst_addr.into(), - &mut icmp_packet, - &self.caps.checksum, - ); - } - #[allow(unreachable_patterns)] - _ => unreachable!(), - } + Self::ipv6_to_sixlowpan(&self.checksum_caps(), packet, &ieee_repr, tx_buf); }); } } + fn ipv6_to_sixlowpan( + checksum_caps: &ChecksumCapabilities, + mut packet: Ipv6Packet, + ieee_repr: &Ieee802154Repr, + mut buffer: &mut [u8], + ) { + let last_header = packet.payload.as_sixlowpan_next_header(); + let next_header = last_header; + + #[cfg(feature = "proto-ipv6-hbh")] + let next_header = if packet.hop_by_hop.is_some() { + SixlowpanNextHeader::Compressed + } else { + next_header + }; + + #[cfg(feature = "proto-ipv6-routing")] + let next_header = if packet.routing.is_some() { + SixlowpanNextHeader::Compressed + } else { + next_header + }; + + let iphc_repr = SixlowpanIphcRepr { + src_addr: packet.header.src_addr, + ll_src_addr: ieee_repr.src_addr, + dst_addr: packet.header.dst_addr, + ll_dst_addr: ieee_repr.dst_addr, + next_header, + hop_limit: packet.header.hop_limit, + ecn: None, + dscp: None, + flow_label: None, + }; + + iphc_repr.emit(&mut SixlowpanIphcPacket::new_unchecked( + &mut buffer[..iphc_repr.buffer_len()], + )); + buffer = &mut buffer[iphc_repr.buffer_len()..]; + + // Emit the Hop-by-Hop header + #[cfg(feature = "proto-ipv6-hbh")] + if let Some(hbh) = packet.hop_by_hop { + #[allow(unused)] + let next_header = last_header; + + #[cfg(feature = "proto-ipv6-routing")] + let next_header = if packet.routing.is_some() { + SixlowpanNextHeader::Compressed + } else { + last_header + }; + + let ext_hdr = SixlowpanExtHeaderRepr { + ext_header_id: SixlowpanExtHeaderId::HopByHopHeader, + next_header, + length: hbh.options.iter().map(|o| o.buffer_len()).sum::() as u8, + }; + ext_hdr.emit(&mut SixlowpanExtHeaderPacket::new_unchecked( + &mut buffer[..ext_hdr.buffer_len()], + )); + buffer = &mut buffer[ext_hdr.buffer_len()..]; + + for opt in &hbh.options { + opt.emit(&mut Ipv6Option::new_unchecked( + &mut buffer[..opt.buffer_len()], + )); + + buffer = &mut buffer[opt.buffer_len()..]; + } + } + + // Emit the Routing header + #[cfg(feature = "proto-ipv6-routing")] + if let Some(routing) = &packet.routing { + let ext_hdr = SixlowpanExtHeaderRepr { + ext_header_id: SixlowpanExtHeaderId::RoutingHeader, + next_header, + length: routing.buffer_len() as u8, + }; + ext_hdr.emit(&mut SixlowpanExtHeaderPacket::new_unchecked( + &mut buffer[..ext_hdr.buffer_len()], + )); + buffer = &mut buffer[ext_hdr.buffer_len()..]; + + routing.emit(&mut Ipv6RoutingHeader::new_unchecked( + &mut buffer[..routing.buffer_len()], + )); + buffer = &mut buffer[routing.buffer_len()..]; + } + + match &mut packet.payload { + IpPayload::Icmpv6(icmp_repr) => { + icmp_repr.emit( + &packet.header.src_addr.into(), + &packet.header.dst_addr.into(), + &mut Icmpv6Packet::new_unchecked(&mut buffer[..icmp_repr.buffer_len()]), + checksum_caps, + ); + } + #[cfg(any(feature = "socket-udp", feature = "socket-dns"))] + IpPayload::Udp(udp_repr, payload) => { + let udp_repr = SixlowpanUdpNhcRepr(*udp_repr); + udp_repr.emit( + &mut SixlowpanUdpNhcPacket::new_unchecked( + &mut buffer[..udp_repr.header_len() + payload.len()], + ), + &iphc_repr.src_addr, + &iphc_repr.dst_addr, + payload.len(), + |buf| buf.copy_from_slice(payload), + checksum_caps, + ); + } + #[cfg(feature = "socket-tcp")] + IpPayload::Tcp(tcp_repr) => { + tcp_repr.emit( + &mut TcpPacket::new_unchecked(&mut buffer[..tcp_repr.buffer_len()]), + &packet.header.src_addr.into(), + &packet.header.dst_addr.into(), + checksum_caps, + ); + } + #[cfg(feature = "socket-raw")] + IpPayload::Raw(_raw) => todo!(), + + #[allow(unreachable_patterns)] + _ => unreachable!(), + } + } + + /// Calculates three sizes: + /// - total size: the size of a compressed IPv6 packet + /// - compressed header size: the size of the compressed headers + /// - uncompressed header size: the size of the headers that are not compressed + /// They are returned as a tuple in the same order. + fn compressed_packet_size( + packet: &Ipv6Packet, + ieee_repr: &Ieee802154Repr, + ) -> (usize, usize, usize) { + let last_header = packet.payload.as_sixlowpan_next_header(); + let next_header = last_header; + + #[cfg(feature = "proto-ipv6-hbh")] + let next_header = if packet.hop_by_hop.is_some() { + SixlowpanNextHeader::Compressed + } else { + next_header + }; + + #[cfg(feature = "proto-ipv6-routing")] + let next_header = if packet.routing.is_some() { + SixlowpanNextHeader::Compressed + } else { + next_header + }; + + let iphc = SixlowpanIphcRepr { + src_addr: packet.header.src_addr, + ll_src_addr: ieee_repr.src_addr, + dst_addr: packet.header.dst_addr, + ll_dst_addr: ieee_repr.dst_addr, + next_header, + hop_limit: packet.header.hop_limit, + ecn: None, + dscp: None, + flow_label: None, + }; + + let mut total_size = iphc.buffer_len(); + let mut compressed_hdr_size = iphc.buffer_len(); + let mut uncompressed_hdr_size = packet.header.buffer_len(); + + // Add the hop-by-hop to the sizes. + #[cfg(feature = "proto-ipv6-hbh")] + if let Some(hbh) = &packet.hop_by_hop { + #[allow(unused)] + let next_header = last_header; + + #[cfg(feature = "proto-ipv6-routing")] + let next_header = if packet.routing.is_some() { + SixlowpanNextHeader::Compressed + } else { + last_header + }; + + let options_size = hbh.options.iter().map(|o| o.buffer_len()).sum::(); + + let ext_hdr = SixlowpanExtHeaderRepr { + ext_header_id: SixlowpanExtHeaderId::HopByHopHeader, + next_header, + length: hbh.buffer_len() as u8 + options_size as u8, + }; + + total_size += ext_hdr.buffer_len() + options_size; + compressed_hdr_size += ext_hdr.buffer_len() + options_size; + uncompressed_hdr_size += hbh.buffer_len() + options_size; + } + + // Add the routing header to the sizes. + #[cfg(feature = "proto-ipv6-routing")] + if let Some(routing) = &packet.routing { + let ext_hdr = SixlowpanExtHeaderRepr { + ext_header_id: SixlowpanExtHeaderId::RoutingHeader, + next_header, + length: routing.buffer_len() as u8, + }; + total_size += ext_hdr.buffer_len() + routing.buffer_len(); + compressed_hdr_size += ext_hdr.buffer_len() + routing.buffer_len(); + uncompressed_hdr_size += routing.buffer_len(); + } + + match packet.payload { + #[cfg(any(feature = "socket-udp", feature = "socket-dns"))] + IpPayload::Udp(udp_hdr, payload) => { + uncompressed_hdr_size += udp_hdr.header_len(); + + let udp_hdr = SixlowpanUdpNhcRepr(udp_hdr); + compressed_hdr_size += udp_hdr.header_len(); + + total_size += udp_hdr.header_len() + payload.len(); + } + _ => { + total_size += packet.header.payload_len; + } + } + + (total_size, compressed_hdr_size, uncompressed_hdr_size) + } + #[cfg(feature = "proto-sixlowpan-fragmentation")] pub(super) fn dispatch_sixlowpan_frag( &mut self, @@ -526,3 +711,204 @@ impl InterfaceInner { ); } } + +#[cfg(test)] +#[cfg(all(feature = "proto-rpl", feature = "proto-ipv6-hbh"))] +mod tests { + use super::*; + + static SIXLOWPAN_COMPRESSED_RPL_DAO: [u8; 99] = [ + 0x61, 0xdc, 0x45, 0xcd, 0xab, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x03, 0x00, + 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x7e, 0xf7, 0x00, 0xe0, 0x3a, 0x06, 0x63, 0x04, 0x00, + 0x1e, 0x08, 0x00, 0x9b, 0x02, 0x3e, 0x63, 0x1e, 0x40, 0x00, 0xf1, 0xfd, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x05, 0x12, 0x00, + 0x80, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x03, 0x00, 0x03, 0x00, 0x03, + 0x00, 0x03, 0x06, 0x14, 0x00, 0x00, 0x00, 0x1e, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, + ]; + + static SIXLOWPAN_UNCOMPRESSED_RPL_DAO: [u8; 114] = [ + 0x60, 0x00, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x40, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x3a, 0x00, 0x63, 0x04, 0x00, + 0x1e, 0x08, 0x00, 0x9b, 0x02, 0x3e, 0x63, 0x1e, 0x40, 0x00, 0xf1, 0xfd, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x05, 0x12, 0x00, + 0x80, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x03, 0x00, 0x03, 0x00, 0x03, + 0x00, 0x03, 0x06, 0x14, 0x00, 0x00, 0x00, 0x1e, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, + ]; + + #[test] + fn test_sixlowpan_decompress_hop_by_hop_with_icmpv6() { + let address_context = [SixlowpanAddressContext([ + 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + ])]; + + let ieee_frame = Ieee802154Frame::new_checked(&SIXLOWPAN_COMPRESSED_RPL_DAO).unwrap(); + let ieee_repr = Ieee802154Repr::parse(&ieee_frame).unwrap(); + + let mut buffer = [0u8; 256]; + let len = InterfaceInner::sixlowpan_to_ipv6( + &address_context, + &ieee_repr, + ieee_frame.payload().unwrap(), + None, + &mut buffer[..], + ) + .unwrap(); + + assert_eq!(&buffer[..len], &SIXLOWPAN_UNCOMPRESSED_RPL_DAO); + } + + #[test] + fn test_sixlowpan_compress_hop_by_hop_with_icmpv6() { + let ieee_repr = Ieee802154Repr { + frame_type: Ieee802154FrameType::Data, + security_enabled: false, + frame_pending: false, + ack_request: true, + sequence_number: Some(69), + pan_id_compression: true, + frame_version: Ieee802154FrameVersion::Ieee802154_2006, + dst_pan_id: Some(Ieee802154Pan(43981)), + dst_addr: Some(Ieee802154Address::Extended([0, 1, 0, 1, 0, 1, 0, 1])), + src_pan_id: None, + src_addr: Some(Ieee802154Address::Extended([0, 3, 0, 3, 0, 3, 0, 3])), + }; + + let mut ip_packet = Ipv6Packet { + header: Ipv6Repr { + src_addr: Ipv6Address::from_bytes(&[ + 253, 0, 0, 0, 0, 0, 0, 0, 2, 3, 0, 3, 0, 3, 0, 3, + ]), + dst_addr: Ipv6Address::from_bytes(&[ + 253, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 1, 0, 1, 0, 1, + ]), + next_header: IpProtocol::Icmpv6, + payload_len: 66, + hop_limit: 64, + }, + #[cfg(feature = "proto-ipv6-hbh")] + hop_by_hop: None, + #[cfg(feature = "proto-ipv6-fragmentation")] + fragment: None, + #[cfg(feature = "proto-ipv6-routing")] + routing: None, + payload: IpPayload::Icmpv6(Icmpv6Repr::Rpl(RplRepr::DestinationAdvertisementObject { + rpl_instance_id: RplInstanceId::Global(30), + expect_ack: false, + sequence: 241, + dodag_id: Some(Ipv6Address::from_bytes(&[ + 253, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 1, 0, 1, 0, 1, + ])), + options: &[], + })), + }; + + let (total_size, _, _) = InterfaceInner::compressed_packet_size(&mut ip_packet, &ieee_repr); + let mut buffer = vec![0u8; total_size]; + + InterfaceInner::ipv6_to_sixlowpan( + &ChecksumCapabilities::default(), + ip_packet, + &ieee_repr, + &mut buffer[..total_size], + ); + + let result = [ + 0x7e, 0x0, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x3, 0x0, 0x3, 0x0, 0x3, 0x0, + 0x3, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x1, 0x0, 0x1, 0x0, 0x1, 0x0, 0x1, + 0xe0, 0x3a, 0x6, 0x63, 0x4, 0x0, 0x1e, 0x3, 0x0, 0x9b, 0x2, 0x3e, 0x63, 0x1e, 0x40, + 0x0, 0xf1, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x1, 0x0, 0x1, 0x0, 0x1, 0x0, + 0x1, 0x5, 0x12, 0x0, 0x80, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x3, 0x0, 0x3, + 0x0, 0x3, 0x0, 0x3, 0x6, 0x14, 0x0, 0x0, 0x0, 0x1e, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x2, 0x1, 0x0, 0x1, 0x0, 0x1, 0x0, 0x1, + ]; + + assert_eq!(&result, &result); + } + + #[test] + fn test_sixlowpan_compress_hop_by_hop_with_udp() { + let ieee_repr = Ieee802154Repr { + frame_type: Ieee802154FrameType::Data, + security_enabled: false, + frame_pending: false, + ack_request: true, + sequence_number: Some(69), + pan_id_compression: true, + frame_version: Ieee802154FrameVersion::Ieee802154_2006, + dst_pan_id: Some(Ieee802154Pan(43981)), + dst_addr: Some(Ieee802154Address::Extended([0, 1, 0, 1, 0, 1, 0, 1])), + src_pan_id: None, + src_addr: Some(Ieee802154Address::Extended([0, 3, 0, 3, 0, 3, 0, 3])), + }; + + let addr = Ipv6Address::from_bytes(&[253, 0, 0, 0, 0, 0, 0, 0, 2, 3, 0, 3, 0, 3, 0, 3]); + let parent_address = + Ipv6Address::from_bytes(&[253, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 1, 0, 1, 0, 1]); + + let mut hbh_options = heapless::Vec::new(); + hbh_options + .push(Ipv6OptionRepr::Rpl(RplHopByHopRepr { + down: false, + rank_error: false, + forwarding_error: false, + instance_id: RplInstanceId::from(0x1e), + sender_rank: 0x300, + })) + .unwrap(); + + let mut ip_packet = Ipv6Packet { + header: Ipv6Repr { + src_addr: addr, + dst_addr: parent_address, + next_header: IpProtocol::Icmpv6, + payload_len: 66, + hop_limit: 64, + }, + #[cfg(feature = "proto-ipv6-hbh")] + hop_by_hop: Some(Ipv6HopByHopRepr { + options: hbh_options, + }), + #[cfg(feature = "proto-ipv6-fragmentation")] + fragment: None, + #[cfg(feature = "proto-ipv6-routing")] + routing: None, + payload: IpPayload::Icmpv6(Icmpv6Repr::Rpl(RplRepr::DestinationAdvertisementObject { + rpl_instance_id: RplInstanceId::Global(30), + expect_ack: false, + sequence: 241, + dodag_id: Some(Ipv6Address::from_bytes(&[ + 253, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 1, 0, 1, 0, 1, + ])), + options: &[ + 5, 18, 0, 128, 253, 0, 0, 0, 0, 0, 0, 0, 2, 3, 0, 3, 0, 3, 0, 3, 6, 20, 0, 0, + 0, 30, 253, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 1, 0, 1, 0, 1, + ], + })), + }; + + let (total_size, _, _) = InterfaceInner::compressed_packet_size(&mut ip_packet, &ieee_repr); + let mut buffer = vec![0u8; total_size]; + + InterfaceInner::ipv6_to_sixlowpan( + &ChecksumCapabilities::default(), + ip_packet, + &ieee_repr, + &mut buffer[..total_size], + ); + + let result = [ + 0x7e, 0x0, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x3, 0x0, 0x3, 0x0, 0x3, 0x0, + 0x3, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x1, 0x0, 0x1, 0x0, 0x1, 0x0, 0x1, + 0xe0, 0x3a, 0x6, 0x63, 0x4, 0x0, 0x1e, 0x3, 0x0, 0x9b, 0x2, 0x3e, 0x63, 0x1e, 0x40, + 0x0, 0xf1, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x1, 0x0, 0x1, 0x0, 0x1, 0x0, + 0x1, 0x5, 0x12, 0x0, 0x80, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x3, 0x0, 0x3, + 0x0, 0x3, 0x0, 0x3, 0x6, 0x14, 0x0, 0x0, 0x0, 0x1e, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x2, 0x1, 0x0, 0x1, 0x0, 0x1, 0x0, 0x1, + ]; + + assert_eq!(&buffer[..total_size], &result); + } +} diff --git a/src/iface/interface/tests/sixlowpan.rs b/src/iface/interface/tests/sixlowpan.rs index 04fedc1d1..229c75b44 100644 --- a/src/iface/interface/tests/sixlowpan.rs +++ b/src/iface/interface/tests/sixlowpan.rs @@ -388,14 +388,14 @@ In at rhoncus tortor. Cras blandit tellus diam, varius vestibulum nibh commodo n &[ 0x41, 0xcc, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0xc0, 0xb4, 0x5, 0x4e, 0x7e, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf0, 0x4, 0xd2, 0x4, 0xd2, 0xf6, - 0x4d, 0x4c, 0x6f, 0x72, 0x65, 0x6d, 0x20, 0x69, 0x70, 0x73, 0x75, 0x6d, 0x20, 0x64, - 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x73, 0x69, 0x74, 0x20, 0x61, 0x6d, 0x65, 0x74, 0x2c, - 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x65, 0x63, 0x74, 0x65, 0x74, 0x75, 0x72, 0x20, 0x61, - 0x64, 0x69, 0x70, 0x69, 0x73, 0x63, 0x69, 0x6e, 0x67, 0x20, 0x65, 0x6c, 0x69, 0x74, - 0x2e, 0x20, 0x49, 0x6e, 0x20, 0x61, 0x74, 0x20, 0x72, 0x68, 0x6f, 0x6e, 0x63, 0x75, - 0x73, 0x20, 0x74, - ] + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf0, 0x4, 0xd2, 0x4, 0xd2, 0x0, 0x0, + 0x4c, 0x6f, 0x72, 0x65, 0x6d, 0x20, 0x69, 0x70, 0x73, 0x75, 0x6d, 0x20, 0x64, 0x6f, + 0x6c, 0x6f, 0x72, 0x20, 0x73, 0x69, 0x74, 0x20, 0x61, 0x6d, 0x65, 0x74, 0x2c, 0x20, + 0x63, 0x6f, 0x6e, 0x73, 0x65, 0x63, 0x74, 0x65, 0x74, 0x75, 0x72, 0x20, 0x61, 0x64, + 0x69, 0x70, 0x69, 0x73, 0x63, 0x69, 0x6e, 0x67, 0x20, 0x65, 0x6c, 0x69, 0x74, 0x2e, + 0x20, 0x49, 0x6e, 0x20, 0x61, 0x74, 0x20, 0x72, 0x68, 0x6f, 0x6e, 0x63, 0x75, 0x73, + 0x20, 0x74, + ], ); assert_eq!( diff --git a/src/iface/ip_packet.rs b/src/iface/ip_packet.rs index fe33b9537..8de72aa1b 100644 --- a/src/iface/ip_packet.rs +++ b/src/iface/ip_packet.rs @@ -153,14 +153,14 @@ pub(crate) struct Ipv4Packet<'p> { #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg(feature = "proto-ipv6")] pub(crate) struct Ipv6Packet<'p> { - header: Ipv6Repr, + pub(crate) header: Ipv6Repr, #[cfg(feature = "proto-ipv6-hbh")] - hop_by_hop: Option>, + pub(crate) hop_by_hop: Option>, #[cfg(feature = "proto-ipv6-fragmentation")] - fragment: Option, + pub(crate) fragment: Option, #[cfg(feature = "proto-ipv6-routing")] - routing: Option>, - payload: IpPayload<'p>, + pub(crate) routing: Option>, + pub(crate) payload: IpPayload<'p>, } #[derive(Debug, PartialEq)] @@ -182,6 +182,28 @@ pub(crate) enum IpPayload<'p> { Dhcpv4(UdpRepr, DhcpRepr<'p>), } +impl<'p> IpPayload<'p> { + #[cfg(feature = "proto-sixlowpan")] + pub(crate) fn as_sixlowpan_next_header(&self) -> SixlowpanNextHeader { + match self { + #[cfg(feature = "proto-ipv4")] + Self::Icmpv4(_) => unreachable!(), + #[cfg(feature = "socket-dhcpv4")] + Self::Dhcpv4(..) => unreachable!(), + #[cfg(feature = "proto-ipv6")] + Self::Icmpv6(_) => SixlowpanNextHeader::Uncompressed(IpProtocol::Icmpv6), + #[cfg(feature = "proto-igmp")] + Self::Igmp(_) => unreachable!(), + #[cfg(feature = "socket-tcp")] + Self::Tcp(_) => SixlowpanNextHeader::Uncompressed(IpProtocol::Tcp), + #[cfg(feature = "socket-udp")] + Self::Udp(..) => SixlowpanNextHeader::Compressed, + #[cfg(feature = "socket-raw")] + Self::Raw(_) => todo!(), + } + } +} + #[cfg(any(feature = "proto-ipv4", feature = "proto-ipv6"))] pub(crate) fn icmp_reply_payload_len(len: usize, mtu: usize, header_len: usize) -> usize { // Send back as much of the original payload as will fit within diff --git a/src/wire/mod.rs b/src/wire/mod.rs index 0370956cd..62b445330 100644 --- a/src/wire/mod.rs +++ b/src/wire/mod.rs @@ -158,9 +158,9 @@ pub use self::sixlowpan::{ frag::{Key as SixlowpanFragKey, Packet as SixlowpanFragPacket, Repr as SixlowpanFragRepr}, iphc::{Packet as SixlowpanIphcPacket, Repr as SixlowpanIphcRepr}, nhc::{ - ExtHeaderPacket as SixlowpanExtHeaderPacket, ExtHeaderRepr as SixlowpanExtHeaderRepr, - NhcPacket as SixlowpanNhcPacket, UdpNhcPacket as SixlowpanUdpNhcPacket, - UdpNhcRepr as SixlowpanUdpNhcRepr, + ExtHeaderId as SixlowpanExtHeaderId, ExtHeaderPacket as SixlowpanExtHeaderPacket, + ExtHeaderRepr as SixlowpanExtHeaderRepr, NhcPacket as SixlowpanNhcPacket, + UdpNhcPacket as SixlowpanUdpNhcPacket, UdpNhcRepr as SixlowpanUdpNhcRepr, }, AddressContext as SixlowpanAddressContext, NextHeader as SixlowpanNextHeader, SixlowpanPacket, }; diff --git a/src/wire/sixlowpan/nhc.rs b/src/wire/sixlowpan/nhc.rs index 0a88557fb..3f16af1cc 100644 --- a/src/wire/sixlowpan/nhc.rs +++ b/src/wire/sixlowpan/nhc.rs @@ -179,6 +179,11 @@ impl> ExtHeaderPacket { } } + /// Return the length field. + pub fn length(&self) -> u8 { + self.buffer.as_ref()[1 + self.next_header_size()] + } + /// Parse the next header field. pub fn next_header(&self) -> NextHeader { if self.nh_field() == 1 { @@ -208,7 +213,8 @@ impl<'a, T: AsRef<[u8]> + ?Sized> ExtHeaderPacket<&'a T> { /// Return a pointer to the payload. pub fn payload(&self) -> &'a [u8] { let start = 2 + self.next_header_size(); - &self.buffer.as_ref()[start..] + let len = self.length() as usize; + &self.buffer.as_ref()[start..][..len] } } @@ -216,7 +222,8 @@ impl + AsMut<[u8]>> ExtHeaderPacket { /// Return a mutable pointer to the payload. pub fn payload_mut(&mut self) -> &mut [u8] { let start = 2 + self.next_header_size(); - &mut self.buffer.as_mut()[start..] + let len = self.length() as usize; + &mut self.buffer.as_mut()[start..][..len] } /// Set the dispatch field to `0b1110`. @@ -270,9 +277,9 @@ impl + AsMut<[u8]>> ExtHeaderPacket { #[derive(Debug, PartialEq, Eq, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct ExtHeaderRepr { - ext_header_id: ExtHeaderId, - next_header: NextHeader, - length: u8, + pub ext_header_id: ExtHeaderId, + pub next_header: NextHeader, + pub length: u8, } impl ExtHeaderRepr { @@ -288,7 +295,7 @@ impl ExtHeaderRepr { Ok(Self { ext_header_id: packet.extension_header_id(), next_header: packet.next_header(), - length: packet.payload().len() as u8, + length: packet.length(), }) } @@ -752,25 +759,28 @@ impl<'a> UdpNhcRepr { dst_addr: &Address, payload_len: usize, emit_payload: impl FnOnce(&mut [u8]), + checksum_caps: &ChecksumCapabilities, ) { packet.set_dispatch_field(); packet.set_ports(self.src_port, self.dst_port); emit_payload(packet.payload_mut()); - let chk_sum = !checksum::combine(&[ - checksum::pseudo_header( - &IpAddress::Ipv6(*src_addr), - &IpAddress::Ipv6(*dst_addr), - crate::wire::ip::Protocol::Udp, - payload_len as u32 + 8, - ), - self.src_port, - self.dst_port, - payload_len as u16 + 8, - checksum::data(packet.payload_mut()), - ]); - - packet.set_checksum(chk_sum); + if checksum_caps.udp.tx() { + let chk_sum = !checksum::combine(&[ + checksum::pseudo_header( + &IpAddress::Ipv6(*src_addr), + &IpAddress::Ipv6(*dst_addr), + crate::wire::ip::Protocol::Udp, + payload_len as u32 + 8, + ), + self.src_port, + self.dst_port, + payload_len as u16 + 8, + checksum::data(packet.payload_mut()), + ]); + + packet.set_checksum(chk_sum); + } } } @@ -848,9 +858,14 @@ mod test { let len = udp.header_len() + payload.len(); let mut buffer = [0u8; 127]; let mut packet = UdpNhcPacket::new_unchecked(&mut buffer[..len]); - udp.emit(&mut packet, &src_addr, &dst_addr, payload.len(), |buf| { - buf.copy_from_slice(&payload[..]) - }); + udp.emit( + &mut packet, + &src_addr, + &dst_addr, + payload.len(), + |buf| buf.copy_from_slice(&payload[..]), + &ChecksumCapabilities::default(), + ); assert_eq!(packet.dispatch_field(), DISPATCH_UDP_HEADER); assert_eq!(packet.src_port(), 0xf0b1); From 81941c9893d913f74ab9556a6612fd36af6b6bcb Mon Sep 17 00:00:00 2001 From: Thibaut Vandervelden Date: Wed, 9 Aug 2023 11:54:21 +0200 Subject: [PATCH 2/2] docs(readme): update 6lowpan ext headers support Signed-off-by: Thibaut Vandervelden --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ede37c7d8..74fa161e9 100644 --- a/README.md +++ b/README.md @@ -65,9 +65,9 @@ There are 3 supported mediums. * Implementation of [RFC6282](https://tools.ietf.org/rfc/rfc6282.txt). * Fragmentation is supported, as defined in [RFC4944](https://tools.ietf.org/rfc/rfc4944.txt). - * UDP header compression is supported. - * Extension header compression is **not** supported (will be soon™). - * Uncompressed IP headers are supported. + * UDP header compression/decompression is supported. + * Extension header compression/decompression is supported. + * Uncompressed IPv6 Extension Headers are **not** supported. ### IP multicast