binator_network/ipv4.rs
1//! Handles parsing of IPv4 headers
2
3use std::{
4 fmt::{
5 Display,
6 Formatter,
7 },
8 net::Ipv4Addr,
9};
10
11use binator::{
12 base::{
13 any,
14 nbit,
15 octet,
16 NBit,
17 },
18 utils::{
19 Acc,
20 Utils,
21 UtilsAtom,
22 },
23 Contexting,
24 CoreAtom,
25 Parse,
26 Parsed,
27 Streaming,
28 Success,
29};
30
31use crate::ip_protocol::{
32 self,
33 IPProtocol,
34};
35
36/// <https://en.wikipedia.org/wiki/Internet_Protocol_version_4>
37#[derive(Clone, Debug, PartialEq, Eq)]
38#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
39pub struct IPv4Header<Span> {
40 /// The first header field in an IP packet is the four-bit version field. For
41 /// IPv4, this is always equal to 4.
42 pub version: u8,
43 /// The IPv4 header is variable in size due to the optional 14th field
44 /// (options). The IHL field contains the size of the IPv4 header;
45 /// it has 4 bits that specify the number of 32-bit words in the header.
46 /// The minimum value for this field is 5, which indicates a length of 5 × 32
47 /// bits = 160 bits = 20 bytes. As a 4-bit field, the maximum value is 15;
48 /// this means that the maximum size of the IPv4 header is 15 × 32 bits = 480
49 /// bits = 60 bytes.
50 pub ihl: u8,
51 /// Originally defined as the type of service (ToS),
52 /// this field specifies differentiated services (DiffServ) per RFC 2474.
53 /// Real-time data streaming makes use of the DSCP field.
54 /// An example is Voice over IP (VoIP), which is used for interactive voice
55 /// services.
56 pub tos: u8,
57 /// This 16-bit field defines the entire packet size in bytes, including
58 /// header and data. The minimum size is 20 bytes (header without data) and
59 /// the maximum is 65,535 bytes. All hosts are required to be able to
60 /// reassemble datagrams of size up to 576 bytes, but most modern hosts handle
61 /// much larger packets. Links may impose further restrictions on the packet
62 /// size, in which case datagrams must be fragmented. Fragmentation in IPv4 is
63 /// performed in either the sending host or in routers. Reassembly is
64 /// performed at the receiving host.
65 pub length: u16,
66 /// This field is an identification field and is primarily used for uniquely
67 /// identifying the group of fragments of a single IP datagram. Some
68 /// experimental work has suggested using the ID field for other purposes,
69 /// such as for adding packet-tracing information to help trace datagrams with
70 /// spoofed source addresses, but RFC 6864 now prohibits any such use.
71 pub id: u16,
72 /// A three-bit field follows and is used to control or identify
73 /// fragments. They are (in order, from most significant to least
74 /// significant):
75 ///
76 /// bit 0: Reserved; must be zero.
77 /// bit 1: Don't Fragment (DF)
78 /// bit 2: More Fragments (MF)
79 ///
80 /// If the DF flag is set, and fragmentation is required to route the packet,
81 /// then the packet is dropped. This can be used when sending packets to a
82 /// host that does not have resources to perform reassembly of fragments. It
83 /// can also be used for path MTU discovery, either automatically by the host
84 /// IP software, or manually using diagnostic tools such as ping or
85 /// traceroute. For unfragmented packets, the MF flag is cleared. For
86 /// fragmented packets, all fragments except the last have the MF flag set.
87 /// The last fragment has a non-zero Fragment Offset field, differentiating it
88 /// from an unfragmented packet.
89 pub flags: u8,
90 /// This field specifies the offset of a particular fragment relative to the
91 /// beginning of the original unfragmented IP datagram. The fragmentation
92 /// offset value for the first fragment is always 0. The field is 13 bits
93 /// wide, so that the offset can be from 0 to 8191 (from (20 –1) to (213 –
94 /// 1)). Fragments are specified in units of 8 bytes, which is why fragment
95 /// length must be a multiple of 8. Therefore, the 13-bit field allows a
96 /// maximum offset of (213 – 1) × 8 = 65,528 bytes, with the header length
97 /// included (65,528 + 20 = 65,548 bytes), supporting fragmentation of packets
98 /// exceeding the maximum IP length of 65,535 bytes.
99 pub fragment_offset: u16,
100 /// An eight-bit time to live field limits a datagram's lifetime to
101 /// prevent network failure in the event of a routing loop. It is specified in
102 /// seconds, but time intervals less than 1 second are rounded up to 1. In
103 /// practice, the field is used as a hop count—when the datagram arrives at a
104 /// router, the router decrements the TTL field by one. When the TTL field
105 /// hits zero, the router discards the packet and typically sends an ICMP time
106 /// exceeded message to the sender. The program traceroute sends messages
107 /// with adjusted TTL values and uses these ICMP time exceeded messages to
108 /// identify the routers traversed by packets from the source to the
109 /// destination.
110 pub ttl: u8,
111 /// This field defines the protocol used in the data portion of the IP
112 /// datagram. IANA maintains a list of IP protocol numbers as directed by RFC
113 /// 790.
114 pub protocol: IPProtocol,
115 /// The 16-bit IPv4 header checksum field is used for error-checking of
116 /// the header. When a packet arrives at a router, the router calculates the
117 /// checksum of the header and compares it to the checksum field. If the
118 /// values do not match, the router discards the packet. Errors in the data
119 /// field must be handled by the encapsulated protocol. Both UDP and TCP have
120 /// separate checksums that apply to their data. When a packet arrives at a
121 /// router, the router decreases the TTL field in the header. Consequently,
122 /// the router must calculate a new header checksum. The checksum field is
123 /// the 16 bit one's complement of the one's complement sum of all 16 bit
124 /// words in the header. For purposes of computing the checksum, the value of
125 /// the checksum field is zero.
126 pub chksum: u16,
127 /// This 32-bit field is the IPv4 address of the sender of the packet. Note
128 /// that this address may be changed in transit by a network address
129 /// translation device.
130 pub source_addr: Ipv4Addr,
131 /// This 32-bit field is the IPv4 address of the receiver of the packet. As
132 /// with the source address, this may be changed in transit by a network
133 /// address translation device.
134 pub dest_addr: Ipv4Addr,
135 /// The options field is not often used. Packets containing some options may
136 /// be considered as dangerous by some routers and be blocked. Note that
137 /// the value in the IHL field must include enough extra 32-bit words to hold
138 /// all the options plus any padding needed to ensure that the header contains
139 /// an integer number of 32-bit words. If IHL is greater than 5 (i.e., it is
140 /// from 6 to 15) it means that the options field is present and must be
141 /// considered. The list of options may be terminated with an EOOL (End of
142 /// Options List, 0x00) option; this is only necessary if the end of the
143 /// options would not otherwise coincide with the end of the header.
144 pub options: Span,
145}
146
147/// Ipv4 failure cause
148#[derive(Debug, Clone, PartialEq, Eq)]
149pub enum Ipv4Atom {
150 /// When version is not 4
151 Version(u8),
152 /// When IHL is less than 5
153 IHL(u8),
154}
155
156impl Display for Ipv4Atom {
157 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
158 match self {
159 Ipv4Atom::Version(version) => {
160 write!(f, "Ipv4Context: Version field is not 4 found {}", version)
161 }
162 Ipv4Atom::IHL(ihl) => {
163 write!(f, "Ipv4Context: IHL field is less than 5 found {}", ihl)
164 }
165 }
166 }
167}
168
169/// Parse ipv4 header.
170pub fn ipv4_header<Stream, Context>(
171 stream: Stream,
172) -> Parsed<IPv4Header<Stream::Span>, Stream, Context>
173where
174 Stream: Eq,
175 Stream: Streaming,
176 Stream::Item: Into<u8>,
177 Context: Contexting<CoreAtom<Stream>>,
178 Context: Contexting<UtilsAtom<Stream>>,
179 Context: Contexting<UtilsAtom<Stream>>,
180 Context: Contexting<UtilsAtom<Stream>>,
181 Context: Contexting<Ipv4Atom>,
182{
183 let Success {
184 token: (version, ihl),
185 stream,
186 } = nbit(NBit::FOUR)
187 .try_map(|(version, ihl)| {
188 if version != 4 {
189 Err(Context::new(Ipv4Atom::Version(version)))
190 } else if ihl < 5 {
191 Err(Context::new(Ipv4Atom::IHL(ihl)))
192 } else {
193 Ok((version, ihl))
194 }
195 })
196 .parse(stream)?;
197
198 let Success { token: tos, stream } = octet.parse(stream)?;
199
200 let Success {
201 token: length,
202 stream,
203 } = octet.fill().map(u16::from_be_bytes).parse(stream)?;
204
205 let Success { token: id, stream } = octet.fill().map(u16::from_be_bytes).parse(stream)?;
206
207 let Success {
208 token: (flags, fragment_offset),
209 stream,
210 } = nbit(NBit::FIVE)
211 .and(octet)
212 .map(|((flags, fragment_offset_0), fragment_offset_1)| {
213 (
214 flags,
215 u16::from_be_bytes([fragment_offset_0, fragment_offset_1]),
216 )
217 })
218 .parse(stream)?;
219
220 let Success { token: ttl, stream } = octet.parse(stream)?;
221
222 let Success {
223 token: protocol,
224 stream,
225 } = ip_protocol::ip_protocol.parse(stream)?;
226
227 let Success {
228 token: chksum,
229 stream,
230 } = octet.fill().map(u16::from_be_bytes).parse(stream)?;
231
232 let Success {
233 token: source_addr,
234 stream,
235 } = octet.fill().map(Ipv4Addr::from).parse(stream)?;
236
237 let Success {
238 token: dest_addr,
239 stream,
240 } = octet.fill().map(Ipv4Addr::from).parse(stream)?;
241
242 let Success {
243 token: Success {
244 stream: options, ..
245 },
246 stream,
247 } = any
248 .drop()
249 .fold_bounds(usize::from(ihl - 5) * 4, || (), Acc::acc)
250 .span()
251 .parse(stream)?;
252
253 Parsed::Success {
254 token: IPv4Header {
255 version,
256 ihl,
257 tos,
258 length,
259 id,
260 flags,
261 fragment_offset,
262 ttl,
263 protocol,
264 chksum,
265 source_addr,
266 dest_addr,
267 options,
268 },
269 stream,
270 }
271}
272
273#[cfg(test)]
274mod tests {
275 use std::net::Ipv4Addr;
276
277 use binator::{
278 context::Ignore,
279 Parsed,
280 };
281
282 use super::{
283 IPProtocol,
284 IPv4Header,
285 };
286
287 #[test]
288 fn ipv4_header() {
289 let data = [
290 0x45, 0x00, 0x05, 0xDC, 0x1A, 0xE6, 0x20, 0x00, 0x40, 0x01, 0x22, 0xED, 0x0A, 0x0A, 0x01,
291 0x87, 0x0A, 0x0A, 0x01, 0xB4,
292 ];
293
294 let expectation = IPv4Header {
295 version: 4,
296 ihl: 5,
297 tos: 0,
298 length: 1500,
299 id: 0x1AE6,
300 flags: 0x01,
301 fragment_offset: 0,
302 ttl: 64,
303 protocol: IPProtocol::ICMP,
304 chksum: 0x22ED,
305 source_addr: Ipv4Addr::new(10, 10, 1, 135),
306 dest_addr: Ipv4Addr::new(10, 10, 1, 180),
307 options: "".as_bytes(),
308 };
309 assert_eq!(
310 Parsed::Success {
311 token: expectation,
312 stream: "".as_bytes(),
313 },
314 super::ipv4_header::<_, Ignore>(data.as_slice())
315 );
316 }
317}