1 use crate::{err::ValueTooBigError, *};
2 use arrayvec::ArrayVec;
3 
4 /// The statically sized data at the start of an ICMPv6 packet (at least the first 8 bytes of an ICMPv6 packet).
5 #[derive(Clone, Debug, PartialEq, Eq)]
6 pub struct Icmpv6Header {
7     /// Type & type specific values & code.
8     pub icmp_type: Icmpv6Type,
9     /// Checksum in the ICMPv6 header.
10     pub checksum: u16,
11 }
12 
13 impl Icmpv6Header {
14     /// Minimum number of bytes an ICMP header needs to have.
15     ///
16     /// Note that minimum size can be larger depending on
17     /// the type and code.
18     pub const MIN_LEN: usize = 8;
19 
20     /// Deprecated, use [`Icmpv6Header::MIN_LEN`] instead.
21     #[deprecated(since = "0.14.0", note = "Please use Icmpv6Header::MIN_LEN instead")]
22     pub const MIN_SERIALIZED_SIZE: usize = Icmpv6Header::MIN_LEN;
23 
24     /// Maximum number of bytes/octets an Icmpv6Header takes up
25     /// in serialized form.
26     ///
27     /// Currently this number is determined by the biggest
28     /// planned ICMPv6 header type, which is currently the
29     /// "Neighbor Discovery Protocol" "Redirect" message.
30     pub const MAX_LEN: usize = 8 + 16 + 16;
31 
32     /// Deprecated, use [`Icmpv6Header::MAX_LEN`] instead.
33     #[deprecated(since = "0.14.0", note = "Please use Icmpv6Header::MAX_LEN instead")]
34     pub const MAX_SERIALIZED_SIZE: usize = Icmpv6Header::MAX_LEN;
35 
36     /// Setups a new header with the checksum being set to 0.
37     #[inline]
new(icmp_type: Icmpv6Type) -> Icmpv6Header38     pub fn new(icmp_type: Icmpv6Type) -> Icmpv6Header {
39         Icmpv6Header {
40             icmp_type,
41             checksum: 0, // will be filled in later
42         }
43     }
44 
45     /// Creates a [`Icmpv6Header`] with a checksum calculated based
46     /// on the given payload & ip addresses from the IPv6 header.
with_checksum( icmp_type: Icmpv6Type, source_ip: [u8; 16], destination_ip: [u8; 16], payload: &[u8], ) -> Result<Icmpv6Header, ValueTooBigError<usize>>47     pub fn with_checksum(
48         icmp_type: Icmpv6Type,
49         source_ip: [u8; 16],
50         destination_ip: [u8; 16],
51         payload: &[u8],
52     ) -> Result<Icmpv6Header, ValueTooBigError<usize>> {
53         let checksum = icmp_type.calc_checksum(source_ip, destination_ip, payload)?;
54         Ok(Icmpv6Header {
55             icmp_type,
56             checksum,
57         })
58     }
59 
60     /// Reads an icmp6 header from a slice directly and returns a tuple
61     /// containing the resulting header & unused part of the slice.
62     #[inline]
from_slice(slice: &[u8]) -> Result<(Icmpv6Header, &[u8]), err::LenError>63     pub fn from_slice(slice: &[u8]) -> Result<(Icmpv6Header, &[u8]), err::LenError> {
64         let header = Icmpv6Slice::from_slice(slice)?.header();
65         let len = header.header_len();
66         Ok((header, &slice[len..]))
67     }
68 
69     /// Read a ICMPv6 header from the given reader
70     #[cfg(feature = "std")]
71     #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
read<T: std::io::Read + Sized>(reader: &mut T) -> Result<Icmpv6Header, std::io::Error>72     pub fn read<T: std::io::Read + Sized>(reader: &mut T) -> Result<Icmpv6Header, std::io::Error> {
73         // read the initial 8 bytes
74         let mut start = [0u8; 8];
75         reader.read_exact(&mut start)?;
76         Ok(Icmpv6Slice { slice: &start }.header())
77     }
78 
79     /// Write the ICMPv6 header to the given writer.
80     #[cfg(feature = "std")]
81     #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
write<T: std::io::Write + Sized>(&self, writer: &mut T) -> Result<(), std::io::Error>82     pub fn write<T: std::io::Write + Sized>(&self, writer: &mut T) -> Result<(), std::io::Error> {
83         writer.write_all(&self.to_bytes())
84     }
85 
86     /// Serialized length of the header in bytes/octets.
87     ///
88     /// Note that this size is not the size of the entire
89     /// ICMPv6 packet but only the header.
90     #[inline]
header_len(&self) -> usize91     pub fn header_len(&self) -> usize {
92         self.icmp_type.header_len()
93     }
94 
95     /// If the ICMP type has a fixed size returns the number of
96     /// bytes that should be present after the header of this type.
97     #[inline]
fixed_payload_size(&self) -> Option<usize>98     pub fn fixed_payload_size(&self) -> Option<usize> {
99         self.icmp_type.fixed_payload_size()
100     }
101 
102     /// Updates the checksum of the header.
update_checksum( &mut self, source_ip: [u8; 16], destination_ip: [u8; 16], payload: &[u8], ) -> Result<(), ValueTooBigError<usize>>103     pub fn update_checksum(
104         &mut self,
105         source_ip: [u8; 16],
106         destination_ip: [u8; 16],
107         payload: &[u8],
108     ) -> Result<(), ValueTooBigError<usize>> {
109         self.checksum = self
110             .icmp_type
111             .calc_checksum(source_ip, destination_ip, payload)?;
112         Ok(())
113     }
114 
115     /// Returns the header on the wire bytes.
116     #[inline]
to_bytes(&self) -> ArrayVec<u8,117     pub fn to_bytes(&self) -> ArrayVec<u8, { Icmpv6Header::MAX_LEN }> {
118         let checksum_be = self.checksum.to_be_bytes();
119 
120         let return_trivial =
121             |type_u8: u8, code_u8: u8| -> ArrayVec<u8, { Icmpv6Header::MAX_LEN }> {
122                 #[rustfmt::skip]
123             let mut re = ArrayVec::from([
124                 type_u8, code_u8, checksum_be[0], checksum_be[1],
125                 0, 0, 0, 0,
126 
127                 0, 0, 0, 0,
128                 0, 0, 0, 0,
129                 0, 0, 0, 0,
130                 0, 0, 0, 0,
131 
132                 0, 0, 0, 0,
133                 0, 0, 0, 0,
134                 0, 0, 0, 0,
135                 0, 0, 0, 0,
136             ]);
137                 // SAFETY: Safe as u8 has no destruction behavior and as 8 is smaller then 20.
138                 unsafe {
139                     re.set_len(8);
140                 }
141                 re
142             };
143 
144         let return_4u8 = |type_u8: u8,
145                           code_u8: u8,
146                           bytes5to8: [u8; 4]|
147          -> ArrayVec<u8, { Icmpv6Header::MAX_LEN }> {
148             #[rustfmt::skip]
149             let mut re = ArrayVec::from([
150                 type_u8, code_u8, checksum_be[0], checksum_be[1],
151                 bytes5to8[0], bytes5to8[1], bytes5to8[2], bytes5to8[3],
152 
153                 0, 0, 0, 0,
154                 0, 0, 0, 0,
155                 0, 0, 0, 0,
156                 0, 0, 0, 0,
157 
158                 0, 0, 0, 0,
159                 0, 0, 0, 0,
160                 0, 0, 0, 0,
161                 0, 0, 0, 0,
162             ]);
163             // SAFETY: Safe as u8 has no destruction behavior and as 8 is smaller then 20.
164             unsafe {
165                 re.set_len(8);
166             }
167             re
168         };
169 
170         use crate::{icmpv6::*, Icmpv6Type::*};
171         match self.icmp_type {
172             Unknown {
173                 type_u8,
174                 code_u8,
175                 bytes5to8,
176             } => return_4u8(type_u8, code_u8, bytes5to8),
177             DestinationUnreachable(header) => return_trivial(TYPE_DST_UNREACH, header.code_u8()),
178             PacketTooBig { mtu } => return_4u8(TYPE_PACKET_TOO_BIG, 0, mtu.to_be_bytes()),
179             TimeExceeded(code) => return_trivial(TYPE_TIME_EXCEEDED, code.code_u8()),
180             ParameterProblem(header) => return_4u8(
181                 TYPE_PARAMETER_PROBLEM,
182                 header.code.code_u8(),
183                 header.pointer.to_be_bytes(),
184             ),
185             EchoRequest(echo) => return_4u8(TYPE_ECHO_REQUEST, 0, echo.to_bytes()),
186             EchoReply(echo) => return_4u8(TYPE_ECHO_REPLY, 0, echo.to_bytes()),
187         }
188     }
189 }
190 
191 #[cfg(test)]
192 mod test {
193     use crate::{
194         err::{ValueTooBigError, ValueType},
195         icmpv6::*,
196         test_gens::*,
197         *,
198     };
199     use alloc::{format, vec::Vec};
200     use arrayvec::ArrayVec;
201     use proptest::prelude::*;
202 
203     proptest! {
204         #[test]
205         fn new(icmp_type in icmpv6_type_any()) {
206             assert_eq!(
207                 Icmpv6Header::new(icmp_type.clone()),
208                 Icmpv6Header {
209                     icmp_type,
210                     checksum: 0,
211                 }
212             );
213         }
214     }
215 
216     proptest! {
217         #[cfg(not(any(target_pointer_width = "16", target_pointer_width = "32")))]
218         #[test]
219         fn with_checksum(
220             ip_header in ipv6_any(),
221             icmp_type in icmpv6_type_any(),
222             // max length is u32::MAX - header_len (7)
223             bad_len in (core::u32::MAX - 7) as usize..=(core::isize::MAX as usize),
224             payload in proptest::collection::vec(any::<u8>(), 0..1024)
225         ) {
226 
227             // error case
228             {
229                 // SAFETY: In case the error is not triggered
230                 //         a segmentation fault will be triggered.
231                 let too_big_slice = unsafe {
232                     //NOTE: The pointer must be initialized with a non null value
233                     //      otherwise a key constraint of slices is not fulfilled
234                     //      which can lead to crashes in release mode.
235                     use core::ptr::NonNull;
236                     core::slice::from_raw_parts(
237                         NonNull::<u8>::dangling().as_ptr(),
238                         bad_len
239                     )
240                 };
241                 assert_eq!(
242                     Icmpv6Header::with_checksum(icmp_type.clone(), ip_header.source, ip_header.destination, too_big_slice),
243                     Err(ValueTooBigError{
244                         actual: bad_len,
245                         max_allowed: (core::u32::MAX - 8) as usize,
246                         value_type: ValueType::Icmpv6PayloadLength,
247                     })
248                 );
249             }
250 
251             // non error case
252             assert_eq!(
253                 Icmpv6Header::with_checksum(icmp_type.clone(), ip_header.source, ip_header.destination, &payload).unwrap(),
254                 Icmpv6Header {
255                     icmp_type,
256                     checksum: icmp_type.calc_checksum(ip_header.source, ip_header.destination, &payload).unwrap(),
257                 }
258             );
259         }
260     }
261 
262     proptest! {
263         #[test]
264         fn from_slice(
265             icmp_type in icmpv6_type_any(),
266             checksum in any::<u16>(),
267         ) {
268             let bytes = {
269                 Icmpv6Header {
270                     icmp_type: icmp_type.clone(),
271                     checksum,
272                 }.to_bytes()
273             };
274 
275             // ok case
276             {
277                 let result = Icmpv6Header::from_slice(&bytes).unwrap();
278                 assert_eq!(
279                     Icmpv6Header{
280                         icmp_type,
281                         checksum,
282                     },
283                     result.0,
284                 );
285                 assert_eq!(&bytes[8..], result.1);
286             }
287 
288 
289             // size error case
290             for length in 0..8 {
291                 assert_eq!(
292                     Icmpv6Header::from_slice(&bytes[..length]).unwrap_err(),
293                     err::LenError{
294                         required_len: bytes.len(),
295                         len: length,
296                         len_source: LenSource::Slice,
297                         layer: err::Layer::Icmpv6,
298                         layer_start_offset: 0
299                     }
300                 );
301             }
302         }
303     }
304 
305     proptest! {
306         #[test]
307         fn read(
308             icmp_type in icmpv6_type_any(),
309             checksum in any::<u16>(),
310         ) {
311             let header = Icmpv6Header {
312                 icmp_type: icmp_type.clone(),
313                 checksum,
314             };
315             let bytes = header.to_bytes();
316 
317             // ok case
318             {
319                 let mut cursor = std::io::Cursor::new(&bytes);
320                 let result = Icmpv6Header::read(&mut cursor).unwrap();
321                 assert_eq!(header, result,);
322                 assert_eq!(header.header_len() as u64, cursor.position());
323             }
324 
325             // size error case
326             for length in 0..header.header_len() {
327                 let mut cursor = std::io::Cursor::new(&bytes[..length]);
328                 assert!(Icmpv6Header::read(&mut cursor).is_err());
329             }
330         }
331     }
332 
333     proptest! {
334         #[test]
335         fn write(
336             icmp_type in icmpv6_type_any(),
337             checksum in any::<u16>(),
338             bad_len in 0..8usize
339         ) {
340             // normal case
341             {
342                 let mut buffer = Vec::with_capacity(icmp_type.header_len());
343                 let header = Icmpv6Header {
344                     icmp_type,
345                     checksum,
346                 };
347                 header.write(&mut buffer).unwrap();
348                 assert_eq!(
349                     &header.to_bytes(),
350                     &buffer[..]
351                 );
352             }
353 
354             // error case
355             {
356                 let mut buffer = [0u8;Icmpv6Header::MAX_LEN];
357                 let mut writer = std::io::Cursor::new(&mut buffer[..bad_len]);
358                 Icmpv6Header {
359                     icmp_type,
360                     checksum,
361                 }.write(&mut writer).unwrap_err();
362             }
363         }
364     }
365 
366     proptest! {
367         #[test]
368         fn header_len(icmp_type in icmpv6_type_any(), checksum in any::<u16>()) {
369             assert_eq!(
370                 icmp_type.header_len(),
371                 Icmpv6Header{
372                     icmp_type,
373                     checksum
374                 }.header_len()
375             );
376         }
377     }
378 
379     proptest! {
380         #[test]
381         fn fixed_payload_size(icmp_type in icmpv6_type_any(), checksum in any::<u16>()) {
382             assert_eq!(
383                 icmp_type.fixed_payload_size(),
384                 Icmpv6Header{
385                     icmp_type,
386                     checksum
387                 }.fixed_payload_size()
388             );
389         }
390     }
391 
392     proptest! {
393         #[test]
394         #[cfg(not(any(target_pointer_width = "16", target_pointer_width = "32")))]
395         fn update_checksum(
396             ip_header in ipv6_any(),
397             icmp_type in icmpv6_type_any(),
398             start_checksum in any::<u16>(),
399             // max length is u32::MAX - header_len (7)
400             bad_len in (core::u32::MAX - 7) as usize..=(core::isize::MAX as usize),
401             payload in proptest::collection::vec(any::<u8>(), 0..1024)
402         ) {
403 
404             // error case
405             {
406                 // SAFETY: In case the error is not triggered
407                 //         a segmentation fault will be triggered.
408                 let too_big_slice = unsafe {
409                     //NOTE: The pointer must be initialized with a non null value
410                     //      otherwise a key constraint of slices is not fulfilled
411                     //      which can lead to crashes in release mode.
412                     use core::ptr::NonNull;
413                     core::slice::from_raw_parts(
414                         NonNull::<u8>::dangling().as_ptr(),
415                         bad_len
416                     )
417                 };
418                 assert_eq!(
419                     Icmpv6Header{
420                         icmp_type,
421                         checksum: 0
422                     }.update_checksum(ip_header.source, ip_header.destination, too_big_slice),
423                     Err(ValueTooBigError{
424                         actual: bad_len,
425                         max_allowed: (u32::MAX - 8) as usize,
426                         value_type: ValueType::Icmpv6PayloadLength
427                     })
428                 );
429             }
430 
431             // normal case
432             assert_eq!(
433                 {
434                     let mut header = Icmpv6Header{
435                         icmp_type,
436                         checksum: start_checksum,
437                     };
438                     header.update_checksum(ip_header.source, ip_header.destination, &payload).unwrap();
439                     header
440                 },
441                 Icmpv6Header{
442                     icmp_type,
443                     checksum: icmp_type.calc_checksum(ip_header.source, ip_header.destination, &payload).unwrap(),
444                 }
445             );
446         }
447     }
448 
449     proptest! {
450         #[test]
451         fn to_bytes(
452             checksum in any::<u16>(),
453             rand_u32 in any::<u32>(),
454             rand_4bytes in any::<[u8;4]>(),
455         ) {
456             use Icmpv6Type::*;
457 
458             let with_5to8_bytes = |type_u8: u8, code_u8: u8, bytes5to8: [u8;4]| -> ArrayVec<u8, { Icmpv6Header::MAX_LEN }> {
459                 let mut bytes = ArrayVec::<u8, { Icmpv6Header::MAX_LEN }>::new();
460                 bytes.push(type_u8);
461                 bytes.push(code_u8);
462                 bytes.try_extend_from_slice(&checksum.to_be_bytes()).unwrap();
463                 bytes.try_extend_from_slice(&bytes5to8).unwrap();
464                 bytes
465             };
466 
467             let simple_bytes = |type_u8: u8, code_u8: u8| -> ArrayVec<u8, { Icmpv6Header::MAX_LEN }> {
468                 with_5to8_bytes(type_u8, code_u8, [0;4])
469             };
470 
471             // destination unreachable
472             for (code, code_u8) in dest_unreachable_code_test_consts::VALID_VALUES {
473                 assert_eq!(
474                     Icmpv6Header{
475                         icmp_type: DestinationUnreachable(code),
476                         checksum
477                     }.to_bytes(),
478                     simple_bytes(TYPE_DST_UNREACH, code_u8)
479                 );
480             }
481 
482             // packet too big
483             assert_eq!(
484                 Icmpv6Header{
485                     icmp_type: PacketTooBig{ mtu: rand_u32 },
486                     checksum
487                 }.to_bytes(),
488                 with_5to8_bytes(TYPE_PACKET_TOO_BIG, 0, rand_u32.to_be_bytes())
489             );
490 
491             // time exceeded
492             for (code, code_u8) in time_exceeded_code_test_consts::VALID_VALUES {
493                 assert_eq!(
494                     Icmpv6Header{
495                         icmp_type: TimeExceeded(code),
496                         checksum
497                     }.to_bytes(),
498                     simple_bytes(TYPE_TIME_EXCEEDED, code_u8)
499                 );
500             }
501 
502             // parameter problem
503             for (code, code_u8) in parameter_problem_code_test_consts::VALID_VALUES {
504                 assert_eq!(
505                     Icmpv6Header{
506                         icmp_type: ParameterProblem(
507                             ParameterProblemHeader{
508                                 code,
509                                 pointer: rand_u32,
510                             }
511                         ),
512                         checksum
513                     }.to_bytes(),
514                     with_5to8_bytes(TYPE_PARAMETER_PROBLEM, code_u8, rand_u32.to_be_bytes())
515                 );
516             }
517 
518             // echo request
519             assert_eq!(
520                 Icmpv6Header{
521                     icmp_type: EchoRequest(IcmpEchoHeader {
522                         id: u16::from_be_bytes([rand_4bytes[0], rand_4bytes[1]]),
523                         seq: u16::from_be_bytes([rand_4bytes[2], rand_4bytes[3]]),
524                     }),
525                     checksum
526                 }.to_bytes(),
527                 with_5to8_bytes(TYPE_ECHO_REQUEST, 0, rand_4bytes)
528             );
529 
530             // echo reply
531             assert_eq!(
532                 Icmpv6Header{
533                     icmp_type: EchoReply(IcmpEchoHeader {
534                         id: u16::from_be_bytes([rand_4bytes[0], rand_4bytes[1]]),
535                         seq: u16::from_be_bytes([rand_4bytes[2], rand_4bytes[3]]),
536                     }),
537                     checksum
538                 }.to_bytes(),
539                 with_5to8_bytes(TYPE_ECHO_REPLY, 0, rand_4bytes)
540             );
541 
542             // unknown
543             for type_u8 in 0..=u8::MAX {
544                 for code_u8 in 0..=u8::MAX {
545                     assert_eq!(
546                         Icmpv6Header{
547                             icmp_type: Unknown {
548                                 type_u8,
549                                 code_u8,
550                                 bytes5to8: rand_4bytes,
551                             },
552                             checksum
553                         }.to_bytes(),
554                         with_5to8_bytes(type_u8, code_u8, rand_4bytes)
555                     );
556                 }
557             }
558         }
559     }
560 
561     #[test]
debug()562     fn debug() {
563         let t = Icmpv6Type::Unknown {
564             type_u8: 0,
565             code_u8: 1,
566             bytes5to8: [2, 3, 4, 5],
567         };
568         assert_eq!(
569             format!(
570                 "{:?}",
571                 Icmpv6Header {
572                     icmp_type: t.clone(),
573                     checksum: 7
574                 }
575             ),
576             format!("Icmpv6Header {{ icmp_type: {:?}, checksum: {:?} }}", t, 7)
577         );
578     }
579 
580     proptest! {
581         #[test]
582         fn clone_eq(icmp_type in icmpv6_type_any(), checksum in any::<u16>()) {
583             let header = Icmpv6Header{ icmp_type, checksum };
584             assert_eq!(header, header.clone());
585         }
586     }
587 }
588