Bladeren bron

Implement a Device that emits a stream in libpcap format.

whitequark 7 jaren geleden
bovenliggende
commit
a70b2415e7
4 gewijzigde bestanden met toevoegingen van 190 en 5 verwijderingen
  1. 2 1
      .gitignore
  2. 3 3
      src/phy/loopback.rs
  3. 3 1
      src/phy/mod.rs
  4. 182 0
      src/phy/pcap_writer.rs

+ 2 - 1
.gitignore

@@ -1,2 +1,3 @@
-target
+/target
 Cargo.lock
+*.pcap

+ 3 - 3
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<RefCell<VecDeque<Vec<u8>>>>);
 
 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())))

+ 3 - 1
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"))]

+ 182 - 0
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<T: AsRef<PcapSink>> PcapSink for T {
+    fn write(&self, data: &[u8]) {
+        self.as_ref().write(data)
+    }
+}
+
+#[cfg(feature = "std")]
+impl<T: AsMut<Write>> PcapSink for RefCell<T> {
+    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<u8>` is cheaply cloneable
+/// but `&std::io::File`
+///
+/// [libpcap]: https://wiki.wireshark.org/Development/LibpcapFileFormat
+/// [sink]: trait.PcapSink.html
+#[derive(Debug)]
+pub struct PcapWriter<D: Device, S: PcapSink + Clone> {
+    lower: D,
+    sink:  S,
+    mode:  PcapMode
+}
+
+impl<D: Device, S: PcapSink + Clone> PcapWriter<D, S> {
+    /// Creates a packet capture writer.
+    pub fn new(lower: D, sink: S, mode: PcapMode, link_type: PcapLinkType) -> PcapWriter<D, S> {
+        sink.global_header(link_type);
+        PcapWriter { lower, sink, mode }
+    }
+}
+
+impl<D: Device, S: PcapSink + Clone> Device for PcapWriter<D, S> {
+    type RxBuffer = D::RxBuffer;
+    type TxBuffer = TxBuffer<D::TxBuffer, S>;
+
+    fn limits(&self) -> DeviceLimits { self.lower.limits() }
+
+    fn receive(&mut self, timestamp: u64) -> Result<Self::RxBuffer, Error> {
+        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<Self::TxBuffer, Error> {
+        let buffer = self.lower.transmit(timestamp, length)?;
+        Ok(TxBuffer { buffer, timestamp, sink: self.sink.clone(), mode: self.mode })
+    }
+}
+
+#[doc(hidden)]
+pub struct TxBuffer<B: AsRef<[u8]> + AsMut<[u8]>, S: PcapSink> {
+    buffer:    B,
+    timestamp: u64,
+    sink:      S,
+    mode:      PcapMode
+}
+
+impl<B, S> AsRef<[u8]> for TxBuffer<B, S>
+        where B: AsRef<[u8]> + AsMut<[u8]>, S: PcapSink {
+    fn as_ref(&self) -> &[u8] { self.buffer.as_ref() }
+}
+
+impl<B, S> AsMut<[u8]> for TxBuffer<B, S>
+        where B: AsRef<[u8]> + AsMut<[u8]>, S: PcapSink {
+    fn as_mut(&mut self) -> &mut [u8] { self.buffer.as_mut() }
+}
+
+impl<B, S> Drop for TxBuffer<B, S>
+        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 => ()
+        }
+    }
+}