Skip to main content

pcap/
lib.rs

1// Copyright 2026 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#![warn(
6    missing_docs,
7    unreachable_patterns,
8    clippy::useless_conversion,
9    clippy::redundant_clone,
10    clippy::precedence
11)]
12
13//! Pcapng parser and serializer.
14//!
15//! The reference document is the [pcapng RFC].
16//!
17//! [pcapng RFC]: https://www.ietf.org/archive/id/draft-ietf-opsawg-pcapng-05.html
18
19use nom::error::{ErrorKind, ParseError};
20use nom::{Finish as _, IResult, Parser};
21use std::borrow::Cow;
22use thiserror::Error;
23use zerocopy::byteorder::little_endian::{U16, U32, U64};
24use zerocopy::{Immutable, IntoBytes};
25
26/// Generated bindings for libpcap.
27#[cfg(feature = "compile")]
28mod bindings;
29/// Module for compiling pcap filters using libpcap.
30#[cfg(feature = "compile")]
31pub mod compile;
32
33/// Link type of an interface.
34///
35/// The values are defined in the pcap linktype [pcapng RFC].
36///
37/// [pcapng RFC]: https://datatracker.ietf.org/doc/html/draft-ietf-opsawg-pcaplinktype-17.
38#[derive(Copy, Clone, Debug, PartialEq, Eq, strum_macros::FromRepr, strum_macros::Display)]
39#[repr(u16)]
40pub enum LinkType {
41    /// Ethernet link type.
42    Ethernet = 1,
43    /// Pure IP link type.
44    PureIp = 101,
45}
46
47impl From<LinkType> for u16 {
48    fn from(link_type: LinkType) -> Self {
49        link_type as u16
50    }
51}
52
53/// Option codes specific to Interface Description Blocks.
54///
55/// See [pcapng RFC Section 4.2] for the full list of codes.
56///
57/// [pcapng RFC Section 4.2]: https://www.ietf.org/archive/id/draft-ietf-opsawg-pcapng-05.html#name-interface-description-block
58#[derive(Copy, Clone, Debug, PartialEq, Eq, strum_macros::FromRepr, strum_macros::Display)]
59#[repr(u16)]
60pub enum InterfaceDescriptionOptionCode {
61    /// End of options.
62    EndOfOpt = 0,
63    /// Interface name.
64    IfName = 2,
65}
66
67impl From<InterfaceDescriptionOptionCode> for u16 {
68    fn from(option_code: InterfaceDescriptionOptionCode) -> Self {
69        option_code as u16
70    }
71}
72
73/// Interface Description Block options.
74///
75/// See [pcapng RFC Section 4.2] for the list of options.
76///
77/// [pcapng RFC Section 4.2]: https://www.ietf.org/archive/id/draft-ietf-opsawg-pcapng-05.html#name-interface-description-block
78#[derive(Debug, PartialEq, Eq)]
79pub enum InterfaceDescriptionOption<'a> {
80    /// Interface name.
81    IfName(Cow<'a, str>),
82    /// End of options.
83    EndOfOpt,
84}
85
86impl<'a> InterfaceDescriptionOption<'a> {
87    /// Returns the Interface Description Block option code as listed in
88    /// [pcapng RFC Section 4.2].
89    ///
90    /// [pcapng RFC Section 4.2]: https://www.ietf.org/archive/id/draft-ietf-opsawg-pcapng-05.html#name-interface-description-block
91    pub fn code(&self) -> InterfaceDescriptionOptionCode {
92        match self {
93            Self::IfName(_) => InterfaceDescriptionOptionCode::IfName,
94            Self::EndOfOpt => InterfaceDescriptionOptionCode::EndOfOpt,
95        }
96    }
97}
98
99/// Errors that can occur while parsing a pcapng file.
100#[derive(Error, Debug, PartialEq)]
101pub enum ParsingError<'a> {
102    /// Encountered a block type different from expected.
103    #[error("Unexpected block type: got {got}, want {want:?}")]
104    UnexpectedBlockType {
105        /// The block type found.
106        got: u32,
107        /// The block type expected.
108        want: BlockType,
109    },
110
111    /// Block length is shorter than the minimum 12 bytes.
112    #[error("Block length is shorter than 12 bytes: {0}")]
113    BlockLengthTooShort(u32),
114
115    /// Header and footer block lengths do not match.
116    #[error("Header and footer block lengths disagree: header {header} footer {footer}")]
117    BlockLengthsDisagree {
118        /// Length in header.
119        header: u32,
120        /// Length in footer.
121        footer: u32,
122    },
123
124    /// Section Header Block options are not supported.
125    #[error("Unsupported Section Header Block options")]
126    SectionHeaderBlockOptionsUnspported,
127
128    /// Enhanced Packet Block options are not supported.
129    #[error("Unsupported Enhanced Packet Block options")]
130    EnhancedPacketBlockOptionsUnspported,
131
132    /// Unsupported option code in Interface Description Block.
133    #[error("Unsupported IDB option code: {0}")]
134    UnsupportedInterfaceDescriptionOption(u16),
135
136    /// End-of-option encountered with non-zero length.
137    #[error("End-of-option encountered with non-zero length")]
138    EndOfOptLengthNotZero(u16),
139
140    /// Duplicate Interface Name option found.
141    #[error("Duplicate Interface Name option: name1 {name1} name2 {name2}")]
142    DuplicateIfNameOption {
143        /// First name.
144        name1: Cow<'a, str>,
145        /// Second name.
146        name2: Cow<'a, str>,
147    },
148
149    /// Data found after end-of-opt.
150    #[error("Data after end-of-opt")]
151    DataAfterEndOfOpt,
152
153    /// Unsupported link type.
154    #[error("Unsupported link type")]
155    UnsupportedLinkType(u16),
156
157    /// Invalid Section Header Block magic.
158    #[error("Invalid Section Header Block magic")]
159    InvalidMagic,
160
161    /// Unsupported Section Header Block major version.
162    #[error("Unsupported Section Header Block major version")]
163    UnsupportedMajorVersion(u16),
164
165    /// Unsupported Section Header Block minor version.
166    #[error("Unsupported Section Header Block minor version")]
167    UnsupportedMinorVersion(u16),
168
169    /// Big endian pcapng is not supported.
170    #[error("Big endian pcapng is not supported")]
171    BigEndianNotSupported,
172
173    /// Nom parsing error.
174    #[error("Nom error at {0:?}: {1:?}")]
175    Nom(&'a [u8], ErrorKind),
176}
177
178impl<'a> ParseError<&'a [u8]> for ParsingError<'a> {
179    fn from_error_kind(input: &'a [u8], kind: ErrorKind) -> Self {
180        ParsingError::Nom(input, kind)
181    }
182    fn append(_: &'a [u8], _: ErrorKind, other: Self) -> Self {
183        // NB: Don't accumulate multiple parse errors. Keep the original.
184        other
185    }
186}
187
188type PcapResult<'a, T> = IResult<&'a [u8], T, ParsingError<'a>>;
189
190fn parse_idb_option<'a>(input: &'a [u8]) -> PcapResult<'a, InterfaceDescriptionOption<'a>> {
191    let (input, code) = nom::number::complete::le_u16(input)?;
192    let (input, len) = nom::number::complete::le_u16(input)?;
193    match InterfaceDescriptionOptionCode::from_repr(code)
194        .ok_or(nom::Err::Failure(ParsingError::UnsupportedInterfaceDescriptionOption(code)))?
195    {
196        InterfaceDescriptionOptionCode::EndOfOpt => {
197            if len != 0 {
198                return Err(nom::Err::Failure(ParsingError::EndOfOptLengthNotZero(len)));
199            }
200            Ok((input, InterfaceDescriptionOption::EndOfOpt))
201        }
202        InterfaceDescriptionOptionCode::IfName => {
203            let (input, value) = nom::bytes::complete::take(len)(input)?;
204            let padding_len = len.next_multiple_of(4) - len;
205            let (input, _) = nom::bytes::complete::take(padding_len)(input)?;
206            // Choose to repair UTF-8 strings with replacement characters.
207            // pcapng RFC Section 3.6.3 states:
208            //
209            //  Implementations MAY discard a string that are invalid UTF-8 or
210            //  MAY repair the string by replacing invalid octet sequences with
211            //  valid sequences.
212            Ok((input, InterfaceDescriptionOption::IfName(String::from_utf8_lossy(value))))
213        }
214    }
215}
216
217/// Options parsed from an Interface Description Block as defined in [pcapng RFC Section 4.2].
218///
219/// [pcapng RFC Section 4.2]: https://www.ietf.org/archive/id/draft-ietf-opsawg-pcapng-05.html#name-interface-description-block
220#[derive(Debug, PartialEq, Eq)]
221pub struct ParsedInterfaceDescriptionOptions<'a> {
222    /// The interface name option.
223    pub if_name: Option<Cow<'a, str>>,
224}
225
226fn parse_idb_options<'a>(
227    mut input: &'a [u8],
228) -> PcapResult<'a, ParsedInterfaceDescriptionOptions<'a>> {
229    let mut rtn = ParsedInterfaceDescriptionOptions { if_name: None };
230    while input.len() > 0 {
231        let (remaining, option) = parse_idb_option(input)?;
232        input = remaining;
233        match option {
234            InterfaceDescriptionOption::EndOfOpt => {
235                if input.len() > 0 {
236                    return Err(nom::Err::Failure(ParsingError::DataAfterEndOfOpt));
237                }
238            }
239            InterfaceDescriptionOption::IfName(if_name) => {
240                if let Some(name) = rtn.if_name.take() {
241                    return Err(nom::Err::Failure(ParsingError::DuplicateIfNameOption {
242                        name1: name,
243                        name2: if_name,
244                    }));
245                }
246                rtn.if_name = Some(if_name);
247            }
248        }
249    }
250    Ok((input, rtn))
251}
252
253/// A parsed Section Header Block as defined in [pcapng RFC Section 4.1].
254///
255/// [pcapng RFC Section 4.1]: https://www.ietf.org/archive/id/draft-ietf-opsawg-pcapng-05.html#name-section-header-block
256#[derive(Debug, PartialEq, Eq)]
257pub struct ParsedSectionHeader {
258    /// The length of the section.
259    pub section_length: u64,
260}
261
262/// Byte order magic as defined in [pcapng RFC Section 4.1].
263///
264/// [pcapng RFC Section 4.1]: https://www.ietf.org/archive/id/draft-ietf-opsawg-pcapng-05.html#name-section-header-block
265const BYTE_ORDER_MAGIC: u32 = 0x1A2B3C4D;
266
267/// A parsed Interface Description Block as defined in [pcapng RFC Section 4.2].
268///
269/// [pcapng RFC Section 4.2]: https://www.ietf.org/archive/id/draft-ietf-opsawg-pcapng-05.html#name-interface-description-block
270#[derive(Debug, PartialEq, Eq)]
271pub struct ParsedInterfaceDescription<'a> {
272    /// The link type of the interface.
273    pub link_type: LinkType,
274    /// The maximum number of bytes captured from each packet.
275    pub snap_len: u32,
276    /// Options associated with the interface.
277    pub options: ParsedInterfaceDescriptionOptions<'a>,
278}
279
280/// A parsed Enhanced Packet Block as defined in [pcapng RFC Section 4.3].
281///
282/// [pcapng RFC Section 4.3]: https://www.ietf.org/archive/id/draft-ietf-opsawg-pcapng-05.html#name-enhanced-packet-block
283#[derive(Debug, PartialEq, Eq)]
284pub struct ParsedEnhancedPacket<'a> {
285    /// The ID of the interface the packet was captured on.
286    pub interface_id: u32,
287    /// The time the packet was captured.
288    pub timestamp: std::time::SystemTime,
289    /// The length of the packet data actually captured.
290    pub captured_length: u32,
291    /// The original length of the packet off the wire.
292    pub original_length: u32,
293    /// The captured packet data.
294    pub packet_data: &'a [u8],
295}
296
297/// Pcapng block type.
298///
299/// See [pcapng RFC Section 10.1] for the list of block type codes.
300///
301/// [pcapng RFC Section 10.1]: https://www.ietf.org/archive/id/draft-ietf-opsawg-pcapng-05.html#section-10.1
302#[derive(Copy, Clone, Debug, PartialEq, Eq, strum_macros::FromRepr, strum_macros::Display)]
303#[repr(u32)]
304pub enum BlockType {
305    /// Section Header Block.
306    SectionHeader = 0x0A0D0D0A,
307    /// Interface Description Block.
308    InterfaceDescription = 0x00000001,
309    /// Enhanced Packet Block.
310    EnhancedPacket = 0x00000006,
311}
312
313impl From<BlockType> for u32 {
314    fn from(block_type: BlockType) -> Self {
315        block_type as u32
316    }
317}
318
319/// A parsed pcapng section.
320///
321/// Provides an iterator to iterate over the Enhanced Packet Blocks.
322#[derive(Debug)]
323pub struct PcapNgSection<'a> {
324    /// The Section Header Block as defined in [pcapng RFC Section 4.1].
325    ///
326    /// [pcapng RFC Section 4.1]: https://www.ietf.org/archive/id/draft-ietf-opsawg-pcapng-05.html#name-section-header-block
327    pub header: ParsedSectionHeader,
328    /// The Interface Description Blocks as defined in [pcapng RFC Section 4.2].
329    ///
330    /// [pcapng RFC Section 4.2]: https://www.ietf.org/archive/id/draft-ietf-opsawg-pcapng-05.html#name-interface-description-block
331    pub interfaces: Vec<ParsedInterfaceDescription<'a>>,
332    input: &'a [u8],
333}
334
335impl<'a> PcapNgSection<'a> {
336    /// Returns an iterator over the packet blocks in the section.
337    pub fn packet_blocks(&self) -> PcapNgPacketIter<'a> {
338        PcapNgPacketIter { input: self.input }
339    }
340}
341
342/// An iterator over Enhanced Packet Blocks in a pcapng section as defined in [pcapng RFC Section 4.3].
343///
344/// [pcapng RFC Section 4.3]: https://www.ietf.org/archive/id/draft-ietf-opsawg-pcapng-05.html#name-enhanced-packet-block
345pub struct PcapNgPacketIter<'a> {
346    input: &'a [u8],
347}
348
349impl<'a> Iterator for PcapNgPacketIter<'a> {
350    type Item = Result<ParsedEnhancedPacket<'a>, nom::Err<ParsingError<'a>>>;
351
352    fn next(&mut self) -> Option<Self::Item> {
353        (!self.input.is_empty()).then(|| {
354            parse_block::<ParsedEnhancedPacket<'a>>(self.input).map(|(rem, epb)| {
355                self.input = rem;
356                epb
357            })
358        })
359    }
360}
361
362const BLOCK_HEADER_FOOTER_LEN: u32 = 12;
363
364/// The only major version of pcapng supported by this crate as found in [pcapng
365/// RFC Section 4.1].
366///
367/// [pcapng RFC section 4.1]: https://www.ietf.org/archive/id/draft-ietf-opsawg-pcapng-05.html#name-section-header-block
368const MAJOR_VERSION: u16 = 1;
369
370/// The only minor version of pcapng supported by this crate as found in [pcapng
371/// RFC Section 4.1].
372///
373/// [pcapng RFC section 4.1]: https://www.ietf.org/archive/id/draft-ietf-opsawg-pcapng-05.html#name-section-header-block
374const MINOR_VERSION: u16 = 0;
375
376/// A trait for types that represent a pcapng block.
377pub trait PcapNgBlock<'a>: Sized {
378    /// The type of block.
379    const BLOCK_TYPE: BlockType;
380    /// Parses the block body excluding the block type and block total length
381    /// header and the repeated block total length footer.
382    fn parse(body: &'a [u8]) -> PcapResult<'a, Self>;
383}
384
385impl<'a> PcapNgBlock<'a> for ParsedSectionHeader {
386    const BLOCK_TYPE: BlockType = BlockType::SectionHeader;
387
388    // Parse a Section Header Block as defined in [pcapng RFC Section 4.1].
389    //
390    // The header is as follows:
391    //
392    //                         1                   2                   3
393    //     0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
394    //    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
395    //  0 |                   Block Type = 0x0A0D0D0A                     |
396    //    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
397    //  4 |                      Block Total Length                       |
398    //    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
399    //  8 |                      Byte-Order Magic                         |
400    //    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
401    // 12 |          Major Version        |         Minor Version         |
402    //    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
403    // 16 |                                                               |
404    //    |                          Section Length                       |
405    //    |                                                               |
406    //    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
407    // 24 /                                                               /
408    //    /                      Options (variable)                       /
409    //    /                                                               /
410    //    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
411    //    |                      Block Total Length                       |
412    //    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
413    //
414    // [pcapng RFC Section 4.1]: https://www.ietf.org/archive/id/draft-ietf-opsawg-pcapng-05.html#name-section-header-block
415    fn parse(body: &'a [u8]) -> PcapResult<'a, Self> {
416        let (body, _magic) = nom::bytes::complete::tag(&BYTE_ORDER_MAGIC.to_le_bytes()[..])(body)?;
417        let (body, major_version) = nom::number::complete::le_u16(body)?;
418        if major_version != MAJOR_VERSION {
419            return Err(nom::Err::Failure(ParsingError::UnsupportedMajorVersion(major_version)));
420        }
421        let (body, minor_version) = nom::number::complete::le_u16(body)?;
422        // According to the pcapng RFC:
423        //
424        //   Some pcapng file writers have used a minor version of 2, but the file
425        //   format did not change incompatibly (new block types were added);
426        //   Readers of pcapng files MUST treat a Minor Version of 2 as equivalent
427        //   to a Minor Version of 0
428        if minor_version != MINOR_VERSION && minor_version != 2 {
429            return Err(nom::Err::Failure(ParsingError::UnsupportedMinorVersion(minor_version)));
430        }
431        let (body, section_length) = nom::number::complete::le_u64(body)?;
432        if body.len() > 0 {
433            return Err(nom::Err::Failure(ParsingError::SectionHeaderBlockOptionsUnspported));
434        }
435        Ok((body, ParsedSectionHeader { section_length }))
436    }
437}
438
439impl<'a> PcapNgBlock<'a> for ParsedInterfaceDescription<'a> {
440    const BLOCK_TYPE: BlockType = BlockType::InterfaceDescription;
441
442    // Parse an Interface Description Block as defined in [pcapng RFC Section 4.2].
443    //
444    // The header is as follows:
445    //
446    //                         1                   2                   3
447    //     0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
448    //    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
449    //  0 |                    Block Type = 0x00000001                    |
450    //    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
451    //  4 |                      Block Total Length                       |
452    //    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
453    //  8 |           LinkType            |           Reserved            |
454    //    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
455    // 12 |                            SnapLen                            |
456    //    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
457    // 16 /                                                               /
458    //    /                      Options (variable)                       /
459    //    /                                                               /
460    //    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
461    //    |                      Block Total Length                       |
462    //    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
463    //
464    // [pcapng RFC Section 4.2]: https://www.ietf.org/archive/id/draft-ietf-opsawg-pcapng-05.html#name-interface-description-block
465    fn parse(body: &'a [u8]) -> PcapResult<'a, Self> {
466        let (body, link_type_code) = nom::number::complete::le_u16(body)?;
467        let link_type = LinkType::from_repr(link_type_code)
468            .ok_or(nom::Err::Failure(ParsingError::UnsupportedLinkType(link_type_code)))?;
469        let (body, _reserved) = nom::number::complete::le_u16(body)?;
470        let (body, snap_len) = nom::number::complete::le_u32(body)?;
471
472        let (body, options) = parse_idb_options(body)?;
473        Ok((body, ParsedInterfaceDescription { link_type, snap_len, options }))
474    }
475}
476
477impl<'a> PcapNgBlock<'a> for ParsedEnhancedPacket<'a> {
478    const BLOCK_TYPE: BlockType = BlockType::EnhancedPacket;
479
480    // Parse an Enhanced Packet Block as defined in [pcapng RFC Section 4.3].
481    //
482    // The header is as follows:
483    //
484    //                         1                   2                   3
485    //     0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
486    //    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
487    //  0 |                    Block Type = 0x00000006                    |
488    //    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
489    //  4 |                      Block Total Length                       |
490    //    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
491    //  8 |                         Interface ID                          |
492    //    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
493    // 12 |                       (Upper 32 bits)                         |
494    //    + - - - - - - - - - - - -  Timestamp  - - - - - - - - - - - - - +
495    // 16 |                       (Lower 32 bits)                         |
496    //    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
497    // 20 |                    Captured Packet Length                     |
498    //    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
499    // 24 |                    Original Packet Length                     |
500    //    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
501    // 28 /                                                               /
502    //    /                          Packet Data                          /
503    //    /              variable length, padded to 32 bits               /
504    //    /                                                               /
505    //    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
506    //    /                                                               /
507    //    /                      Options (variable)                       /
508    //    /                                                               /
509    //    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
510    //    |                      Block Total Length                       |
511    //    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
512    //
513    // [pcapng RFC Section 4.3]: https://www.ietf.org/archive/id/draft-ietf-opsawg-pcapng-05.html#name-enhanced-packet-block
514    fn parse(body: &'a [u8]) -> PcapResult<'a, Self> {
515        let (body, interface_id) = nom::number::complete::le_u32(body)?;
516        let (body, timestamp_high) = nom::number::complete::le_u32(body)?;
517        let (body, timestamp_low) = nom::number::complete::le_u32(body)?;
518        let timestamp = std::time::UNIX_EPOCH
519            + std::time::Duration::from_micros(
520                (u64::from(timestamp_high) << 32) | u64::from(timestamp_low),
521            );
522        let (body, captured_length) = nom::number::complete::le_u32(body)?;
523        let (body, original_length) = nom::number::complete::le_u32(body)?;
524        let (body, packet_data) = nom::bytes::complete::take(captured_length)(body)?;
525        let padded_len = captured_length.next_multiple_of(4) - captured_length;
526        let (body, _) = nom::bytes::complete::take(padded_len)(body)?;
527        if body.len() > 0 {
528            return Err(nom::Err::Failure(ParsingError::EnhancedPacketBlockOptionsUnspported));
529        }
530        Ok((
531            body,
532            ParsedEnhancedPacket {
533                interface_id,
534                timestamp,
535                captured_length,
536                original_length,
537                packet_data,
538            },
539        ))
540    }
541}
542
543fn parse_block<'a, T: PcapNgBlock<'a>>(input: &'a [u8]) -> PcapResult<'a, T> {
544    let (input, block_type) = nom::number::complete::le_u32(input)?;
545    if block_type != u32::from(T::BLOCK_TYPE) {
546        return Err(nom::Err::Error(ParsingError::UnexpectedBlockType {
547            got: block_type,
548            want: T::BLOCK_TYPE,
549        }));
550    }
551
552    // NB: The total_length value should not be used until after the byte order
553    // magic is checked for endianness!
554    let (input, total_length) = nom::number::complete::le_u32(input)?;
555    if T::BLOCK_TYPE == BlockType::SectionHeader {
556        let (_, byte_order_magic) =
557            nom::combinator::peek(nom::bytes::complete::take(4usize)).parse(input)?;
558        if byte_order_magic == BYTE_ORDER_MAGIC.to_be_bytes() {
559            return Err(nom::Err::Failure(ParsingError::BigEndianNotSupported));
560        }
561        if byte_order_magic != BYTE_ORDER_MAGIC.to_le_bytes() {
562            return Err(nom::Err::Failure(ParsingError::InvalidMagic));
563        }
564    }
565    let body_len = total_length
566        .checked_sub(BLOCK_HEADER_FOOTER_LEN)
567        .ok_or(nom::Err::Failure(ParsingError::BlockLengthTooShort(total_length)))?;
568
569    let (input, body) = nom::bytes::complete::take(body_len)(input)?;
570    let (input, verify_length) = nom::number::complete::le_u32(input)?;
571    if total_length != verify_length {
572        return Err(nom::Err::Failure(ParsingError::BlockLengthsDisagree {
573            header: total_length,
574            footer: verify_length,
575        }));
576    }
577
578    let (_, result) = T::parse(body)?;
579    Ok((input, result))
580}
581
582/// Parses a pcapng file from a byte slice.
583///
584/// This parsing logic is not aiming to support general purpose pcap files.
585/// It expects a single section block, which begins with N interface blocks and
586/// ends with M enhanced packet blocks.
587pub fn parse_pcapng<'a>(input: &'a [u8]) -> Result<PcapNgSection<'a>, ParsingError<'a>> {
588    let (input, header) = parse_block::<ParsedSectionHeader>(input).finish()?;
589    let (input, interfaces) =
590        nom::multi::many1(parse_block::<ParsedInterfaceDescription<'a>>).parse(input).finish()?;
591    Ok(PcapNgSection { header, interfaces, input })
592}
593
594/// A Section Header Block structure for serialization as defined in [pcapng RFC Section 4.1].
595///
596/// [pcapng RFC Section 4.1]: https://www.ietf.org/archive/id/draft-ietf-opsawg-pcapng-05.html#name-section-header-block
597#[derive(IntoBytes, Immutable)]
598#[repr(C)]
599pub struct SectionHeaderBlock {
600    /// Block type code.
601    pub block_type: U32,
602    /// Total length of the block.
603    pub block_total_length: U32,
604    /// Byte order magic.
605    pub byte_order_magic: U32,
606    /// Major version.
607    pub major_version: U16,
608    /// Minor version.
609    pub minor_version: U16,
610    /// Section length.
611    pub section_length: U64,
612    /// Total length of the block (repeated).
613    pub block_total_length2: U32,
614}
615
616impl SectionHeaderBlock {
617    /// The size of [`Self`].
618    pub const SIZE: u32 = std::mem::size_of::<Self>() as u32;
619}
620
621/// According to [pcapng RFC Section 4.1]:
622///
623///  If the Section Length is -1 (0xFFFFFFFFFFFFFFFF), this means that the size
624///  of the section is not specified, and the only way to skip the section is to
625///  parse the blocks that it contains.
626///
627/// [pcapng RFC Section 4.1]:
628///     https://www.ietf.org/archive/id/draft-ietf-opsawg-pcapng-05.html#name-section-header-block
629pub const UNSPECIFIED_SECTION_SIZE: u64 = u64::MAX;
630
631impl SectionHeaderBlock {
632    /// Creates a new Section Header Block with default values as defined in
633    /// [pcapng RFC Section 4.1].
634    ///
635    /// [pcapng RFC Section 4.1]:
636    ///     https://www.ietf.org/archive/id/draft-ietf-opsawg-pcapng-05.html#name-section-header-block
637    pub fn new() -> Self {
638        Self {
639            block_type: U32::new(BlockType::SectionHeader.into()),
640            block_total_length: U32::new(Self::SIZE),
641            byte_order_magic: U32::new(BYTE_ORDER_MAGIC),
642            major_version: U16::new(MAJOR_VERSION),
643            minor_version: U16::new(MINOR_VERSION),
644            section_length: U64::new(UNSPECIFIED_SECTION_SIZE),
645            block_total_length2: U32::new(Self::SIZE),
646        }
647    }
648}
649
650/// An Interface Description Block structure for serialization as defined in [pcapng RFC Section 4.2].
651///
652/// [pcapng RFC Section 4.2]: https://www.ietf.org/archive/id/draft-ietf-opsawg-pcapng-05.html#name-interface-description-block
653#[derive(IntoBytes, Immutable)]
654#[repr(C)]
655pub struct InterfaceDescriptionBlockHeader {
656    /// Block type code.
657    pub block_type: U32,
658    /// Total length of the block.
659    pub block_total_length: U32,
660    /// Link type.
661    pub link_type: U16,
662    /// Reserved.
663    pub reserved: U16,
664    /// Maximum number of bytes from the start of a packet that gets captured.
665    pub snap_len: U32,
666}
667
668impl InterfaceDescriptionBlockHeader {
669    /// The size of [`Self`].
670    pub const SIZE: u32 = std::mem::size_of::<Self>() as u32;
671
672    /// Creates a new Interface Description Block header as defined in [pcapng RFC Section 4.2].
673    ///
674    /// [pcapng RFC Section 4.2]: https://www.ietf.org/archive/id/draft-ietf-opsawg-pcapng-05.html#name-interface-description-block
675    pub fn new(link_type: LinkType, block_total_length: u32) -> Self {
676        Self {
677            block_type: U32::new(BlockType::InterfaceDescription.into()),
678            block_total_length: U32::new(block_total_length),
679            link_type: U16::new(link_type.into()),
680            reserved: U16::new(0),
681            snap_len: U32::new(0),
682        }
683    }
684}
685
686/// The size of the block length footer that appears in all pcapng blocks.
687pub const BLOCK_LEN_FOOTER_SIZE: u32 = 4;
688
689/// Write an interface description block with an interface name option.
690pub fn write_interface_description_block<W: std::io::Write>(
691    mut writer: W,
692    link_type: LinkType,
693    interface_name: &str,
694) -> Result<(), std::io::Error> {
695    let total_len = InterfaceDescriptionBlockHeader::SIZE
696        + OptionHeader::SIZE // if_name option header
697        + u32::try_from(interface_name.len().next_multiple_of(4)).unwrap()
698        + OptionHeader::SIZE // end_of_opt option header
699        + BLOCK_LEN_FOOTER_SIZE;
700    let idb_header = InterfaceDescriptionBlockHeader::new(link_type, total_len);
701    writer.write_all(idb_header.as_bytes())?;
702    write_if_name_option(&mut writer, interface_name)?;
703    writer.write_all(OptionHeader::new_end_of_opt().as_bytes())?;
704    writer.write_all(&total_len.to_le_bytes())?;
705    Ok(())
706}
707
708/// Writes a Section Header Block and an Interface Description Block.
709pub fn write_prelude<W: std::io::Write>(
710    mut writer: W,
711    link_type: LinkType,
712    interface_name: &str,
713) -> Result<(), std::io::Error> {
714    writer.write_all(SectionHeaderBlock::new().as_bytes())?;
715    write_interface_description_block(&mut writer, link_type, interface_name)?;
716    Ok(())
717}
718
719/// An Enhanced Packet Block structure for serialization as defined in [pcapng RFC Section 4.3].
720///
721/// [pcapng RFC Section 4.3]: https://www.ietf.org/archive/id/draft-ietf-opsawg-pcapng-05.html#name-enhanced-packet-block
722#[derive(IntoBytes, Immutable)]
723#[repr(C)]
724pub struct EnhancedPacketBlockHeader {
725    /// Block type code.
726    pub block_type: U32,
727    /// Total length of the block.
728    pub block_total_length: U32,
729    /// Interface ID.
730    pub interface_id: U32,
731    /// Upper 32 bits of timestamp.
732    pub timestamp_upper: U32,
733    /// Lower 32 bits of timestamp.
734    pub timestamp_lower: U32,
735    /// Captured packet length.
736    pub captured_packet_length: U32,
737    /// Original packet length.
738    pub original_packet_length: U32,
739}
740
741impl EnhancedPacketBlockHeader {
742    /// The size of [`Self`].
743    pub const SIZE: u32 = std::mem::size_of::<Self>() as u32;
744
745    /// Creates a new Enhanced Packet Block header as defined in [pcapng RFC Section 4.3].
746    ///
747    /// [pcapng RFC Section 4.3]: https://www.ietf.org/archive/id/draft-ietf-opsawg-pcapng-05.html#name-enhanced-packet-block
748    pub fn new(
749        block_total_length: u32,
750        interface_id: u32,
751        timestamp: std::time::SystemTime,
752        captured_packet_length: u32,
753        original_packet_length: u32,
754    ) -> Self {
755        let timestamp = timestamp
756            .duration_since(std::time::UNIX_EPOCH)
757            .expect("current time should be after UNIX epoch")
758            .as_micros();
759        let timestamp = u64::try_from(timestamp)
760            .expect("current time overflows microseconds since UNIX epoch in u64");
761        Self {
762            block_type: U32::new(BlockType::EnhancedPacket.into()),
763            block_total_length: U32::new(block_total_length),
764            interface_id: U32::new(interface_id),
765            timestamp_upper: U32::new((timestamp >> 32) as u32),
766            timestamp_lower: U32::new(timestamp as u32),
767            captured_packet_length: U32::new(captured_packet_length),
768            original_packet_length: U32::new(original_packet_length),
769        }
770    }
771}
772
773/// Writes an Enhanced Packet Block.
774pub fn write_enhanced_packet_block<W: std::io::Write>(
775    mut writer: W,
776    interface_id: u32,
777    timestamp: std::time::SystemTime,
778    packet: &[u8],
779    original_packet_len: u32,
780) -> Result<(), std::io::Error> {
781    let packet_padded_len = packet.len().next_multiple_of(4);
782    let total_len = EnhancedPacketBlockHeader::SIZE
783        + u32::try_from(packet_padded_len).unwrap()
784        + BLOCK_LEN_FOOTER_SIZE;
785    let header = EnhancedPacketBlockHeader::new(
786        total_len,
787        interface_id,
788        timestamp,
789        u32::try_from(packet.len()).unwrap(),
790        original_packet_len,
791    );
792    writer.write_all(header.as_bytes())?;
793    writer.write_all(packet)?;
794    writer.write_all(&[0; 3][..packet_padded_len - packet.len()])?;
795    writer.write_all(&total_len.to_le_bytes())?;
796    Ok(())
797}
798
799/// An option header structure for serialization.
800#[derive(IntoBytes, Immutable)]
801#[repr(C)]
802pub struct OptionHeader {
803    /// Option code.
804    pub code: U16,
805    /// Option length.
806    pub length: U16,
807}
808
809impl OptionHeader {
810    /// The size of [`Self`].
811    pub const SIZE: u32 = std::mem::size_of::<Self>() as u32;
812
813    /// Creates a new Option Header for EndOfOpt.
814    pub fn new_end_of_opt() -> Self {
815        Self {
816            code: U16::new(InterfaceDescriptionOptionCode::EndOfOpt.into()),
817            length: U16::new(0),
818        }
819    }
820
821    fn new_if_name(length: u16) -> Self {
822        Self {
823            code: U16::new(InterfaceDescriptionOptionCode::IfName.into()),
824            length: U16::new(length),
825        }
826    }
827}
828
829/// Writes a single if_name option.
830///
831/// `buf` must be long enough to write the option and padding.
832pub fn write_if_name_option<W: std::io::Write>(mut writer: W, name: &str) -> std::io::Result<()> {
833    let len = name.len();
834    let total_len = len.next_multiple_of(4);
835    writer.write_all(OptionHeader::new_if_name(len.try_into().unwrap()).as_bytes())?;
836    writer.write_all(name.as_bytes())?;
837    writer.write_all(&[0; 3][..total_len - len])?;
838    Ok(())
839}
840
841#[cfg(test)]
842mod tests {
843    use super::*;
844    use zerocopy::IntoBytes;
845    use zerocopy::byteorder::little_endian::U16;
846
847    #[test]
848    fn test_parse_idb_options() {
849        let mut buf = Vec::new();
850        write_if_name_option(&mut buf, "lo").expect("write if name option");
851        buf.extend_from_slice(OptionHeader::new_end_of_opt().as_bytes());
852
853        let (rem, options) = parse_idb_options(&buf).expect("parse options failed");
854        assert!(rem.is_empty());
855        assert_eq!(
856            options,
857            ParsedInterfaceDescriptionOptions { if_name: Some(Cow::Borrowed("lo")) }
858        );
859    }
860
861    #[test]
862    fn test_write_idb_includes_end_of_opt() {
863        let mut buf = Vec::new();
864        let if_name = "lo";
865        write_interface_description_block(&mut buf, LinkType::Ethernet, if_name)
866            .expect("write interface description block");
867
868        // Sanity check with regular parser.
869        let (remaining, _parsed_idb) =
870            parse_block::<ParsedInterfaceDescription<'_>>(&buf).expect("parse block failed");
871        assert!(remaining.is_empty());
872
873        // Now strictly verify EndOfOpt is present by parsing options manually.
874        // IDB header is 16 bytes. Footer is 4 bytes.
875        let option_start_offset = std::mem::size_of::<InterfaceDescriptionBlockHeader>();
876        let option_end_offset = buf.len()
877            - usize::try_from(BLOCK_LEN_FOOTER_SIZE).expect("block len footer size fits in usize");
878        let mut options_buf = &buf[option_start_offset..option_end_offset];
879
880        let mut end_of_opt_found = false;
881        while !options_buf.is_empty() {
882            let (rem, option) = parse_idb_option(options_buf).expect("parse option failed");
883            options_buf = rem;
884            if let InterfaceDescriptionOption::EndOfOpt = option {
885                assert_eq!(options_buf, &[] as &[u8]);
886                end_of_opt_found = true;
887                break;
888            }
889        }
890        assert!(end_of_opt_found);
891    }
892
893    #[test]
894    fn test_parse_options_succeed_no_end_of_opt() {
895        let mut buf = Vec::new();
896        write_if_name_option(&mut buf, "lo").expect("write if name option");
897
898        let (rem, options) = parse_idb_options(&buf).expect("parse options failed");
899        assert!(rem.is_empty());
900        assert_eq!(
901            options,
902            ParsedInterfaceDescriptionOptions { if_name: Some(Cow::Borrowed("lo")) }
903        );
904    }
905
906    #[test]
907    fn test_parse_options_fail_non_zero_len_end_of_opt() {
908        let mut buf = Vec::new();
909        buf.extend_from_slice(
910            (OptionHeader {
911                code: U16::new(InterfaceDescriptionOptionCode::EndOfOpt.into()),
912                length: U16::new(1),
913            })
914            .as_bytes(),
915        );
916
917        let res = parse_idb_options(&buf);
918        assert_eq!(res, Err(nom::Err::Failure(ParsingError::EndOfOptLengthNotZero(1))));
919    }
920
921    #[test]
922    fn test_parse_options_fail_if_name_after_end_of_opt() {
923        let mut buf = Vec::new();
924        buf.extend_from_slice(OptionHeader::new_end_of_opt().as_bytes());
925        write_if_name_option(&mut buf, "lo").expect("write if name option");
926
927        let res = parse_idb_options(&buf);
928        assert_eq!(res, Err(nom::Err::Failure(ParsingError::DataAfterEndOfOpt)));
929    }
930
931    #[test]
932    fn test_parse_shb_invalid_magic() {
933        let mut buf = Vec::new();
934        let mut shb = SectionHeaderBlock::new();
935        shb.byte_order_magic = U32::new(BYTE_ORDER_MAGIC + 1);
936        buf.extend_from_slice(shb.as_bytes());
937
938        let res = parse_block::<ParsedSectionHeader>(&buf);
939        assert_eq!(res, Err(nom::Err::Failure(ParsingError::InvalidMagic)));
940    }
941
942    #[test]
943    fn test_parse_shb_unsupported_major() {
944        let mut buf = Vec::new();
945        let mut shb = SectionHeaderBlock::new();
946        let wrong_major_version = 2;
947        shb.major_version = U16::new(wrong_major_version);
948        buf.extend_from_slice(shb.as_bytes());
949
950        let res = parse_block::<ParsedSectionHeader>(&buf);
951        assert_eq!(
952            res,
953            Err(nom::Err::Failure(ParsingError::UnsupportedMajorVersion(wrong_major_version)))
954        );
955    }
956
957    #[test]
958    fn test_parse_shb_unsupported_minor() {
959        let mut buf = Vec::new();
960        let mut shb = SectionHeaderBlock::new();
961        let wrong_minor_version = 1;
962        shb.minor_version = U16::new(wrong_minor_version);
963        buf.extend_from_slice(shb.as_bytes());
964
965        let res = parse_block::<ParsedSectionHeader>(&buf);
966        assert_eq!(
967            res,
968            Err(nom::Err::Failure(ParsingError::UnsupportedMinorVersion(wrong_minor_version)))
969        );
970    }
971
972    #[test]
973    fn test_parse_pcap_valid() {
974        let mut buf = Vec::new();
975
976        // Section Header Block
977        let shb = SectionHeaderBlock::new();
978        buf.extend_from_slice(shb.as_bytes());
979
980        // Interface Description Block
981        write_interface_description_block(&mut buf, LinkType::Ethernet, "lo")
982            .expect("write interface description block");
983
984        // Enhanced Packet Block
985        let timestamp = std::time::UNIX_EPOCH + std::time::Duration::from_secs(1_000_000_000);
986        let packet = [1, 2, 3, 4, 5];
987        let packet_len = packet.len() as u32;
988        write_enhanced_packet_block(
989            &mut buf, 0, /* interface id */
990            timestamp, &packet, packet_len,
991        )
992        .expect("write enhanced packet block");
993
994        // Second Enhanced Packet Block
995        let timestamp2 = timestamp + std::time::Duration::from_secs(2_000_000_000);
996        let packet2 = [6, 7, 8];
997        let packet_len2 = packet2.len() as u32;
998        write_enhanced_packet_block(
999            &mut buf,
1000            0, /* interface id */
1001            timestamp2,
1002            &packet2,
1003            packet_len2,
1004        )
1005        .expect("write enhanced packet block");
1006
1007        let file = parse_pcapng(&buf).expect("parse pcap failed");
1008
1009        assert_eq!(file.header, ParsedSectionHeader { section_length: u64::MAX });
1010        assert_eq!(
1011            file.interfaces,
1012            vec![ParsedInterfaceDescription {
1013                link_type: LinkType::Ethernet,
1014                snap_len: 0,
1015                options: ParsedInterfaceDescriptionOptions { if_name: Some(Cow::Borrowed("lo")) },
1016            }]
1017        );
1018
1019        let packets: Vec<_> =
1020            file.packet_blocks().collect::<Result<Vec<_>, _>>().expect("iterate failed");
1021
1022        let expected = vec![
1023            ParsedEnhancedPacket {
1024                interface_id: 0,
1025                timestamp,
1026                captured_length: packet_len,
1027                original_length: packet_len,
1028                packet_data: &packet,
1029            },
1030            ParsedEnhancedPacket {
1031                interface_id: 0,
1032                timestamp: timestamp2,
1033                captured_length: packet_len2,
1034                original_length: packet_len2,
1035                packet_data: &packet2,
1036            },
1037        ];
1038        assert_eq!(packets, expected);
1039    }
1040    #[test]
1041    fn test_parse_block_too_short() {
1042        let mut buf = Vec::new();
1043        let mut shb = SectionHeaderBlock::new();
1044        let short_block_len = BLOCK_HEADER_FOOTER_LEN - 1;
1045        shb.block_total_length = U32::new(short_block_len);
1046        buf.extend_from_slice(shb.as_bytes());
1047
1048        let res = parse_block::<ParsedSectionHeader>(&buf);
1049        assert_eq!(res, Err(nom::Err::Failure(ParsingError::BlockLengthTooShort(short_block_len))));
1050    }
1051
1052    #[test]
1053    fn test_parse_block_length_disagrees() {
1054        let mut buf = Vec::new();
1055        let mut shb = SectionHeaderBlock::new();
1056        shb.block_total_length2 = U32::new(SectionHeaderBlock::SIZE + 1);
1057        buf.extend_from_slice(shb.as_bytes());
1058
1059        let res = parse_block::<ParsedSectionHeader>(&buf);
1060        assert_eq!(
1061            res,
1062            Err(nom::Err::Failure(ParsingError::BlockLengthsDisagree {
1063                header: SectionHeaderBlock::SIZE,
1064                footer: SectionHeaderBlock::SIZE + 1,
1065            }))
1066        );
1067    }
1068
1069    #[test]
1070    fn test_parse_shb_big_endian_not_supported() {
1071        let mut buf = Vec::new();
1072        buf.extend_from_slice(&u32::from(BlockType::SectionHeader).to_be_bytes());
1073        // NB: It's important that the block size is big endian because this has
1074        // a higher probability of tripping up a bad parser.
1075        buf.extend_from_slice(&SectionHeaderBlock::SIZE.to_be_bytes());
1076        buf.extend_from_slice(&BYTE_ORDER_MAGIC.to_be_bytes());
1077        // This is an incomplete SHB but the byte order magic value should be
1078        // checked before anything else happens anyway.
1079
1080        let res = parse_block::<ParsedSectionHeader>(&buf);
1081        assert_eq!(res, Err(nom::Err::Failure(ParsingError::BigEndianNotSupported)));
1082    }
1083}