stackforge_core/layer/icmp/
builder.rs1use std::net::Ipv4Addr;
22
23use super::checksum::icmp_checksum;
24use super::types::types;
25use super::{ICMP_MIN_HEADER_LEN, offsets};
26use crate::layer::field::FieldError;
27
28#[derive(Debug, Clone)]
33pub struct IcmpBuilder {
34 icmp_type: u8,
36 code: u8,
37 checksum: Option<u16>,
38
39 type_specific: [u8; 4],
47
48 timestamp_data: Option<[u8; 12]>,
51
52 payload: Vec<u8>,
54
55 auto_checksum: bool,
57}
58
59impl Default for IcmpBuilder {
60 fn default() -> Self {
61 Self {
62 icmp_type: types::ECHO_REQUEST,
63 code: 0,
64 checksum: None,
65 type_specific: [0; 4],
66 timestamp_data: None,
67 payload: Vec::new(),
68 auto_checksum: true,
69 }
70 }
71}
72
73impl IcmpBuilder {
74 pub fn new() -> Self {
76 Self::default()
77 }
78
79 pub fn echo_request(id: u16, seq: u16) -> Self {
87 let mut builder = Self::new();
88 builder.icmp_type = types::ECHO_REQUEST;
89 builder.code = 0;
90 builder.set_id_seq(id, seq);
91 builder
92 }
93
94 pub fn echo_reply(id: u16, seq: u16) -> Self {
100 let mut builder = Self::new();
101 builder.icmp_type = types::ECHO_REPLY;
102 builder.code = 0;
103 builder.set_id_seq(id, seq);
104 builder
105 }
106
107 pub fn dest_unreach(code: u8) -> Self {
117 let mut builder = Self::new();
118 builder.icmp_type = types::DEST_UNREACH;
119 builder.code = code;
120 builder.type_specific = [0; 4]; builder
122 }
123
124 pub fn dest_unreach_need_frag(mtu: u16) -> Self {
129 let mut builder = Self::new();
130 builder.icmp_type = types::DEST_UNREACH;
131 builder.code = 4; builder.type_specific[0] = 0; builder.type_specific[1] = 0; builder.type_specific[2..4].copy_from_slice(&mtu.to_be_bytes());
135 builder
136 }
137
138 pub fn redirect(code: u8, gateway: Ipv4Addr) -> Self {
148 let mut builder = Self::new();
149 builder.icmp_type = types::REDIRECT;
150 builder.code = code;
151 builder.type_specific.copy_from_slice(&gateway.octets());
152 builder
153 }
154
155 pub fn time_exceeded(code: u8) -> Self {
162 let mut builder = Self::new();
163 builder.icmp_type = types::TIME_EXCEEDED;
164 builder.code = code;
165 builder.type_specific = [0; 4]; builder
167 }
168
169 pub fn param_problem(ptr: u8) -> Self {
174 let mut builder = Self::new();
175 builder.icmp_type = types::PARAM_PROBLEM;
176 builder.code = 0;
177 builder.type_specific[0] = ptr;
178 builder.type_specific[1] = 0; builder.type_specific[2] = 0; builder.type_specific[3] = 0; builder
182 }
183
184 pub fn source_quench() -> Self {
186 let mut builder = Self::new();
187 builder.icmp_type = types::SOURCE_QUENCH;
188 builder.code = 0;
189 builder.type_specific = [0; 4]; builder
191 }
192
193 pub fn timestamp_request(id: u16, seq: u16, ts_ori: u32, ts_rx: u32, ts_tx: u32) -> Self {
202 let mut builder = Self::new();
203 builder.icmp_type = types::TIMESTAMP;
204 builder.code = 0;
205 builder.set_id_seq(id, seq);
206
207 let mut ts_data = [0u8; 12];
208 ts_data[0..4].copy_from_slice(&ts_ori.to_be_bytes());
209 ts_data[4..8].copy_from_slice(&ts_rx.to_be_bytes());
210 ts_data[8..12].copy_from_slice(&ts_tx.to_be_bytes());
211 builder.timestamp_data = Some(ts_data);
212
213 builder
214 }
215
216 pub fn timestamp_reply(id: u16, seq: u16, ts_ori: u32, ts_rx: u32, ts_tx: u32) -> Self {
225 let mut builder = Self::new();
226 builder.icmp_type = types::TIMESTAMP_REPLY;
227 builder.code = 0;
228 builder.set_id_seq(id, seq);
229
230 let mut ts_data = [0u8; 12];
231 ts_data[0..4].copy_from_slice(&ts_ori.to_be_bytes());
232 ts_data[4..8].copy_from_slice(&ts_rx.to_be_bytes());
233 ts_data[8..12].copy_from_slice(&ts_tx.to_be_bytes());
234 builder.timestamp_data = Some(ts_data);
235
236 builder
237 }
238
239 pub fn address_mask_request(id: u16, seq: u16) -> Self {
245 let mut builder = Self::new();
246 builder.icmp_type = types::ADDRESS_MASK_REQUEST;
247 builder.code = 0;
248 builder.set_id_seq(id, seq);
249 builder
250 }
251
252 pub fn address_mask_reply(id: u16, seq: u16, mask: Ipv4Addr) -> Self {
259 let mut builder = Self::new();
260 builder.icmp_type = types::ADDRESS_MASK_REPLY;
261 builder.code = 0;
262 builder.set_id_seq(id, seq);
263 builder.payload = mask.octets().to_vec();
265 builder
266 }
267
268 fn set_id_seq(&mut self, id: u16, seq: u16) {
272 self.type_specific[0..2].copy_from_slice(&id.to_be_bytes());
273 self.type_specific[2..4].copy_from_slice(&seq.to_be_bytes());
274 }
275
276 pub fn icmp_type(mut self, t: u8) -> Self {
280 self.icmp_type = t;
281 self
282 }
283
284 pub fn code(mut self, c: u8) -> Self {
286 self.code = c;
287 self
288 }
289
290 pub fn checksum(mut self, csum: u16) -> Self {
294 self.checksum = Some(csum);
295 self.auto_checksum = false;
296 self
297 }
298
299 pub fn chksum(self, csum: u16) -> Self {
301 self.checksum(csum)
302 }
303
304 pub fn enable_auto_checksum(mut self) -> Self {
306 self.auto_checksum = true;
307 self.checksum = None;
308 self
309 }
310
311 pub fn disable_auto_checksum(mut self) -> Self {
313 self.auto_checksum = false;
314 self
315 }
316
317 pub fn payload<T: Into<Vec<u8>>>(mut self, data: T) -> Self {
319 self.payload = data.into();
320 self
321 }
322
323 pub fn append_payload<T: AsRef<[u8]>>(mut self, data: T) -> Self {
325 self.payload.extend_from_slice(data.as_ref());
326 self
327 }
328
329 pub fn packet_size(&self) -> usize {
333 let mut size = ICMP_MIN_HEADER_LEN; if self.timestamp_data.is_some() {
337 size += 12;
338 }
339
340 size + self.payload.len()
341 }
342
343 pub fn header_size(&self) -> usize {
345 if self.timestamp_data.is_some() {
346 20 } else {
348 ICMP_MIN_HEADER_LEN
349 }
350 }
351
352 pub fn build(&self) -> Vec<u8> {
356 let total_size = self.packet_size();
357 let mut buf = vec![0u8; total_size];
358 self.build_into(&mut buf)
359 .expect("buffer is correctly sized");
360 buf
361 }
362
363 pub fn build_into(&self, buf: &mut [u8]) -> Result<usize, FieldError> {
365 let total_size = self.packet_size();
366
367 if buf.len() < total_size {
368 return Err(FieldError::BufferTooShort {
369 offset: 0,
370 need: total_size,
371 have: buf.len(),
372 });
373 }
374
375 buf[offsets::TYPE] = self.icmp_type;
377
378 buf[offsets::CODE] = self.code;
380
381 buf[offsets::CHECKSUM..offsets::CHECKSUM + 2].copy_from_slice(&[0, 0]);
383
384 buf[4..8].copy_from_slice(&self.type_specific);
386
387 let mut offset = 8;
388
389 if let Some(ts_data) = &self.timestamp_data {
391 buf[offset..offset + 12].copy_from_slice(ts_data);
392 offset += 12;
393 }
394
395 if !self.payload.is_empty() {
397 buf[offset..offset + self.payload.len()].copy_from_slice(&self.payload);
398 }
399
400 if self.auto_checksum {
402 let csum = icmp_checksum(&buf[..total_size]);
403 buf[offsets::CHECKSUM..offsets::CHECKSUM + 2].copy_from_slice(&csum.to_be_bytes());
404 } else if let Some(csum) = self.checksum {
405 buf[offsets::CHECKSUM..offsets::CHECKSUM + 2].copy_from_slice(&csum.to_be_bytes());
406 }
407
408 Ok(total_size)
409 }
410
411 pub fn build_header(&self) -> Vec<u8> {
413 let header_size = self.header_size();
414 let mut buf = vec![0u8; header_size];
415
416 let builder = Self {
418 payload: Vec::new(),
419 ..self.clone()
420 };
421 builder
422 .build_into(&mut buf)
423 .expect("buffer is correctly sized");
424
425 buf
426 }
427}
428
429#[cfg(test)]
430mod tests {
431 use super::*;
432
433 #[test]
434 fn test_echo_request() {
435 let packet = IcmpBuilder::echo_request(0x1234, 5)
436 .payload(b"Hello")
437 .build();
438
439 assert_eq!(packet[0], types::ECHO_REQUEST); assert_eq!(packet[1], 0); assert_eq!(u16::from_be_bytes([packet[4], packet[5]]), 0x1234); assert_eq!(u16::from_be_bytes([packet[6], packet[7]]), 5); assert_eq!(&packet[8..], b"Hello"); }
446
447 #[test]
448 fn test_echo_reply() {
449 let packet = IcmpBuilder::echo_reply(0x5678, 10).build();
450
451 assert_eq!(packet[0], types::ECHO_REPLY);
452 assert_eq!(u16::from_be_bytes([packet[4], packet[5]]), 0x5678);
453 assert_eq!(u16::from_be_bytes([packet[6], packet[7]]), 10);
454 }
455
456 #[test]
457 fn test_dest_unreach() {
458 let packet = IcmpBuilder::dest_unreach(3).build(); assert_eq!(packet[0], types::DEST_UNREACH);
461 assert_eq!(packet[1], 3); }
463
464 #[test]
465 fn test_dest_unreach_need_frag() {
466 let packet = IcmpBuilder::dest_unreach_need_frag(1500).build();
467
468 assert_eq!(packet[0], types::DEST_UNREACH);
469 assert_eq!(packet[1], 4); assert_eq!(u16::from_be_bytes([packet[6], packet[7]]), 1500); }
472
473 #[test]
474 fn test_redirect() {
475 let gateway = Ipv4Addr::new(192, 168, 1, 1);
476 let packet = IcmpBuilder::redirect(1, gateway).build();
477
478 assert_eq!(packet[0], types::REDIRECT);
479 assert_eq!(packet[1], 1); assert_eq!(&packet[4..8], &[192, 168, 1, 1]); }
482
483 #[test]
484 fn test_time_exceeded() {
485 let packet = IcmpBuilder::time_exceeded(0).build(); assert_eq!(packet[0], types::TIME_EXCEEDED);
488 assert_eq!(packet[1], 0);
489 }
490
491 #[test]
492 fn test_param_problem() {
493 let packet = IcmpBuilder::param_problem(20).build();
494
495 assert_eq!(packet[0], types::PARAM_PROBLEM);
496 assert_eq!(packet[4], 20); }
498
499 #[test]
500 fn test_timestamp_request() {
501 let packet = IcmpBuilder::timestamp_request(0x1234, 1, 1000, 0, 0).build();
502
503 assert_eq!(packet[0], types::TIMESTAMP);
504 assert_eq!(packet.len(), 20); assert_eq!(u16::from_be_bytes([packet[4], packet[5]]), 0x1234); assert_eq!(u16::from_be_bytes([packet[6], packet[7]]), 1); assert_eq!(
508 u32::from_be_bytes([packet[8], packet[9], packet[10], packet[11]]),
509 1000
510 ); }
512
513 #[test]
514 fn test_checksum_calculation() {
515 let packet = IcmpBuilder::echo_request(1, 1).payload(b"test").build();
516
517 let checksum = u16::from_be_bytes([packet[2], packet[3]]);
519 assert_ne!(checksum, 0);
520 }
521
522 #[test]
523 fn test_manual_checksum() {
524 let packet = IcmpBuilder::echo_request(1, 1).checksum(0xABCD).build();
525
526 assert_eq!(u16::from_be_bytes([packet[2], packet[3]]), 0xABCD);
527 }
528
529 #[test]
530 fn test_build_header_only() {
531 let header = IcmpBuilder::echo_request(1, 1)
532 .payload(b"this should not be included")
533 .build_header();
534
535 assert_eq!(header.len(), 8); }
537
538 #[test]
539 fn test_timestamp_header_size() {
540 let header = IcmpBuilder::timestamp_request(1, 1, 1000, 2000, 3000).build_header();
541
542 assert_eq!(header.len(), 20); }
544}