Skip to content

Commit

Permalink
Store metadata in capture database and write it to PcapNG files.
Browse files Browse the repository at this point in the history
  • Loading branch information
martinling committed Sep 17, 2024
1 parent f5f7fbb commit a2709d5
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 15 deletions.
18 changes: 18 additions & 0 deletions src/capture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::ops::Range;
use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64};
use std::sync::atomic::Ordering::{Acquire, Release};
use std::sync::Arc;
use std::time::SystemTime;
use std::mem::size_of;

use crate::id::{Id, HasLength};
Expand All @@ -24,8 +25,24 @@ use num_enum::{IntoPrimitive, FromPrimitive};
// Use 2MB block size for packet data, which is a large page size on x86_64.
const PACKET_DATA_BLOCK_SIZE: usize = 0x200000;

/// Metadata about the capture.
#[derive(Clone, Default)]
pub struct CaptureMetadata {
// Fields corresponding to PcapNG interface description.
pub iface_name: Option<String>,
pub iface_desc: Option<String>,
pub iface_hardware: Option<String>,
pub iface_os: Option<String>,
pub iface_speed: Option<Speed>,

// Fields corresponding to PcapNG interface statistics.
pub start_time: Option<SystemTime>,
pub end_time: Option<SystemTime>,
}

/// Capture state shared between readers and writers.
pub struct CaptureShared {
pub metadata: ArcSwap<CaptureMetadata>,
pub device_data: ArcSwap<VecMap<DeviceId, Arc<DeviceData>>>,
pub endpoint_readers: ArcSwap<VecMap<EndpointId, Arc<EndpointReader>>>,
pub complete: AtomicBool,
Expand Down Expand Up @@ -87,6 +104,7 @@ pub fn create_capture()

// Create the state shared by readers and writer.
let shared = Arc::new(CaptureShared {
metadata: ArcSwap::new(Arc::new(CaptureMetadata::default())),
device_data: ArcSwap::new(Arc::new(VecMap::new())),
endpoint_readers: ArcSwap::new(Arc::new(VecMap::new())),
complete: AtomicBool::from(false),
Expand Down
130 changes: 117 additions & 13 deletions src/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ use std::borrow::Cow;
use std::io::{BufReader, BufWriter, Read, Write};
use std::mem::size_of;
use std::ops::Deref;
use std::time::Duration;
use std::sync::Arc;
use std::time::{SystemTime, Duration};

use anyhow::{Context, Error, anyhow};
use byteorder_slice::{
Expand All @@ -16,17 +17,30 @@ use pcap_file::{
blocks::{
Block, RawBlock,
ENHANCED_PACKET_BLOCK,
section_header:: {
SectionHeaderBlock,
SectionHeaderOption,
},
interface_description::{
InterfaceDescriptionBlock,
InterfaceDescriptionOption,
},
interface_statistics::{
InterfaceStatisticsBlock,
InterfaceStatisticsOption,
},
enhanced_packet::EnhancedPacketBlock,
},
},
DataLink,
Endianness,
TsResolution,
};
use once_cell::sync::Lazy;

use crate::capture::CaptureMetadata;
use crate::usb::Speed;
use crate::version::version;

/// Item type generated by file loaders.
pub enum LoaderItem<PacketData> {
Expand Down Expand Up @@ -66,7 +80,7 @@ pub trait GenericSaver<Dest>
where Self: Sized, Dest: Write
{
/// Create a saver for a byte sink.
fn new(dest: Dest) -> Result<Self, Error>;
fn new(dest: Dest, meta: Arc<CaptureMetadata>) -> Result<Self, Error>;

/// Add the next packet.
fn add_packet(&mut self, bytes: &[u8], timestamp_ns: u64) -> Result<(), Error>;
Expand Down Expand Up @@ -147,7 +161,7 @@ impl<Dest> GenericSaver<Dest>
for PcapSaver<Dest>
where Self: Sized, Dest: Write
{
fn new(dest: Dest) -> Result<Self, Error> {
fn new(dest: Dest, _meta: Arc<CaptureMetadata>) -> Result<Self, Error> {
let writer = BufWriter::new(dest);
let header = PcapHeader {
datalink: DataLink::USB_2_0,
Expand Down Expand Up @@ -199,6 +213,7 @@ pub struct PcapNgLoader<Source: Read> {
/// Saver for pcap-ng format.
pub struct PcapNgSaver<Dest: Write> {
pcap: PcapNgWriter<BufWriter<Dest>>,
meta: Arc<CaptureMetadata>,
}

/// Helper for parsing Enhanced Packet Blocks.
Expand Down Expand Up @@ -320,25 +335,99 @@ where Source: Read
}
}

fn string(string: &str) -> Option<Cow<'_, str>> {
Some(Cow::from(string))
}

fn speed_bps(speed: &Speed) -> Option<u64> {
use Speed::*;
match speed {
Low => Some( 1_500_000),
Full => Some( 12_000_000),
High => Some(480_000_000),
Auto => None,
}
}

fn time_micros(time: &SystemTime) -> Option<u64> {
time.duration_since(SystemTime::UNIX_EPOCH)
.ok()
.and_then(|duration| duration.as_micros().try_into().ok())
}

macro_rules! option {
($src: ident,
$dest: ident,
$name: ident,
$variant: ident,
$converter: expr) => {
if let Some($name) = &$src.$name {
if let Some(value) = $converter($name) {
$dest.push($variant(value))
}
}
}
}

fn iface_options(meta: &CaptureMetadata)
-> Vec<InterfaceDescriptionOption>
{
use InterfaceDescriptionOption::*;
// Always store nanosecond resolution.
let mut opt = vec![IfTsResol(9)];
option!(meta, opt, iface_name, IfName, string);
option!(meta, opt, iface_desc, IfDescription, string);
option!(meta, opt, iface_hardware, IfHardware, string);
option!(meta, opt, iface_os, IfOs, string);
option!(meta, opt, iface_speed, IfSpeed, speed_bps);
opt
}

fn stats_options(meta: &CaptureMetadata)
-> Vec<InterfaceStatisticsOption>
{
use InterfaceStatisticsOption::*;
let mut opt = Vec::new();
option!(meta, opt, start_time, IsbStartTime, time_micros);
option!(meta, opt, end_time, IsbEndTime, time_micros);
opt
}

impl<Dest> GenericSaver<Dest>
for PcapNgSaver<Dest>
where Self: Sized, Dest: Write
{
fn new(dest: Dest) -> Result<Self, Error> {
fn new(dest: Dest, meta: Arc<CaptureMetadata>) -> Result<Self, Error> {
static APPLICATION: Lazy<String> = Lazy::new(||
format!("Packetry {}", version())
);
static SECTION: Lazy<SectionHeaderBlock> = Lazy::new(||
SectionHeaderBlock {
options: vec![
SectionHeaderOption::UserApplication(
Cow::from(Lazy::force(&APPLICATION))
),
SectionHeaderOption::OS(
Cow::from(std::env::consts::OS)
),
SectionHeaderOption::Hardware(
Cow::from(std::env::consts::ARCH)
),
],
.. Default::default()
}
);
let writer = BufWriter::new(dest);
let mut pcap = PcapNgWriter::with_endianness(
writer, Endianness::native())?;
let section = Lazy::force(&SECTION).clone();
let mut pcap = PcapNgWriter::with_section_header(writer, section)?;
pcap.write_block(&Block::InterfaceDescription(
InterfaceDescriptionBlock {
linktype: DataLink::USB_2_0,
snaplen: 0,
options: vec![
// Nanosecond resolution.
InterfaceDescriptionOption::IfTsResol(9),
],
options: iface_options(&meta),
}
))?;
Ok(PcapNgSaver { pcap })
Ok(PcapNgSaver { pcap, meta })
}

fn add_packet(&mut self, bytes: &[u8], timestamp_ns: u64)
Expand All @@ -348,10 +437,11 @@ where Self: Sized, Dest: Write
.len()
.try_into()
.context("Packet too large for pcap file")?;
let timestamp = Duration::from_nanos(timestamp_ns);
self.pcap.write_block(&Block::EnhancedPacket(
EnhancedPacketBlock {
interface_id: 0,
timestamp: Duration::from_nanos(timestamp_ns),
timestamp,
original_len: length,
data: Cow::from(bytes),
options: vec![],
Expand All @@ -360,7 +450,21 @@ where Self: Sized, Dest: Write
Ok(())
}

fn close(self) -> Result<(), Error> {
fn close(mut self) -> Result<(), Error> {
self.pcap.write_block(&Block::InterfaceStatistics(
InterfaceStatisticsBlock {
interface_id: 0,
timestamp: match self.meta.end_time {
Some(end) => end
.duration_since(SystemTime::UNIX_EPOCH)?
.as_nanos()
.try_into()
.context("Timestamp overflow")?,
None => 0
},
options: stats_options(&self.meta)
}
))?;
self.pcap.into_inner().flush()?;
Ok(())
}
Expand Down
3 changes: 2 additions & 1 deletion src/test_cynthion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,8 @@ fn test(save_capture: bool,
// Write the capture to a file.
let path = PathBuf::from(format!("./HITL-{name}.pcap"));
let file = File::create(path)?;
let mut saver = PcapSaver::new(file)?;
let meta = reader.shared.metadata.load_full();
let mut saver = PcapSaver::new(file, meta)?;
for i in 0..reader.packet_index.len() {
let packet_id = PacketId::from(i);
let packet = reader.packet(packet_id)?;
Expand Down
3 changes: 2 additions & 1 deletion src/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1103,7 +1103,8 @@ where
Dest: Write
{
let packet_count = capture.packet_index.len();
let mut saver = Saver::new(dest)?;
let meta = capture.shared.metadata.load_full();
let mut saver = Saver::new(dest, meta)?;
for (result, i) in capture.timestamped_packets()?.zip(0..packet_count) {
let (timestamp_ns, packet) = result?;
saver.add_packet(&packet, timestamp_ns)?;
Expand Down

0 comments on commit a2709d5

Please sign in to comment.