Skip to main content

wireforge_core/
ipv4.rs

1//! IPv4 packet parser and builder.
2
3use alloc::vec;
4use alloc::vec::Vec;
5use core::net::Ipv4Addr;
6
7use crate::types::IpProtocol;
8use crate::util::{internet_checksum, read_u16be, read_u32be, write_u16be, write_u32be, BuildError};
9
10/// Minimum IPv4 header length in bytes (without options).
11pub const IPV4_MIN_HEADER_LEN: usize = 20;
12
13/// Zero-copy IPv4 packet parser.
14#[derive(Debug, Clone)]
15pub struct Ipv4Packet<'a> {
16    buf: &'a [u8],
17}
18
19impl<'a> Ipv4Packet<'a> {
20    /// Attempt to parse an IPv4 packet. Returns `None` if `buf` is shorter than
21    /// the minimum header length, or if the IHL-declared header length exceeds `buf`.
22    pub fn new(buf: &'a [u8]) -> Option<Self> {
23        if buf.len() < IPV4_MIN_HEADER_LEN {
24            return None;
25        }
26        let ihl = (buf[0] & 0x0F) as usize * 4;
27        if ihl < IPV4_MIN_HEADER_LEN || buf.len() < ihl {
28            return None;
29        }
30        Some(Self { buf })
31    }
32
33    #[inline]
34    pub fn version(&self) -> u8 {
35        self.buf[0] >> 4
36    }
37
38    #[inline]
39    pub fn ihl(&self) -> u8 {
40        self.buf[0] & 0x0F
41    }
42
43    #[inline]
44    pub fn dscp(&self) -> u8 {
45        self.buf[1] >> 2
46    }
47
48    #[inline]
49    pub fn ecn(&self) -> u8 {
50        self.buf[1] & 0x03
51    }
52
53    #[inline]
54    pub fn total_length(&self) -> u16 {
55        read_u16be(&self.buf[2..4])
56    }
57
58    #[inline]
59    pub fn identification(&self) -> u16 {
60        read_u16be(&self.buf[4..6])
61    }
62
63    #[inline]
64    pub fn flags(&self) -> u8 {
65        self.buf[6] >> 5
66    }
67
68    #[inline]
69    pub fn dont_fragment(&self) -> bool {
70        self.flags() & 0x02 != 0
71    }
72
73    #[inline]
74    pub fn more_fragments(&self) -> bool {
75        self.flags() & 0x01 != 0
76    }
77
78    #[inline]
79    pub fn fragment_offset(&self) -> u16 {
80        read_u16be(&self.buf[6..8]) & 0x1FFF
81    }
82
83    #[inline]
84    pub fn ttl(&self) -> u8 {
85        self.buf[8]
86    }
87
88    #[inline]
89    pub fn protocol(&self) -> IpProtocol {
90        IpProtocol::from(self.buf[9])
91    }
92
93    #[inline]
94    pub fn checksum(&self) -> u16 {
95        read_u16be(&self.buf[10..12])
96    }
97
98    #[inline]
99    pub fn source(&self) -> Ipv4Addr {
100        Ipv4Addr::from_bits(read_u32be(&self.buf[12..16]))
101    }
102
103    #[inline]
104    pub fn destination(&self) -> Ipv4Addr {
105        Ipv4Addr::from_bits(read_u32be(&self.buf[16..20]))
106    }
107
108    /// IP options bytes (if any).
109    #[inline]
110    pub fn options(&self) -> &'a [u8] {
111        let hdr_len = self.ihl() as usize * 4;
112        if hdr_len > IPV4_MIN_HEADER_LEN {
113            &self.buf[IPV4_MIN_HEADER_LEN..hdr_len]
114        } else {
115            &[]
116        }
117    }
118
119    /// Payload following the IPv4 header.
120    #[inline]
121    pub fn payload(&self) -> &'a [u8] {
122        &self.buf[self.ihl() as usize * 4..]
123    }
124
125    /// Total header length in bytes (IHL × 4).
126    #[inline]
127    pub fn header_length(&self) -> usize {
128        self.ihl() as usize * 4
129    }
130
131    /// Verify the IPv4 header checksum.
132    pub fn verify_checksum(&self) -> bool {
133        internet_checksum(&self.buf[..self.header_length()]) == 0
134    }
135}
136
137// ---------------------------------------------------------------------------
138// Builder
139// ---------------------------------------------------------------------------
140
141/// Builder for IPv4 packets.
142#[derive(Debug, Clone)]
143pub struct Ipv4PacketBuilder {
144    buf: Vec<u8>,
145    payload: Option<Vec<u8>>,
146}
147
148impl Default for Ipv4PacketBuilder {
149    fn default() -> Self {
150        Self::new()
151    }
152}
153
154impl Ipv4PacketBuilder {
155    /// Create a new builder with a default IPv4 header (version=4, IHL=5, TTL=64).
156    pub fn new() -> Self {
157        let mut buf = vec![0u8; IPV4_MIN_HEADER_LEN];
158        buf[0] = 0x45; // version=4, IHL=5
159        buf[8] = 64; // TTL=64
160        Self { buf, payload: None }
161    }
162
163    pub fn dscp(mut self, dscp: u8) -> Self {
164        self.buf[1] = (self.buf[1] & 0x03) | (dscp << 2);
165        self
166    }
167
168    pub fn ecn(mut self, ecn: u8) -> Self {
169        self.buf[1] = (self.buf[1] & 0xFC) | (ecn & 0x03);
170        self
171    }
172
173    pub fn identification(mut self, id: u16) -> Self {
174        write_u16be(&mut self.buf[4..6], id);
175        self
176    }
177
178    pub fn dont_fragment(mut self, df: bool) -> Self {
179        if df {
180            self.buf[6] |= 0x40;
181        } else {
182            self.buf[6] &= !0x40;
183        }
184        self
185    }
186
187    pub fn more_fragments(mut self, mf: bool) -> Self {
188        if mf {
189            self.buf[6] |= 0x20;
190        } else {
191            self.buf[6] &= !0x20;
192        }
193        self
194    }
195
196    pub fn fragment_offset(mut self, offset: u16) -> Self {
197        let current = read_u16be(&self.buf[6..8]);
198        let new = (current & 0xE000) | (offset & 0x1FFF);
199        write_u16be(&mut self.buf[6..8], new);
200        self
201    }
202
203    pub fn ttl(mut self, ttl: u8) -> Self {
204        self.buf[8] = ttl;
205        self
206    }
207
208    pub fn protocol(mut self, proto: IpProtocol) -> Self {
209        self.buf[9] = proto.into();
210        self
211    }
212
213    pub fn source(mut self, addr: Ipv4Addr) -> Self {
214        write_u32be(&mut self.buf[12..16], addr.to_bits());
215        self
216    }
217
218    pub fn destination(mut self, addr: Ipv4Addr) -> Self {
219        write_u32be(&mut self.buf[16..20], addr.to_bits());
220        self
221    }
222
223    pub fn payload(mut self, data: &[u8]) -> Result<Self, BuildError> {
224        if data.len() > 65535 - self.buf.len() {
225            return Err(BuildError::PayloadTooLarge {
226                max: 65535 - self.buf.len(),
227                actual: data.len(),
228            });
229        }
230        self.payload = Some(data.to_vec());
231        Ok(self)
232    }
233
234    /// Build the IPv4 packet, computing total_length and header checksum automatically.
235    pub fn build(mut self) -> Vec<u8> {
236        let total_len = self.buf.len() + self.payload.as_ref().map_or(0, |p| p.len());
237        write_u16be(&mut self.buf[2..4], total_len as u16);
238
239        // Zero the checksum field before computing
240        self.buf[10] = 0;
241        self.buf[11] = 0;
242        let checksum = internet_checksum(&self.buf);
243        write_u16be(&mut self.buf[10..12], checksum);
244
245        let mut packet = self.buf;
246        if let Some(p) = self.payload {
247            packet.extend_from_slice(&p);
248        }
249        packet
250    }
251}
252
253#[cfg(test)]
254mod tests {
255    use super::*;
256
257    // Real IPv4 header: 192.168.10.99 → 172.16.10.12, TCP, TTL=64
258    const SAMPLE_IPV4: &[u8] = &[
259        0x45, 0x00, 0x00, 0x3C, 0x1C, 0x46, 0x40, 0x00,
260        0x40, 0x06, 0x00, 0x00, 0xAC, 0x10, 0x0A, 0x63,
261        0xAC, 0x10, 0x0A, 0x0C,
262    ];
263
264    #[test]
265    fn parse_ipv4() {
266        let pkt = Ipv4Packet::new(SAMPLE_IPV4).unwrap();
267        assert_eq!(pkt.version(), 4);
268        assert_eq!(pkt.ihl(), 5);
269        assert_eq!(pkt.header_length(), 20);
270        assert_eq!(pkt.protocol(), IpProtocol::Tcp);
271        assert_eq!(pkt.ttl(), 64);
272        assert_eq!(pkt.source(), Ipv4Addr::new(172, 16, 10, 99));
273        assert_eq!(pkt.destination(), Ipv4Addr::new(172, 16, 10, 12));
274        assert!(pkt.dont_fragment());
275        assert!(!pkt.more_fragments());
276    }
277
278    #[test]
279    fn parse_too_short() {
280        assert!(Ipv4Packet::new(&[0u8; 19]).is_none());
281        assert!(Ipv4Packet::new(&[]).is_none());
282    }
283
284    #[test]
285    fn parse_ihl_exceeds_buffer() {
286        // IHL=6 (24 bytes) but only 22 bytes provided
287        let mut data = [0u8; 22];
288        data[0] = 0x46; // version=4, IHL=6
289        assert!(Ipv4Packet::new(&data).is_none());
290    }
291
292    #[test]
293    fn verify_checksum() {
294        // Build a packet and verify its checksum
295        let pkt_bytes = Ipv4PacketBuilder::new()
296            .source(Ipv4Addr::new(10, 0, 0, 1))
297            .destination(Ipv4Addr::new(10, 0, 0, 2))
298            .protocol(IpProtocol::Tcp)
299            .ttl(64)
300            .build();
301
302        let pkt = Ipv4Packet::new(&pkt_bytes).unwrap();
303        assert!(pkt.verify_checksum());
304    }
305
306    #[test]
307    fn build_and_parse_roundtrip() {
308        let pkt_bytes = Ipv4PacketBuilder::new()
309            .source(Ipv4Addr::new(192, 168, 1, 1))
310            .destination(Ipv4Addr::new(192, 168, 1, 2))
311            .protocol(IpProtocol::Udp)
312            .ttl(128)
313            .dont_fragment(true)
314            .identification(0x1234)
315            .build();
316
317        let pkt = Ipv4Packet::new(&pkt_bytes).unwrap();
318        assert_eq!(pkt.source(), Ipv4Addr::new(192, 168, 1, 1));
319        assert_eq!(pkt.destination(), Ipv4Addr::new(192, 168, 1, 2));
320        assert_eq!(pkt.protocol(), IpProtocol::Udp);
321        assert_eq!(pkt.ttl(), 128);
322        assert!(pkt.dont_fragment());
323        assert_eq!(pkt.identification(), 0x1234);
324        assert!(pkt.verify_checksum());
325    }
326
327    #[test]
328    fn parse_with_options() {
329        // IPv4 header with 4 bytes of options (IHL=6, total header=24)
330        let mut data = vec![0x46u8, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00,
331            0x40, 0x06, 0x00, 0x00,
332            0xC0, 0xA8, 0x01, 0x01, 0xC0, 0xA8, 0x01, 0x02,
333            0x01, 0x02, 0x03, 0x04, // options
334            0xAA, 0xBB, // payload
335        ];
336        // Compute checksum
337        let csum = internet_checksum(&data[..24]);
338        data[10..12].copy_from_slice(&csum.to_be_bytes());
339
340        let pkt = Ipv4Packet::new(&data).unwrap();
341        assert_eq!(pkt.ihl(), 6);
342        assert_eq!(pkt.header_length(), 24);
343        assert_eq!(pkt.options(), &[0x01, 0x02, 0x03, 0x04]);
344        assert_eq!(pkt.payload(), &[0xAA, 0xBB]);
345        assert!(pkt.verify_checksum());
346    }
347}