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