Skip to content

Commit 0f88245

Browse files
committed
icmpv6: truncate the packet to MTU
This follows RFC4443 section 2.4.
1 parent cfc17ba commit 0f88245

File tree

1 file changed

+95
-15
lines changed

1 file changed

+95
-15
lines changed

src/wire/icmpv6.rs

Lines changed: 95 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ use crate::wire::NdiscRepr;
1010
#[cfg(feature = "proto-rpl")]
1111
use crate::wire::RplRepr;
1212
use crate::wire::{IpAddress, IpProtocol, Ipv6Packet, Ipv6Repr};
13+
use crate::wire::{IPV6_HEADER_LEN, IPV6_MIN_MTU};
14+
15+
/// Error packets must not exceed min MTU
16+
const MAX_ERROR_PACKET_LEN: usize = IPV6_MIN_MTU - IPV6_HEADER_LEN;
1317

1418
enum_with_unknown! {
1519
/// Internet protocol control message type.
@@ -617,17 +621,21 @@ impl<'a> Repr<'a> {
617621
where
618622
T: AsRef<[u8]> + ?Sized,
619623
{
620-
let ip_packet = Ipv6Packet::new_checked(packet.payload())?;
624+
// The packet must be truncated to fit the min MTU. Since we don't know the offset of
625+
// the ICMPv6 header in the L2 frame, we should only check whether the payload's IPv6
626+
// header is present, the rest is allowed to be truncated.
627+
let ip_packet = if packet.buffer.as_ref().len() >= IPV6_HEADER_LEN {
628+
Ipv6Packet::new_unchecked(packet.payload())
629+
} else {
630+
return Err(Error);
631+
};
621632

622633
let payload = &packet.payload()[ip_packet.header_len()..];
623-
if payload.len() < 8 {
624-
return Err(Error);
625-
}
626634
let repr = Ipv6Repr {
627635
src_addr: ip_packet.src_addr(),
628636
dst_addr: ip_packet.dst_addr(),
629637
next_header: ip_packet.next_header(),
630-
payload_len: payload.len(),
638+
payload_len: ip_packet.payload_len().into(),
631639
hop_limit: ip_packet.hop_limit(),
632640
};
633641
Ok((payload, repr))
@@ -696,9 +704,10 @@ impl<'a> Repr<'a> {
696704
&Repr::DstUnreachable { header, data, .. }
697705
| &Repr::PktTooBig { header, data, .. }
698706
| &Repr::TimeExceeded { header, data, .. }
699-
| &Repr::ParamProblem { header, data, .. } => {
700-
field::UNUSED.end + header.buffer_len() + data.len()
701-
}
707+
| &Repr::ParamProblem { header, data, .. } => cmp::min(
708+
field::UNUSED.end + header.buffer_len() + data.len(),
709+
MAX_ERROR_PACKET_LEN,
710+
),
702711
&Repr::EchoRequest { data, .. } | &Repr::EchoReply { data, .. } => {
703712
field::ECHO_SEQNO.end + data.len()
704713
}
@@ -721,11 +730,21 @@ impl<'a> Repr<'a> {
721730
) where
722731
T: AsRef<[u8]> + AsMut<[u8]> + ?Sized,
723732
{
724-
fn emit_contained_packet(buffer: &mut [u8], header: Ipv6Repr, data: &[u8]) {
725-
let mut ip_packet = Ipv6Packet::new_unchecked(buffer);
733+
fn emit_contained_packet<T>(packet: &mut Packet<&mut T>, header: Ipv6Repr, data: &[u8])
734+
where
735+
T: AsRef<[u8]> + AsMut<[u8]> + ?Sized,
736+
{
737+
let icmp_header_len = packet.header_len();
738+
let mut ip_packet = Ipv6Packet::new_unchecked(packet.payload_mut());
726739
header.emit(&mut ip_packet);
727740
let payload = &mut ip_packet.into_inner()[header.buffer_len()..];
728-
payload.copy_from_slice(data);
741+
// FIXME: this should rather be checked at IPv6 level, as we can't know in advance how
742+
// much space we have for the packet due to IPv6 options and etc
743+
let payload_len = cmp::min(
744+
data.len(),
745+
MAX_ERROR_PACKET_LEN - icmp_header_len - IPV6_HEADER_LEN,
746+
);
747+
payload[..payload_len].copy_from_slice(&data[..payload_len]);
729748
}
730749

731750
match *self {
@@ -737,15 +756,15 @@ impl<'a> Repr<'a> {
737756
packet.set_msg_type(Message::DstUnreachable);
738757
packet.set_msg_code(reason.into());
739758

740-
emit_contained_packet(packet.payload_mut(), header, data);
759+
emit_contained_packet(packet, header, data);
741760
}
742761

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

748-
emit_contained_packet(packet.payload_mut(), header, data);
767+
emit_contained_packet(packet, header, data);
749768
}
750769

751770
Repr::TimeExceeded {
@@ -756,7 +775,7 @@ impl<'a> Repr<'a> {
756775
packet.set_msg_type(Message::TimeExceeded);
757776
packet.set_msg_code(reason.into());
758777

759-
emit_contained_packet(packet.payload_mut(), header, data);
778+
emit_contained_packet(packet, header, data);
760779
}
761780

762781
Repr::ParamProblem {
@@ -769,7 +788,7 @@ impl<'a> Repr<'a> {
769788
packet.set_msg_code(reason.into());
770789
packet.set_param_problem_ptr(pointer);
771790

772-
emit_contained_packet(packet.payload_mut(), header, data);
791+
emit_contained_packet(packet, header, data);
773792
}
774793

775794
Repr::EchoRequest {
@@ -981,4 +1000,65 @@ mod test {
9811000
);
9821001
assert_eq!(&*packet.into_inner(), &PKT_TOO_BIG_BYTES[..]);
9831002
}
1003+
1004+
#[test]
1005+
fn test_buffer_length_is_truncated_to_mtu() {
1006+
let repr = Repr::PktTooBig {
1007+
mtu: 1280,
1008+
header: Ipv6Repr {
1009+
src_addr: Default::default(),
1010+
dst_addr: Default::default(),
1011+
next_header: IpProtocol::Tcp,
1012+
hop_limit: 64,
1013+
payload_len: 1280,
1014+
},
1015+
data: &vec![0; 9999],
1016+
};
1017+
assert_eq!(repr.buffer_len(), 1280 - IPV6_HEADER_LEN);
1018+
}
1019+
1020+
#[test]
1021+
fn test_mtu_truncated_payload_roundtrip() {
1022+
let ip_packet_repr = Ipv6Repr {
1023+
src_addr: Default::default(),
1024+
dst_addr: Default::default(),
1025+
next_header: IpProtocol::Tcp,
1026+
hop_limit: 64,
1027+
payload_len: IPV6_MIN_MTU - IPV6_HEADER_LEN,
1028+
};
1029+
let mut ip_packet = Ipv6Packet::new_unchecked(vec![0; IPV6_MIN_MTU]);
1030+
ip_packet_repr.emit(&mut ip_packet);
1031+
1032+
let repr1 = Repr::PktTooBig {
1033+
mtu: IPV6_MIN_MTU as u32,
1034+
header: ip_packet_repr,
1035+
data: &ip_packet.as_ref()[IPV6_HEADER_LEN..],
1036+
};
1037+
// this is needed to make sure roundtrip gives the same value
1038+
// it is not needed for ensuring the correct bytes get emitted
1039+
let repr1 = Repr::PktTooBig {
1040+
mtu: IPV6_MIN_MTU as u32,
1041+
header: ip_packet_repr,
1042+
data: &ip_packet.as_ref()[IPV6_HEADER_LEN..repr1.buffer_len() - field::UNUSED.end],
1043+
};
1044+
let mut data = vec![0; MAX_ERROR_PACKET_LEN];
1045+
let mut packet = Packet::new_unchecked(&mut data);
1046+
repr1.emit(
1047+
&MOCK_IP_ADDR_1,
1048+
&MOCK_IP_ADDR_2,
1049+
&mut packet,
1050+
&ChecksumCapabilities::default(),
1051+
);
1052+
1053+
let packet = Packet::new_unchecked(&data);
1054+
let repr2 = Repr::parse(
1055+
&MOCK_IP_ADDR_1,
1056+
&MOCK_IP_ADDR_2,
1057+
&packet,
1058+
&ChecksumCapabilities::default(),
1059+
)
1060+
.unwrap();
1061+
1062+
assert_eq!(repr1, repr2);
1063+
}
9841064
}

0 commit comments

Comments
 (0)