Skip to content
Draft
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
2 changes: 1 addition & 1 deletion .github/workflows/rust_matrix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
- stable
- beta
- nightly
- 1.64.0 # MSRV
- 1.70.0 # MSRV

steps:
- name: Install dependencies
Expand Down
4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
[workspace]
resolver = "2"
members = [
"luomu-common",
"luomu-libpcap",
"luomu-libpcap-sys",
"luomu-libpcap",
"luomu-pcap-file",
"luomu-tpacketv3",
]
2 changes: 1 addition & 1 deletion luomu-libpcap-sys/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ keywords = [ "pcap", "libpcap", "network" ]
categories = [ "api-bindings", "external-ffi-bindings", "network-programming", "no-std" ]

[dependencies]
libc = "0.2"
libc = { version = "0.2", features = [ "extra_traits" ] }

[build-dependencies]
cc = "1"
Expand Down
55 changes: 16 additions & 39 deletions luomu-libpcap-sys/src/pcap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -265,44 +265,20 @@ pub const PCAP_SAMP_FIRST_AFTER_N_MS: u32 = 2;
pub type u_char = ::std::os::raw::c_uchar;
pub type u_short = ::std::os::raw::c_ushort;
pub type u_int = ::std::os::raw::c_uint;
#[repr(C)]
#[repr(align(8))]
#[derive(Debug, Copy, Clone)]
pub struct timeval {
pub _bindgen_opaque_blob: [u64; 2usize],
}
#[test]
fn bindgen_test_layout_timeval() {
assert_eq!(
::std::mem::size_of::<timeval>(),
16usize,
concat!("Size of: ", stringify!(timeval))
);
assert_eq!(
::std::mem::align_of::<timeval>(),
8usize,
concat!("Alignment of ", stringify!(timeval))
);
}
#[repr(C)]
#[repr(align(1))]
#[derive(Debug, Copy, Clone)]
pub struct sockaddr {
pub _bindgen_opaque_blob: [u8; 16usize],
}
#[test]
fn bindgen_test_layout_sockaddr() {
assert_eq!(
::std::mem::size_of::<sockaddr>(),
16usize,
concat!("Size of: ", stringify!(sockaddr))
);
assert_eq!(
::std::mem::align_of::<sockaddr>(),
1usize,
concat!("Alignment of ", stringify!(sockaddr))
);
}
// #[repr(C)]
// #[repr(align(8))]
// #[derive(Debug, Copy, Clone)]
// pub struct timeval {
// pub _bindgen_opaque_blob: [u64; 2usize],
// }
pub use libc::timeval;
// #[repr(C)]
// #[repr(align(1))]
// #[derive(Debug, Copy, Clone)]
// pub struct sockaddr {
// pub _bindgen_opaque_blob: [u8; 16usize],
// }
pub use libc::sockaddr;
pub type bpf_int32 = ::std::os::raw::c_int;
pub type bpf_u_int32 = u_int;
#[repr(C)]
Expand Down Expand Up @@ -409,7 +385,8 @@ fn bindgen_test_layout_bpf_insn() {
)
);
}
pub type FILE = [u64; 19usize];
// pub type FILE = [u64; 19usize];
pub use libc::FILE;
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct pcap {
Expand Down
5 changes: 4 additions & 1 deletion luomu-libpcap/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::fmt;
use std::io;

/// Errors produced by luomu-libpcap.
#[derive(Debug, PartialEq, Eq)]
#[derive(Debug)]
pub enum Error {
/// Loop terminated by pcap_breakloop (PCAP_ERROR_BREAK).
Break,
Expand Down Expand Up @@ -46,6 +46,8 @@ pub enum Error {
Timeout,
/// Error from Rust <-> C String conversion
CStringError(CStringError),
/// IO error
IO(io::Error),
}

#[derive(Debug, PartialEq, Eq)]
Expand Down Expand Up @@ -113,6 +115,7 @@ impl fmt::Display for Error {
Error::CStringError(CStringError::FromBytesWithNul(err)) => err.fmt(f),
Error::CStringError(CStringError::Nul(err)) => err.fmt(f),
Error::CStringError(CStringError::Utf8(err)) => err.fmt(f),
Error::IO(err) => err.fmt(f),
}
}
}
Expand Down
109 changes: 96 additions & 13 deletions luomu-libpcap/src/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,18 @@ use std::collections::BTreeSet;
use std::ffi::{c_void, CStr, CString};
use std::mem::MaybeUninit;
use std::net::{Ipv4Addr, Ipv6Addr};
use std::os::fd::AsRawFd;
use std::path::Path;
use std::time::{Duration, UNIX_EPOCH};

use log::trace;

use crate::{Address, BorrowedPacket, MacAddr};
use luomu_libpcap_sys as libpcap;

use super::{
AddressIter, Error, Interface, InterfaceAddress, InterfaceFlag, PcapFilter, PcapIfT, PcapStat,
PcapT, Result,
use crate::{
Address, AddressIter, BorrowedPacket, Error, Interface, InterfaceAddress, InterfaceFlag,
MacAddr, PcapDumper, PcapFilter, PcapIfT, PcapStat, PcapT, Result,
};

use luomu_libpcap_sys as libpcap;

// libpcap doesn't have constant for success, but man pages state 0 is success.
// For symmetry define it here together with PCAP_ERROR.
const PCAP_SUCCESS: i32 = 0;
Expand Down Expand Up @@ -340,12 +339,7 @@ pub fn pcap_next_ex(pcap_t: &PcapT) -> Result<BorrowedPacket> {
panic!("header or packet NULL.");
}

let ts: libc::timeval = unsafe { std::mem::transmute((*header).ts) };
let len: usize = unsafe { (*header).caplen } as usize;

let timestamp = UNIX_EPOCH + Duration::new(ts.tv_sec as u64, (ts.tv_usec as u32) * 1000);

Ok(BorrowedPacket::new(timestamp, packet, len))
Ok(BorrowedPacket::new(header, packet))
}

