pcapsql_core/protocol/
ipv6.rs

1//! IPv6 protocol parser with extension header support.
2
3use etherparse::Ipv6HeaderSlice;
4use smallvec::SmallVec;
5
6use super::ethernet::ethertype;
7use super::{FieldValue, ParseContext, ParseResult, Protocol, TunnelType};
8use crate::schema::{DataKind, FieldDescriptor};
9
10/// IPv6 Next Header values for extension headers and encapsulation.
11pub mod next_header {
12    pub const HOP_BY_HOP: u8 = 0;
13    /// IPv4 encapsulated in IPv6 (IP-in-IP)
14    pub const IPIP: u8 = 4;
15    #[allow(dead_code)] // RFC constant for protocol identification
16    pub const TCP: u8 = 6;
17    #[allow(dead_code)] // RFC constant for protocol identification
18    pub const UDP: u8 = 17;
19    /// IPv6 encapsulated in IPv6 (IPv6-in-IPv6)
20    pub const IPV6_IN_IP: u8 = 41;
21    pub const ROUTING: u8 = 43;
22    pub const FRAGMENT: u8 = 44;
23    #[allow(dead_code)] // RFC constant for ESP extension header
24    pub const ESP: u8 = 50;
25    pub const AH: u8 = 51;
26    #[allow(dead_code)] // RFC constant for ICMPv6
27    pub const ICMPV6: u8 = 58;
28    #[allow(dead_code)] // RFC constant for no next header
29    pub const NO_NEXT_HEADER: u8 = 59;
30    pub const DESTINATION: u8 = 60;
31    pub const MOBILITY: u8 = 135;
32}
33
34/// Check if a next header value is an extension header.
35fn is_extension_header(nh: u8) -> bool {
36    matches!(
37        nh,
38        next_header::HOP_BY_HOP
39            | next_header::ROUTING
40            | next_header::FRAGMENT
41            | next_header::DESTINATION
42            | next_header::AH
43            | next_header::MOBILITY
44    )
45}
46
47/// IPv6 protocol parser.
48#[derive(Debug, Clone, Copy)]
49pub struct Ipv6Protocol;
50
51impl Protocol for Ipv6Protocol {
52    fn name(&self) -> &'static str {
53        "ipv6"
54    }
55
56    fn display_name(&self) -> &'static str {
57        "IPv6"
58    }
59
60    fn can_parse(&self, context: &ParseContext) -> Option<u32> {
61        match context.hint("ethertype") {
62            Some(et) if et == ethertype::IPV6 as u64 => Some(100),
63            _ => None,
64        }
65    }
66
67    fn parse<'a>(&self, data: &'a [u8], _context: &ParseContext) -> ParseResult<'a> {
68        match Ipv6HeaderSlice::from_slice(data) {
69            Ok(ipv6) => {
70                let mut fields = SmallVec::new();
71
72                fields.push(("version", FieldValue::UInt8(6)));
73                fields.push(("traffic_class", FieldValue::UInt8(ipv6.traffic_class())));
74                fields.push(("flow_label", FieldValue::UInt32(ipv6.flow_label().value())));
75                fields.push(("payload_length", FieldValue::UInt16(ipv6.payload_length())));
76                fields.push(("next_header", FieldValue::UInt8(ipv6.next_header().0)));
77                fields.push(("hop_limit", FieldValue::UInt8(ipv6.hop_limit())));
78                fields.push(("src_ip", FieldValue::ipv6(&ipv6.source())));
79                fields.push(("dst_ip", FieldValue::ipv6(&ipv6.destination())));
80
81                let base_header_len = ipv6.slice().len();
82                let payload = &data[base_header_len..];
83                let first_next_header = ipv6.next_header().0;
84
85                // Parse extension headers
86                let (final_next_header, ext_consumed, ext_fields) =
87                    parse_extension_headers(first_next_header, payload);
88
89                // Merge extension header fields
90                for (k, v) in ext_fields {
91                    fields.push((k, v));
92                }
93
94                let mut child_hints = SmallVec::new();
95                child_hints.push(("ip_protocol", final_next_header as u64));
96                child_hints.push(("ip_version", 6));
97
98                // Check for IP-in-IP encapsulation
99                if final_next_header == next_header::IPIP {
100                    // IPv4 encapsulated in IPv6 - signal tunnel boundary
101                    child_hints.push(("tunnel_type", TunnelType::Ip4InIp6 as u64));
102                    // Set ethertype hint so inner IPv4 can be parsed
103                    child_hints.push(("ethertype", ethertype::IPV4 as u64));
104                } else if final_next_header == next_header::IPV6_IN_IP {
105                    // IPv6 encapsulated in IPv6 - signal tunnel boundary
106                    child_hints.push(("tunnel_type", TunnelType::Ip6InIp6 as u64));
107                    // Set ethertype hint so inner IPv6 can be parsed
108                    child_hints.push(("ethertype", ethertype::IPV6 as u64));
109                }
110
111                let total_consumed = base_header_len + ext_consumed;
112                ParseResult::success(fields, &data[total_consumed..], child_hints)
113            }
114            Err(e) => ParseResult::error(format!("IPv6 parse error: {e}"), data),
115        }
116    }
117
118    fn schema_fields(&self) -> Vec<FieldDescriptor> {
119        vec![
120            // Basic IPv6 header fields
121            FieldDescriptor::new("ipv6.version", DataKind::UInt8).set_nullable(true),
122            FieldDescriptor::new("ipv6.traffic_class", DataKind::UInt8).set_nullable(true),
123            FieldDescriptor::new("ipv6.flow_label", DataKind::UInt32).set_nullable(true),
124            FieldDescriptor::new("ipv6.payload_length", DataKind::UInt16).set_nullable(true),
125            FieldDescriptor::new("ipv6.next_header", DataKind::UInt8).set_nullable(true),
126            FieldDescriptor::new("ipv6.hop_limit", DataKind::UInt8).set_nullable(true),
127            FieldDescriptor::new("ipv6.src_ip", DataKind::String).set_nullable(true),
128            FieldDescriptor::new("ipv6.dst_ip", DataKind::String).set_nullable(true),
129            // Extension header tracking
130            FieldDescriptor::new("ipv6.ext_hop_by_hop", DataKind::Bool).set_nullable(true),
131            FieldDescriptor::new("ipv6.ext_routing", DataKind::Bool).set_nullable(true),
132            FieldDescriptor::new("ipv6.ext_fragment", DataKind::Bool).set_nullable(true),
133            FieldDescriptor::new("ipv6.ext_destination", DataKind::Bool).set_nullable(true),
134            // Fragment header fields
135            FieldDescriptor::new("ipv6.frag_offset", DataKind::UInt16).set_nullable(true),
136            FieldDescriptor::new("ipv6.frag_more", DataKind::Bool).set_nullable(true),
137            FieldDescriptor::new("ipv6.frag_id", DataKind::UInt32).set_nullable(true),
138            // Routing header fields
139            FieldDescriptor::new("ipv6.routing_type", DataKind::UInt8).set_nullable(true),
140            FieldDescriptor::new("ipv6.segments_left", DataKind::UInt8).set_nullable(true),
141        ]
142    }
143
144    fn child_protocols(&self) -> &[&'static str] {
145        &["tcp", "udp", "icmpv6"]
146    }
147
148    fn dependencies(&self) -> &'static [&'static str] {
149        &["ethernet", "vlan", "mpls", "gre", "vxlan", "gtp"]
150    }
151}
152
153/// Parse IPv6 extension headers and return (final_next_header, bytes_consumed, fields).
154fn parse_extension_headers(
155    first_nh: u8,
156    data: &[u8],
157) -> (u8, usize, SmallVec<[(&'static str, FieldValue<'_>); 16]>) {
158    let mut fields = SmallVec::new();
159    let mut offset = 0;
160    let mut current_nh = first_nh;
161
162    // Track which extension headers we've seen
163    let mut has_hop_by_hop = false;
164    let mut has_routing = false;
165    let mut has_fragment = false;
166    let mut has_destination = false;
167
168    while is_extension_header(current_nh) && offset < data.len() {
169        match current_nh {
170            next_header::HOP_BY_HOP => {
171                has_hop_by_hop = true;
172                if let Some((next_nh, consumed)) = parse_generic_ext_header(&data[offset..]) {
173                    current_nh = next_nh;
174                    offset += consumed;
175                } else {
176                    break;
177                }
178            }
179            next_header::ROUTING => {
180                has_routing = true;
181                if let Some((next_nh, consumed, routing_fields)) =
182                    parse_routing_header(&data[offset..])
183                {
184                    for (k, v) in routing_fields {
185                        fields.push((k, v));
186                    }
187                    current_nh = next_nh;
188                    offset += consumed;
189                } else {
190                    break;
191                }
192            }
193            next_header::FRAGMENT => {
194                has_fragment = true;
195                if let Some((next_nh, consumed, frag_fields)) =
196                    parse_fragment_header(&data[offset..])
197                {
198                    for (k, v) in frag_fields {
199                        fields.push((k, v));
200                    }
201                    current_nh = next_nh;
202                    offset += consumed;
203                } else {
204                    break;
205                }
206            }
207            next_header::DESTINATION => {
208                has_destination = true;
209                if let Some((next_nh, consumed)) = parse_generic_ext_header(&data[offset..]) {
210                    current_nh = next_nh;
211                    offset += consumed;
212                } else {
213                    break;
214                }
215            }
216            next_header::AH => {
217                // Authentication Header has different length calculation
218                if let Some((next_nh, consumed)) = parse_ah_header(&data[offset..]) {
219                    current_nh = next_nh;
220                    offset += consumed;
221                } else {
222                    break;
223                }
224            }
225            next_header::MOBILITY => {
226                if let Some((next_nh, consumed)) = parse_generic_ext_header(&data[offset..]) {
227                    current_nh = next_nh;
228                    offset += consumed;
229                } else {
230                    break;
231                }
232            }
233            _ => break,
234        }
235    }
236
237    // Add extension header presence flags
238    fields.push(("ext_hop_by_hop", FieldValue::Bool(has_hop_by_hop)));
239    fields.push(("ext_routing", FieldValue::Bool(has_routing)));
240    fields.push(("ext_fragment", FieldValue::Bool(has_fragment)));
241    fields.push(("ext_destination", FieldValue::Bool(has_destination)));
242
243    (current_nh, offset, fields)
244}
245
246/// Parse a generic extension header (Hop-by-Hop, Destination Options, Mobility).
247/// Returns (next_header, bytes_consumed) or None if parsing fails.
248fn parse_generic_ext_header(data: &[u8]) -> Option<(u8, usize)> {
249    if data.len() < 2 {
250        return None;
251    }
252
253    let next_header = data[0];
254    let hdr_ext_len = data[1] as usize;
255    // Length is in units of 8 octets, not including the first 8 octets
256    let total_len = (hdr_ext_len + 1) * 8;
257
258    if data.len() < total_len {
259        return None;
260    }
261
262    Some((next_header, total_len))
263}
264
265/// Parse Fragment Header.
266/// Returns (next_header, bytes_consumed, fields) or None if parsing fails.
267#[allow(clippy::type_complexity)]
268fn parse_fragment_header(
269    data: &[u8],
270) -> Option<(u8, usize, SmallVec<[(&'static str, FieldValue<'_>); 16]>)> {
271    // Fragment header is exactly 8 bytes
272    if data.len() < 8 {
273        return None;
274    }
275
276    let mut fields = SmallVec::new();
277
278    let next_header = data[0];
279    // data[1] is reserved
280    let frag_offset_and_flags = u16::from_be_bytes([data[2], data[3]]);
281    let frag_offset = frag_offset_and_flags >> 3; // Upper 13 bits
282    let more_fragments = (frag_offset_and_flags & 0x0001) != 0; // Lowest bit is M flag
283    let identification = u32::from_be_bytes([data[4], data[5], data[6], data[7]]);
284
285    fields.push(("frag_offset", FieldValue::UInt16(frag_offset)));
286    fields.push(("frag_more", FieldValue::Bool(more_fragments)));
287    fields.push(("frag_id", FieldValue::UInt32(identification)));
288
289    Some((next_header, 8, fields))
290}
291
292/// Parse Routing Header.
293/// Returns (next_header, bytes_consumed, fields) or None if parsing fails.
294#[allow(clippy::type_complexity)]
295fn parse_routing_header(
296    data: &[u8],
297) -> Option<(u8, usize, SmallVec<[(&'static str, FieldValue<'_>); 16]>)> {
298    if data.len() < 4 {
299        return None;
300    }
301
302    let mut fields = SmallVec::new();
303
304    let next_header = data[0];
305    let hdr_ext_len = data[1] as usize;
306    let routing_type = data[2];
307    let segments_left = data[3];
308
309    // Length is in units of 8 octets, not including the first 8 octets
310    let total_len = (hdr_ext_len + 1) * 8;
311
312    if data.len() < total_len {
313        return None;
314    }
315
316    fields.push(("routing_type", FieldValue::UInt8(routing_type)));
317    fields.push(("segments_left", FieldValue::UInt8(segments_left)));
318
319    Some((next_header, total_len, fields))
320}
321
322/// Parse Authentication Header.
323/// Returns (next_header, bytes_consumed) or None if parsing fails.
324fn parse_ah_header(data: &[u8]) -> Option<(u8, usize)> {
325    if data.len() < 8 {
326        return None;
327    }
328
329    let next_header = data[0];
330    let payload_len = data[1] as usize;
331    // AH length = (payload_len + 2) * 4 bytes
332    let total_len = (payload_len + 2) * 4;
333
334    if data.len() < total_len {
335        return None;
336    }
337
338    Some((next_header, total_len))
339}
340
341#[cfg(test)]
342mod tests {
343    use super::*;
344
345    fn create_ipv6_context() -> ParseContext {
346        let mut context = ParseContext::new(1);
347        context.insert_hint("ethertype", ethertype::IPV6 as u64);
348        context
349    }
350
351    #[test]
352    fn test_parse_ipv6() {
353        // IPv6 header (40 bytes) with TCP next header
354        let header = [
355            0x60, 0x00, 0x00, 0x00, // Version (6) + Traffic class + Flow label
356            0x00, 0x14, // Payload length: 20
357            0x06, // Next header: TCP
358            0x40, // Hop limit: 64
359            // Source: 2001:db8::1
360            0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
361            0x00, 0x01, // Destination: 2001:db8::2
362            0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
363            0x00, 0x02,
364        ];
365
366        let parser = Ipv6Protocol;
367        let context = create_ipv6_context();
368
369        let result = parser.parse(&header, &context);
370
371        assert!(result.is_ok());
372        assert_eq!(result.get("version"), Some(&FieldValue::UInt8(6)));
373        assert_eq!(result.get("hop_limit"), Some(&FieldValue::UInt8(64)));
374        assert_eq!(result.get("next_header"), Some(&FieldValue::UInt8(6)));
375        assert_eq!(result.hint("ip_protocol"), Some(6u64));
376        assert_eq!(result.hint("ip_version"), Some(6u64));
377    }
378
379    #[test]
380    fn test_can_parse_with_ipv6_ethertype() {
381        let parser = Ipv6Protocol;
382
383        // Without ethertype hint
384        let context1 = ParseContext::new(1);
385        assert!(parser.can_parse(&context1).is_none());
386
387        // With IPv4 ethertype
388        let mut context2 = ParseContext::new(1);
389        context2.insert_hint("ethertype", ethertype::IPV4 as u64);
390        assert!(parser.can_parse(&context2).is_none());
391
392        // With IPv6 ethertype
393        let mut context3 = ParseContext::new(1);
394        context3.insert_hint("ethertype", ethertype::IPV6 as u64);
395        assert!(parser.can_parse(&context3).is_some());
396    }
397
398    #[test]
399    fn test_parse_ipv6_with_udp() {
400        // IPv6 header with UDP next header
401        let header = [
402            0x60, 0x0a, 0xbc, 0xde, // Version (6) + Traffic class (0x0a) + Flow label
403            0x00, 0x08, // Payload length: 8 (UDP header)
404            0x11, // Next header: UDP (17)
405            0x80, // Hop limit: 128
406            // Source: ::1
407            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
408            0x00, 0x01, // Destination: ::1
409            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
410            0x00, 0x01,
411        ];
412
413        let parser = Ipv6Protocol;
414        let context = create_ipv6_context();
415
416        let result = parser.parse(&header, &context);
417
418        assert!(result.is_ok());
419        assert_eq!(result.get("next_header"), Some(&FieldValue::UInt8(17)));
420        assert_eq!(result.get("hop_limit"), Some(&FieldValue::UInt8(128)));
421        assert_eq!(result.hint("ip_protocol"), Some(17u64));
422    }
423
424    #[test]
425    fn test_parse_ipv6_too_short() {
426        let short_data = [0x60, 0x00, 0x00, 0x00]; // Only 4 bytes
427
428        let parser = Ipv6Protocol;
429        let context = create_ipv6_context();
430
431        let result = parser.parse(&short_data, &context);
432
433        assert!(!result.is_ok());
434        assert!(result.error.is_some());
435    }
436
437    #[test]
438    fn test_parse_hop_by_hop_options() {
439        // IPv6 header with Hop-by-Hop options header followed by TCP
440        let mut packet = vec![
441            0x60, 0x00, 0x00, 0x00, // Version + Traffic class + Flow label
442            0x00, 0x10, // Payload length: 16 (8 byte HBH + 8 byte TCP stub)
443            0x00, // Next header: Hop-by-Hop (0)
444            0x40, // Hop limit: 64
445        ];
446        // Source: ::1
447        packet.extend_from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
448        // Destination: ::2
449        packet.extend_from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2]);
450        // Hop-by-Hop Options header (8 bytes)
451        packet.extend_from_slice(&[
452            0x06, // Next header: TCP
453            0x00, // Length: 0 (8 bytes total)
454            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Padding
455        ]);
456        // TCP stub
457        packet.extend_from_slice(&[0x00, 0x50, 0x00, 0x51, 0x00, 0x00, 0x00, 0x00]);
458
459        let parser = Ipv6Protocol;
460        let context = create_ipv6_context();
461
462        let result = parser.parse(&packet, &context);
463
464        assert!(result.is_ok());
465        assert_eq!(result.get("ext_hop_by_hop"), Some(&FieldValue::Bool(true)));
466        assert_eq!(result.get("ext_routing"), Some(&FieldValue::Bool(false)));
467        assert_eq!(result.hint("ip_protocol"), Some(6u64)); // TCP
468    }
469
470    #[test]
471    fn test_parse_routing_header() {
472        // IPv6 header with Routing header followed by TCP
473        let mut packet = vec![
474            0x60, 0x00, 0x00, 0x00, // Version + Traffic class + Flow label
475            0x00, 0x10, // Payload length
476            0x2b, // Next header: Routing (43)
477            0x40, // Hop limit: 64
478        ];
479        // Source: ::1
480        packet.extend_from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
481        // Destination: ::2
482        packet.extend_from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2]);
483        // Routing header (8 bytes minimum)
484        packet.extend_from_slice(&[
485            0x06, // Next header: TCP
486            0x00, // Length: 0 (8 bytes total)
487            0x02, // Routing type: 2 (Type 2 Routing Header)
488            0x01, // Segments left: 1
489            0x00, 0x00, 0x00, 0x00, // Reserved/data
490        ]);
491
492        let parser = Ipv6Protocol;
493        let context = create_ipv6_context();
494
495        let result = parser.parse(&packet, &context);
496
497        assert!(result.is_ok());
498        assert_eq!(result.get("ext_routing"), Some(&FieldValue::Bool(true)));
499        assert_eq!(result.get("routing_type"), Some(&FieldValue::UInt8(2)));
500        assert_eq!(result.get("segments_left"), Some(&FieldValue::UInt8(1)));
501        assert_eq!(result.hint("ip_protocol"), Some(6u64));
502    }
503
504    #[test]
505    fn test_parse_fragment_header() {
506        // IPv6 header with Fragment header followed by TCP
507        let mut packet = vec![
508            0x60, 0x00, 0x00, 0x00, // Version + Traffic class + Flow label
509            0x00, 0x10, // Payload length
510            0x2c, // Next header: Fragment (44)
511            0x40, // Hop limit: 64
512        ];
513        // Source: ::1
514        packet.extend_from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
515        // Destination: ::2
516        packet.extend_from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2]);
517        // Fragment header (8 bytes)
518        packet.extend_from_slice(&[
519            0x06, // Next header: TCP
520            0x00, // Reserved
521            0x00, 0x09, // Fragment Offset: 1 (8 bytes), M flag: 1
522            0x12, 0x34, 0x56, 0x78, // Identification
523        ]);
524
525        let parser = Ipv6Protocol;
526        let context = create_ipv6_context();
527
528        let result = parser.parse(&packet, &context);
529
530        assert!(result.is_ok());
531        assert_eq!(result.get("ext_fragment"), Some(&FieldValue::Bool(true)));
532        assert_eq!(result.get("frag_offset"), Some(&FieldValue::UInt16(1)));
533        assert_eq!(result.get("frag_more"), Some(&FieldValue::Bool(true)));
534        assert_eq!(result.get("frag_id"), Some(&FieldValue::UInt32(0x12345678)));
535    }
536
537    #[test]
538    fn test_parse_destination_options() {
539        // IPv6 header with Destination Options header followed by TCP
540        let mut packet = vec![
541            0x60, 0x00, 0x00, 0x00, 0x00, 0x10, 0x3c, // Destination (60)
542            0x40,
543        ];
544        // Source/Destination addresses
545        packet.extend_from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
546        packet.extend_from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2]);
547        // Destination Options header (8 bytes)
548        packet.extend_from_slice(&[0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
549
550        let parser = Ipv6Protocol;
551        let context = create_ipv6_context();
552
553        let result = parser.parse(&packet, &context);
554
555        assert!(result.is_ok());
556        assert_eq!(result.get("ext_destination"), Some(&FieldValue::Bool(true)));
557        assert_eq!(result.hint("ip_protocol"), Some(6u64));
558    }
559
560    #[test]
561    fn test_parse_extension_header_chaining() {
562        // IPv6 with Hop-by-Hop -> Routing -> Fragment -> TCP
563        let mut packet = vec![
564            0x60, 0x00, 0x00, 0x00, 0x00, 0x20, // Payload length: 32
565            0x00, // Hop-by-Hop
566            0x40, // Hop limit
567        ];
568        // Addresses
569        packet.extend_from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
570        packet.extend_from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2]);
571        // Hop-by-Hop (points to Routing)
572        packet.extend_from_slice(&[0x2b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
573        // Routing (points to Fragment)
574        packet.extend_from_slice(&[0x2c, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00]);
575        // Fragment (points to TCP)
576        packet.extend_from_slice(&[0x06, 0x00, 0x00, 0x00, 0xab, 0xcd, 0xef, 0x12]);
577
578        let parser = Ipv6Protocol;
579        let context = create_ipv6_context();
580
581        let result = parser.parse(&packet, &context);
582
583        assert!(result.is_ok());
584        assert_eq!(result.get("ext_hop_by_hop"), Some(&FieldValue::Bool(true)));
585        assert_eq!(result.get("ext_routing"), Some(&FieldValue::Bool(true)));
586        assert_eq!(result.get("ext_fragment"), Some(&FieldValue::Bool(true)));
587        assert_eq!(result.hint("ip_protocol"), Some(6u64));
588    }
589
590    #[test]
591    fn test_fragment_offset_and_m_flag() {
592        // Test fragment header with offset = 185 (1480/8) and M=0 (last fragment)
593        let mut packet = vec![0x60, 0x00, 0x00, 0x00, 0x00, 0x10, 0x2c, 0x40];
594        packet.extend_from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
595        packet.extend_from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2]);
596        // Fragment header: offset=185 (0x0b9 << 3 = 0x05c8), M=0
597        packet.extend_from_slice(&[0x06, 0x00, 0x05, 0xc8, 0x00, 0x00, 0x00, 0x01]);
598
599        let parser = Ipv6Protocol;
600        let context = create_ipv6_context();
601
602        let result = parser.parse(&packet, &context);
603
604        assert!(result.is_ok());
605        assert_eq!(result.get("frag_offset"), Some(&FieldValue::UInt16(185)));
606        assert_eq!(result.get("frag_more"), Some(&FieldValue::Bool(false)));
607    }
608
609    #[test]
610    fn test_segments_left_field() {
611        let mut packet = vec![0x60, 0x00, 0x00, 0x00, 0x00, 0x10, 0x2b, 0x40];
612        packet.extend_from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
613        packet.extend_from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2]);
614        // Routing header with segments_left = 5
615        packet.extend_from_slice(&[0x06, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00]);
616
617        let parser = Ipv6Protocol;
618        let context = create_ipv6_context();
619
620        let result = parser.parse(&packet, &context);
621
622        assert!(result.is_ok());
623        assert_eq!(result.get("segments_left"), Some(&FieldValue::UInt8(5)));
624    }
625
626    #[test]
627    fn test_unknown_extension_header_skipping() {
628        // When we hit an unknown next header value, we should stop parsing extensions
629        let mut packet = vec![0x60, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x40];
630        packet.extend_from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
631        packet.extend_from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2]);
632        // Hop-by-Hop points to protocol 250 (unknown)
633        packet.extend_from_slice(&[0xfa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
634
635        let parser = Ipv6Protocol;
636        let context = create_ipv6_context();
637
638        let result = parser.parse(&packet, &context);
639
640        assert!(result.is_ok());
641        assert_eq!(result.get("ext_hop_by_hop"), Some(&FieldValue::Bool(true)));
642        // Final protocol should be 250 (unknown)
643        assert_eq!(result.hint("ip_protocol"), Some(250u64));
644    }
645
646    #[test]
647    fn test_ipv4_in_ipv6_tunnel() {
648        // IPv6 header with next_header=4 (IPv4 encapsulated in IPv6)
649        let mut packet = vec![
650            0x60, 0x00, 0x00, 0x00, // Version (6) + Traffic class + Flow label
651            0x00, 0x14, // Payload length: 20 (inner IPv4 header)
652            0x04, // Next header: IPIP (4) - IPv4 encapsulated
653            0x40, // Hop limit: 64
654        ];
655        // Source: 2001:db8::1
656        packet.extend_from_slice(&[
657            0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
658            0x00, 0x01,
659        ]);
660        // Destination: 2001:db8::2
661        packet.extend_from_slice(&[
662            0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
663            0x00, 0x02,
664        ]);
665        // Inner IPv4 header stub (first few bytes)
666        packet.extend_from_slice(&[0x45, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00]);
667
668        let parser = Ipv6Protocol;
669        let context = create_ipv6_context();
670
671        let result = parser.parse(&packet, &context);
672
673        assert!(result.is_ok());
674        assert_eq!(result.get("next_header"), Some(&FieldValue::UInt8(4)));
675        // Should set ip_protocol hint for inner protocol
676        assert_eq!(result.hint("ip_protocol"), Some(4u64));
677        // Should set ethertype hint for IPv4 so inner IPv4 parser can match
678        assert_eq!(result.hint("ethertype"), Some(ethertype::IPV4 as u64));
679        // Should indicate tunnel type
680        assert_eq!(
681            result.hint("tunnel_type"),
682            Some(TunnelType::Ip4InIp6 as u64)
683        );
684    }
685
686    #[test]
687    fn test_ipv6_in_ipv6_tunnel() {
688        // IPv6 header with next_header=41 (IPv6 encapsulated in IPv6)
689        let mut packet = vec![
690            0x60, 0x00, 0x00, 0x00, // Version (6) + Traffic class + Flow label
691            0x00, 0x28, // Payload length: 40 (inner IPv6 header)
692            0x29, // Next header: IPv6 (41) - IPv6 encapsulated
693            0x40, // Hop limit: 64
694        ];
695        // Source: 2001:db8::1
696        packet.extend_from_slice(&[
697            0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
698            0x00, 0x01,
699        ]);
700        // Destination: 2001:db8::2
701        packet.extend_from_slice(&[
702            0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
703            0x00, 0x02,
704        ]);
705        // Inner IPv6 header stub
706        packet.extend_from_slice(&[0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x40]);
707
708        let parser = Ipv6Protocol;
709        let context = create_ipv6_context();
710
711        let result = parser.parse(&packet, &context);
712
713        assert!(result.is_ok());
714        assert_eq!(result.get("next_header"), Some(&FieldValue::UInt8(41)));
715        // Should set ip_protocol hint for inner protocol
716        assert_eq!(result.hint("ip_protocol"), Some(41u64));
717        // Should set ethertype hint for IPv6 so inner IPv6 parser can match
718        assert_eq!(result.hint("ethertype"), Some(ethertype::IPV6 as u64));
719        // Should indicate tunnel type
720        assert_eq!(
721            result.hint("tunnel_type"),
722            Some(TunnelType::Ip6InIp6 as u64)
723        );
724    }
725}