Skip to content

Commit 03c53ba

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

File tree

1 file changed

+95
-12
lines changed

1 file changed

+95
-12
lines changed

src/wire/icmpv6.rs

Lines changed: 95 additions & 12 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,7 +621,14 @@ 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. In case the packet is potentially
625+
// truncated, don't do length validation. It's fine because at this length we're
626+
// guaranteed to have the original IPv6 header.
627+
let ip_packet = if packet.buffer.as_ref().len() >= MAX_ERROR_PACKET_LEN {
628+
Ipv6Packet::new_unchecked(packet.payload())
629+
} else {
630+
Ipv6Packet::new_checked(packet.payload())?
631+
};
621632

622633
let payload = &packet.payload()[ip_packet.header_len()..];
623634
if payload.len() < 8 {
@@ -627,7 +638,7 @@ impl<'a> Repr<'a> {
627638
src_addr: ip_packet.src_addr(),
628639
dst_addr: ip_packet.dst_addr(),
629640
next_header: ip_packet.next_header(),
630-
payload_len: payload.len(),
641+
payload_len: ip_packet.payload_len().into(),
631642
hop_limit: ip_packet.hop_limit(),
632643
};
633644
Ok((payload, repr))
@@ -696,9 +707,10 @@ impl<'a> Repr<'a> {
696707
&Repr::DstUnreachable { header, data, .. }
697708
| &Repr::PktTooBig { header, data, .. }
698709
| &Repr::TimeExceeded { header, data, .. }
699-
| &Repr::ParamProblem { header, data, .. } => {
700-
field::UNUSED.end + header.buffer_len() + data.len()
701-
}
710+
| &Repr::ParamProblem { header, data, .. } => cmp::min(
711+
field::UNUSED.end + header.buffer_len() + data.len(),
712+
MAX_ERROR_PACKET_LEN,
713+
),
702714
&Repr::EchoRequest { data, .. } | &Repr::EchoReply { data, .. } => {
703715
field::ECHO_SEQNO.end + data.len()
704716
}
@@ -721,11 +733,21 @@ impl<'a> Repr<'a> {
721733
) where
722734
T: AsRef<[u8]> + AsMut<[u8]> + ?Sized,
723735
{
724-
fn emit_contained_packet(buffer: &mut [u8], header: Ipv6Repr, data: &[u8]) {
725-
let mut ip_packet = Ipv6Packet::new_unchecked(buffer);
736+
fn emit_contained_packet<T>(packet: &mut Packet<&mut T>, header: Ipv6Repr, data: &[u8])
737+
where
738+
T: AsRef<[u8]> + AsMut<[u8]> + ?Sized,
739+
{
740+
let icmp_header_len = packet.header_len();
741+
let mut ip_packet = Ipv6Packet::new_unchecked(packet.payload_mut());
726742
header.emit(&mut ip_packet);
727743
let payload = &mut ip_packet.into_inner()[header.buffer_len()..];
728-
payload.copy_from_slice(data);
744+
// FIXME: this should rather be checked at IPv6 level, as we can't know in advance how
745+
// much space we have for the packet due to IPv6 options and etc
746+
let payload_len = cmp::min(
747+
data.len(),
748+
MAX_ERROR_PACKET_LEN - icmp_header_len - IPV6_HEADER_LEN,
749+
);
750+
payload[..payload_len].copy_from_slice(&data[..payload_len]);
729751
}
730752

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

740-
emit_contained_packet(packet.payload_mut(), header, data);
762+
emit_contained_packet(packet, header, data);
741763
}
742764

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

748-
emit_contained_packet(packet.payload_mut(), header, data);
770+
emit_contained_packet(packet, header, data);
749771
}
750772

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

759-
emit_contained_packet(packet.payload_mut(), header, data);
781+
emit_contained_packet(packet, header, data);
760782
}
761783

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

772-
emit_contained_packet(packet.payload_mut(), header, data);
794+
emit_contained_packet(packet, header, data);
773795
}
774796

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

0 commit comments

Comments
 (0)