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}