Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Correct timestamp handling for all fields and options #42

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,9 @@ impl From<std::string::FromUtf8Error> for PcapError {
PcapError::FromUtf8Error(err)
}
}

impl From<std::io::Error> for PcapError {
martinling marked this conversation as resolved.
Show resolved Hide resolved
fn from(err: std::io::Error) -> Self {
PcapError::IoError(err)
}
}
73 changes: 37 additions & 36 deletions src/pcapng/blocks/block_common.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Common block types.

use std::borrow::Cow;
use std::io::{Result as IoResult, Write};
use std::io::Write;

use byteorder_slice::byteorder::WriteBytesExt;
use byteorder_slice::result::ReadSlice;
Expand All @@ -17,6 +17,7 @@ use super::section_header::SectionHeaderBlock;
use super::simple_packet::SimplePacketBlock;
use super::systemd_journal_export::SystemdJournalExportBlock;
use super::unknown::UnknownBlock;
use crate::pcapng::PcapNgState;
use crate::errors::PcapError;
use crate::PcapResult;

Expand Down Expand Up @@ -127,7 +128,7 @@ impl<'a> RawBlock<'a> {
/// Writes a [`RawBlock`] to a writer.
///
/// Uses the endianness of the header.
pub fn write_to<B: ByteOrder, W: Write>(&self, writer: &mut W) -> IoResult<usize> {
pub fn write_to<B: ByteOrder, W: Write>(&self, writer: &mut W) -> Result<usize, PcapError> {
writer.write_u32::<B>(self.type_)?;
writer.write_u32::<B>(self.initial_len)?;
writer.write_all(&self.body[..])?;
Expand All @@ -136,9 +137,9 @@ impl<'a> RawBlock<'a> {
Ok(self.body.len() + 12)
}

/// Tries to convert a [`RawBlock`] into a [`Block`]
pub fn try_into_block<B: ByteOrder>(self) -> PcapResult<Block<'a>> {
Block::try_from_raw_block::<B>(self)
/// Tries to convert a [`RawBlock`] into a [`Block`], using a [`PcapNgState`].
pub fn try_into_block<'s, B: ByteOrder>(self, state: &'s PcapNgState) -> PcapResult<Block<'a>> {
Block::try_from_raw_block::<B>(state, self)
}
}

Expand Down Expand Up @@ -166,85 +167,85 @@ pub enum Block<'a> {
}

