Skip to content

Commit

Permalink
icmpv6: truncate the packet to MTU
Browse files Browse the repository at this point in the history
This follows RFC4443 section 2.4.
  • Loading branch information
chayleaf committed Jun 26, 2023
1 parent cfc17ba commit 03c53ba
Showing 1 changed file with 95 additions and 12 deletions.
107 changes: 95 additions & 12 deletions src/wire/icmpv6.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ use crate::wire::NdiscRepr;
#[cfg(feature = "proto-rpl")]
use crate::wire::RplRepr;
use crate::wire::{IpAddress, IpProtocol, Ipv6Packet, Ipv6Repr};
use crate::wire::{IPV6_HEADER_LEN, IPV6_MIN_MTU};

/// Error packets must not exceed min MTU
const MAX_ERROR_PACKET_LEN: usize = IPV6_MIN_MTU - IPV6_HEADER_LEN;

enum_with_unknown! {
/// Internet protocol control message type.
Expand Down Expand Up @@ -617,7 +621,14 @@ impl<'a> Repr<'a> {
where
T: AsRef<[u8]> + ?Sized,
{
let ip_packet = Ipv6Packet::new_checked(packet.payload())?;
// The packet must be truncated to fit the min MTU. In case the packet is potentially
// truncated, don't do length validation. It's fine because at this length we're
// guaranteed to have the original IPv6 header.
let ip_packet = if packet.buffer.as_ref().len() >= MAX_ERROR_PACKET_LEN {
Ipv6Packet::new_unchecked(packet.payload())
} else {
Ipv6Packet::new_checked(packet.payload())?
};

let payload = &packet.payload()[ip_packet.header_len()..];
if payload.len() < 8 {
Expand All @@ -627,7 +638,7 @@ impl<'a> Repr<'a> {
src_addr: ip_packet.src_addr(),
dst_addr: ip_packet.dst_addr(),
next_header: ip_packet.next_header(),
payload_len: payload.len(),
payload_len: ip_packet.payload_len().into(),
hop_limit: ip_packet.hop_limit(),
};
Ok((payload, repr))
Expand Down Expand Up @@ -696,9 +707,10 @@ impl<'a> Repr<'a> {
&Repr::DstUnreachable { header, data, .. }
| &Repr::PktTooBig { header, data, .. }
| &Repr::TimeExceeded { header, data, .. }
| &Repr::ParamProblem { header, data, .. } => {
field::UNUSED.end + header.buffer_len() + data.len()
}
| &Repr::ParamProblem { header, data, .. } => cmp::min(
field::UNUSED.end + header.buffer_len() + data.len(),
MAX_ERROR_PACKET_LEN,
),
&Repr::EchoRequest { data, .. } | &Repr::EchoReply { data, .. } => {
field::ECHO_SEQNO.end + data.len()
}
Expand All @@ -721,11 +733,21 @@ impl<'a> Repr<'a> {
) where
T: AsRef<[u8]> + AsMut<[u8]> + ?Sized,
{
fn emit_contained_packet(buffer: &mut [u8], header: Ipv6Repr, data: &[u8]) {
let mut ip_packet = Ipv6Packet::new_unchecked(buffer);
fn emit_contained_packet<T>(packet: &mut Packet<&mut T>, header: Ipv6Repr, data: &[u8])
where
T: AsRef<[u8]> + AsMut<[u8]> + ?Sized,
{
let icmp_header_len = packet.header_len();
let mut ip_packet = Ipv6Packet::new_unchecked(packet.payload_mut());
header.emit(&mut ip_packet);
let payload = &mut ip_packet.into_inner()[header.buffer_len()..];
payload.copy_from_slice(data);
// FIXME: this should rather be checked at IPv6 level, as we can't know in advance how
// much space we have for the packet due to IPv6 options and etc
let payload_len = cmp::min(
data.len(),
MAX_ERROR_PACKET_LEN - icmp_header_len - IPV6_HEADER_LEN,
);
payload[..payload_len].copy_from_slice(&data[..payload_len]);
}

match *self {
Expand All @@ -737,15 +759,15 @@ impl<'a> Repr<'a> {
packet.set_msg_type(Message::DstUnreachable);
packet.set_msg_code(reason.into());

emit_contained_packet(packet.payload_mut(), header, data);
emit_contained_packet(packet, header, data);
}

Repr::PktTooBig { mtu, header, data } => {
packet.set_msg_type(Message::PktTooBig);
packet.set_msg_code(0);
packet.set_pkt_too_big_mtu(mtu);

emit_contained_packet(packet.payload_mut(), header, data);
emit_contained_packet(packet, header, data);
}

Repr::TimeExceeded {
Expand All @@ -756,7 +778,7 @@ impl<'a> Repr<'a> {
packet.set_msg_type(Message::TimeExceeded);
packet.set_msg_code(reason.into());

emit_contained_packet(packet.payload_mut(), header, data);
emit_contained_packet(packet, header, data);
}

Repr::ParamProblem {
Expand All @@ -769,7 +791,7 @@ impl<'a> Repr<'a> {
packet.set_msg_code(reason.into());
packet.set_param_problem_ptr(pointer);

emit_contained_packet(packet.payload_mut(), header, data);
emit_contained_packet(packet, header, data);
}

Repr::EchoRequest {
Expand Down Expand Up @@ -981,4 +1003,65 @@ mod test {
);
assert_eq!(&*packet.into_inner(), &PKT_TOO_BIG_BYTES[..]);
}

#[test]
fn test_buffer_length_is_truncated_to_mtu() {
let repr = Repr::PktTooBig {
mtu: 1280,
header: Ipv6Repr {
src_addr: Default::default(),
dst_addr: Default::default(),
next_header: IpProtocol::Tcp,
hop_limit: 64,
payload_len: 1280,
},
data: &vec![0; 9999],
};
assert_eq!(repr.buffer_len(), 1280 - IPV6_HEADER_LEN);
}

#[test]
fn test_mtu_truncated_payload_roundtrip() {
let ip_packet_repr = Ipv6Repr {
src_addr: Default::default(),
dst_addr: Default::default(),
next_header: IpProtocol::Tcp,
hop_limit: 64,
payload_len: IPV6_MIN_MTU - IPV6_HEADER_LEN,
};
let mut ip_packet = Ipv6Packet::new_unchecked(vec![0; IPV6_MIN_MTU]);
ip_packet_repr.emit(&mut ip_packet);

let repr1 = Repr::PktTooBig {
mtu: IPV6_MIN_MTU as u32,
header: ip_packet_repr,
data: &ip_packet.as_ref()[IPV6_HEADER_LEN..],
};
// this is needed to make sure roundtrip gives the same value
// it is not needed for ensuring the correct bytes get emitted
let repr1 = Repr::PktTooBig {
mtu: IPV6_MIN_MTU as u32,
header: ip_packet_repr,
data: &ip_packet.as_ref()[IPV6_HEADER_LEN..repr1.buffer_len() - field::UNUSED.end],
};
let mut data = vec![0; MAX_ERROR_PACKET_LEN];
let mut packet = Packet::new_unchecked(&mut data);
repr1.emit(
&MOCK_IP_ADDR_1,
&MOCK_IP_ADDR_2,
&mut packet,
&ChecksumCapabilities::default(),
);

let packet = Packet::new_unchecked(&data);
let repr2 = Repr::parse(
&MOCK_IP_ADDR_1,
&MOCK_IP_ADDR_2,
&packet,
&ChecksumCapabilities::default(),
)
.unwrap();

assert_eq!(repr1, repr2);
}
}

0 comments on commit 03c53ba

Please sign in to comment.