diff --git a/examples/sixlowpan.rs b/examples/sixlowpan.rs index 6d5ce8fa..b31d54e0 100644 --- a/examples/sixlowpan.rs +++ b/examples/sixlowpan.rs @@ -47,11 +47,13 @@ use std::collections::BTreeMap; use std::os::unix::io::AsRawFd; use std::str; -use smoltcp::iface::{InterfaceBuilder, NeighborCache}; +use smoltcp::iface::{FragmentsCache, InterfaceBuilder, NeighborCache}; use smoltcp::phy::{wait as phy_wait, Medium, RawSocket}; use smoltcp::socket::udp; use smoltcp::time::Instant; use smoltcp::wire::{Ieee802154Pan, IpAddress, IpCidr}; +use smoltcp::socket::tcp; +use smoltcp::storage::RingBuffer; fn main() { utils::setup_logging(""); @@ -72,6 +74,10 @@ fn main() { let udp_tx_buffer = udp::PacketBuffer::new(vec![udp::PacketMetadata::EMPTY], vec![0; 128]); let udp_socket = udp::Socket::new(udp_rx_buffer, udp_tx_buffer); + let tcp_rx_buffer = tcp::SocketBuffer::new(vec![0; 4096]); + let tcp_tx_buffer = tcp::SocketBuffer::new(vec![0; 4096]); + let tcp_socket = tcp::Socket::new(tcp_rx_buffer, tcp_tx_buffer); + let ieee802154_addr = smoltcp::wire::Ieee802154Address::Extended([ 0x1a, 0x0b, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, ]); @@ -80,22 +86,44 @@ fn main() { 64, )]; + let cache = FragmentsCache::new(vec![], BTreeMap::new()); + + let buffer: Vec<(usize, managed::ManagedSlice<'_, u8>)> = (0..12) + .into_iter() + .map(|_| (0_usize, managed::ManagedSlice::from(vec![0; 127]))) + .collect(); + + let out_fragments_cache = RingBuffer::new(buffer); + let mut builder = InterfaceBuilder::new(device, vec![]) .ip_addrs(ip_addrs) .pan_id(Ieee802154Pan(0xbeef)); builder = builder .hardware_addr(ieee802154_addr.into()) - .neighbor_cache(neighbor_cache); + .neighbor_cache(neighbor_cache) + .sixlowpan_fragments_cache(cache) + .out_fragments_cache(out_fragments_cache); let mut iface = builder.finalize(); let udp_handle = iface.add_socket(udp_socket); + let tcp_handle = iface.add_socket(tcp_socket); + + let socket = iface.get_socket::(tcp_handle); + socket.listen(50000).unwrap(); + + let mut tcp_active = false; loop { let timestamp = Instant::now(); - match iface.poll(timestamp) { - Ok(_) => {} - Err(e) => { - debug!("poll error: {}", e); + + let mut poll = true; + while poll { + match iface.poll(timestamp) { + Ok(r) => poll = r, + Err(e) => { + debug!("poll error: {}", e); + break; + } } } @@ -105,6 +133,7 @@ fn main() { socket.bind(6969).unwrap() } + let mut buffer = vec![0; 1500]; let client = match socket.recv() { Ok((data, endpoint)) => { debug!( @@ -112,17 +141,51 @@ fn main() { str::from_utf8(data).unwrap(), endpoint ); - Some(endpoint) + buffer[..data.len()].copy_from_slice(data); + Some((data.len(), endpoint)) } Err(_) => None, }; - if let Some(endpoint) = client { - let data = b"hello\n"; + if let Some((len, endpoint)) = client { debug!( "udp:6969 send data: {:?}", - str::from_utf8(data.as_ref()).unwrap() + str::from_utf8(&buffer[..len]).unwrap() ); - socket.send_slice(data, endpoint).unwrap(); + socket.send_slice(&buffer[..len], endpoint).unwrap(); + } + + let socket = iface.get_socket::(tcp_handle); + if socket.is_active() && !tcp_active { + debug!("connected"); + } else if !socket.is_active() && tcp_active { + debug!("disconnected"); + } + tcp_active = socket.is_active(); + + if socket.may_recv() { + let data = socket + .recv(|data| { + let data = data.to_owned(); + if !data.is_empty() { + debug!( + "recv data: {:?}", + str::from_utf8(data.as_ref()).unwrap_or("(invalid utf8)") + ); + } + (data.len(), data) + }) + .unwrap(); + + if socket.can_send() && !data.is_empty() { + debug!( + "send data: {:?}", + str::from_utf8(data.as_ref()).unwrap_or("(invalid utf8)") + ); + socket.send_slice(&data[..]).unwrap(); + } + } else if socket.may_send() { + debug!("close"); + socket.close(); } phy_wait(fd, iface.poll_delay(timestamp)).expect("wait error"); diff --git a/fuzz/fuzz_targets/sixlowpan_udp_header.rs b/fuzz/fuzz_targets/sixlowpan_udp_header.rs index 9a2079b4..de876a5e 100644 --- a/fuzz/fuzz_targets/sixlowpan_udp_header.rs +++ b/fuzz/fuzz_targets/sixlowpan_udp_header.rs @@ -1,6 +1,6 @@ #![no_main] use libfuzzer_sys::fuzz_target; -use smoltcp::wire::{Ipv6Address, SixlowpanUdpPacket, SixlowpanUdpRepr}; +use smoltcp::wire::{Ipv6Address, SixlowpanUdpNhcPacket, SixlowpanUdpNhcRepr}; #[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, arbitrary::Arbitrary)] pub struct AddressFuzzer(pub [u8; 16]); @@ -16,21 +16,19 @@ struct SixlowpanUdpPacketFuzzer<'a> { data: &'a [u8], src_addr: AddressFuzzer, dst_addr: AddressFuzzer, - checksum: Option, } fuzz_target!(|fuzz: SixlowpanUdpPacketFuzzer| { - if let Ok(ref frame) = SixlowpanUdpPacket::new_checked(fuzz.data) { - if let Ok(repr) = SixlowpanUdpRepr::parse( + if let Ok(ref frame) = SixlowpanUdpNhcPacket::new_checked(fuzz.data) { + if let Ok(repr) = SixlowpanUdpNhcRepr::parse( frame, &fuzz.src_addr.into(), &fuzz.dst_addr.into(), - fuzz.checksum, ) { let payload = frame.payload(); let mut buffer = vec![0; repr.header_len() + payload.len()]; - let mut frame = SixlowpanUdpPacket::new_unchecked(&mut buffer[..]); + let mut frame = SixlowpanUdpNhcPacket::new_unchecked(&mut buffer[..]); repr.emit( &mut frame, &fuzz.src_addr.into(), diff --git a/src/iface/fragmentation.rs b/src/iface/fragmentation.rs index 512d7195..cebc11d5 100644 --- a/src/iface/fragmentation.rs +++ b/src/iface/fragmentation.rs @@ -25,6 +25,7 @@ enum AssemblerState { total_size: usize, last_updated: Instant, started_on: Instant, + offset_correction: isize, }, } @@ -48,7 +49,12 @@ impl<'a> PacketAssembler<'a> { /// /// - Returns [`Error::PacketAssemblerBufferTooSmall`] when the buffer is too small for holding all the /// fragments of a packet. - pub(crate) fn start(&mut self, total_size: usize, start_time: Instant) -> Result<()> { + pub(crate) fn start( + &mut self, + total_size: usize, + start_time: Instant, + offset_correction: isize, + ) -> Result<()> { match &mut self.buffer { ManagedSlice::Borrowed(b) if b.len() < total_size => { return Err(Error::PacketAssemblerBufferTooSmall); @@ -65,6 +71,7 @@ impl<'a> PacketAssembler<'a> { total_size, last_updated: start_time, started_on: start_time, + offset_correction, }; Ok(()) @@ -86,8 +93,12 @@ impl<'a> PacketAssembler<'a> { ref mut assembler, total_size, ref mut last_updated, + offset_correction, .. } => { + let offset = offset as isize + offset_correction; + let offset = if offset <= 0 { 0 } else { offset as usize }; + if offset + data.len() > total_size { return Err(Error::PacketAssemblerBufferTooSmall); } @@ -392,10 +403,10 @@ mod tests { let mut p_assembler = PacketAssembler::new(&mut storage[..]); assert_eq!( - p_assembler.start(2, Instant::now()), + p_assembler.start(2, Instant::now(), 0), Err(Error::PacketAssemblerBufferTooSmall) ); - assert_eq!(p_assembler.start(1, Instant::now()), Ok(())); + assert_eq!(p_assembler.start(1, Instant::now(), 0), Ok(())); let data = b"Hello World!"; assert_eq!( @@ -409,7 +420,7 @@ mod tests { let mut storage = [0u8; 5]; let mut p_assembler = PacketAssembler::new(&mut storage[..]); - p_assembler.start(5, Instant::now()).unwrap(); + p_assembler.start(5, Instant::now(), 0).unwrap(); let data = b"Rust"; p_assembler.add(&data[..], 0, Instant::now()).unwrap(); @@ -424,7 +435,7 @@ mod tests { let data = b"Hello World!"; - p_assembler.start(data.len(), Instant::now()).unwrap(); + p_assembler.start(data.len(), Instant::now(), 0).unwrap(); p_assembler.add(b"Hello ", 0, Instant::now()).unwrap(); assert_eq!( @@ -483,7 +494,7 @@ mod tests { set.reserve_with_key(&key).unwrap(); set.get_packet_assembler_mut(&key) .unwrap() - .start(0, Instant::now()) + .start(0, Instant::now(), 0) .unwrap(); set.get_assembled_packet(&key).unwrap(); @@ -491,7 +502,7 @@ mod tests { set.reserve_with_key(&key).unwrap(); set.get_packet_assembler_mut(&key) .unwrap() - .start(0, Instant::now()) + .start(0, Instant::now(), 0) .unwrap(); set.get_assembled_packet(&key).unwrap(); @@ -499,7 +510,7 @@ mod tests { set.reserve_with_key(&key).unwrap(); set.get_packet_assembler_mut(&key) .unwrap() - .start(0, Instant::now()) + .start(0, Instant::now(), 0) .unwrap(); set.get_assembled_packet(&key).unwrap(); } diff --git a/src/iface/interface.rs b/src/iface/interface.rs index ce981f41..8a0fc7ce 100644 --- a/src/iface/interface.rs +++ b/src/iface/interface.rs @@ -5,7 +5,6 @@ use core::cmp; use managed::{ManagedMap, ManagedSlice}; -#[allow(unused)] #[cfg(feature = "proto-sixlowpan")] use super::fragmentation::PacketAssemblerSet; use super::socket_set::SocketSet; @@ -20,10 +19,16 @@ use crate::socket::dhcpv4; #[cfg(feature = "socket-dns")] use crate::socket::dns; use crate::socket::*; +use crate::storage::RingBuffer; use crate::time::{Duration, Instant}; use crate::wire::*; use crate::{Error, Result}; +type OutBuffer<'a> = RingBuffer<'a, (usize, ManagedSlice<'a, u8>)>; + +#[cfg(feature = "proto-sixlowpan")] +type SixlowpanFragmentsBuffer<'a> = PacketAssemblerSet<'a, SixlowpanFragKey>; + macro_rules! check { ($e:expr) => { match $e { @@ -49,6 +54,11 @@ pub struct Interface<'a, DeviceT: for<'d> Device<'d>> { device: DeviceT, sockets: SocketSet<'a>, inner: InterfaceInner<'a>, + // Currently this is behind the sixlowpan feature. However, this buffer could also be used for other protocols. + #[cfg(feature = "proto-sixlowpan")] + out_fragments: OutBuffer<'a>, + #[cfg(feature = "proto-sixlowpan")] + sixlowpan_fragments: SixlowpanFragmentsBuffer<'a>, } /// The device independent part of an Ethernet network interface. @@ -61,6 +71,7 @@ pub struct Interface<'a, DeviceT: for<'d> Device<'d>> { pub struct InterfaceInner<'a> { caps: DeviceCapabilities, now: Instant, + #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))] neighbor_cache: Option>, #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))] @@ -69,6 +80,8 @@ pub struct InterfaceInner<'a> { sequence_no: u8, #[cfg(feature = "medium-ieee802154")] pan_id: Option, + #[cfg(feature = "proto-sixlowpan")] + tag: u16, ip_addrs: ManagedSlice<'a, IpCidr>, #[cfg(feature = "proto-ipv4")] any_ip: bool, @@ -99,6 +112,10 @@ pub struct InterfaceBuilder<'a, DeviceT: for<'d> Device<'d>> { #[cfg(feature = "proto-igmp")] ipv4_multicast_groups: ManagedMap<'a, Ipv4Address, ()>, random_seed: u64, + #[cfg(feature = "proto-sixlowpan")] + sixlowpan_fragments: Option>, + #[cfg(feature = "proto-sixlowpan")] + out_fragments: Option>, } impl<'a, DeviceT> InterfaceBuilder<'a, DeviceT> @@ -108,7 +125,7 @@ where /// Create a builder used for creating a network interface using the /// given device and address. #[cfg_attr( - feature = "medium-ethernet", + all(feature = "medium-ethernet", not(feature = "proto-sixlowpan")), doc = r##" # Examples @@ -139,7 +156,7 @@ let iface = InterfaceBuilder::new(device, vec![]) SocketsT: Into>>, { InterfaceBuilder { - device: device, + device, sockets: SocketSet::new(sockets), #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))] @@ -157,6 +174,11 @@ let iface = InterfaceBuilder::new(device, vec![]) #[cfg(feature = "proto-igmp")] ipv4_multicast_groups: ManagedMap::Borrowed(&mut []), random_seed: 0, + + #[cfg(feature = "proto-sixlowpan")] + sixlowpan_fragments: None, + #[cfg(feature = "proto-sixlowpan")] + out_fragments: None, } } @@ -268,6 +290,18 @@ let iface = InterfaceBuilder::new(device, vec![]) self } + #[cfg(feature = "proto-sixlowpan")] + pub fn sixlowpan_fragments_cache(mut self, storage: SixlowpanFragmentsBuffer<'a>) -> Self { + self.sixlowpan_fragments = Some(storage); + self + } + + #[cfg(feature = "proto-sixlowpan")] + pub fn out_fragments_cache(mut self, storage: OutBuffer<'a>) -> Self { + self.out_fragments = Some(storage); + self + } + /// Create a network interface using the previously provided configuration. /// /// # Panics @@ -337,9 +371,28 @@ let iface = InterfaceBuilder::new(device, vec![]) } } + #[cfg(feature = "proto-sixlowpan")] + let mut tag; + + #[cfg(feature = "proto-sixlowpan")] + loop { + tag = (rand.rand_u32() & 0xffff) as u16; + if tag != 0 { + break; + } + } + Interface { device: self.device, sockets: self.sockets, + #[cfg(feature = "proto-sixlowpan")] + sixlowpan_fragments: self + .sixlowpan_fragments + .expect("Cache for incoming 6LoWPAN fragments is required"), + #[cfg(feature = "proto-sixlowpan")] + out_fragments: self + .out_fragments + .expect("Cache for outgoing fragments is required"), inner: InterfaceInner { now: Instant::from_secs(0), caps, @@ -359,6 +412,8 @@ let iface = InterfaceBuilder::new(device, vec![]) sequence_no, #[cfg(feature = "medium-ieee802154")] pan_id: self.pan_id, + #[cfg(feature = "proto-sixlowpan")] + tag, rand, }, } @@ -654,7 +709,7 @@ where { // Send initial membership report let tx_token = self.device.transmit().ok_or(Error::Exhausted)?; - self.inner.dispatch_ip(tx_token, pkt)?; + self.inner.dispatch_ip(tx_token, pkt, None)?; Ok(true) } else { Ok(false) @@ -686,7 +741,7 @@ where } else if let Some(pkt) = self.inner.igmp_leave_packet(addr) { // Send group leave packet let tx_token = self.device.transmit().ok_or(Error::Exhausted)?; - self.inner.dispatch_ip(tx_token, pkt)?; + self.inner.dispatch_ip(tx_token, pkt, None)?; Ok(true) } else { Ok(false) @@ -771,6 +826,7 @@ where self.inner.now = timestamp; let mut readiness_may_have_changed = false; + loop { let processed_any = self.socket_ingress(); let emitted_any = self.socket_egress(); @@ -784,6 +840,19 @@ where break; } } + + #[cfg(feature = "proto-sixlowpan")] + while !self.out_fragments.is_empty() { + let tx_token = self.device.transmit().ok_or(Error::Exhausted)?; + + // FIXME(thvdveld): remove the unwrap + let (len, buffer) = self.out_fragments.dequeue_one().unwrap(); + tx_token.consume(self.inner.now, *len, |tx_token| { + tx_token.copy_from_slice(&buffer[..*len]); + Ok(()) + })?; + } + Ok(readiness_may_have_changed) } @@ -838,7 +907,10 @@ where device, inner, sockets, - .. + #[cfg(feature = "proto-sixlowpan")] + sixlowpan_fragments, + #[cfg(feature = "proto-sixlowpan")] + out_fragments, } = self; while let Some((rx_token, tx_token)) = device.receive() { let res = rx_token.consume(inner.now, |frame| { @@ -854,15 +926,19 @@ where #[cfg(feature = "medium-ip")] Medium::Ip => { if let Some(packet) = inner.process_ip(sockets, &frame) { - if let Err(err) = inner.dispatch_ip(tx_token, packet) { + if let Err(err) = inner.dispatch_ip(tx_token, packet, None) { net_debug!("Failed to send response: {}", err); } } } #[cfg(feature = "medium-ieee802154")] Medium::Ieee802154 => { - if let Some(packet) = inner.process_ieee802154(sockets, &frame) { - if let Err(err) = inner.dispatch_ieee802154(tx_token, packet) { + if let Some(packet) = + inner.process_ieee802154(sockets, &frame, sixlowpan_fragments) + { + if let Err(err) = + inner.dispatch_ip(tx_token, packet, Some(out_fragments)) + { net_debug!("Failed to send response: {}", err); } } @@ -885,6 +961,8 @@ where device, inner, sockets, + #[cfg(feature = "proto-sixlowpan")] + out_fragments, .. } = self; let _caps = device.capabilities(); @@ -901,8 +979,16 @@ where let mut neighbor_addr = None; let mut respond = |inner: &mut InterfaceInner, response: IpPacket| { neighbor_addr = Some(response.ip_repr().dst_addr()); - let tx_token = device.transmit().ok_or(Error::Exhausted)?; - inner.dispatch_ip(tx_token, response)?; + // FIXME(thvdveld): remove unwrap + let tx_token = device.transmit().ok_or(Error::Exhausted).unwrap(); + #[cfg(feature = "proto-sixlowpan")] + { + inner.dispatch_ip(tx_token, response, Some(out_fragments)).unwrap(); + } + #[cfg(not(feature = "proto-sixlowpan"))] + { + check!(inner.dispatch_ip(tx_token, response, None)); + } emitted_any = true; Ok(()) }; @@ -982,7 +1068,7 @@ where if let Some(pkt) = self.inner.igmp_report_packet(version, group) { // Send initial membership report let tx_token = self.device.transmit().ok_or(Error::Exhausted)?; - self.inner.dispatch_ip(tx_token, pkt)?; + self.inner.dispatch_ip(tx_token, pkt, None)?; } self.inner.igmp_report_state = IgmpReportState::Inactive; @@ -1006,7 +1092,7 @@ where if let Some(pkt) = self.inner.igmp_report_packet(version, addr) { // Send initial membership report let tx_token = self.device.transmit().ok_or(Error::Exhausted)?; - self.inner.dispatch_ip(tx_token, pkt)?; + self.inner.dispatch_ip(tx_token, pkt, None)?; } let next_timeout = (timeout + interval).max(self.inner.now); @@ -1142,7 +1228,10 @@ impl<'a> InterfaceInner<'a> { #[cfg(feature = "medium-ieee802154")] pan_id: Some(crate::wire::Ieee802154Pan(0xabcd)), #[cfg(feature = "medium-ieee802154")] - sequence_no: 0, + sequence_no: 1, + + #[cfg(feature = "proto-sixlowpan")] + tag: 1, #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))] hardware_addr: Some(crate::wire::HardwareAddress::Ethernet( @@ -1186,6 +1275,13 @@ impl<'a> InterfaceInner<'a> { no } + #[cfg(feature = "proto-sixlowpan")] + fn get_sixlowpan_fragment_tag(&mut self) -> u16 { + let tag = self.tag; + self.tag = self.tag.wrapping_add(1); + tag + } + /// Determine if the given `Ipv6Address` is the solicited node /// multicast address for a IPv6 addresses assigned to the interface. /// See [RFC 4291 § 2.7.1] for more details. @@ -1299,11 +1395,12 @@ impl<'a> InterfaceInner<'a> { } #[cfg(feature = "medium-ieee802154")] - fn process_ieee802154<'frame, T: AsRef<[u8]> + ?Sized>( + fn process_ieee802154<'output, 'payload: 'output, T: AsRef<[u8]> + ?Sized>( &mut self, sockets: &mut SocketSet, - sixlowpan_payload: &'frame T, - ) -> Option> { + sixlowpan_payload: &'payload T, + fragments: &'output mut SixlowpanFragmentsBuffer<'a>, + ) -> Option> { let ieee802154_frame = check!(Ieee802154Frame::new_checked(sixlowpan_payload)); let ieee802154_repr = check!(Ieee802154Repr::parse(&ieee802154_frame)); @@ -1319,25 +1416,130 @@ impl<'a> InterfaceInner<'a> { && ieee802154_repr.dst_pan_id != Some(Ieee802154Pan::BROADCAST) { net_debug!( - "dropping {:?} because not our PAN id (or not broadcast)", + "IEEE802.15.4: dropping {:?} because not our PAN id (or not broadcast)", ieee802154_repr ); return None; } match ieee802154_frame.payload() { - Some(payload) => self.process_sixlowpan(sockets, &ieee802154_repr, payload), + Some(payload) => self.process_sixlowpan(sockets, &ieee802154_repr, payload, fragments), None => None, } } #[cfg(feature = "proto-sixlowpan")] - fn process_sixlowpan<'frame, T: AsRef<[u8]> + ?Sized>( + fn process_sixlowpan<'output, 'payload: 'output, T: AsRef<[u8]> + ?Sized>( &mut self, sockets: &mut SocketSet, ieee802154_repr: &Ieee802154Repr, - payload: &'frame T, - ) -> Option> { + payload: &'payload T, + fragments: &'output mut SixlowpanFragmentsBuffer<'a>, + ) -> Option> { + check!(fragments.remove_when(|frag| Ok( + self.now - frag.start_time().unwrap() > Duration::from_secs(60) + ))); + fragments.remove_discarded(); + + let payload = match check!(SixlowpanPacket::dispatch(payload)) { + SixlowpanPacket::FragmentHeader => { + // We have a fragment header, which means we cannot process the 6LoWPAN packet, + // unless we have a complete one after processing this fragment. + let frag = check!(SixlowpanFragPacket::new_checked(payload)); + + // The key specifies to which 6LoWPAN fragment it belongs too. + // It is based on the link layer addresses, the tag and the size. + let key = frag.get_key(ieee802154_repr); + + // The offset of this fragment in increments of 8 octets. + let offset = frag.datagram_offset() as usize * 8; + + if frag.is_first_fragment() { + // The first fragment contains the total size of the IPv6 packet. + // However, we received a packet that is compressed following the 6LoWPAN + // standard. This means we need to convert the IPv6 packet size to a 6LoWPAN + // packet size. The packet size can be different because of first the + // compression of the IP header and when UDP is used (because the UDP header + // can also be compressed). Other headers are not compressed by 6LoWPAN. + + let iphc = check!(SixlowpanIphcPacket::new_checked(frag.payload())); + let iphc_repr = check!(SixlowpanIphcRepr::parse( + &iphc, + ieee802154_repr.src_addr, + ieee802154_repr.dst_addr, + )); + + // The uncompressed header size always starts with 40, since this is the size + // of a IPv6 header. + let mut uncompressed_header_size = 40; + let mut compressed_header_size = iphc.header_len(); + + // We need to check if we have an UDP packet, since this header can also be + // compressed by 6LoWPAN. We currently don't support extension headers yet. + match iphc_repr.next_header { + SixlowpanNextHeader::Compressed => { + match check!(SixlowpanNhcPacket::dispatch(iphc.payload())) { + SixlowpanNhcPacket::ExtHeader => { + net_debug!("6LoWPAN: extension headers not supported"); + return None; + } + SixlowpanNhcPacket::UdpHeader => { + let udp_packet = + check!(SixlowpanUdpNhcPacket::new_checked(iphc.payload())); + + uncompressed_header_size += 8; + compressed_header_size += + 1 + udp_packet.ports_size() + udp_packet.checksum_size(); + } + } + } + SixlowpanNextHeader::Uncompressed(_) => (), + } + + // We reserve a spot in the packet assembler set and add the required + // information to the packet assembler. + // This information is the total size of the packet when it is fully assmbled. + // We also pass the header size, since this is needed when other fragments + // (other than the first one) are added. + check!(check!(fragments.reserve_with_key(&key)).start( + frag.datagram_size() as usize - uncompressed_header_size + + compressed_header_size, + self.now, + -((uncompressed_header_size - compressed_header_size) as isize), + )); + } + + let frags = check!(fragments.get_packet_assembler_mut(&key)); + + // Check if 60 seconds have passed since the start of the first fragment. + if self.now - frags.start_time().unwrap() > Duration::from_secs(60) { + frags.mark_discarded(); + return None; + } + + net_trace!("6LoWPAN: received packet fragment"); + + // Add the fragment to the packet assembler. + match frags.add(frag.payload(), offset, self.now) { + Ok(true) => { + net_trace!("6LoWPAN: fragmented packet now complete"); + check!(fragments.get_assembled_packet(&key)) + } + Ok(false) => { + return None; + } + Err(Error::PacketAssemblerOverlap) => { + net_trace!("6LoWPAN: overlap in packet"); + frags.mark_discarded(); + return None; + } + Err(_) => return None, + } + } + SixlowpanPacket::IphcHeader => payload.as_ref(), + }; + + // At this point we should have a valid 6LoWPAN packet. // The first header needs to be an IPHC header. let iphc_packet = check!(SixlowpanIphcPacket::new_checked(payload)); let iphc_repr = check!(SixlowpanIphcRepr::parse( @@ -1352,10 +1554,9 @@ impl<'a> InterfaceInner<'a> { dst_addr: iphc_repr.dst_addr, hop_limit: iphc_repr.hop_limit, next_header: IpProtocol::Unknown(0), - payload_len: iphc_repr.buffer_len(), + payload_len: 40, }; - // Currently we assume the next header is a UDP, so we ignore everything else. match iphc_repr.next_header { SixlowpanNextHeader::Compressed => { match check!(SixlowpanNhcPacket::dispatch(payload)) { @@ -1370,9 +1571,10 @@ impl<'a> InterfaceInner<'a> { } #[cfg(feature = "socket-udp")] SixlowpanNhcPacket::UdpHeader => { - ipv6_repr.next_header = IpProtocol::Udp; - // Handle the UDP let udp_packet = check!(SixlowpanUdpNhcPacket::new_checked(payload)); + ipv6_repr.next_header = IpProtocol::Udp; + ipv6_repr.payload_len += 8 + udp_packet.payload().len(); + let udp_repr = check!(SixlowpanUdpNhcRepr::parse( &udp_packet, &iphc_repr.src_addr, @@ -1381,6 +1583,10 @@ impl<'a> InterfaceInner<'a> { // Look for UDP sockets that will accept the UDP packet. // If it does not accept the packet, then send an ICMP message. + // + // NOTE(thvdveld): this is currently the same code as in self.process_udp. + // However, we cannot use that one because the payload passed to it is a + // normal IPv6 UDP payload, which is not what we have here. for udp_socket in sockets .iter_mut() .filter_map(|i| udp::Socket::downcast(&mut i.socket)) @@ -1396,6 +1602,8 @@ impl<'a> InterfaceInner<'a> { } } + // When we are here then then there was no UDP socket that accepted the UDP + // message. let payload_len = icmp_reply_payload_len( payload.len(), IPV6_MIN_MTU, @@ -1415,8 +1623,14 @@ impl<'a> InterfaceInner<'a> { ipv6_repr.next_header = IpProtocol::Icmpv6; self.process_icmpv6(sockets, IpRepr::Ipv6(ipv6_repr), iphc_packet.payload()) } - _ => { - net_debug!("Headers other than ICMPv6 and compressed headers are currently not supported for 6LoWPAN"); + #[cfg(feature = "socket-tcp")] + IpProtocol::Tcp => { + ipv6_repr.next_header = nxt_hdr; + ipv6_repr.payload_len += payload.len(); + self.process_tcp(sockets, IpRepr::Ipv6(ipv6_repr), iphc_packet.payload()) + } + proto => { + net_debug!("6LoWPAN: {} currently not supported", proto); None } }, @@ -2046,7 +2260,7 @@ impl<'a> InterfaceInner<'a> { Icmpv4Repr::EchoReply { .. } => match self.ipv4_address() { Some(src_addr) => { let ipv4_reply_repr = Ipv4Repr { - src_addr: src_addr, + src_addr, dst_addr: ipv4_repr.src_addr, next_header: IpProtocol::Icmp, payload_len: icmp_repr.buffer_len(), @@ -2214,7 +2428,7 @@ impl<'a> InterfaceInner<'a> { arp_repr.emit(&mut packet); }) } - EthernetPacket::Ip(packet) => self.dispatch_ip(tx_token, packet), + EthernetPacket::Ip(packet) => self.dispatch_ip(tx_token, packet, None), } } @@ -2406,7 +2620,7 @@ impl<'a> InterfaceInner<'a> { solicit, )); - self.dispatch_ip(tx_token, packet)?; + self.dispatch_ip(tx_token, packet, None)?; } #[allow(unreachable_patterns)] @@ -2424,7 +2638,12 @@ impl<'a> InterfaceInner<'a> { } } - fn dispatch_ip(&mut self, tx_token: Tx, packet: IpPacket) -> Result<()> { + fn dispatch_ip( + &mut self, + tx_token: Tx, + packet: IpPacket, + _out_fragments: Option<&mut OutBuffer<'_>>, + ) -> Result<()> { let ip_repr = packet.ip_repr(); assert!(!ip_repr.dst_addr().is_unspecified()); @@ -2471,17 +2690,6 @@ impl<'a> InterfaceInner<'a> { Ok(()) }) } - #[cfg(feature = "medium-ieee802154")] - Medium::Ieee802154 => self.dispatch_ieee802154(tx_token, packet), - } - } - - #[cfg(feature = "medium-ieee802154")] - fn dispatch_ieee802154(&mut self, tx_token: Tx, packet: IpPacket) -> Result<()> { - let ip_repr = packet.ip_repr(); - assert!(!ip_repr.dst_addr().is_unspecified()); - - match self.caps.medium { #[cfg(feature = "medium-ieee802154")] Medium::Ieee802154 => { let (dst_hardware_addr, tx_token) = match self.lookup_hardware_addr( @@ -2493,134 +2701,246 @@ impl<'a> InterfaceInner<'a> { _ => unreachable!(), }; - let ack_request = dst_hardware_addr.is_unicast(); + self.dispatch_ieee802154( + dst_hardware_addr, + &ip_repr, + tx_token, + packet, + _out_fragments, + ) + } + } + } - let ack_request = match packet { - IpPacket::Icmpv6(_) => false, - _ => ack_request, - }; + #[cfg(feature = "medium-ieee802154")] + fn dispatch_ieee802154( + &mut self, + ll_dst_addr: Ieee802154Address, + ip_repr: &IpRepr, + tx_token: Tx, + packet: IpPacket, + out_fragments: Option<&mut OutBuffer<'_>>, + ) -> Result<()> { + // We first need to convert the IPv6 packet to a 6LoWPAN compressed packet. + // Whenever this packet is to big to fit in the IEEE802.15.4 packet, then we need to + // fragment it. + let ll_src_addr = self.hardware_addr.map_or_else( + || Err(Error::Malformed), + |addr| match addr { + HardwareAddress::Ieee802154(addr) => Ok(addr), + _ => Err(Error::Malformed), + }, + )?; - let mut tx_len = 0; + 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), + _ => return Err(Error::Unaddressable), + }; - let ll_src_addr = - if let Some(HardwareAddress::Ieee802154(addr)) = self.hardware_addr { - Some(addr) - } else { - return Err(Error::Malformed); - }; - - let ieee_repr = Ieee802154Repr { - frame_type: Ieee802154FrameType::Data, - security_enabled: false, - frame_pending: false, - ack_request, - sequence_number: Some(self.get_sequence_number()), - pan_id_compression: true, - frame_version: Ieee802154FrameVersion::Ieee802154_2003, - dst_pan_id: self.pan_id, - dst_addr: Some(dst_hardware_addr), - src_pan_id: self.pan_id, - src_addr: ll_src_addr, - }; - - 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)] - _ => return Err(Error::Unaddressable), - }; + // Create the IEEE802.15.4 header. + let mut ieee_repr = Ieee802154Repr { + frame_type: Ieee802154FrameType::Data, + security_enabled: false, + frame_pending: false, + ack_request: false, + sequence_number: Some(self.get_sequence_number()), + pan_id_compression: true, + frame_version: Ieee802154FrameVersion::Ieee802154_2003, + dst_pan_id: self.pan_id, + dst_addr: Some(ll_dst_addr), + src_pan_id: self.pan_id, + src_addr: Some(ll_src_addr), + }; + // Create the 6LoWPAN IPHC header. + let iphc_repr = SixlowpanIphcRepr { + src_addr, + ll_src_addr: Some(ll_src_addr), + dst_addr, + ll_dst_addr: Some(ll_dst_addr), + next_header: match &packet { + IpPacket::Icmpv6(_) => SixlowpanNextHeader::Uncompressed(IpProtocol::Icmpv6), + #[cfg(feature = "socket-tcp")] + IpPacket::Tcp(_) => SixlowpanNextHeader::Uncompressed(IpProtocol::Tcp), + #[cfg(feature = "socket-udp")] + IpPacket::Udp(_) => SixlowpanNextHeader::Compressed, #[allow(unreachable_patterns)] - let (next_header, hop_limit) = match &packet { - #[cfg(feature = "socket-udp")] - IpPacket::Udp(_) => (SixlowpanNextHeader::Compressed, 64), - IpPacket::Icmpv6((_, repr)) => ( - SixlowpanNextHeader::Uncompressed(IpProtocol::Icmpv6), - match repr { - Icmpv6Repr::Ndisc(_) => 255, - _ => 64, - }, - ), - _ => return Err(Error::Unrecognized), - }; + _ => return Err(Error::Unrecognized), + }, + hop_limit: ip_repr.hop_limit(), + ecn: None, + dscp: None, + flow_label: None, + }; - let iphc_repr = SixlowpanIphcRepr { - src_addr, - ll_src_addr, - dst_addr, - ll_dst_addr: Some(dst_hardware_addr), - next_header, - hop_limit, - ecn: None, - dscp: None, - flow_label: None, - }; + // We will first emit every packet into this buffer. + // When we emitted everything we know if we need fragmentation or not. + // This method is not ideal, but emitting UDP frames require that the full buffer is passed + // to the `emit` function. However, the buffer for IEEE802.15.4 is usually 125 bytes. + let mut buffer = [0; 4096]; + let mut total_size = 0; - tx_len += ieee_repr.buffer_len(); - tx_len += iphc_repr.buffer_len(); + let mut ieee_packet = Ieee802154Frame::new_unchecked(&mut buffer[..]); + ieee_repr.emit(&mut ieee_packet); + total_size += ieee_repr.buffer_len(); - #[allow(unreachable_patterns)] - match &packet { - #[cfg(feature = "socket-udp")] - IpPacket::Udp((_, udp_repr, payload)) => { - let udp_repr = SixlowpanUdpNhcRepr(*udp_repr); - tx_len += udp_repr.header_len() + payload.len(); - } - IpPacket::Icmpv6((_, icmp)) => { - tx_len += icmp.buffer_len(); - } - _ => return Err(Error::Unrecognized), - } + let mut iphc_packet = SixlowpanIphcPacket::new_unchecked(&mut buffer[total_size..]); + iphc_repr.emit(&mut iphc_packet); + total_size += iphc_repr.buffer_len(); - tx_token.consume(self.now, tx_len, |mut tx_buffer| { - // 1. Create the header of 802.15.4 - let mut ieee_packet = Ieee802154Frame::new_unchecked(&mut tx_buffer); - ieee_repr.emit(&mut ieee_packet); + let mut compressed_headers_len = iphc_repr.buffer_len(); - let mut start = ieee_repr.buffer_len(); + #[allow(unreachable_patterns)] + match packet { + #[cfg(feature = "socket-udp")] + IpPacket::Udp((_, udpv6_repr, payload)) => { + let udp_repr = SixlowpanUdpNhcRepr(udpv6_repr); + let mut udp_packet = SixlowpanUdpNhcPacket::new_unchecked( + &mut buffer[total_size..][..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), + ); - // 2. Create the header for 6LoWPAN IPHC - let mut iphc_packet = - SixlowpanIphcPacket::new_unchecked(&mut tx_buffer[start..tx_len]); - iphc_repr.emit(&mut iphc_packet); - start += iphc_repr.buffer_len(); + compressed_headers_len += udp_repr.header_len(); + total_size += udp_repr.header_len() + payload.len(); + } + #[cfg(feature = "socket-tcp")] + IpPacket::Tcp((_, tcp_repr)) => { + let mut tcp_packet = + TcpPacket::new_unchecked(&mut buffer[total_size..][..tcp_repr.buffer_len()]); + tcp_repr.emit( + &mut tcp_packet, + &iphc_repr.src_addr.into(), + &iphc_repr.dst_addr.into(), + &self.caps.checksum, + ); - #[allow(unreachable_patterns)] - match packet { - #[cfg(feature = "socket-udp")] - IpPacket::Udp((_, udp_repr, payload)) => { - // 3. Create the header for 6LoWPAN UDP - let mut udp_packet = - SixlowpanUdpNhcPacket::new_unchecked(&mut tx_buffer[start..tx_len]); + total_size += tcp_repr.buffer_len(); + } + #[cfg(feature = "proto-ipv6")] + IpPacket::Icmpv6((_, icmp_repr)) => { + let mut icmp_packet = Icmpv6Packet::new_unchecked( + &mut buffer[total_size..][..icmp_repr.buffer_len()], + ); + icmp_repr.emit( + &iphc_repr.src_addr.into(), + &iphc_repr.dst_addr.into(), + &mut icmp_packet, + &self.caps.checksum, + ); - SixlowpanUdpNhcRepr(udp_repr).emit( - &mut udp_packet, - &iphc_repr.src_addr, - &iphc_repr.dst_addr, - payload.len(), - |buf| buf.copy_from_slice(payload), - ); - } - #[cfg(feature = "proto-ipv6")] - IpPacket::Icmpv6((_, icmp_repr)) => { - // 3. Create the header for ICMPv6 - let mut icmp_packet = - Icmpv6Packet::new_unchecked(&mut tx_buffer[start..tx_len]); + total_size += icmp_repr.buffer_len(); + } + _ => return Err(Error::Unrecognized), + } - icmp_repr.emit( - &iphc_repr.src_addr.into(), - &iphc_repr.dst_addr.into(), - &mut icmp_packet, - &self.caps.checksum, - ); - } - _ => return Err(Error::Unrecognized), - } + if total_size > 125 { + // Fragmentation is required because the data does not fit into a IEEE802.15.4 packet. + // Everything has already been emitted in the buffer, thus we only need to insert the + // fragmentation headers in the correct places. + // + // The first fragment header is right after the first IEEE802.15.4 header. + // + // Since we can only use `tx_token` once we need to store other packets into the + // out_fragments buffer. + + let out_fragments = out_fragments.unwrap(); + + // The datagram size that we need to set in the first fragment header is equal to the + // IPv6 payload length + 40. + let datagram_size = (packet.ip_repr().payload_len() + 40) as u16; + // We generate a random tag. + let tag = self.get_sixlowpan_fragment_tag(); + + let ieee_len = ieee_repr.buffer_len(); + + // We calculate how much data we can send in the first fragment and the other + // fragments. The eventual IPv6 sizes of these fragments need to be a multiple of eight + // (except for the last fragment) since the offset field in the fragment is an offset + // in multiples of 8 octets. This is explained in [RFC 4944 § 5.3]. + // + // [RFC 4944 § 5.3]: https://datatracker.ietf.org/doc/html/rfc4944#section-5.3 + + let frag1 = SixlowpanFragRepr::FirstFragment { + size: datagram_size, + tag, + }; + let mut fragn = SixlowpanFragRepr::Fragment { + size: datagram_size, + tag, + offset: 0, + }; + + let frag1_size = ((125 - ieee_len - frag1.buffer_len() - compressed_headers_len) + & 0xffff_fff8) + + compressed_headers_len; + let fragn_size = (125 - ieee_len - fragn.buffer_len()) & 0xffff_fff8; + + tx_token.consume( + self.now, + ieee_len + frag1.buffer_len() + frag1_size, + |mut tx_buf| { + // Copy the IEEE header + tx_buf[..ieee_len].copy_from_slice(&buffer[..ieee_len]); + tx_buf = &mut tx_buf[ieee_len..]; + + // Add the first fragment header + let mut frag1_packet = SixlowpanFragPacket::new_unchecked(&mut tx_buf); + frag1.emit(&mut frag1_packet); + tx_buf = &mut tx_buf[frag1.buffer_len()..]; + + // Add the buffer part. + tx_buf[..frag1_size].copy_from_slice(&buffer[ieee_len..][..frag1_size]); Ok(()) - }) + }, + )?; + + let mut written = ieee_len + frag1_size; + + // We need to save the the rest of the packets into the `out_fragments` buffer. + while written < total_size { + out_fragments.enqueue_one_with::<'_, (), Error, _>(|(len, tx_buf)| { + let mut tx_buf = &mut tx_buf[..]; + + // Modify the sequence number of the IEEE header + ieee_repr.sequence_number = Some(self.get_sequence_number()); + let mut ieee_packet = Ieee802154Frame::new_unchecked(&mut tx_buf[..ieee_len]); + ieee_repr.emit(&mut ieee_packet); + tx_buf = &mut tx_buf[ieee_len..]; + + // Add the next fragment header + let datagram_offset = ((40 + written - ieee_len) / 8) as u8; + fragn.set_offset(datagram_offset); + let mut frag_packet = + SixlowpanFragPacket::new_unchecked(&mut tx_buf[..fragn.buffer_len()]); + fragn.emit(&mut frag_packet); + tx_buf = &mut tx_buf[fragn.buffer_len()..]; + + // Add the buffer part + let frag_size = (total_size - written).min(fragn_size); + tx_buf[..frag_size].copy_from_slice(&buffer[written..][..frag_size]); + written += frag_size; + + // Save the lenght of this packet. + *len = ieee_len + fragn.buffer_len() + frag_size; + Ok(()) + }).unwrap().unwrap(); } - #[allow(unreachable_patterns)] - _ => Err(Error::NotSupported), + + Ok(()) + } else { + // No fragmentation is needed thus we can copy everything over that already has been + // emitted in the buffer. + tx_token.consume(self.now, total_size, |tx_buffer| { + tx_buffer.copy_from_slice(&buffer[..total_size]); + Ok(()) + }) } } @@ -2732,10 +3052,20 @@ mod test { IpCidr::new(IpAddress::v6(0xfdbe, 0, 0, 0, 0, 0, 0, 1), 64), ]; + #[cfg(feature = "proto-sixlowpan")] + let iface_builder = InterfaceBuilder::new(device, vec![]) + .hardware_addr(EthernetAddress::default().into()) + .neighbor_cache(NeighborCache::new(BTreeMap::new())) + .sixlowpan_fragments_cache(PacketAssemblerSet::new(vec![], BTreeMap::new())) + .out_fragments_cache(RingBuffer::new(vec![])) + .ip_addrs(ip_addrs); + + #[cfg(not(feature = "proto-sixlowpan"))] let iface_builder = InterfaceBuilder::new(device, vec![]) .hardware_addr(EthernetAddress::default().into()) .neighbor_cache(NeighborCache::new(BTreeMap::new())) .ip_addrs(ip_addrs); + #[cfg(feature = "proto-igmp")] let iface_builder = iface_builder.ipv4_multicast_groups(BTreeMap::new()); iface_builder.finalize() @@ -2985,7 +3315,7 @@ mod test { payload_len: udp_repr.header_len() + UDP_PAYLOAD.len(), hop_limit: 64, }, - data: data, + data, }; let expected_repr = IpPacket::Icmpv4(( Ipv4Repr { @@ -3232,8 +3562,8 @@ mod test { }; #[cfg(feature = "proto-ipv6")] let ip_repr = Ipv6Repr { - src_addr: src_addr, - dst_addr: dst_addr, + src_addr, + dst_addr, next_header: IpProtocol::Udp, hop_limit: 64, payload_len: udp_repr.header_len() + MAX_PAYLOAD_LEN, @@ -3468,7 +3798,11 @@ mod test { } #[test] - #[cfg(all(feature = "medium-ethernet", feature = "proto-ipv4"))] + #[cfg(all( + feature = "medium-ethernet", + feature = "proto-ipv4", + not(feature = "medium-ieee802154") + ))] fn test_arp_flush_after_update_ip() { let mut iface = create_loopback_ethernet(); diff --git a/src/wire/sixlowpan.rs b/src/wire/sixlowpan.rs index d5219472..8dc8a235 100644 --- a/src/wire/sixlowpan.rs +++ b/src/wire/sixlowpan.rs @@ -2059,7 +2059,7 @@ mod test { #[test] fn sixlowpan_three_fragments() { - use crate::iface::{FragmentsCache, SixlowpanAssemblerInfo}; + use crate::iface::FragmentsCache; use crate::time::Instant; use crate::wire::ieee802154::Frame as Ieee802154Frame; use crate::wire::ieee802154::Repr as Ieee802154Repr; @@ -2106,8 +2106,8 @@ mod test { .unwrap() .start( frag.datagram_size() as usize - uncompressed + compressed, - SixlowpanAssemblerInfo::new(uncompressed - compressed), Instant::now(), + -((uncompressed - compressed) as isize), ) .unwrap(); frags_cache