impl<'a> Block<'a> {
/// Parses a [`Block`] from a slice
pub fn from_slice<B: ByteOrder>(slice: &'a [u8]) -> Result<(&'a [u8], Self), PcapError> {
/// Parses a [`Block`] from a slice, using a [`PcapNgState`].
pub fn from_slice<'s, B: ByteOrder>(state: &'s PcapNgState, slice: &'a [u8]) -> Result<(&'a [u8], Self), PcapError> {
let (rem, raw_block) = RawBlock::from_slice::<B>(slice)?;
let block = Self::try_from_raw_block::<B>(raw_block)?;
let block = Self::try_from_raw_block::<B>(state, raw_block)?;

Ok((rem, block))
}

/// Writes a [`Block`] to a writer.
pub fn write_to<B: ByteOrder, W: Write>(&self, writer: &mut W) -> IoResult<usize> {
/// Writes a [`Block`] to a writer, using a [`PcapNgState`].
pub fn write_to<B: ByteOrder, W: Write>(&self, state: &PcapNgState, writer: &mut W) -> Result<usize, PcapError> {
return match self {
Self::SectionHeader(b) => inner_write_to::<B, _, W>(b, SECTION_HEADER_BLOCK, writer),
Self::InterfaceDescription(b) => inner_write_to::<B, _, W>(b, INTERFACE_DESCRIPTION_BLOCK, writer),
Self::Packet(b) => inner_write_to::<B, _, W>(b, PACKET_BLOCK, writer),
Self::SimplePacket(b) => inner_write_to::<B, _, W>(b, SIMPLE_PACKET_BLOCK, writer),
Self::NameResolution(b) => inner_write_to::<B, _, W>(b, NAME_RESOLUTION_BLOCK, writer),
Self::InterfaceStatistics(b) => inner_write_to::<B, _, W>(b, INTERFACE_STATISTIC_BLOCK, writer),
Self::EnhancedPacket(b) => inner_write_to::<B, _, W>(b, ENHANCED_PACKET_BLOCK, writer),
Self::SystemdJournalExport(b) => inner_write_to::<B, _, W>(b, SYSTEMD_JOURNAL_EXPORT_BLOCK, writer),
Self::Unknown(b) => inner_write_to::<B, _, W>(b, b.type_, writer),
Self::SectionHeader(b) => inner_write_to::<B, _, W>(state, b, SECTION_HEADER_BLOCK, writer),
Self::InterfaceDescription(b) => inner_write_to::<B, _, W>(state, b, INTERFACE_DESCRIPTION_BLOCK, writer),
Self::Packet(b) => inner_write_to::<B, _, W>(state, b, PACKET_BLOCK, writer),
Self::SimplePacket(b) => inner_write_to::<B, _, W>(state, b, SIMPLE_PACKET_BLOCK, writer),
Self::NameResolution(b) => inner_write_to::<B, _, W>(state, b, NAME_RESOLUTION_BLOCK, writer),
Self::InterfaceStatistics(b) => inner_write_to::<B, _, W>(state, b, INTERFACE_STATISTIC_BLOCK, writer),
Self::EnhancedPacket(b) => inner_write_to::<B, _, W>(state, b, ENHANCED_PACKET_BLOCK, writer),
Self::SystemdJournalExport(b) => inner_write_to::<B, _, W>(state, b, SYSTEMD_JOURNAL_EXPORT_BLOCK, writer),
Self::Unknown(b) => inner_write_to::<B, _, W>(state, b, b.type_, writer),
};

fn inner_write_to<'a, B: ByteOrder, BL: PcapNgBlock<'a>, W: Write>(block: &BL, block_code: u32, writer: &mut W) -> IoResult<usize> {
fn inner_write_to<'s, 'a, B: ByteOrder, BL: PcapNgBlock<'a>, W: Write>(state: &'s PcapNgState, block: &BL, block_code: u32, writer: &mut W) -> Result<usize, PcapError> {
// Fake write to compute the data length
let data_len = block.write_to::<B, _>(&mut std::io::sink()).unwrap();
let data_len = block.write_to::<B, _>(state, &mut std::io::sink()).unwrap();
let pad_len = (4 - (data_len % 4)) % 4;

let block_len = data_len + pad_len + 12;

writer.write_u32::<B>(block_code)?;
writer.write_u32::<B>(block_len as u32)?;
block.write_to::<B, _>(writer)?;
block.write_to::<B, _>(state, writer)?;
writer.write_all(&[0_u8; 3][..pad_len])?;
writer.write_u32::<B>(block_len as u32)?;

Ok(block_len)
}
}

/// Tries to create a [`Block`] from a [`RawBlock`].
/// Tries to create a [`Block`] from a [`RawBlock`], given a [`PcapNgState`].
///
/// The RawBlock must be Borrowed.
pub fn try_from_raw_block<B: ByteOrder>(raw_block: RawBlock<'a>) -> Result<Block<'a>, PcapError> {
pub fn try_from_raw_block<'s, B: ByteOrder>(state: &'s PcapNgState, raw_block: RawBlock<'a>) -> Result<Block<'a>, PcapError> {
let body = match raw_block.body {
Cow::Borrowed(b) => b,
_ => panic!("The raw block is not borrowed"),
};

match raw_block.type_ {
SECTION_HEADER_BLOCK => {
let (_, block) = SectionHeaderBlock::from_slice::<BigEndian>(body)?;
let (_, block) = SectionHeaderBlock::from_slice::<BigEndian>(state, body)?;
Ok(Block::SectionHeader(block))
},
INTERFACE_DESCRIPTION_BLOCK => {
let (_, block) = InterfaceDescriptionBlock::from_slice::<B>(body)?;
let (_, block) = InterfaceDescriptionBlock::from_slice::<B>(state, body)?;
Ok(Block::InterfaceDescription(block))
},
PACKET_BLOCK => {
let (_, block) = PacketBlock::from_slice::<B>(body)?;
let (_, block) = PacketBlock::from_slice::<B>(state, body)?;
Ok(Block::Packet(block))
},
SIMPLE_PACKET_BLOCK => {
let (_, block) = SimplePacketBlock::from_slice::<B>(body)?;
let (_, block) = SimplePacketBlock::from_slice::<B>(state, body)?;
Ok(Block::SimplePacket(block))
},
NAME_RESOLUTION_BLOCK => {
let (_, block) = NameResolutionBlock::from_slice::<B>(body)?;
let (_, block) = NameResolutionBlock::from_slice::<B>(state, body)?;
Ok(Block::NameResolution(block))
},
INTERFACE_STATISTIC_BLOCK => {
let (_, block) = InterfaceStatisticsBlock::from_slice::<B>(body)?;
let (_, block) = InterfaceStatisticsBlock::from_slice::<B>(state, body)?;
Ok(Block::InterfaceStatistics(block))
},
ENHANCED_PACKET_BLOCK => {
let (_, block) = EnhancedPacketBlock::from_slice::<B>(body)?;
let (_, block) = EnhancedPacketBlock::from_slice::<B>(state, body)?;
Ok(Block::EnhancedPacket(block))
},
SYSTEMD_JOURNAL_EXPORT_BLOCK => {
let (_, block) = SystemdJournalExportBlock::from_slice::<B>(body)?;
let (_, block) = SystemdJournalExportBlock::from_slice::<B>(state, body)?;
Ok(Block::SystemdJournalExport(block))
},
type_ => Ok(Block::Unknown(UnknownBlock::new(type_, raw_block.initial_len, body))),
Expand Down Expand Up @@ -383,13 +384,13 @@ impl<'a> Block<'a> {

/// Common interface for the PcapNg blocks
pub trait PcapNgBlock<'a> {
/// Parse a new block from a slice
fn from_slice<B: ByteOrder>(slice: &'a [u8]) -> Result<(&[u8], Self), PcapError>
/// Parse a new block from a slice, using a [`PcapNgState`].
fn from_slice<'s, B: ByteOrder>(state: &'s PcapNgState, slice: &'a [u8]) -> Result<(&'a [u8], Self), PcapError>
where
Self: std::marker::Sized;

/// Write the content of a block into a writer
fn write_to<B: ByteOrder, W: Write>(&self, writer: &mut W) -> IoResult<usize>;
/// Write the content of a block into a writer, using a [`PcapNgState`].
fn write_to<B: ByteOrder, W: Write>(&self, state: &PcapNgState, writer: &mut W) -> Result<usize, PcapError>;

/// Convert a block into the [`Block`] enumeration
fn into_block(self) -> Block<'a>;
Expand Down
72 changes: 14 additions & 58 deletions src/pcapng/blocks/enhanced_packet.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
//! Enhanced Packet Block (EPB).

use std::borrow::Cow;
use std::cell::Cell;
use std::io::{Result as IoResult, Write};
use std::io::Write;
use std::time::Duration;

use byteorder_slice::byteorder::WriteBytesExt;
Expand All @@ -11,23 +10,21 @@ use byteorder_slice::ByteOrder;
use derive_into_owned::IntoOwned;

use super::block_common::{Block, PcapNgBlock};
use super::interface_description::TsResolution;
use super::opt_common::{CustomBinaryOption, CustomUtf8Option, PcapNgOption, UnknownOption, WriteOptTo};
use crate::errors::PcapError;
use crate::pcapng::PcapNgState;


/// An Enhanced Packet Block (EPB) is the standard container for storing the packets coming from the network.
#[derive(Clone, Debug, IntoOwned, Eq, PartialEq)]
#[derive(Clone, Debug, Default, IntoOwned, Eq, PartialEq)]
pub struct EnhancedPacketBlock<'a> {
/// It specifies the interface this packet comes from.
///
/// The correct interface will be the one whose Interface Description Block
/// (within the current Section of the file) is identified by the same number of this field.
pub interface_id: u32,

/// Number of units of time that have elapsed since 1970-01-01 00:00:00 UTC.
/// By default the timestamp read from the format is considered by have a nano_second resolution.
/// If it is not the case, call [`self.correct_ts_with_ts_resolution()`].
/// Time elapsed since 1970-01-01 00:00:00 UTC.
pub timestamp: Duration,

/// Actual length of the packet when it was transmitted on the network.
Expand All @@ -38,36 +35,17 @@ pub struct EnhancedPacketBlock<'a> {

/// Options
pub options: Vec<EnhancedPacketOption<'a>>,

/// TsResolution to use when writing the block
write_ts_resolution: Cell<TsResolution>,
}

impl<'a> EnhancedPacketBlock<'a> {
/// Set the [`TsResolution`] to use for writing this block.
pub fn set_write_ts_resolution(&self, ts_resolution: TsResolution) {
self.write_ts_resolution.set(ts_resolution)
}

/// Ajust the parsed timestamp field with the right [`TsResolution`].
/// Must be called only once.
pub(crate) fn adjust_parsed_timestamp(&mut self, ts_resolution: TsResolution) {
self.timestamp *= ts_resolution.to_nano_secs();
}
}

impl<'a> PcapNgBlock<'a> for EnhancedPacketBlock<'a> {
fn from_slice<B: ByteOrder>(mut slice: &'a [u8]) -> Result<(&'a [u8], Self), PcapError> {
fn from_slice<'s, B: ByteOrder>(state: &'s PcapNgState, mut slice: &'a [u8]) -> Result<(&'a [u8], Self), PcapError> {
if slice.len() < 20 {
return Err(PcapError::InvalidField("EnhancedPacketBlock: block length length < 20"));
}

let interface_id = slice.read_u32::<B>().unwrap();

let timestamp_high = slice.read_u32::<B>().unwrap() as u64;
let timestamp_low = slice.read_u32::<B>().unwrap() as u64;
let ts_raw = (timestamp_high << 32) + timestamp_low;
let timestamp = Duration::from_nanos(ts_raw);
let timestamp = state.decode_timestamp::<B>(interface_id, &mut slice)?;

let captured_len = slice.read_u32::<B>().unwrap();
let original_len = slice.read_u32::<B>().unwrap();
Expand All @@ -82,40 +60,31 @@ impl<'a> PcapNgBlock<'a> for EnhancedPacketBlock<'a> {
let data = &slice[..captured_len as usize];
slice = &slice[tot_len..];

let (slice, options) = EnhancedPacketOption::opts_from_slice::<B>(slice)?;
let (slice, options) = EnhancedPacketOption::opts_from_slice::<B>(state, Some(interface_id), slice)?;
let block = EnhancedPacketBlock {
interface_id,
timestamp,
original_len,
data: Cow::Borrowed(data),
options,
write_ts_resolution: Cell::new(TsResolution::NANO),
};

Ok((slice, block))
}

fn write_to<B: ByteOrder, W: Write>(&self, writer: &mut W) -> IoResult<usize> {
fn write_to<B: ByteOrder, W: Write>(&self, state: &PcapNgState, writer: &mut W) -> Result<usize, PcapError> {
let pad_len = (4 - (&self.data.len() % 4)) % 4;

writer.write_u32::<B>(self.interface_id)?;

let ts_raw = self.timestamp.as_nanos() / self.write_ts_resolution.get().to_nano_secs() as u128;
let ts_raw: u64 = ts_raw
.try_into()
.map_err(|_| std::io::Error::other("Timestamp too big, please use a bigger timestamp resolution"))?;

let timestamp_high = (ts_raw >> 32) as u32;
let timestamp_low = (ts_raw & 0xFFFFFFFF) as u32;
writer.write_u32::<B>(timestamp_high)?;
writer.write_u32::<B>(timestamp_low)?;
state.encode_timestamp::<B, W>(self.interface_id, self.timestamp, writer)?;

writer.write_u32::<B>(self.data.len() as u32)?;
writer.write_u32::<B>(self.original_len)?;
writer.write_all(&self.data)?;
writer.write_all(&[0_u8; 3][..pad_len])?;

let opt_len = EnhancedPacketOption::write_opts_to::<B, W>(&self.options, writer)?;
let opt_len = EnhancedPacketOption::write_opts_to::<B, W>(&self.options, state, Some(self.interface_id), writer)?;

Ok(20 + &self.data.len() + pad_len + opt_len)
}
Expand All @@ -125,19 +94,6 @@ impl<'a> PcapNgBlock<'a> for EnhancedPacketBlock<'a> {
}
}

impl Default for EnhancedPacketBlock<'_> {
fn default() -> Self {
Self {
interface_id: Default::default(),
timestamp: Duration::ZERO,
original_len: Default::default(),
data: Default::default(),
options: Default::default(),
write_ts_resolution: Default::default(),
}
}
}


/* ----- */

Expand Down Expand Up @@ -170,7 +126,7 @@ pub enum EnhancedPacketOption<'a> {
}

impl<'a> PcapNgOption<'a> for EnhancedPacketOption<'a> {
fn from_slice<B: ByteOrder>(code: u16, length: u16, mut slice: &'a [u8]) -> Result<Self, PcapError> {
fn from_slice<B: ByteOrder>(_state: &PcapNgState, _interface_id: Option<u32>, code: u16, length: u16, mut slice: &'a [u8]) -> Result<Self, PcapError> {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like that this function takes a state and interface_id, but doesn't use it. I see that it is needed because this is a trait. But, I don't see a better solution.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's pretty common to have unused parameters when implementing traits. The trait has to include all parameters that might be necessary, and in the general case, decoding an option correctly may require knowledge of the state as well as which interface is involved.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I understand. In particular, the optional interface_id is a bit awkward in a context where there is no interface defined. As I said, I don't see a better solution.

let opt = match code {
1 => EnhancedPacketOption::Comment(Cow::Borrowed(std::str::from_utf8(slice)?)),
2 => {
Expand All @@ -196,15 +152,15 @@ impl<'a> PcapNgOption<'a> for EnhancedPacketOption<'a> {
Ok(opt)
}

fn write_to<B: ByteOrder, W: Write>(&self, writer: &mut W) -> IoResult<usize> {
match self {
fn write_to<B: ByteOrder, W: Write>(&self, _state: &PcapNgState, _interface_id: Option<u32>, writer: &mut W) -> Result<usize, PcapError> {
Ok(match self {
EnhancedPacketOption::Comment(a) => a.write_opt_to::<B, W>(1, writer),
EnhancedPacketOption::Flags(a) => a.write_opt_to::<B, W>(2, writer),
EnhancedPacketOption::Hash(a) => a.write_opt_to::<B, W>(3, writer),
EnhancedPacketOption::DropCount(a) => a.write_opt_to::<B, W>(4, writer),
EnhancedPacketOption::CustomBinary(a) => a.write_opt_to::<B, W>(a.code, writer),
EnhancedPacketOption::CustomUtf8(a) => a.write_opt_to::<B, W>(a.code, writer),
EnhancedPacketOption::Unknown(a) => a.write_opt_to::<B, W>(a.code, writer),
}
}?)
}
}
Loading