From 250a84e51329f6b282764ce0406ed1747cadb258 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sun, 23 Jul 2017 12:50:26 +0000 Subject: [PATCH] Implement a Device that emits a stream in libpcap format. --- .gitignore | 3 +- src/phy/loopback.rs | 6 +- src/phy/mod.rs | 4 +- src/phy/pcap_writer.rs | 182 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 190 insertions(+), 5 deletions(-) create mode 100644 src/phy/pcap_writer.rs diff --git a/.gitignore b/.gitignore index a9d37c5..41ca801 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ -target +/target Cargo.lock +*.pcap diff --git a/src/phy/loopback.rs b/src/phy/loopback.rs index 62e45fb..5db7e15 100644 --- a/src/phy/loopback.rs +++ b/src/phy/loopback.rs @@ -14,14 +14,14 @@ use collections::{Vec, VecDeque}; use Error; use super::{Device, DeviceLimits}; -/// A loopback interface. +/// A loopback device. #[derive(Debug)] pub struct Loopback(Rc>>>); impl Loopback { - /// Creates a loopback interface. + /// Creates a loopback device. /// - /// Every packet transmitted through this interface will be received through it + /// Every packet transmitted through this device will be received through it /// in FIFO order. pub fn new() -> Loopback { Loopback(Rc::new(RefCell::new(VecDeque::new()))) diff --git a/src/phy/mod.rs b/src/phy/mod.rs index f78be88..3777a5d 100644 --- a/src/phy/mod.rs +++ b/src/phy/mod.rs @@ -36,7 +36,7 @@ fn rx_setup(_buf: *mut u8, _length: &mut usize) { } fn tx_empty() -> bool { - /* platform-specific code to check if the outgoing packet was sent */ + /* platform-specific code to check if an outgoing packet can be sent */ false } @@ -111,6 +111,7 @@ mod sys; mod tracer; mod fault_injector; +mod pcap_writer; #[cfg(any(feature = "std", feature = "collections"))] mod loopback; #[cfg(feature = "raw_socket")] @@ -120,6 +121,7 @@ mod tap_interface; pub use self::tracer::Tracer; pub use self::fault_injector::FaultInjector; +pub use self::pcap_writer::{PcapLinkType, PcapMode, PcapSink, PcapWriter}; #[cfg(any(feature = "std", feature = "collections"))] pub use self::loopback::Loopback; #[cfg(any(feature = "raw_socket"))] diff --git a/src/phy/pcap_writer.rs b/src/phy/pcap_writer.rs new file mode 100644 index 0000000..24d56c7 --- /dev/null +++ b/src/phy/pcap_writer.rs @@ -0,0 +1,182 @@ +use core::cell::RefCell; +#[cfg(feature = "std")] +use std::io::Write; +use byteorder::{ByteOrder, NativeEndian}; + +use Error; +use super::{DeviceLimits, Device}; + +enum_with_unknown! { + /// Captured packet header type. + pub doc enum PcapLinkType(u32) { + /// Ethernet frames + Ethernet = 1, + /// IPv4 or IPv6 packets (depending on the version field) + Ip = 101 + } +} + +/// Packet capture mode. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum PcapMode { + /// Capture both received and transmitted packets. + Both, + /// Capture only received packets. + RxOnly, + /// Capture only transmitted packets. + TxOnly +} + +/// A packet capture sink. +/// +/// A sink is an interface to the platform functions, providing timestamping +/// and streaming data output. +pub trait PcapSink { + /// Write data into the sink. + fn write(&self, data: &[u8]); + + /// Write an `u16` into the sink, in native byte order. + fn write_u16(&self, value: u16) { + let mut bytes = [0u8; 2]; + NativeEndian::write_u16(&mut bytes, value); + self.write(&bytes[..]) + } + + /// Write an `u32` into the sink, in native byte order. + fn write_u32(&self, value: u32) { + let mut bytes = [0u8; 4]; + NativeEndian::write_u32(&mut bytes, value); + self.write(&bytes[..]) + } + + /// Write the libpcap global header into the sink. + /// + /// This method may be overridden e.g. if special synchronization is necessary. + fn global_header(&self, link_type: PcapLinkType) { + self.write_u32(0xa1b2c3d4); // magic number + self.write_u16(2); // major version + self.write_u16(4); // minor version + self.write_u32(0); // timezone (= UTC) + self.write_u32(0); // accuracy (not used) + self.write_u32(65535); // maximum packet length + self.write_u32(link_type.into()); // link-layer header type + } + + /// Write the libpcap packet header into the sink. + /// + /// See also the note for [global_header](#method.global_header). + fn packet_header(&self, timestamp: u64, length: usize) { + assert!(length <= 65535); + + let (seconds, micros) = (timestamp / 1000, timestamp % 1000 * 1000); + self.write_u32(seconds as u32); // timestamp seconds + self.write_u32(micros as u32); // timestamp microseconds + self.write_u32(length as u32); // captured length + self.write_u32(length as u32); // original length + } + + /// Write the libpcap packet header followed by packet data into the sink. + /// + /// See also the note for [global_header](#method.global_header). + fn packet(&self, timestamp: u64, packet: &[u8]) { + self.packet_header(timestamp, packet.len()); + self.write(packet) + } +} + +impl> PcapSink for T { + fn write(&self, data: &[u8]) { + self.as_ref().write(data) + } +} + +#[cfg(feature = "std")] +impl> PcapSink for RefCell { + fn write(&self, data: &[u8]) { + self.borrow_mut().as_mut().write_all(data).expect("cannot write") + } + + fn packet(&self, timestamp: u64, packet: &[u8]) { + self.packet_header(timestamp, packet.len()); + PcapSink::write(self, packet); + self.borrow_mut().as_mut().flush().expect("cannot flush") + } +} + +/// A packet capture writer device. +/// +/// Every packet transmitted or received through this device is timestamped +/// and written (in the [libpcap] format) using the provided [sink]. +/// Note that writes are fine-grained, and buffering is recommended. +/// +/// The packet sink should be cheaply cloneable, as it is cloned on every +/// transmitted packet. For example, `&'a mut Vec` is cheaply cloneable +/// but `&std::io::File` +/// +/// [libpcap]: https://wiki.wireshark.org/Development/LibpcapFileFormat +/// [sink]: trait.PcapSink.html +#[derive(Debug)] +pub struct PcapWriter { + lower: D, + sink: S, + mode: PcapMode +} + +impl PcapWriter { + /// Creates a packet capture writer. + pub fn new(lower: D, sink: S, mode: PcapMode, link_type: PcapLinkType) -> PcapWriter { + sink.global_header(link_type); + PcapWriter { lower, sink, mode } + } +} + +impl Device for PcapWriter { + type RxBuffer = D::RxBuffer; + type TxBuffer = TxBuffer; + + fn limits(&self) -> DeviceLimits { self.lower.limits() } + + fn receive(&mut self, timestamp: u64) -> Result { + let buffer = self.lower.receive(timestamp)?; + match self.mode { + PcapMode::Both | PcapMode::RxOnly => + self.sink.packet(timestamp, buffer.as_ref()), + PcapMode::TxOnly => () + } + Ok(buffer) + } + + fn transmit(&mut self, timestamp: u64, length: usize) -> Result { + let buffer = self.lower.transmit(timestamp, length)?; + Ok(TxBuffer { buffer, timestamp, sink: self.sink.clone(), mode: self.mode }) + } +} + +#[doc(hidden)] +pub struct TxBuffer + AsMut<[u8]>, S: PcapSink> { + buffer: B, + timestamp: u64, + sink: S, + mode: PcapMode +} + +impl AsRef<[u8]> for TxBuffer + where B: AsRef<[u8]> + AsMut<[u8]>, S: PcapSink { + fn as_ref(&self) -> &[u8] { self.buffer.as_ref() } +} + +impl AsMut<[u8]> for TxBuffer + where B: AsRef<[u8]> + AsMut<[u8]>, S: PcapSink { + fn as_mut(&mut self) -> &mut [u8] { self.buffer.as_mut() } +} + +impl Drop for TxBuffer + where B: AsRef<[u8]> + AsMut<[u8]>, S: PcapSink { + fn drop(&mut self) { + match self.mode { + PcapMode::Both | PcapMode::TxOnly => + self.sink.packet(self.timestamp, self.as_ref()), + PcapMode::RxOnly => () + } + } +}