Skip to main content

wireforge_core/
ipv6.rs

1//! IPv6 packet parser and builder.
2
3use alloc::vec;
4use alloc::vec::Vec;
5use core::net::Ipv6Addr;
6
7use crate::types::IpProtocol;
8use crate::util::{read_u16be, read_u32be, write_u16be, write_u32be, BuildError};
9
10/// IPv6 fixed header length (40 bytes).
11pub const IPV6_HEADER_LEN: usize = 40;
12
13/// Maximum extension header chain depth before we stop (prevents loops).
14const MAX_EXTENSION_DEPTH: usize = 8;
15
16/// Zero-copy IPv6 packet parser.
17#[derive(Debug, Clone)]
18pub struct Ipv6Packet<'a> {
19    buf: &'a [u8],
20}
21
22impl<'a> Ipv6Packet<'a> {
23    pub fn new(buf: &'a [u8]) -> Option<Self> {
24        if buf.len() < IPV6_HEADER_LEN {
25            return None;
26        }
27        Some(Self { buf })
28    }
29
30    #[inline]
31    pub fn version(&self) -> u8 {
32        self.buf[0] >> 4
33    }
34
35    #[inline]
36    pub fn traffic_class(&self) -> u8 {
37        ((self.buf[0] & 0x0F) << 4) | (self.buf[1] >> 4)
38    }
39
40    #[inline]
41    pub fn flow_label(&self) -> u32 {
42        read_u32be(&self.buf[..4]) & 0x000F_FFFF
43    }
44
45    #[inline]
46    pub fn payload_length(&self) -> u16 {
47        read_u16be(&self.buf[4..6])
48    }
49
50    #[inline]
51    pub fn next_header(&self) -> IpProtocol {
52        IpProtocol::from(self.buf[6])
53    }
54
55    #[inline]
56    pub fn hop_limit(&self) -> u8 {
57        self.buf[7]
58    }
59
60    #[inline]
61    pub fn source(&self) -> Ipv6Addr {
62        let b: [u8; 16] = self.buf[8..24].try_into().unwrap();
63        Ipv6Addr::from(b)
64    }
65
66    #[inline]
67    pub fn destination(&self) -> Ipv6Addr {
68        let b: [u8; 16] = self.buf[24..40].try_into().unwrap();
69        Ipv6Addr::from(b)
70    }
71
72    /// Iterate over IPv6 extension headers.
73    pub fn extension_headers(&self) -> Ipv6ExtensionHeadersIter<'a> {
74        Ipv6ExtensionHeadersIter {
75            next_header: self.next_header(),
76            data: &self.buf[IPV6_HEADER_LEN..],
77            depth: 0,
78        }
79    }
80
81    /// The final upper-layer protocol after skipping all extension headers.
82    pub fn final_protocol(&self) -> IpProtocol {
83        let mut next = self.next_header();
84        let mut remaining = &self.buf[IPV6_HEADER_LEN..];
85        for _ in 0..MAX_EXTENSION_DEPTH {
86            if !is_extension_header(next) {
87                return next;
88            }
89            if remaining.len() < 2 {
90                break;
91            }
92            let hdr_len = extension_header_len(next, remaining);
93            if hdr_len == 0 {
94                break;
95            }
96            next = IpProtocol::from(remaining[0]);
97            if remaining.len() < hdr_len {
98                break;
99            }
100            remaining = &remaining[hdr_len..];
101        }
102        next
103    }
104
105    /// Payload after all extension headers.
106    pub fn payload(&self) -> &'a [u8] {
107        let mut remaining = &self.buf[IPV6_HEADER_LEN..];
108        let mut next = self.next_header();
109        for _ in 0..MAX_EXTENSION_DEPTH {
110            if !is_extension_header(next) {
111                return remaining;
112            }
113            if remaining.len() < 2 {
114                return &[];
115            }
116            let hdr_len = extension_header_len(next, remaining);
117            if remaining.len() < hdr_len {
118                return &[];
119            }
120            next = IpProtocol::from(remaining[0]);
121            remaining = &remaining[hdr_len..];
122        }
123        remaining
124    }
125}
126
127fn is_extension_header(proto: IpProtocol) -> bool {
128    matches!(proto, IpProtocol::Unknown(0 | 43 | 44 | 60))
129}
130
131fn extension_header_len(proto: IpProtocol, data: &[u8]) -> usize {
132    match proto {
133        IpProtocol::Unknown(0 | 60) => (data[1] as usize + 1) * 8,
134        IpProtocol::Unknown(43) => (data[1] as usize + 1) * 8,
135        IpProtocol::Unknown(44) => 8,
136        _ => 0,
137    }
138}
139
140/// IPv6 extension header variant.
141#[derive(Debug, Clone)]
142pub enum Ipv6ExtensionHeader<'a> {
143    HopByHop { next_header: IpProtocol, data: &'a [u8] },
144    Routing { next_header: IpProtocol, routing_type: u8, segments_left: u8, data: &'a [u8] },
145    Fragment { next_header: IpProtocol, fragment_offset: u16, more_fragments: bool, id: u32 },
146    Destination { next_header: IpProtocol, data: &'a [u8] },
147}
148
149/// Iterator over IPv6 extension headers.
150pub struct Ipv6ExtensionHeadersIter<'a> {
151    next_header: IpProtocol,
152    data: &'a [u8],
153    depth: usize,
154}
155
156impl<'a> Iterator for Ipv6ExtensionHeadersIter<'a> {
157    type Item = Ipv6ExtensionHeader<'a>;
158
159    fn next(&mut self) -> Option<Self::Item> {
160        if self.depth >= MAX_EXTENSION_DEPTH || !is_extension_header(self.next_header) {
161            return None;
162        }
163        if self.data.len() < 2 {
164            return None;
165        }
166
167        let current_next = IpProtocol::from(self.data[0]);
168        let hdr_len = extension_header_len(self.next_header, self.data);
169        if self.data.len() < hdr_len || hdr_len == 0 {
170            return None;
171        }
172
173        let hdr_data = &self.data[..hdr_len];
174        let item = match self.next_header {
175            IpProtocol::Unknown(0) => Ipv6ExtensionHeader::HopByHop {
176                next_header: current_next,
177                data: hdr_data,
178            },
179            IpProtocol::Unknown(43) => Ipv6ExtensionHeader::Routing {
180                next_header: current_next,
181                routing_type: hdr_data[2],
182                segments_left: hdr_data[3],
183                data: hdr_data,
184            },
185            IpProtocol::Unknown(44) => {
186                let offset = read_u16be(&hdr_data[2..4]);
187                Ipv6ExtensionHeader::Fragment {
188                    next_header: current_next,
189                    fragment_offset: (offset >> 3) & 0x1FFF,
190                    more_fragments: offset & 0x01 != 0,
191                    id: read_u32be(&hdr_data[4..8]),
192                }
193            }
194            IpProtocol::Unknown(60) => Ipv6ExtensionHeader::Destination {
195                next_header: current_next,
196                data: hdr_data,
197            },
198            _ => return None,
199        };
200
201        self.next_header = current_next;
202        self.data = &self.data[hdr_len..];
203        self.depth += 1;
204
205        Some(item)
206    }
207}
208
209// ---------------------------------------------------------------------------
210// Builder
211// ---------------------------------------------------------------------------
212
213pub struct Ipv6PacketBuilder {
214    buf: Vec<u8>,
215    ext_headers: Vec<(IpProtocol, Vec<u8>)>,
216    payload: Option<Vec<u8>>,
217}
218
219impl Default for Ipv6PacketBuilder {
220    fn default() -> Self {
221        Self::new()
222    }
223}
224
225impl Ipv6PacketBuilder {
226    pub fn new() -> Self {
227        let mut buf = vec![0u8; IPV6_HEADER_LEN];
228        buf[0] = 0x60; // version=6
229        Self { buf, ext_headers: Vec::new(), payload: None }
230    }
231
232    pub fn traffic_class(mut self, tc: u8) -> Self {
233        self.buf[0] = (self.buf[0] & 0xF0) | (tc >> 4);
234        self.buf[1] = (tc & 0x0F) << 4 | (self.buf[1] & 0x0F);
235        self
236    }
237
238    pub fn flow_label(mut self, label: u32) -> Self {
239        let val = read_u32be(&self.buf[..4]);
240        let updated = (val & 0xFFF0_0000) | (label & 0x000F_FFFF);
241        write_u32be(&mut self.buf[..4], updated);
242        self
243    }
244
245    pub fn next_header(mut self, proto: IpProtocol) -> Self {
246        self.buf[6] = proto.into();
247        self
248    }
249
250    pub fn hop_limit(mut self, hl: u8) -> Self {
251        self.buf[7] = hl;
252        self
253    }
254
255    pub fn source(mut self, addr: Ipv6Addr) -> Self {
256        self.buf[8..24].copy_from_slice(&addr.octets());
257        self
258    }
259
260    pub fn destination(mut self, addr: Ipv6Addr) -> Self {
261        self.buf[24..40].copy_from_slice(&addr.octets());
262        self
263    }
264
265    pub fn add_extension_header(mut self, proto: IpProtocol, data: &[u8]) -> Self {
266        // Fragment header is always 8 bytes per RFC 8200.
267        if proto == IpProtocol::Unknown(44) && data.len() != 8 {
268            return self;
269        }
270        // Minimum 2 bytes (next_header + hdr_ext_len), practical minimum is 8.
271        if data.len() < 2 {
272            return self;
273        }
274        self.ext_headers.push((proto, data.to_vec()));
275        self
276    }
277
278    pub fn payload(mut self, data: &[u8]) -> Result<Self, BuildError> {
279        if data.len() > 65535 {
280            return Err(BuildError::PayloadTooLarge { max: 65535, actual: data.len() });
281        }
282        self.payload = Some(data.to_vec());
283        Ok(self)
284    }
285
286    pub fn build(mut self) -> Vec<u8> {
287        // Cap extension headers at MAX_EXTENSION_DEPTH to match parser limits.
288        if self.ext_headers.len() > MAX_EXTENSION_DEPTH {
289            self.ext_headers.truncate(MAX_EXTENSION_DEPTH);
290        }
291
292        let final_proto: u8 = self.buf[6];
293
294        let mut total_ext_len = 0usize;
295        let mut ext_data = Vec::new();
296
297        // Build extension header chain: each header's next_header field
298        // points to the next header in the chain (or the final upper-layer proto).
299        if !self.ext_headers.is_empty() {
300            // Fixed header's next_header → first extension header
301            self.buf[6] = self.ext_headers[0].0.into();
302
303            for i in 0..self.ext_headers.len() {
304                let (_proto, ref data) = self.ext_headers[i];
305                let mut hdr = Vec::with_capacity(data.len());
306                hdr.push(if i + 1 < self.ext_headers.len() {
307                    self.ext_headers[i + 1].0.into()
308                } else {
309                    final_proto
310                });
311                hdr.extend_from_slice(&data[1..]); // skip the first byte of data (next_header)
312                ext_data.extend_from_slice(&hdr);
313                total_ext_len += hdr.len();
314            }
315        }
316
317        let payload_len = total_ext_len + self.payload.as_ref().map_or(0, |p| p.len());
318        write_u16be(&mut self.buf[4..6], payload_len as u16);
319
320        let mut packet = self.buf;
321        packet.extend_from_slice(&ext_data);
322        if let Some(p) = self.payload {
323            packet.extend_from_slice(&p);
324        }
325        packet
326    }
327}
328
329#[cfg(test)]
330mod tests {
331    use super::*;
332
333    // IPv6 header: src=::1, dst=::1, next=ICMPv6, hop_limit=64
334    const SAMPLE_IPV6: &[u8] = &[
335        0x60, 0x00, 0x00, 0x00, // version, TC, flow label
336        0x00, 0x08, // payload length: 8
337        0x3A, // next header: ICMPv6 (58)
338        0x40, // hop limit: 64
339        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
340        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // src: ::1
341        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
342        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // dst: ::1
343    ];
344
345    #[test]
346    fn parse_ipv6() {
347        let pkt = Ipv6Packet::new(SAMPLE_IPV6).unwrap();
348        assert_eq!(pkt.version(), 6);
349        assert_eq!(pkt.next_header(), IpProtocol::Icmpv6);
350        assert_eq!(pkt.hop_limit(), 64);
351        assert_eq!(pkt.source(), Ipv6Addr::LOCALHOST);
352        assert_eq!(pkt.destination(), Ipv6Addr::LOCALHOST);
353        assert_eq!(pkt.payload_length(), 8);
354    }
355
356    #[test]
357    fn parse_too_short() {
358        assert!(Ipv6Packet::new(&[0u8; 39]).is_none());
359        assert!(Ipv6Packet::new(&[]).is_none());
360    }
361
362    #[test]
363    fn build_and_parse_roundtrip() {
364        let src = Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1);
365        let dst = Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 2);
366        let pkt_bytes = Ipv6PacketBuilder::new()
367            .source(src)
368            .destination(dst)
369            .hop_limit(128)
370            .next_header(IpProtocol::Tcp)
371            .payload(&[0xAA, 0xBB])
372            .unwrap()
373            .build();
374
375        let pkt = Ipv6Packet::new(&pkt_bytes).unwrap();
376        assert_eq!(pkt.source(), src);
377        assert_eq!(pkt.destination(), dst);
378        assert_eq!(pkt.hop_limit(), 128);
379        assert_eq!(pkt.next_header(), IpProtocol::Tcp);
380        assert_eq!(pkt.payload(), &[0xAA, 0xBB]);
381    }
382
383    #[test]
384    fn no_ext_headers() {
385        let pkt = Ipv6Packet::new(SAMPLE_IPV6).unwrap();
386        let headers: Vec<_> = pkt.extension_headers().collect();
387        assert!(headers.is_empty());
388        assert_eq!(pkt.final_protocol(), IpProtocol::Icmpv6);
389    }
390
391    /// Build an IPv6 packet with a Hop-by-Hop extension header, then parse it back.
392    #[test]
393    fn single_ext_header_hop_by_hop() {
394        // Hop-by-Hop: next_header=ICMPv6, hdr_ext_len=0 (8 bytes), padding
395        let hbh_data: &[u8] = &[
396            0x3A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
397        ];
398        let payload = &[0xAA, 0xBB, 0xCC, 0xDD];
399        let src = Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1);
400        let dst = Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 2);
401
402        let pkt_bytes = Ipv6PacketBuilder::new()
403            .source(src)
404            .destination(dst)
405            .hop_limit(64)
406            .next_header(IpProtocol::Icmpv6)
407            .add_extension_header(IpProtocol::Unknown(0), hbh_data)
408            .payload(payload)
409            .unwrap()
410            .build();
411
412        let pkt = Ipv6Packet::new(&pkt_bytes).unwrap();
413        assert_eq!(pkt.next_header(), IpProtocol::Unknown(0));
414        assert_eq!(pkt.final_protocol(), IpProtocol::Icmpv6);
415        assert_eq!(pkt.payload(), payload);
416
417        let ext: Vec<_> = pkt.extension_headers().collect();
418        assert_eq!(ext.len(), 1);
419        match &ext[0] {
420            Ipv6ExtensionHeader::HopByHop { next_header, .. } => {
421                assert_eq!(*next_header, IpProtocol::Icmpv6);
422            }
423            _ => panic!("expected HopByHop"),
424        }
425    }
426
427    /// IPv6 with a 3-extension header chain: HopByHop → Routing → Fragment → TCP.
428    #[test]
429    fn multi_ext_header_chain() {
430        let hbh: &[u8] = &[0x2B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; // next=Routing(43)
431        let routing: &[u8] = &[0x2C, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00]; // next=Fragment(44), hdr_ext_len=0 (8 bytes), routing_type=0, segments_left=2
432        let fragment: &[u8] = &[0x06, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x42]; // next=TCP(6), offset=1 (8 bytes), MF=1
433
434        let pkt_bytes = Ipv6PacketBuilder::new()
435            .source(Ipv6Addr::LOCALHOST)
436            .destination(Ipv6Addr::LOCALHOST)
437            .next_header(IpProtocol::Tcp)
438            .add_extension_header(IpProtocol::Unknown(0), hbh)
439            .add_extension_header(IpProtocol::Unknown(43), routing)
440            .add_extension_header(IpProtocol::Unknown(44), fragment)
441            .payload(&[0x01, 0x02])
442            .unwrap()
443            .build();
444
445        let pkt = Ipv6Packet::new(&pkt_bytes).unwrap();
446        assert_eq!(pkt.final_protocol(), IpProtocol::Tcp);
447
448        let ext: Vec<_> = pkt.extension_headers().collect();
449        assert_eq!(ext.len(), 3);
450        assert!(matches!(ext[0], Ipv6ExtensionHeader::HopByHop { .. }));
451        match &ext[1] {
452            Ipv6ExtensionHeader::Routing { routing_type, segments_left, .. } => {
453                assert_eq!(*routing_type, 0);
454                assert_eq!(*segments_left, 2);
455            }
456            _ => panic!("expected Routing"),
457        }
458        match &ext[2] {
459            Ipv6ExtensionHeader::Fragment { fragment_offset, more_fragments, id, .. } => {
460                assert_eq!(*fragment_offset, 1);
461                assert!(more_fragments);
462                assert_eq!(*id, 0x42);
463            }
464            _ => panic!("expected Fragment"),
465        }
466    }
467
468    /// Verify the max-depth guard: extension header chain with 8+ hops should stop.
469    #[test]
470    fn extension_header_depth_limit() {
471        // Build a packet with 9 HopByHop headers chained to test depth limit.
472        // hdr_ext_len=0 means 8 bytes per header.
473        let mut pkt_bytes = Ipv6PacketBuilder::new()
474            .source(Ipv6Addr::LOCALHOST)
475            .destination(Ipv6Addr::LOCALHOST)
476            .next_header(IpProtocol::Tcp);
477        for _ in 0..9 {
478            pkt_bytes = pkt_bytes.add_extension_header(
479                IpProtocol::Unknown(0),
480                &[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
481            );
482        }
483        let pkt_bytes = pkt_bytes.payload(&[0x99]).unwrap().build();
484
485        let pkt = Ipv6Packet::new(&pkt_bytes).unwrap();
486        // Iterator should stop at depth 8; final_protocol will be Unknown(0) (next to 9th).
487        let count = pkt.extension_headers().count();
488        assert_eq!(count, 8, "iterator should stop at MAX_EXTENSION_DEPTH (8)");
489    }
490
491    /// Builder silently drops extension headers with invalid data (too short or wrong Fragment length).
492    #[test]
493    fn builder_rejects_invalid_ext_header_data() {
494        // Fragment header must be exactly 8 bytes; shorter data is dropped.
495        let pkt = Ipv6PacketBuilder::new()
496            .source(Ipv6Addr::LOCALHOST)
497            .destination(Ipv6Addr::LOCALHOST)
498            .next_header(IpProtocol::Tcp)
499            .add_extension_header(IpProtocol::Unknown(44), &[0x06, 0x00, 0x00]) // only 3 bytes
500            .payload(&[0xAA])
501            .unwrap()
502            .build();
503        let parsed = Ipv6Packet::new(&pkt).unwrap();
504        assert_eq!(parsed.extension_headers().count(), 0);
505
506        // Data shorter than 2 bytes is dropped.
507        let pkt = Ipv6PacketBuilder::new()
508            .source(Ipv6Addr::LOCALHOST)
509            .destination(Ipv6Addr::LOCALHOST)
510            .next_header(IpProtocol::Tcp)
511            .add_extension_header(IpProtocol::Unknown(0), &[0x06]) // only 1 byte
512            .payload(&[0xAA])
513            .unwrap()
514            .build();
515        let parsed = Ipv6Packet::new(&pkt).unwrap();
516        assert_eq!(parsed.extension_headers().count(), 0);
517    }
518
519    /// Build and parse roundtrip with valid Fragment extension header (exactly 8 bytes).
520    #[test]
521    fn builder_fragment_ext_header_roundtrip() {
522        let frag: &[u8] = &[0x06, 0x00, 0x00, 0x09, 0x12, 0x34, 0x56, 0x78];
523        let pkt = Ipv6PacketBuilder::new()
524            .source(Ipv6Addr::LOCALHOST)
525            .destination(Ipv6Addr::LOCALHOST)
526            .next_header(IpProtocol::Tcp)
527            .add_extension_header(IpProtocol::Unknown(44), frag)
528            .payload(&[0xCC])
529            .unwrap()
530            .build();
531        let parsed = Ipv6Packet::new(&pkt).unwrap();
532        let ext: Vec<_> = parsed.extension_headers().collect();
533        assert_eq!(ext.len(), 1);
534        match &ext[0] {
535            Ipv6ExtensionHeader::Fragment { fragment_offset, more_fragments, id, .. } => {
536                assert_eq!(*fragment_offset, 1);
537                assert!(more_fragments);
538                assert_eq!(*id, 0x12345678);
539            }
540            _ => panic!("expected Fragment"),
541        }
542    }
543
544    /// Parse a manually constructed IPv6 packet with a single extension header.
545    #[test]
546    fn parse_single_ext_header() {
547        // Construct manually: fixed header + HopByHop + payload
548        let mut data = vec![
549            0x60, 0x00, 0x00, 0x00, // version, TC, flow label
550            0x00, 0x00, // payload_length (will fix up)
551            0x00, // next_header = HopByHop (0)
552            0x40, // hop_limit = 64
553            // src: ::1
554            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
555            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
556            // dst: ::1
557            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
558            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
559            // HopByHop: next=ICMPv6(58), hdr_ext_len=0, padding
560            0x3A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
561            // payload
562            0xAA, 0xBB,
563        ];
564        // Fix payload_length = 8 (HBH) + 2 (payload) = 10
565        data[4] = 0x00;
566        data[5] = 10;
567
568        let pkt = Ipv6Packet::new(&data).unwrap();
569        assert_eq!(pkt.next_header(), IpProtocol::Unknown(0));
570        assert_eq!(pkt.final_protocol(), IpProtocol::Icmpv6);
571        assert_eq!(pkt.payload(), &[0xAA, 0xBB]);
572
573        let ext: Vec<_> = pkt.extension_headers().collect();
574        assert_eq!(ext.len(), 1);
575    }
576}