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