Skip to main content

net_wire/ipv4/
packet.rs

1//! IPv4 packet views and header-checksum handling (RFC 791).
2
3use super::address::Ipv4Address;
4use super::protocol::Ipv4Protocol;
5use crate::{error::ParseError, internet_checksum};
6
7pub(super) const HEADER_LENGTH: usize = 20;
8
9/// A structurally validated immutable RFC 791 packet view.
10///
11/// Parsing validates version, IHL, and declared total length, then excludes trailing capture
12/// bytes. It deliberately accepts bad checksums and reserved flags for capture/offload and
13/// malformed-packet inspection.
14#[derive(Clone, Copy, Debug, Eq, PartialEq)]
15pub struct Ipv4Packet<'a> {
16    bytes: &'a [u8],
17    header_length: usize,
18}
19impl<'a> Ipv4Packet<'a> {
20    /// Validates RFC 791 structural bounds; use `checksum_is_valid` for checksum acceptance.
21    #[inline]
22    pub fn parse(bytes: &'a [u8]) -> Result<Self, ParseError> {
23        let (header_length, total_length) = validate(bytes)?;
24        Ok(Self {
25            bytes: &bytes[..total_length],
26            header_length,
27        })
28    }
29    /// Returns the combined DSCP/ECN octet.
30    #[inline]
31    pub fn dscp_ecn(&self) -> u8 {
32        self.bytes[1]
33    }
34    /// Returns the RFC 791 total-length field.
35    #[inline]
36    pub fn total_length(&self) -> u16 {
37        read_u16(self.bytes, 2)
38    }
39    /// Returns the identification field.
40    #[inline]
41    pub fn identification(&self) -> u16 {
42        read_u16(self.bytes, 4)
43    }
44    /// Returns the raw flags and fragment-offset field, including the reserved flag.
45    #[inline]
46    pub fn flags_fragment_offset(&self) -> u16 {
47        read_u16(self.bytes, 6)
48    }
49    /// Returns the time-to-live field.
50    #[inline]
51    pub fn ttl(&self) -> u8 {
52        self.bytes[8]
53    }
54    /// Returns the protocol field while preserving unknown values.
55    #[inline]
56    pub fn protocol(&self) -> Ipv4Protocol {
57        Ipv4Protocol::new(self.bytes[9])
58    }
59    /// Returns the encoded RFC 791 header checksum.
60    #[inline]
61    pub fn header_checksum(&self) -> u16 {
62        read_u16(self.bytes, 10)
63    }
64    /// Returns the source address.
65    #[inline]
66    pub fn source(&self) -> Ipv4Address {
67        Ipv4Address::new(
68            self.bytes[12..16]
69                .try_into()
70                .expect("validated IPv4 header"),
71        )
72    }
73    /// Returns the destination address.
74    #[inline]
75    pub fn destination(&self) -> Ipv4Address {
76        Ipv4Address::new(
77            self.bytes[16..20]
78                .try_into()
79                .expect("validated IPv4 header"),
80        )
81    }
82    /// Returns RFC 791 option bytes included by IHL.
83    #[inline]
84    pub fn options(&self) -> &'a [u8] {
85        &self.bytes[HEADER_LENGTH..self.header_length]
86    }
87    /// Returns declared payload bytes; they are not header-checksum covered.
88    #[inline]
89    pub fn payload(&self) -> &'a [u8] {
90        &self.bytes[self.header_length..]
91    }
92    /// Returns exactly the declared IPv4 packet bytes.
93    #[inline]
94    pub fn as_bytes(&self) -> &'a [u8] {
95        self.bytes
96    }
97    /// Checks the one's-complement checksum across the complete IHL, including options.
98    #[inline]
99    pub fn checksum_is_valid(&self) -> bool {
100        internet_checksum::sum(&self.bytes[..self.header_length]) == 0xffff
101    }
102}
103/// A structurally validated mutable RFC 791 packet view.
104#[derive(Debug, Eq, PartialEq)]
105pub struct Ipv4PacketMut<'a> {
106    bytes: &'a mut [u8],
107    header_length: usize,
108}
109impl<'a> Ipv4PacketMut<'a> {
110    /// Validates RFC 791 structural bounds and excludes trailing bytes.
111    #[inline]
112    pub fn parse(bytes: &'a mut [u8]) -> Result<Self, ParseError> {
113        let (header_length, total_length) = validate(bytes)?;
114        Ok(Self::from_validated(
115            &mut bytes[..total_length],
116            header_length,
117        ))
118    }
119    pub(super) fn from_validated(bytes: &'a mut [u8], header_length: usize) -> Self {
120        Self {
121            bytes,
122            header_length,
123        }
124    }
125    /// Returns the combined DSCP/ECN octet.
126    #[inline]
127    pub fn dscp_ecn(&self) -> u8 {
128        self.bytes[1]
129    }
130    /// Returns the RFC 791 total-length field.
131    #[inline]
132    pub fn total_length(&self) -> u16 {
133        read_u16(self.bytes, 2)
134    }
135    /// Returns the identification field.
136    #[inline]
137    pub fn identification(&self) -> u16 {
138        read_u16(self.bytes, 4)
139    }
140    /// Returns raw flags and fragment offset.
141    #[inline]
142    pub fn flags_fragment_offset(&self) -> u16 {
143        read_u16(self.bytes, 6)
144    }
145    /// Returns the time-to-live field.
146    #[inline]
147    pub fn ttl(&self) -> u8 {
148        self.bytes[8]
149    }
150    /// Returns the protocol value, including unknown values.
151    #[inline]
152    pub fn protocol(&self) -> Ipv4Protocol {
153        Ipv4Protocol::new(self.bytes[9])
154    }
155    /// Returns the encoded header checksum.
156    #[inline]
157    pub fn header_checksum(&self) -> u16 {
158        read_u16(self.bytes, 10)
159    }
160    /// Returns the source address.
161    #[inline]
162    pub fn source(&self) -> Ipv4Address {
163        Ipv4Address::new(
164            self.bytes[12..16]
165                .try_into()
166                .expect("validated IPv4 header"),
167        )
168    }
169    /// Returns the destination address.
170    #[inline]
171    pub fn destination(&self) -> Ipv4Address {
172        Ipv4Address::new(
173            self.bytes[16..20]
174                .try_into()
175                .expect("validated IPv4 header"),
176        )
177    }
178    /// Returns option bytes included by IHL.
179    #[inline]
180    pub fn options(&self) -> &[u8] {
181        &self.bytes[HEADER_LENGTH..self.header_length]
182    }
183    /// Returns payload bytes, which are not header-checksum covered.
184    #[inline]
185    pub fn payload(&self) -> &[u8] {
186        &self.bytes[self.header_length..]
187    }
188    /// Returns mutable options; this does not update the header checksum.
189    #[inline]
190    pub fn options_mut(&mut self) -> &mut [u8] {
191        &mut self.bytes[HEADER_LENGTH..self.header_length]
192    }
193    /// Returns mutable payload bytes, which are not header-checksum covered.
194    #[inline]
195    pub fn payload_mut(&mut self) -> &mut [u8] {
196        &mut self.bytes[self.header_length..]
197    }
198    /// Returns the represented packet bytes.
199    #[inline]
200    pub fn as_bytes(&self) -> &[u8] {
201        self.bytes
202    }
203    /// Returns all represented bytes mutably.
204    #[inline]
205    pub fn as_bytes_mut(&mut self) -> &mut [u8] {
206        self.bytes
207    }
208    /// Replaces DSCP/ECN without updating the checksum.
209    #[inline]
210    pub fn set_dscp_ecn(&mut self, v: u8) {
211        self.bytes[1] = v
212    }
213    /// Replaces identification without updating the checksum.
214    #[inline]
215    pub fn set_identification(&mut self, v: u16) {
216        write_u16(self.bytes, 4, v)
217    }
218    /// Replaces raw flags and fragment offset without updating the checksum.
219    #[inline]
220    pub fn set_flags_fragment_offset(&mut self, v: u16) {
221        write_u16(self.bytes, 6, v)
222    }
223    /// Replaces TTL without updating the checksum.
224    #[inline]
225    pub fn set_ttl(&mut self, v: u8) {
226        self.bytes[8] = v
227    }
228    /// Replaces protocol without updating the checksum.
229    #[inline]
230    pub fn set_protocol(&mut self, v: Ipv4Protocol) {
231        self.bytes[9] = v.raw()
232    }
233    /// Replaces the encoded checksum directly.
234    #[inline]
235    pub fn set_header_checksum(&mut self, v: u16) {
236        write_u16(self.bytes, 10, v)
237    }
238    /// Replaces source without updating the checksum.
239    #[inline]
240    pub fn set_source(&mut self, v: Ipv4Address) {
241        self.bytes[12..16].copy_from_slice(&v.octets())
242    }
243    /// Replaces destination without updating the checksum.
244    #[inline]
245    pub fn set_destination(&mut self, v: Ipv4Address) {
246        self.bytes[16..20].copy_from_slice(&v.octets())
247    }
248    /// Recomputes and writes the checksum across the full IHL, including options.
249    #[inline]
250    pub fn update_header_checksum(&mut self) {
251        self.bytes[10] = 0;
252        self.bytes[11] = 0;
253        write_u16(
254            self.bytes,
255            10,
256            !internet_checksum::sum(&self.bytes[..self.header_length]),
257        )
258    }
259    /// Checks the current checksum across the full IHL.
260    #[inline]
261    pub fn checksum_is_valid(&self) -> bool {
262        internet_checksum::sum(&self.bytes[..self.header_length]) == 0xffff
263    }
264}
265#[inline]
266fn read_u16(bytes: &[u8], offset: usize) -> u16 {
267    u16::from_be_bytes(
268        bytes[offset..offset + 2]
269            .try_into()
270            .expect("validated IPv4 header"),
271    )
272}
273#[inline]
274fn write_u16(bytes: &mut [u8], offset: usize, value: u16) {
275    bytes[offset..offset + 2].copy_from_slice(&value.to_be_bytes())
276}
277#[inline]
278fn validate(bytes: &[u8]) -> Result<(usize, usize), ParseError> {
279    if bytes.len() < HEADER_LENGTH {
280        return Err(ParseError::Truncated {
281            minimum: HEADER_LENGTH,
282            available: bytes.len(),
283        });
284    }
285    let version = bytes[0] >> 4;
286    if version != 4 {
287        return Err(ParseError::InvalidVersion {
288            expected: 4,
289            actual: version,
290        });
291    }
292    let header_length = usize::from(bytes[0] & 15) * 4;
293    if header_length < HEADER_LENGTH {
294        return Err(ParseError::InvalidHeaderLength {
295            minimum: HEADER_LENGTH,
296            actual: header_length,
297        });
298    }
299    if bytes.len() < header_length {
300        return Err(ParseError::Truncated {
301            minimum: header_length,
302            available: bytes.len(),
303        });
304    }
305    let total_length = usize::from(read_u16(bytes, 2));
306    if total_length < header_length {
307        return Err(ParseError::InvalidTotalLength {
308            header_length,
309            total_length,
310        });
311    }
312    if bytes.len() < total_length {
313        return Err(ParseError::Truncated {
314            minimum: total_length,
315            available: bytes.len(),
316        });
317    }
318    Ok((header_length, total_length))
319}