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