Skip to main content

stackforge_core/layer/icmpv6/
builder.rs

1//! `ICMPv6` packet builder.
2//!
3//! Provides a fluent API for constructing `ICMPv6` packets with type-specific
4//! fields and automatic checksum calculation using the IPv6 pseudo-header.
5//!
6//! # Example
7//!
8//! ```rust
9//! use stackforge_core::layer::icmpv6::Icmpv6Builder;
10//! use std::net::Ipv6Addr;
11//!
12//! // Build an echo request
13//! let src = Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1);
14//! let dst = Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 2);
15//! let packet = Icmpv6Builder::echo_request(0x1234, 1)
16//!     .set_src_ip(src)
17//!     .set_dst_ip(dst)
18//!     .build();
19//! ```
20
21use std::net::Ipv6Addr;
22
23use super::{ICMPV6_MIN_HEADER_LEN, icmpv6_checksum, offsets, types};
24
25/// Builder for `ICMPv6` packets.
26#[derive(Debug, Clone)]
27pub struct Icmpv6Builder {
28    /// `ICMPv6` Type
29    icmpv6_type: u8,
30    /// `ICMPv6` Code
31    code: u8,
32    /// Optional manual checksum override
33    checksum: Option<u16>,
34    /// Identifier (for echo request/reply)
35    id: Option<u16>,
36    /// Sequence number (for echo request/reply)
37    seq: Option<u16>,
38    /// Target IPv6 address (for NS/NA/Redirect)
39    target: Option<Ipv6Addr>,
40    /// MTU (for Packet Too Big)
41    mtu: Option<u32>,
42    /// 4-byte type-specific field (bytes 4-7 of header)
43    type_specific: [u8; 4],
44    /// Payload data (appended after the 8-byte base header)
45    payload: Vec<u8>,
46    /// If true, compute checksum automatically when src/dst are set
47    auto_checksum: bool,
48    /// Source IPv6 address (for checksum calculation)
49    src_ip: Option<Ipv6Addr>,
50    /// Destination IPv6 address (for checksum calculation)
51    dst_ip: Option<Ipv6Addr>,
52}
53
54impl Default for Icmpv6Builder {
55    fn default() -> Self {
56        Self {
57            icmpv6_type: types::ECHO_REQUEST,
58            code: 0,
59            checksum: None,
60            id: None,
61            seq: None,
62            target: None,
63            mtu: None,
64            type_specific: [0; 4],
65            payload: Vec::new(),
66            auto_checksum: true,
67            src_ip: None,
68            dst_ip: None,
69        }
70    }
71}
72
73impl Icmpv6Builder {
74    /// Create a new `ICMPv6` builder with default values.
75    #[must_use]
76    pub fn new() -> Self {
77        Self::default()
78    }
79
80    // ========== Factory Methods ==========
81
82    /// Create an Echo Request (ping6) packet.
83    ///
84    /// # Arguments
85    /// * `id` - Identifier
86    /// * `seq` - Sequence number
87    #[must_use]
88    pub fn echo_request(id: u16, seq: u16) -> Self {
89        let mut b = Self::new();
90        b.icmpv6_type = types::ECHO_REQUEST;
91        b.code = 0;
92        b.id = Some(id);
93        b.seq = Some(seq);
94        b.type_specific[0] = (id >> 8) as u8;
95        b.type_specific[1] = (id & 0xFF) as u8;
96        b.type_specific[2] = (seq >> 8) as u8;
97        b.type_specific[3] = (seq & 0xFF) as u8;
98        b
99    }
100
101    /// Create an Echo Reply (pong6) packet.
102    ///
103    /// # Arguments
104    /// * `id` - Identifier (should match the request)
105    /// * `seq` - Sequence number (should match the request)
106    #[must_use]
107    pub fn echo_reply(id: u16, seq: u16) -> Self {
108        let mut b = Self::echo_request(id, seq);
109        b.icmpv6_type = types::ECHO_REPLY;
110        b
111    }
112
113    /// Create a Neighbor Solicitation (NDP) packet.
114    ///
115    /// # Arguments
116    /// * `target` - The IPv6 address being queried
117    #[must_use]
118    pub fn neighbor_solicitation(target: Ipv6Addr) -> Self {
119        let mut b = Self::new();
120        b.icmpv6_type = types::NEIGHBOR_SOLICIT;
121        b.code = 0;
122        b.target = Some(target);
123        // Bytes 4-7 are Reserved (zero)
124        b
125    }
126
127    /// Create a Neighbor Advertisement (NDP) packet.
128    ///
129    /// # Arguments
130    /// * `target` - The IPv6 address being advertised
131    #[must_use]
132    pub fn neighbor_advertisement(target: Ipv6Addr) -> Self {
133        let mut b = Self::new();
134        b.icmpv6_type = types::NEIGHBOR_ADVERT;
135        b.code = 0;
136        b.target = Some(target);
137        // Bytes 4-7: R|S|O flags (bit 31=Router, bit 30=Solicited, bit 29=Override)
138        // Default to Solicited + Override flags set (typical response)
139        b.type_specific[0] = 0x60; // S=1, O=1
140        b
141    }
142
143    /// Create a Router Solicitation (NDP) packet.
144    #[must_use]
145    pub fn router_solicitation() -> Self {
146        let mut b = Self::new();
147        b.icmpv6_type = types::ROUTER_SOLICIT;
148        b.code = 0;
149        // Bytes 4-7: Reserved (zero)
150        b
151    }
152
153    /// Create a Router Advertisement (NDP) packet.
154    #[must_use]
155    pub fn router_advertisement() -> Self {
156        let mut b = Self::new();
157        b.icmpv6_type = types::ROUTER_ADVERT;
158        b.code = 0;
159        // Bytes 4-7: Cur Hop Limit, M/O flags, Router Lifetime
160        // (left at zero defaults; caller can customize via payload)
161        b
162    }
163
164    /// Create a Destination Unreachable packet.
165    ///
166    /// # Arguments
167    /// * `code` - Reason code (0=No route, 1=Admin prohibited, 3=Port unreachable, etc.)
168    /// * `payload` - The offending packet bytes (or truncation thereof)
169    #[must_use]
170    pub fn dest_unreachable(code: u8, payload: Vec<u8>) -> Self {
171        let mut b = Self::new();
172        b.icmpv6_type = types::DEST_UNREACH;
173        b.code = code;
174        b.payload = payload;
175        // Bytes 4-7: Unused (zero)
176        b
177    }
178
179    /// Create a Time Exceeded packet.
180    ///
181    /// # Arguments
182    /// * `code` - 0=Hop limit exceeded, 1=Fragment reassembly time exceeded
183    /// * `payload` - The offending packet bytes (or truncation thereof)
184    #[must_use]
185    pub fn time_exceeded(code: u8, payload: Vec<u8>) -> Self {
186        let mut b = Self::new();
187        b.icmpv6_type = types::TIME_EXCEEDED;
188        b.code = code;
189        b.payload = payload;
190        // Bytes 4-7: Unused (zero)
191        b
192    }
193
194    /// Create a Packet Too Big packet.
195    ///
196    /// # Arguments
197    /// * `mtu` - Maximum Transmission Unit of the next-hop link
198    /// * `payload` - The offending packet bytes (or truncation thereof)
199    #[must_use]
200    pub fn pkt_too_big(mtu: u32, payload: Vec<u8>) -> Self {
201        let mut b = Self::new();
202        b.icmpv6_type = types::PKT_TOO_BIG;
203        b.code = 0;
204        b.mtu = Some(mtu);
205        b.type_specific = [
206            (mtu >> 24) as u8,
207            ((mtu >> 16) & 0xFF) as u8,
208            ((mtu >> 8) & 0xFF) as u8,
209            (mtu & 0xFF) as u8,
210        ];
211        b.payload = payload;
212        b
213    }
214
215    // ========== Field Setters ==========
216
217    /// Set the `ICMPv6` type manually.
218    #[must_use]
219    pub fn icmpv6_type(mut self, t: u8) -> Self {
220        self.icmpv6_type = t;
221        self
222    }
223
224    /// Set the `ICMPv6` code.
225    #[must_use]
226    pub fn code(mut self, c: u8) -> Self {
227        self.code = c;
228        self
229    }
230
231    /// Set the checksum manually (disables auto-checksum).
232    #[must_use]
233    pub fn checksum(mut self, csum: u16) -> Self {
234        self.checksum = Some(csum);
235        self.auto_checksum = false;
236        self
237    }
238
239    /// Alias for checksum (Scapy compatibility).
240    #[must_use]
241    pub fn chksum(self, csum: u16) -> Self {
242        self.checksum(csum)
243    }
244
245    /// Set the payload bytes.
246    pub fn payload<T: Into<Vec<u8>>>(mut self, data: T) -> Self {
247        self.payload = data.into();
248        self
249    }
250
251    /// Enable automatic checksum calculation (default: true).
252    #[must_use]
253    pub fn enable_auto_checksum(mut self) -> Self {
254        self.auto_checksum = true;
255        self.checksum = None;
256        self
257    }
258
259    /// Disable automatic checksum calculation.
260    #[must_use]
261    pub fn disable_auto_checksum(mut self) -> Self {
262        self.auto_checksum = false;
263        self
264    }
265
266    /// Set the source IP address (used for checksum calculation).
267    #[must_use]
268    pub fn set_src_ip(mut self, src: Ipv6Addr) -> Self {
269        self.src_ip = Some(src);
270        self
271    }
272
273    /// Set the destination IP address (used for checksum calculation).
274    #[must_use]
275    pub fn set_dst_ip(mut self, dst: Ipv6Addr) -> Self {
276        self.dst_ip = Some(dst);
277        self
278    }
279
280    // ========== Size Calculation ==========
281
282    /// Get the total packet size.
283    #[must_use]
284    pub fn packet_size(&self) -> usize {
285        let base = ICMPV6_MIN_HEADER_LEN;
286        let extra = match self.icmpv6_type {
287            types::NEIGHBOR_SOLICIT | types::NEIGHBOR_ADVERT => 16, // target address
288            _ => 0,
289        };
290        base + extra + self.payload.len()
291    }
292
293    /// Get the header size (base 8 bytes + any type-specific fixed fields).
294    #[must_use]
295    pub fn header_size(&self) -> usize {
296        ICMPV6_MIN_HEADER_LEN
297    }
298
299    // ========== Build Methods ==========
300
301    /// Build the `ICMPv6` packet into a byte vector.
302    ///
303    /// # Byte layout:
304    /// - Byte 0: type
305    /// - Byte 1: code
306    /// - Bytes 2-3: checksum (computed or manual or zero)
307    /// - Bytes 4-7: type-specific data
308    /// - Bytes 8+: type-specific body (target addr for NS/NA) + payload
309    #[must_use]
310    pub fn build(&self) -> Vec<u8> {
311        let total = self.packet_size();
312        let mut buf = vec![0u8; total];
313
314        // Type
315        buf[offsets::TYPE] = self.icmpv6_type;
316
317        // Code
318        buf[offsets::CODE] = self.code;
319
320        // Checksum: set to zero initially
321        buf[offsets::CHECKSUM] = 0;
322        buf[offsets::CHECKSUM + 1] = 0;
323
324        // Type-specific bytes 4-7
325        buf[4..8].copy_from_slice(&self.type_specific);
326
327        let mut offset = 8;
328
329        // Type-specific body
330        match self.icmpv6_type {
331            types::NEIGHBOR_SOLICIT | types::NEIGHBOR_ADVERT | types::REDIRECT => {
332                if let Some(target) = self.target
333                    && offset + 16 <= buf.len()
334                {
335                    buf[offset..offset + 16].copy_from_slice(&target.octets());
336                    offset += 16;
337                }
338            },
339            _ => {},
340        }
341
342        // Payload
343        if !self.payload.is_empty() && offset + self.payload.len() <= buf.len() {
344            buf[offset..offset + self.payload.len()].copy_from_slice(&self.payload);
345        }
346
347        // Calculate checksum if auto_checksum is enabled and src/dst are available
348        if self.auto_checksum {
349            if let (Some(src), Some(dst)) = (self.src_ip, self.dst_ip) {
350                let csum = icmpv6_checksum(src, dst, &buf);
351                buf[offsets::CHECKSUM] = (csum >> 8) as u8;
352                buf[offsets::CHECKSUM + 1] = (csum & 0xFF) as u8;
353            }
354            // If no src/dst, leave checksum as zero
355        } else if let Some(csum) = self.checksum {
356            buf[offsets::CHECKSUM] = (csum >> 8) as u8;
357            buf[offsets::CHECKSUM + 1] = (csum & 0xFF) as u8;
358        }
359
360        buf
361    }
362}
363
364#[cfg(test)]
365mod tests {
366    use super::*;
367
368    #[test]
369    fn test_echo_request_builder() {
370        let src = Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1);
371        let dst = Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 2);
372
373        let bytes = Icmpv6Builder::echo_request(0x1234, 5)
374            .set_src_ip(src)
375            .set_dst_ip(dst)
376            .payload(b"ping".as_ref())
377            .build();
378
379        assert_eq!(bytes[0], types::ECHO_REQUEST);
380        assert_eq!(bytes[1], 0); // code
381        assert_eq!(u16::from_be_bytes([bytes[4], bytes[5]]), 0x1234); // id
382        assert_eq!(u16::from_be_bytes([bytes[6], bytes[7]]), 5); // seq
383        assert_eq!(&bytes[8..], b"ping");
384
385        // Checksum should be non-zero
386        let chksum = u16::from_be_bytes([bytes[2], bytes[3]]);
387        assert_ne!(chksum, 0);
388    }
389
390    #[test]
391    fn test_echo_reply_builder() {
392        let bytes = Icmpv6Builder::echo_reply(0x5678, 10).build();
393
394        assert_eq!(bytes[0], types::ECHO_REPLY);
395        assert_eq!(u16::from_be_bytes([bytes[4], bytes[5]]), 0x5678);
396        assert_eq!(u16::from_be_bytes([bytes[6], bytes[7]]), 10);
397    }
398
399    #[test]
400    fn test_neighbor_solicitation_builder() {
401        let target = Ipv6Addr::new(0xfe80, 0, 0, 0, 1, 0, 0, 1);
402        let bytes = Icmpv6Builder::neighbor_solicitation(target).build();
403
404        assert_eq!(bytes[0], types::NEIGHBOR_SOLICIT);
405        assert_eq!(bytes.len(), 24); // 8 base + 16 target addr
406
407        let mut addr_bytes = [0u8; 16];
408        addr_bytes.copy_from_slice(&bytes[8..24]);
409        assert_eq!(Ipv6Addr::from(addr_bytes), target);
410    }
411
412    #[test]
413    fn test_neighbor_advertisement_builder() {
414        let target = Ipv6Addr::new(0xfe80, 0, 0, 0, 1, 0, 0, 1);
415        let bytes = Icmpv6Builder::neighbor_advertisement(target).build();
416
417        assert_eq!(bytes[0], types::NEIGHBOR_ADVERT);
418        assert_eq!(bytes.len(), 24); // 8 base + 16 target addr
419        // S and O flags should be set
420        assert_eq!(bytes[4] & 0x60, 0x60);
421    }
422
423    #[test]
424    fn test_router_solicitation_builder() {
425        let bytes = Icmpv6Builder::router_solicitation().build();
426        assert_eq!(bytes[0], types::ROUTER_SOLICIT);
427        assert_eq!(bytes.len(), 8);
428    }
429
430    #[test]
431    fn test_pkt_too_big_builder() {
432        let payload = vec![0xAAu8; 20];
433        let bytes = Icmpv6Builder::pkt_too_big(1500, payload.clone()).build();
434
435        assert_eq!(bytes[0], types::PKT_TOO_BIG);
436        assert_eq!(bytes[1], 0); // code must be 0
437        let mtu = u32::from_be_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
438        assert_eq!(mtu, 1500);
439        assert_eq!(&bytes[8..], payload.as_slice());
440    }
441
442    #[test]
443    fn test_dest_unreachable_builder() {
444        let payload = vec![0u8; 10];
445        let bytes = Icmpv6Builder::dest_unreachable(3, payload.clone()).build();
446
447        assert_eq!(bytes[0], types::DEST_UNREACH);
448        assert_eq!(bytes[1], 3); // Port unreachable
449        assert_eq!(&bytes[8..], payload.as_slice());
450    }
451
452    #[test]
453    fn test_time_exceeded_builder() {
454        let bytes = Icmpv6Builder::time_exceeded(0, vec![]).build();
455        assert_eq!(bytes[0], types::TIME_EXCEEDED);
456        assert_eq!(bytes[1], 0);
457    }
458
459    #[test]
460    fn test_manual_checksum() {
461        let bytes = Icmpv6Builder::echo_request(1, 1).checksum(0xABCD).build();
462
463        assert_eq!(u16::from_be_bytes([bytes[2], bytes[3]]), 0xABCD);
464    }
465
466    #[test]
467    fn test_checksum_verification() {
468        let src = Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1);
469        let dst = Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 2);
470
471        let bytes = Icmpv6Builder::echo_request(0x1234, 1)
472            .set_src_ip(src)
473            .set_dst_ip(dst)
474            .build();
475
476        // Verify the checksum
477        assert!(super::super::verify_icmpv6_checksum(src, dst, &bytes));
478    }
479
480    #[test]
481    fn test_no_checksum_without_src_dst() {
482        let bytes = Icmpv6Builder::echo_request(1, 1).build();
483        // Without src/dst, checksum should be zero
484        let chksum = u16::from_be_bytes([bytes[2], bytes[3]]);
485        assert_eq!(chksum, 0);
486    }
487
488    #[test]
489    fn test_packet_size() {
490        let builder = Icmpv6Builder::echo_request(1, 1).payload(vec![0u8; 10]);
491        assert_eq!(builder.packet_size(), 18); // 8 + 10
492
493        let ns_builder = Icmpv6Builder::neighbor_solicitation(Ipv6Addr::LOCALHOST);
494        assert_eq!(ns_builder.packet_size(), 24); // 8 + 16
495    }
496}