/// open a fake `PcapT` for compiling filters
Expand All @@ -372,6 +366,95 @@ pub fn pcap_open_dead() -> Result<PcapT> {
})
}

/// open a file to which to write packets
///
/// `pcap_dump_fopen()` is called to write data to an existing open file; The
/// stream is assumed to be at the beginning of a file that has been newly
/// created or truncated, so that writes will start at the beginning of the
/// file.
///
/// <https://www.tcpdump.org/manpages/pcap_dump_open.3pcap.html>
//
// Rust compiler doesn't require it, but we want to take `&mut PcapDumper`
// because we write into a file pointed by `PcapDumper`.
#[allow(clippy::needless_pass_by_value)]
pub fn pcap_dump_fopen(pcap_t: &PcapT, file: &mut std::fs::File) -> Result<PcapDumper> {
trace!("pcap_dump_fopen({:p}, {:?})", pcap_t.pcap_t, file);
let mode = b"wb\0";

let filedesc = unsafe { libc::fdopen(file.as_raw_fd(), mode.as_ptr() as *const libc::c_char) };
if filedesc.is_null() {
return Err(Error::IO(std::io::Error::last_os_error()));
}

let ret = unsafe { libpcap::pcap_dump_fopen(pcap_t.pcap_t, filedesc) };
if ret.is_null() {
return Err(get_error(pcap_t)?);
}

Ok(PcapDumper { pcap_dumper_t: ret })
}

/// flush to a savefile packets dumped
///
/// `pcap_dump_flush()` flushes the output buffer to the savefile, so that
/// any packets written with pcap_dump(3PCAP) but not yet written to the
/// savefile will be written.
///
/// <https://www.tcpdump.org/manpages/pcap_dump_flush.3pcap.html>
//
// Rust compiler doesn't require it, but we want to take `&mut PcapDumper`
// because we write into a file pointed by `PcapDumper`.
#[allow(clippy::needless_pass_by_value)]
pub fn pcap_dump_flush(dumper: &mut PcapDumper) -> Result<()> {
trace!("pcap_dump_flush({:p})", dumper.pcap_dumper_t);
let ret = unsafe { libpcap::pcap_dump_flush(dumper.pcap_dumper_t) };
if ret == 0 {
Ok(())
} else {
// https://github.com/the-tcpdump-group/libpcap/blob/f828383609906455b9369b1ae86a75f0bd4ba374/sf-pcap.c#L1171C1-L1179
//
// pcap_dump_flush calls fflush() which sets errno so we can probably
// dig that error out.
Err(Error::IO(std::io::Error::last_os_error()))
}
}

/// write a packet to a capture file
///
/// `pcap_dump()` outputs a packet to the savefile opened with
/// `pcap_dump_open()`. Note that its calling arguments are suitable for use
/// with `pcap_dispatch()` or `pcap_loop`. If called directly, the user
/// parameter is of type `pcap_dumper_t` as returned by `pcap_dump_open()`.
///
/// <https://www.tcpdump.org/manpages/pcap_dump.3pcap.html>
//
// Rust compiler doesn't require it, but we want to take `&mut PcapDumper`
// because we write into a file pointed by `PcapDumper`.
#[allow(clippy::needless_pass_by_value)]
pub fn pcap_dump(dumper: &mut PcapDumper, pkthdr: &libpcap::pcap_pkthdr, bytes: &[u8]) {
trace!("pcap_dump({:p}, {:?})", dumper.pcap_dumper_t, pkthdr);
unsafe {
libpcap::pcap_dump(
dumper.pcap_dumper_t as *mut libc::c_uchar,
pkthdr,
bytes.as_ptr(),
)
}
}

/// get the link-layer header type
///
/// `pcap_datalink()` returns the link-layer header type for the live capture or
/// ``savefile''.
///
/// <https://www.tcpdump.org/manpages/pcap_datalink.3pcap.html>
pub fn pcap_datalink(pcap_t: &PcapT) -> Result<usize> {
let ret = unsafe { libpcap::pcap_datalink(pcap_t.pcap_t) };
check_pcap_error(pcap_t, ret)?;
Ok(ret as usize)
}

/// get a list of capture devices
///
/// `pcap_findalldevs()` constructs a list of network devices that can be opened
Expand Down
25 changes: 25 additions & 0 deletions luomu-libpcap/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,31 @@ impl Drop for PcapFilter {
}
}

/// A PcapDumper
pub struct PcapDumper {
pcap_dumper_t: *mut libpcap::pcap_dumper_t,
}

impl PcapDumper {
/// Dump (save) a [Packet] to a savefile.
pub fn dump<P: Packet>(&mut self, packet: P) {
self.dump_raw(packet.pkthdr(), packet.packet())
}

/// Dump (save) a header and bytes to a savefile.
pub fn dump_raw(&mut self, pkthdr: &luomu_libpcap_sys::pcap_pkthdr, bytes: &[u8]) {
pcap_dump(self, pkthdr, bytes)
}
}

impl Drop for PcapDumper {
fn drop(&mut self) {
log::trace!("PcapDumper::drop({:p})", self.pcap_dumper_t);
_ = pcap_dump_flush(self);
unsafe { luomu_libpcap_sys::pcap_dump_close(self.pcap_dumper_t) }
}
}

/// Pcap capture iterator
pub struct PcapIter<'p> {
pcap_t: &'p PcapT,
Expand Down
Loading