⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/net/
icmpv6.rs

1//! ICMPv6 protocol implementation
2//!
3//! Provides ICMPv6 message handling for IPv6, including echo request/reply
4//! (ping6), destination unreachable, packet too big, time exceeded, and
5//! integration with NDP for neighbor/router discovery.
6
7#![allow(dead_code)] // Phase 7 network stack -- functions called as stack matures
8
9use alloc::vec::Vec;
10
11use super::Ipv6Address;
12use crate::error::KernelError;
13
14// ============================================================================
15// ICMPv6 Message Type Constants
16// ============================================================================
17
18// Error messages (types 0-127)
19/// Destination Unreachable
20pub const ICMPV6_DEST_UNREACHABLE: u8 = 1;
21/// Packet Too Big
22pub const ICMPV6_PACKET_TOO_BIG: u8 = 2;
23/// Time Exceeded
24pub const ICMPV6_TIME_EXCEEDED: u8 = 3;
25/// Parameter Problem
26pub const ICMPV6_PARAMETER_PROBLEM: u8 = 4;
27
28// Informational messages (types 128-255)
29/// Echo Request (ping)
30pub const ICMPV6_ECHO_REQUEST: u8 = 128;
31/// Echo Reply (pong)
32pub const ICMPV6_ECHO_REPLY: u8 = 129;
33
34// NDP message types (handled by ipv6::handle_ndp)
35/// Router Solicitation
36pub const ICMPV6_ROUTER_SOLICIT: u8 = 133;
37/// Router Advertisement
38pub const ICMPV6_ROUTER_ADVERT: u8 = 134;
39/// Neighbor Solicitation
40pub const ICMPV6_NEIGHBOR_SOLICIT: u8 = 135;
41/// Neighbor Advertisement
42pub const ICMPV6_NEIGHBOR_ADVERT: u8 = 136;
43
44// Destination Unreachable codes
45/// No route to destination
46pub const ICMPV6_NO_ROUTE: u8 = 0;
47/// Communication with destination administratively prohibited
48pub const ICMPV6_ADMIN_PROHIBITED: u8 = 1;
49/// Beyond scope of source address
50pub const ICMPV6_BEYOND_SCOPE: u8 = 2;
51/// Address unreachable
52pub const ICMPV6_ADDR_UNREACHABLE: u8 = 3;
53/// Port unreachable
54pub const ICMPV6_PORT_UNREACHABLE: u8 = 4;
55
56// Time Exceeded codes
57/// Hop limit exceeded in transit
58pub const ICMPV6_HOP_LIMIT_EXCEEDED: u8 = 0;
59/// Fragment reassembly time exceeded
60pub const ICMPV6_FRAGMENT_REASSEMBLY_EXCEEDED: u8 = 1;
61
62/// Minimum ICMPv6 header size (type + code + checksum = 4 bytes)
63pub const ICMPV6_HEADER_SIZE: usize = 4;
64
65/// ICMPv6 echo header size (type + code + checksum + id + seq = 8 bytes)
66pub const ICMPV6_ECHO_HEADER_SIZE: usize = 8;
67
68// ============================================================================
69// ICMPv6 Header
70// ============================================================================
71
72/// ICMPv6 message header (4 bytes minimum)
73#[derive(Debug, Clone, Copy)]
74#[repr(C)]
75pub struct Icmpv6Header {
76    /// ICMPv6 message type
77    pub icmp_type: u8,
78    /// Type-specific code
79    pub code: u8,
80    /// Checksum (covers pseudo-header + ICMPv6 message)
81    pub checksum: u16,
82}
83
84impl Icmpv6Header {
85    /// Parse an ICMPv6 header from bytes
86    pub fn from_bytes(data: &[u8]) -> Result<Self, KernelError> {
87        if data.len() < ICMPV6_HEADER_SIZE {
88            return Err(KernelError::InvalidArgument {
89                name: "icmpv6_header",
90                value: "too_short",
91            });
92        }
93
94        Ok(Self {
95            icmp_type: data[0],
96            code: data[1],
97            checksum: u16::from_be_bytes([data[2], data[3]]),
98        })
99    }
100
101    /// Serialize to bytes
102    pub fn to_bytes(&self) -> [u8; ICMPV6_HEADER_SIZE] {
103        let mut bytes = [0u8; ICMPV6_HEADER_SIZE];
104        bytes[0] = self.icmp_type;
105        bytes[1] = self.code;
106        bytes[2..4].copy_from_slice(&self.checksum.to_be_bytes());
107        bytes
108    }
109}
110
111// ============================================================================
112// ICMPv6 Message Handling
113// ============================================================================
114
115/// Handle an incoming ICMPv6 message.
116///
117/// Dispatches to the appropriate handler based on message type:
118/// - Echo Request -> builds Echo Reply
119/// - NDP messages -> delegates to ipv6::handle_ndp
120/// - Error messages -> logs and drops (informational for now)
121///
122/// Returns an optional response packet to send back (fully wrapped in IPv6).
123pub fn handle_icmpv6(
124    src: &Ipv6Address,
125    dst: &Ipv6Address,
126    data: &[u8],
127) -> Result<Option<Vec<u8>>, KernelError> {
128    if data.len() < ICMPV6_HEADER_SIZE {
129        return Err(KernelError::InvalidArgument {
130            name: "icmpv6_packet",
131            value: "too_short",
132        });
133    }
134
135    let header = Icmpv6Header::from_bytes(data)?;
136
137    // Verify checksum
138    if !verify_checksum(src, dst, data) {
139        return Err(KernelError::InvalidArgument {
140            name: "icmpv6_checksum",
141            value: "invalid",
142        });
143    }
144
145    match header.icmp_type {
146        ICMPV6_ECHO_REQUEST => handle_echo_request(src, dst, data),
147        ICMPV6_ECHO_REPLY => {
148            handle_echo_reply(src, data);
149            Ok(None)
150        }
151        ICMPV6_DEST_UNREACHABLE => {
152            handle_dest_unreachable(src, header.code, data);
153            Ok(None)
154        }
155        ICMPV6_PACKET_TOO_BIG => {
156            handle_packet_too_big(src, data);
157            Ok(None)
158        }
159        ICMPV6_TIME_EXCEEDED => {
160            handle_time_exceeded(src, header.code, data);
161            Ok(None)
162        }
163        // NDP messages (133-137) -- delegate to IPv6 NDP handler
164        ICMPV6_ROUTER_SOLICIT
165        | ICMPV6_ROUTER_ADVERT
166        | ICMPV6_NEIGHBOR_SOLICIT
167        | ICMPV6_NEIGHBOR_ADVERT => {
168            if let Some(reply_icmpv6) = super::ipv6::handle_ndp(src, dst, data)? {
169                // NDP handler returns the ICMPv6 payload; wrap in IPv6
170                let reply_src =
171                    super::ipv6::select_source_address(src).unwrap_or(Ipv6Address::UNSPECIFIED);
172                let reply_packet = super::ipv6::build_ipv6(
173                    &reply_src,
174                    src,
175                    super::ipv6::NEXT_HEADER_ICMPV6,
176                    &reply_icmpv6,
177                );
178                // Transmit the NDP reply
179                let _ = super::ipv6::send(
180                    &reply_src,
181                    src,
182                    super::ipv6::NEXT_HEADER_ICMPV6,
183                    &reply_icmpv6,
184                );
185                // We already transmitted, so don't return a packet
186                let _ = reply_packet;
187                Ok(None)
188            } else {
189                Ok(None)
190            }
191        }
192        _ => {
193            // Unknown ICMPv6 type -- silently drop
194            Ok(None)
195        }
196    }
197}
198
199/// Handle an Echo Request (ping) by building and sending an Echo Reply.
200fn handle_echo_request(
201    src: &Ipv6Address,
202    dst: &Ipv6Address,
203    data: &[u8],
204) -> Result<Option<Vec<u8>>, KernelError> {
205    if data.len() < ICMPV6_ECHO_HEADER_SIZE {
206        return Err(KernelError::InvalidArgument {
207            name: "icmpv6_echo",
208            value: "too_short",
209        });
210    }
211
212    // Extract echo identifier and sequence number
213    let id = u16::from_be_bytes([data[4], data[5]]);
214    let seq = u16::from_be_bytes([data[6], data[7]]);
215    let echo_data = &data[ICMPV6_ECHO_HEADER_SIZE..];
216
217    // Determine our source address for the reply
218    let reply_src = super::ipv6::select_source_address(src).unwrap_or(*dst);
219
220    // Build and send echo reply
221    let reply = build_echo_reply(&reply_src, src, id, seq, echo_data);
222
223    // Send the reply via IPv6
224    let _ = super::ipv6::send(&reply_src, src, super::ipv6::NEXT_HEADER_ICMPV6, &reply);
225
226    Ok(None)
227}
228
229/// Handle an Echo Reply -- log the response for ping6 command.
230fn handle_echo_reply(src: &Ipv6Address, data: &[u8]) {
231    if data.len() >= ICMPV6_ECHO_HEADER_SIZE {
232        let id = u16::from_be_bytes([data[4], data[5]]);
233        let seq = u16::from_be_bytes([data[6], data[7]]);
234        let payload_len = data.len() - ICMPV6_ECHO_HEADER_SIZE;
235
236        println!(
237            "[ICMPv6] Echo reply from {}: id={} seq={} len={}",
238            super::ipv6::format_ipv6_compressed(src),
239            id,
240            seq,
241            payload_len,
242        );
243
244        // Update echo reply tracking for the ping6 command
245        LAST_ECHO_REPLY.store(seq as u64, core::sync::atomic::Ordering::Relaxed);
246    }
247}
248
249/// Handle Destination Unreachable message.
250fn handle_dest_unreachable(src: &Ipv6Address, code: u8, _data: &[u8]) {
251    let reason = match code {
252        ICMPV6_NO_ROUTE => "no route to destination",
253        ICMPV6_ADMIN_PROHIBITED => "administratively prohibited",
254        ICMPV6_BEYOND_SCOPE => "beyond scope",
255        ICMPV6_ADDR_UNREACHABLE => "address unreachable",
256        ICMPV6_PORT_UNREACHABLE => "port unreachable",
257        _ => "unknown",
258    };
259    println!(
260        "[ICMPv6] Destination unreachable from {}: {} (code {})",
261        super::ipv6::format_ipv6_compressed(src),
262        reason,
263        code,
264    );
265}
266
267/// Handle Packet Too Big message.
268fn handle_packet_too_big(src: &Ipv6Address, data: &[u8]) {
269    if data.len() >= 8 {
270        let mtu = u32::from_be_bytes([data[4], data[5], data[6], data[7]]);
271        println!(
272            "[ICMPv6] Packet too big from {}: MTU={}",
273            super::ipv6::format_ipv6_compressed(src),
274            mtu,
275        );
276    }
277}
278
279/// Handle Time Exceeded message.
280fn handle_time_exceeded(src: &Ipv6Address, code: u8, _data: &[u8]) {
281    let reason = match code {
282        ICMPV6_HOP_LIMIT_EXCEEDED => "hop limit exceeded",
283        ICMPV6_FRAGMENT_REASSEMBLY_EXCEEDED => "fragment reassembly time exceeded",
284        _ => "unknown",
285    };
286    println!(
287        "[ICMPv6] Time exceeded from {}: {} (code {})",
288        super::ipv6::format_ipv6_compressed(src),
289        reason,
290        code,
291    );
292}
293
294// ============================================================================
295// ICMPv6 Message Construction
296// ============================================================================
297
298/// Build an ICMPv6 Echo Reply message.
299///
300/// Returns the raw ICMPv6 message bytes (not wrapped in IPv6 header).
301pub fn build_echo_reply(
302    src: &Ipv6Address,
303    dst: &Ipv6Address,
304    id: u16,
305    seq: u16,
306    data: &[u8],
307) -> Vec<u8> {
308    let mut msg = Vec::with_capacity(ICMPV6_ECHO_HEADER_SIZE + data.len());
309
310    // ICMPv6 header
311    msg.push(ICMPV6_ECHO_REPLY); // Type
312    msg.push(0); // Code
313    msg.extend_from_slice(&[0u8; 2]); // Checksum (filled later)
314
315    // Echo header
316    msg.extend_from_slice(&id.to_be_bytes());
317    msg.extend_from_slice(&seq.to_be_bytes());
318
319    // Echo data
320    msg.extend_from_slice(data);
321
322    // Compute and fill checksum
323    let checksum = super::ipv6::compute_icmpv6_checksum(&src.0, &dst.0, &msg);
324    msg[2] = (checksum >> 8) as u8;
325    msg[3] = (checksum & 0xff) as u8;
326
327    msg
328}
329
330/// Build an ICMPv6 Echo Request message.
331///
332/// Returns the raw ICMPv6 message bytes (not wrapped in IPv6 header).
333pub fn build_echo_request(
334    src: &Ipv6Address,
335    dst: &Ipv6Address,
336    id: u16,
337    seq: u16,
338    data: &[u8],
339) -> Vec<u8> {
340    let mut msg = Vec::with_capacity(ICMPV6_ECHO_HEADER_SIZE + data.len());
341
342    // ICMPv6 header
343    msg.push(ICMPV6_ECHO_REQUEST); // Type
344    msg.push(0); // Code
345    msg.extend_from_slice(&[0u8; 2]); // Checksum (filled later)
346
347    // Echo header
348    msg.extend_from_slice(&id.to_be_bytes());
349    msg.extend_from_slice(&seq.to_be_bytes());
350
351    // Echo data
352    msg.extend_from_slice(data);
353
354    // Compute and fill checksum
355    let checksum = super::ipv6::compute_icmpv6_checksum(&src.0, &dst.0, &msg);
356    msg[2] = (checksum >> 8) as u8;
357    msg[3] = (checksum & 0xff) as u8;
358
359    msg
360}
361
362/// Build a Destination Unreachable message.
363///
364/// `invoking_packet` should be the start of the invoking IPv6 packet
365/// (as much as possible without exceeding the minimum MTU).
366pub fn build_dest_unreachable(
367    src: &Ipv6Address,
368    dst: &Ipv6Address,
369    code: u8,
370    invoking_packet: &[u8],
371) -> Vec<u8> {
372    // Maximum payload: ensure total ICMPv6 message fits in minimum IPv6 MTU
373    let max_payload = super::ipv6::IPV6_MIN_MTU - super::ipv6::IPV6_HEADER_SIZE - 8;
374    let payload_len = invoking_packet.len().min(max_payload);
375
376    let mut msg = Vec::with_capacity(8 + payload_len);
377
378    msg.push(ICMPV6_DEST_UNREACHABLE); // Type
379    msg.push(code); // Code
380    msg.extend_from_slice(&[0u8; 2]); // Checksum (filled later)
381    msg.extend_from_slice(&[0u8; 4]); // Unused (must be zero)
382    msg.extend_from_slice(&invoking_packet[..payload_len]);
383
384    // Compute and fill checksum
385    let checksum = super::ipv6::compute_icmpv6_checksum(&src.0, &dst.0, &msg);
386    msg[2] = (checksum >> 8) as u8;
387    msg[3] = (checksum & 0xff) as u8;
388
389    msg
390}
391
392/// Build a Packet Too Big message.
393pub fn build_packet_too_big(
394    src: &Ipv6Address,
395    dst: &Ipv6Address,
396    mtu: u32,
397    invoking_packet: &[u8],
398) -> Vec<u8> {
399    let max_payload = super::ipv6::IPV6_MIN_MTU - super::ipv6::IPV6_HEADER_SIZE - 8;
400    let payload_len = invoking_packet.len().min(max_payload);
401
402    let mut msg = Vec::with_capacity(8 + payload_len);
403
404    msg.push(ICMPV6_PACKET_TOO_BIG); // Type
405    msg.push(0); // Code (always 0)
406    msg.extend_from_slice(&[0u8; 2]); // Checksum (filled later)
407    msg.extend_from_slice(&mtu.to_be_bytes()); // MTU
408    msg.extend_from_slice(&invoking_packet[..payload_len]);
409
410    // Compute and fill checksum
411    let checksum = super::ipv6::compute_icmpv6_checksum(&src.0, &dst.0, &msg);
412    msg[2] = (checksum >> 8) as u8;
413    msg[3] = (checksum & 0xff) as u8;
414
415    msg
416}
417
418/// Build a Time Exceeded message.
419pub fn build_time_exceeded(
420    src: &Ipv6Address,
421    dst: &Ipv6Address,
422    code: u8,
423    invoking_packet: &[u8],
424) -> Vec<u8> {
425    let max_payload = super::ipv6::IPV6_MIN_MTU - super::ipv6::IPV6_HEADER_SIZE - 8;
426    let payload_len = invoking_packet.len().min(max_payload);
427
428    let mut msg = Vec::with_capacity(8 + payload_len);
429
430    msg.push(ICMPV6_TIME_EXCEEDED); // Type
431    msg.push(code); // Code
432    msg.extend_from_slice(&[0u8; 2]); // Checksum (filled later)
433    msg.extend_from_slice(&[0u8; 4]); // Unused
434    msg.extend_from_slice(&invoking_packet[..payload_len]);
435
436    // Compute and fill checksum
437    let checksum = super::ipv6::compute_icmpv6_checksum(&src.0, &dst.0, &msg);
438    msg[2] = (checksum >> 8) as u8;
439    msg[3] = (checksum & 0xff) as u8;
440
441    msg
442}
443
444// ============================================================================
445// Checksum Verification
446// ============================================================================
447
448/// Verify the checksum of an incoming ICMPv6 message.
449///
450/// Returns true if the checksum is valid (or zero, which some implementations
451/// skip).
452fn verify_checksum(src: &Ipv6Address, dst: &Ipv6Address, data: &[u8]) -> bool {
453    if data.len() < ICMPV6_HEADER_SIZE {
454        return false;
455    }
456
457    // Compute checksum over the entire message (with existing checksum field
458    // included)
459    let computed = super::ipv6::compute_icmpv6_checksum(&src.0, &dst.0, data);
460
461    // A valid checksum should compute to 0 (since the stored checksum participates)
462    computed == 0
463}
464
465/// Compute ICMPv6 checksum (delegates to ipv6 module).
466///
467/// This is a convenience wrapper for building ICMPv6 messages.
468pub fn compute_icmpv6_checksum(src: &[u8; 16], dst: &[u8; 16], data: &[u8]) -> u16 {
469    super::ipv6::compute_icmpv6_checksum(src, dst, data)
470}
471
472// ============================================================================
473// Echo Reply Tracking (for ping6 command)
474// ============================================================================
475
476/// Last received echo reply sequence number (for ping6 display)
477static LAST_ECHO_REPLY: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
478
479/// Get the last received echo reply sequence number.
480pub fn get_last_echo_reply_seq() -> u64 {
481    LAST_ECHO_REPLY.load(core::sync::atomic::Ordering::Relaxed)
482}
483
484/// Reset the echo reply tracker.
485pub fn reset_echo_reply_tracker() {
486    LAST_ECHO_REPLY.store(0, core::sync::atomic::Ordering::Relaxed);
487}
488
489// ============================================================================
490// ICMPv6 Statistics
491// ============================================================================
492
493/// ICMPv6 statistics
494#[derive(Debug, Clone, Copy, Default)]
495pub struct Icmpv6Stats {
496    /// Echo requests received
497    pub echo_requests_received: u64,
498    /// Echo replies sent
499    pub echo_replies_sent: u64,
500    /// Echo replies received
501    pub echo_replies_received: u64,
502    /// Error messages received
503    pub errors_received: u64,
504    /// NDP messages processed
505    pub ndp_messages: u64,
506}
507
508// ============================================================================
509// Initialization
510// ============================================================================
511
512/// Initialize the ICMPv6 subsystem.
513pub fn init() -> Result<(), KernelError> {
514    println!("[ICMPv6] Initializing ICMPv6...");
515    println!("[ICMPv6] ICMPv6 initialized");
516    Ok(())
517}
518
519// ============================================================================
520// Tests
521// ============================================================================
522
523#[cfg(test)]
524mod tests {
525    use super::*;
526
527    #[test]
528    fn test_icmpv6_header_parse() {
529        let data = [128u8, 0, 0x12, 0x34]; // Echo Request, code 0, checksum 0x1234
530        let header = Icmpv6Header::from_bytes(&data).unwrap();
531        assert_eq!(header.icmp_type, ICMPV6_ECHO_REQUEST);
532        assert_eq!(header.code, 0);
533        assert_eq!(header.checksum, 0x1234);
534    }
535
536    #[test]
537    fn test_icmpv6_header_roundtrip() {
538        let header = Icmpv6Header {
539            icmp_type: ICMPV6_ECHO_REPLY,
540            code: 0,
541            checksum: 0xABCD,
542        };
543        let bytes = header.to_bytes();
544        let parsed = Icmpv6Header::from_bytes(&bytes).unwrap();
545        assert_eq!(parsed.icmp_type, header.icmp_type);
546        assert_eq!(parsed.code, header.code);
547        assert_eq!(parsed.checksum, header.checksum);
548    }
549
550    #[test]
551    fn test_icmpv6_header_too_short() {
552        let data = [128u8, 0];
553        assert!(Icmpv6Header::from_bytes(&data).is_err());
554    }
555
556    #[test]
557    fn test_build_echo_request() {
558        let src = Ipv6Address::LOCALHOST;
559        let dst = Ipv6Address([0xfe, 0x80, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8]);
560        let data = b"ping6";
561        let msg = build_echo_request(&src, &dst, 1, 1, data);
562
563        assert_eq!(msg[0], ICMPV6_ECHO_REQUEST);
564        assert_eq!(msg[1], 0); // code
565                               // id = 1
566        assert_eq!(u16::from_be_bytes([msg[4], msg[5]]), 1);
567        // seq = 1
568        assert_eq!(u16::from_be_bytes([msg[6], msg[7]]), 1);
569        // Payload
570        assert_eq!(&msg[8..], data);
571        // Checksum should be non-zero
572        let checksum = u16::from_be_bytes([msg[2], msg[3]]);
573        assert_ne!(checksum, 0);
574    }
575
576    #[test]
577    fn test_build_echo_reply() {
578        let src = Ipv6Address([0xfe, 0x80, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8]);
579        let dst = Ipv6Address::LOCALHOST;
580        let data = b"pong6";
581        let msg = build_echo_reply(&src, &dst, 42, 7, data);
582
583        assert_eq!(msg[0], ICMPV6_ECHO_REPLY);
584        assert_eq!(u16::from_be_bytes([msg[4], msg[5]]), 42);
585        assert_eq!(u16::from_be_bytes([msg[6], msg[7]]), 7);
586        assert_eq!(&msg[8..], data);
587    }
588
589    #[test]
590    fn test_build_dest_unreachable() {
591        let src = Ipv6Address::LOCALHOST;
592        let dst = Ipv6Address([0xfe, 0x80, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8]);
593        let invoking = [0u8; 64];
594        let msg = build_dest_unreachable(&src, &dst, ICMPV6_PORT_UNREACHABLE, &invoking);
595
596        assert_eq!(msg[0], ICMPV6_DEST_UNREACHABLE);
597        assert_eq!(msg[1], ICMPV6_PORT_UNREACHABLE);
598        assert!(msg.len() >= 8 + 64);
599    }
600
601    #[test]
602    fn test_build_packet_too_big() {
603        let src = Ipv6Address::LOCALHOST;
604        let dst = Ipv6Address::LOCALHOST;
605        let invoking = [0u8; 32];
606        let msg = build_packet_too_big(&src, &dst, 1280, &invoking);
607
608        assert_eq!(msg[0], ICMPV6_PACKET_TOO_BIG);
609        assert_eq!(msg[1], 0);
610        let mtu = u32::from_be_bytes([msg[4], msg[5], msg[6], msg[7]]);
611        assert_eq!(mtu, 1280);
612    }
613
614    #[test]
615    fn test_echo_reply_tracker() {
616        reset_echo_reply_tracker();
617        assert_eq!(get_last_echo_reply_seq(), 0);
618
619        LAST_ECHO_REPLY.store(42, core::sync::atomic::Ordering::Relaxed);
620        assert_eq!(get_last_echo_reply_seq(), 42);
621
622        reset_echo_reply_tracker();
623        assert_eq!(get_last_echo_reply_seq(), 0);
624    }
625}