Skip to content
10 changes: 9 additions & 1 deletion benches/bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

mod wire {
use smoltcp::phy::ChecksumCapabilities;
use smoltcp::wire::{IpAddress, IpProtocol};
use smoltcp::wire::{IPV4_HEADER_LEN, IPV4_MAX_OPTIONS_SIZE, IpAddress, IpProtocol};
#[cfg(feature = "proto-ipv4")]
use smoltcp::wire::{Ipv4Address, Ipv4Packet, Ipv4Repr};
#[cfg(feature = "proto-ipv6")]
Expand Down Expand Up @@ -83,8 +83,16 @@ mod wire {
src_addr: Ipv4Address::new(192, 168, 1, 1),
dst_addr: Ipv4Address::new(192, 168, 1, 2),
next_header: IpProtocol::Tcp,
header_len: IPV4_HEADER_LEN,
payload_len: 100,
dscp: 0,
ecn: 0,
ident: 0,
dont_frag: true,
more_frags: false,
frag_offset: 0,
hop_limit: 64,
options: [0u8; IPV4_MAX_OPTIONS_SIZE],
};
let mut bytes = vec![0xa5; repr.buffer_len()];

Expand Down
317 changes: 317 additions & 0 deletions src/iface/fragmentation.rs

Large diffs are not rendered by default.

53 changes: 45 additions & 8 deletions src/iface/interface/ipv4.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use super::*;
use crate::wire::ipv4::MAX_OPTIONS_SIZE;

impl Interface {
/// Process fragments that still need to be sent for IPv4 packets.
Expand Down Expand Up @@ -100,13 +101,21 @@ impl InterfaceInner {
ipv4_packet: &Ipv4Packet<&'a [u8]>,
frag: &'a mut FragmentsBuffer,
) -> Option<Packet<'a>> {
let ipv4_repr = check!(Ipv4Repr::parse(ipv4_packet, &self.caps.checksum));
let mut ipv4_repr = check!(Ipv4Repr::parse(ipv4_packet, &self.caps.checksum));
if !self.is_unicast_v4(ipv4_repr.src_addr) && !ipv4_repr.src_addr.is_unspecified() {
// Discard packets with non-unicast source addresses but allow unspecified
net_debug!("non-unicast or unspecified source address");
return None;
}

// If this is the first fragment, capture the options.
#[cfg(feature = "proto-ipv4-fragmentation")]
if ipv4_packet.frag_offset() == 0 && ipv4_packet.has_options() {
frag.options_buffer[..ipv4_repr.options_len()]
.copy_from_slice(&ipv4_repr.options[..ipv4_repr.options_len()]);
frag.options_len = ipv4_repr.options_len()
}

#[cfg(feature = "proto-ipv4-fragmentation")]
let ip_payload = {
if ipv4_packet.more_frags() || ipv4_packet.frag_offset() != 0 {
Expand All @@ -133,10 +142,12 @@ impl InterfaceInner {
return None;
}

// NOTE: according to the standard, the total length needs to be
// recomputed, as well as the checksum. However, we don't really use
// the IPv4 header after the packet is reassembled.
f.assemble()?
// Returns early if assembly is incomplete.
let payload = f.assemble()?;

// Update the payload length, so that the raw sockets get the correct value.
ipv4_repr.payload_len = payload.len();
payload
} else {
ipv4_packet.payload()
}
Expand All @@ -145,6 +156,16 @@ impl InterfaceInner {
#[cfg(not(feature = "proto-ipv4-fragmentation"))]
let ip_payload = ipv4_packet.payload();

#[cfg(feature = "proto-ipv4-fragmentation")]
// The first fragment will by definition have all options. The length of the options it
// contains will be greater than or equal to size of the options in the other fragments.
// Therefore, this is guaranteed to overwrite whatever was captured when the repr object
// parsed the current packet.
if let Err(e) = ipv4_repr.set_options(&frag.options_buffer[..frag.options_len]) {
net_debug!("fragmentation assembler options error: {:?}", e);
return None;
}

let ip_repr = IpRepr::Ipv4(ipv4_repr);

#[cfg(feature = "socket-raw")]
Expand Down Expand Up @@ -386,8 +407,16 @@ impl InterfaceInner {
src_addr: ipv4_repr.dst_addr,
dst_addr: ipv4_repr.src_addr,
next_header: IpProtocol::Icmp,
header_len: IPV4_HEADER_LEN,
payload_len: icmp_repr.buffer_len(),
dscp: 0,
ecn: 0,
ident: 0,
dont_frag: true,
more_frags: false,
frag_offset: 0,
hop_limit: 64,
options: [0u8; MAX_OPTIONS_SIZE],
};
Some(Packet::new_ipv4(
ipv4_reply_repr,
Expand All @@ -402,8 +431,16 @@ impl InterfaceInner {
src_addr,
dst_addr: ipv4_repr.src_addr,
next_header: IpProtocol::Icmp,
header_len: IPV4_HEADER_LEN,
payload_len: icmp_repr.buffer_len(),
dscp: 0,
ecn: 0,
ident: 0,
dont_frag: true,
more_frags: false,
frag_offset: 0,
hop_limit: 64,
options: [0u8; MAX_OPTIONS_SIZE],
};
Some(Packet::new_ipv4(
ipv4_reply_repr,
Expand All @@ -423,9 +460,9 @@ impl InterfaceInner {
pub(super) fn dispatch_ipv4_frag<Tx: TxToken>(&mut self, tx_token: Tx, frag: &mut Fragmenter) {
let caps = self.caps.clone();

let mtu_max = self.ip_mtu();
let ip_len = (frag.packet_len - frag.sent_bytes + frag.ipv4.repr.buffer_len()).min(mtu_max);
let payload_len = ip_len - frag.ipv4.repr.buffer_len();
let max_fragment_size = caps.max_ipv4_fragment_size(frag.ipv4.repr.buffer_len());
let payload_len = (frag.packet_len - frag.sent_bytes).min(max_fragment_size);
let ip_len = payload_len + frag.ipv4.repr.buffer_len();

let more_frags = (frag.packet_len - frag.sent_bytes) != payload_len;
frag.ipv4.repr.payload_len = payload_len;
Expand Down
38 changes: 31 additions & 7 deletions src/iface/interface/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ macro_rules! check {
}
};
}
#[cfg(feature = "proto-ipv4")]
use crate::wire::ipv4::MAX_OPTIONS_SIZE;
use check;

/// Result returned by [`Interface::poll`].
Expand Down Expand Up @@ -239,6 +241,12 @@ impl Interface {
assembler: PacketAssemblerSet::new(),
#[cfg(feature = "_proto-fragmentation")]
reassembly_timeout: Duration::from_secs(60),

#[cfg(feature = "proto-ipv4-fragmentation")]
options_buffer: [0u8; MAX_OPTIONS_SIZE],

#[cfg(feature = "proto-ipv4-fragmentation")]
options_len: 0,
},
fragmenter: Fragmenter::new(),
inner: InterfaceInner {
Expand Down Expand Up @@ -1223,10 +1231,16 @@ impl InterfaceInner {
net_debug!("start fragmentation");

// Calculate how much we will send now (including the Ethernet header).
let tx_len = self.caps.max_transmission_unit;

let ip_header_len = repr.buffer_len();
let first_frag_ip_len = self.caps.ip_mtu();
let first_frag_data_len =
self.caps.max_ipv4_fragment_size(repr.buffer_len());
let first_frag_ip_len = first_frag_data_len + ip_header_len;
let mut tx_len = first_frag_ip_len;
#[cfg(feature = "medium-ethernet")]
if matches!(caps.medium, Medium::Ethernet) {
tx_len += EthernetFrame::<&[u8]>::header_len();
}

if frag.buffer.len() < total_ip_len {
net_debug!(
Expand All @@ -1248,15 +1262,23 @@ impl InterfaceInner {
// Save the IP header for other fragments.
frag.ipv4.repr = *repr;

// Save how much bytes we will send now.
frag.sent_bytes = first_frag_ip_len;

// Modify the IP header
repr.payload_len = first_frag_ip_len - repr.buffer_len();
repr.payload_len = first_frag_data_len;

// Save the number of bytes we will send now.
frag.sent_bytes = first_frag_ip_len;

// Emit the IP header to the buffer.
emit_ip(&ip_repr, &mut frag.buffer);

// Verify that we can filter the options for the subsequent packets.
if frag.ipv4.filter_options().is_err() {
net_debug!(
"Could not fragment packet because options cannot be filtered. Dropping."
);
return Ok(());
};

let mut ipv4_packet = Ipv4Packet::new_unchecked(&mut frag.buffer[..]);
frag.ipv4.ident = ipv4_id;
ipv4_packet.set_ident(ipv4_id);
Expand Down Expand Up @@ -1336,12 +1358,14 @@ impl InterfaceInner {

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
enum DispatchError {
pub enum DispatchError {
/// No route to dispatch this packet. Retrying won't help unless
/// configuration is changed.
NoRoute,
/// We do have a route to dispatch this packet, but we haven't discovered
/// the neighbor for it yet. Discovery has been initiated, dispatch
/// should be retried later.
NeighborPending,
/// The packet must be fragmented but there was a parse error.
CannotFragment,
}
18 changes: 18 additions & 0 deletions src/iface/interface/multicast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ use super::{Interface, InterfaceInner};
use super::{IpPayload, Packet, check};
use crate::config::{IFACE_MAX_ADDR_COUNT, IFACE_MAX_MULTICAST_GROUP_COUNT};
use crate::phy::{Device, PacketMeta};
#[cfg(feature = "proto-ipv4")]
use crate::wire::ipv4::MAX_OPTIONS_SIZE;
use crate::wire::*;

/// Error type for `join_multicast_group`, `leave_multicast_group`.
Expand Down Expand Up @@ -480,7 +482,15 @@ impl InterfaceInner {
dst_addr: group_addr,
next_header: IpProtocol::Igmp,
payload_len: igmp_repr.buffer_len(),
header_len: IPV4_HEADER_LEN,
dscp: 0,
ecn: 0,
ident: 0,
dont_frag: false,
more_frags: false,
frag_offset: 0,
hop_limit: 1,
options: [0u8; MAX_OPTIONS_SIZE],
// [#183](https://github.com/m-labs/smoltcp/issues/183).
},
IpPayload::Igmp(igmp_repr),
Expand All @@ -498,7 +508,15 @@ impl InterfaceInner {
dst_addr: IPV4_MULTICAST_ALL_ROUTERS,
next_header: IpProtocol::Igmp,
payload_len: igmp_repr.buffer_len(),
header_len: IPV4_HEADER_LEN,
dscp: 0,
ecn: 0,
ident: 0,
dont_frag: false,
more_frags: false,
frag_offset: 0,
hop_limit: 1,
options: [0u8; MAX_OPTIONS_SIZE],
},
IpPayload::Igmp(igmp_repr),
)
Expand Down
Loading