瀏覽代碼

Merge pull request #807 from chayleaf/rfc4443-mtu-trunc

icmpv6: truncate the packet to MTU
Thibaut Vandervelden 1 年之前
父節點
當前提交
aa5d887e23
共有 1 個文件被更改,包括 95 次插入15 次删除
  1. 95 15
      src/wire/icmpv6.rs

+ 95 - 15
src/wire/icmpv6.rs

@@ -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.
@@ -617,17 +621,21 @@ 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. Since we don't know the offset of
+            // the ICMPv6 header in the L2 frame, we should only check whether the payload's IPv6
+            // header is present, the rest is allowed to be truncated.
+            let ip_packet = if packet.buffer.as_ref().len() >= IPV6_HEADER_LEN {
+                Ipv6Packet::new_unchecked(packet.payload())
+            } else {
+                return Err(Error);
+            };
 
             let payload = &packet.payload()[ip_packet.header_len()..];
-            if payload.len() < 8 {
-                return Err(Error);
-            }
             let repr = Ipv6Repr {
                 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))
@@ -696,9 +704,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()
             }
@@ -721,11 +730,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 {
@@ -737,7 +756,7 @@ 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 } => {
@@ -745,7 +764,7 @@ impl<'a> Repr<'a> {
                 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 {
@@ -756,7 +775,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 {
@@ -769,7 +788,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 {
@@ -981,4 +1000,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);
+    }
 }