Browse Source

Add IP medium support to PcapWriter and Tracer.

Dario Nieuwenhuis 4 years ago
parent
commit
56bff5dcca
5 changed files with 91 additions and 48 deletions
  1. 10 10
      README.md
  2. 11 7
      examples/utils.rs
  3. 0 5
      src/phy/mod.rs
  4. 10 2
      src/phy/pcap_writer.rs
  5. 60 24
      src/phy/tracer.rs

+ 10 - 10
README.md

@@ -270,19 +270,19 @@ The host is assigned the hardware address `02-00-00-00-00-02`, IPv4 address `192
 Read its [source code](/examples/httpclient.rs), then run it as:
 
 ```sh
-cargo run --example httpclient -- tap0 ADDRESS URL
+cargo run --example httpclient -- --tap tap0 ADDRESS URL
 ```
 
 For example:
 
 ```sh
-cargo run --example httpclient -- tap0 93.184.216.34 http://example.org/
+cargo run --example httpclient -- --tap tap0 93.184.216.34 http://example.org/
 ```
 
 or:
 
 ```sh
-cargo run --example httpclient -- tap0 2606:2800:220:1:248:1893:25c8:1946 http://example.org/
+cargo run --example httpclient -- --tap tap0 2606:2800:220:1:248:1893:25c8:1946 http://example.org/
 ```
 
 It connects to the given address (not a hostname) and URL, and prints any returned response data.
@@ -297,7 +297,7 @@ The host is assigned the hardware address `02-00-00-00-00-02` and IPv4 address `
 Read its [source code](/examples/ping.rs), then run it as:
 
 ```sh
-cargo run --example ping -- tap0 ADDRESS
+cargo run --example ping -- --tap tap0 ADDRESS
 ```
 
 It sends a series of 4 ICMP ECHO\_REQUEST packets to the given address at one second intervals and
@@ -319,7 +319,7 @@ The host is assigned the hardware address `02-00-00-00-00-01` and IPv4 address `
 Read its [source code](/examples/server.rs), then run it as:
 
 ```sh
-cargo run --example server -- tap0
+cargo run --example server -- --tap tap0
 ```
 
 It responds to:
@@ -349,7 +349,7 @@ The host is assigned the hardware address `02-00-00-00-00-02` and IPv4 address `
 Read its [source code](/examples/client.rs), then run it as:
 
 ```sh
-cargo run --example client -- tap0 ADDRESS PORT
+cargo run --example client -- --tap tap0 ADDRESS PORT
 ```
 
 It connects to the given address (not a hostname) and port (e.g. `socat stdio tcp4-listen:1234`),
@@ -362,7 +362,7 @@ _examples/benchmark.rs_ implements a simple throughput benchmark.
 Read its [source code](/examples/benchmark.rs), then run it as:
 
 ```sh
-cargo run --release --example benchmark -- tap0 [reader|writer]
+cargo run --release --example benchmark -- --tap tap0 [reader|writer]
 ```
 
 It establishes a connection to itself from a different thread and reads or writes a large amount
@@ -372,9 +372,9 @@ A typical result (achieved on a Intel Core i7-7500U CPU and a Linux 4.9.65 x86_6
 on a Dell XPS 13 9360 laptop) is as follows:
 
 ```
-$ cargo run -q --release --example benchmark tap0 reader
+$ cargo run -q --release --example benchmark -- --tap tap0 reader
 throughput: 2.556 Gbps
-$ cargo run -q --release --example benchmark tap0 writer
+$ cargo run -q --release --example benchmark -- --tap tap0 writer
 throughput: 5.301 Gbps
 ```
 
@@ -391,7 +391,7 @@ Although it does not require `std`, this example still requires the `alloc` feat
 Read its [source code](/examples/loopback.rs), then run it without `std`:
 
 ```sh
-cargo run --example loopback --no-default-features --features="log proto-ipv4  socket-tcp alloc"
+cargo run --example loopback --no-default-features --features="log proto-ipv4 socket-tcp alloc"
 ```
 
 ... or with `std` (in this case the features don't have to be explicitly listed):

+ 11 - 7
examples/utils.rs

@@ -14,10 +14,10 @@ use log::{Level, LevelFilter, trace};
 use env_logger::Builder;
 use getopts::{Options, Matches};
 
-use smoltcp::phy::{Device, EthernetTracer, FaultInjector, Medium};
+use smoltcp::phy::{Device, Tracer, FaultInjector, Medium};
 #[cfg(feature = "phy-tuntap_interface")]
 use smoltcp::phy::TunTapInterface;
-use smoltcp::phy::{PcapWriter, PcapSink, PcapMode, PcapLinkType};
+use smoltcp::phy::{PcapWriter, PcapSink, PcapMode};
 use smoltcp::phy::RawSocket;
 use smoltcp::time::{Duration, Instant};
 
@@ -111,7 +111,7 @@ pub fn add_middleware_options(opts: &mut Options, _free: &mut Vec<&str>) {
 }
 
 pub fn parse_middleware_options<D>(matches: &mut Matches, device: D, loopback: bool)
-        -> FaultInjector<EthernetTracer<PcapWriter<D, Rc<dyn PcapSink>>>>
+        -> FaultInjector<Tracer<PcapWriter<D, Rc<dyn PcapSink>>>>
     where D: for<'a> Device<'a>
 {
     let drop_chance      = matches.opt_str("drop-chance").map(|s| u8::from_str(&s).unwrap())
@@ -136,13 +136,17 @@ pub fn parse_middleware_options<D>(matches: &mut Matches, device: D, loopback: b
 
     let seed = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().subsec_nanos();
 
-    let device = PcapWriter::new(device, Rc::new(RefCell::new(pcap_writer)) as Rc<dyn PcapSink>,
-                                 if loopback { PcapMode::TxOnly } else { PcapMode::Both },
-                                 PcapLinkType::Ethernet);
-    let device = EthernetTracer::new(device, |_timestamp, _printer| {
+    let device = PcapWriter::new(
+        device,
+        Rc::new(RefCell::new(pcap_writer)) as Rc<dyn PcapSink>,
+        if loopback { PcapMode::TxOnly } else { PcapMode::Both },
+    );
+
+    let device = Tracer::new(device, |_timestamp, _printer| {
         #[cfg(feature = "log")]
         trace!("{}", _printer);
     });
+
     let mut device = FaultInjector::new(device, seed);
     device.set_drop_chance(drop_chance);
     device.set_corrupt_chance(corrupt_chance);

+ 0 - 5
src/phy/mod.rs

@@ -117,11 +117,6 @@ pub use self::raw_socket::RawSocket;
 #[cfg(all(feature = "phy-tuntap_interface", any(target_os = "linux", target_os = "android")))]
 pub use self::tuntap_interface::TunTapInterface;
 
-
-#[cfg(feature = "medium-ethernet")]
-/// A tracer device for Ethernet frames.
-pub type EthernetTracer<T> = Tracer<T, super::wire::EthernetFrame<&'static [u8]>>;
-
 /// A description of checksum behavior for a particular protocol.
 #[derive(Debug, Clone, Copy)]
 pub enum Checksum {

+ 10 - 2
src/phy/pcap_writer.rs

@@ -3,6 +3,7 @@ use std::cell::RefCell;
 #[cfg(feature = "std")]
 use std::io::Write;
 use byteorder::{ByteOrder, NativeEndian};
+use phy::Medium;
 
 use crate::Result;
 use crate::phy::{self, DeviceCapabilities, Device};
@@ -14,7 +15,7 @@ enum_with_unknown! {
         /// Ethernet frames
         Ethernet =   1,
         /// IPv4 or IPv6 packets (depending on the version field)
-        Ip       = 101
+        Ip       = 101,
     }
 }
 
@@ -128,7 +129,14 @@ pub struct PcapWriter<D, S>
 
 impl<D: for<'a> Device<'a>, 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> {
+    pub fn new(lower: D, sink: S, mode: PcapMode) -> PcapWriter<D, S> {
+        let medium = lower.capabilities().medium;
+        let link_type = match medium {
+            #[cfg(feature = "medium-ip")]
+            Medium::Ip => PcapLinkType::Ip,
+            #[cfg(feature = "medium-ethernet")]
+            Medium::Ethernet => PcapLinkType::Ethernet,
+        };
         sink.global_header(link_type);
         PcapWriter { lower, sink, mode }
     }

+ 60 - 24
src/phy/tracer.rs

@@ -1,6 +1,7 @@
-use crate::Result;
-use crate::wire::pretty_print::{PrettyPrint, PrettyPrinter};
-use crate::phy::{self, DeviceCapabilities, Device};
+use core::fmt;
+
+use crate::{Result, wire::pretty_print::{PrettyIndent, PrettyPrint}};
+use crate::phy::{self, DeviceCapabilities, Device, Medium};
 use crate::time::Instant;
 
 /// A tracer device.
@@ -8,14 +9,14 @@ use crate::time::Instant;
 /// A tracer is a device that pretty prints all packets traversing it
 /// using the provided writer function, and then passes them to another
 /// device.
-pub struct Tracer<D: for<'a> Device<'a>, P: PrettyPrint> {
+pub struct Tracer<D: for<'a> Device<'a>> {
     inner:  D,
-    writer: fn(Instant, PrettyPrinter<P>),
+    writer: fn(Instant, Packet),
 }
 
-impl<D: for<'a> Device<'a>, P: PrettyPrint> Tracer<D, P> {
+impl<D: for<'a> Device<'a>> Tracer<D> {
     /// Create a tracer device.
-    pub fn new(inner: D, writer: fn(timestamp: Instant, printer: PrettyPrinter<P>)) -> Tracer<D, P> {
+    pub fn new(inner: D, writer: fn(timestamp: Instant, packet: Packet)) -> Tracer<D> {
         Tracer { inner, writer }
     }
 
@@ -40,65 +41,100 @@ impl<D: for<'a> Device<'a>, P: PrettyPrint> Tracer<D, P> {
     }
 }
 
-impl<'a, D, P> Device<'a> for Tracer<D, P>
+impl<'a, D> Device<'a> for Tracer<D>
     where D: for<'b> Device<'b>,
-          P: PrettyPrint + 'a,
 {
-    type RxToken = RxToken<<D as Device<'a>>::RxToken, P>;
-    type TxToken = TxToken<<D as Device<'a>>::TxToken, P>;
+    type RxToken = RxToken<<D as Device<'a>>::RxToken>;
+    type TxToken = TxToken<<D as Device<'a>>::TxToken>;
 
     fn capabilities(&self) -> DeviceCapabilities { self.inner.capabilities() }
 
     fn receive(&'a mut self) -> Option<(Self::RxToken, Self::TxToken)> {
         let &mut Self { ref mut inner, writer, .. } = self;
+        let medium = inner.capabilities().medium;
         inner.receive().map(|(rx_token, tx_token)| {
-            let rx = RxToken { token: rx_token, writer };
-            let tx = TxToken { token: tx_token, writer };
+            let rx = RxToken { token: rx_token, writer, medium };
+            let tx = TxToken { token: tx_token, writer, medium };
             (rx, tx)
         })
     }
 
     fn transmit(&'a mut self) -> Option<Self::TxToken> {
         let &mut Self { ref mut inner, writer } = self;
+        let medium = inner.capabilities().medium;
         inner.transmit().map(|tx_token| {
-            TxToken { token: tx_token, writer }
+            TxToken { token: tx_token, medium, writer }
         })
     }
 }
 
 #[doc(hidden)]
-pub struct RxToken<Rx: phy::RxToken, P: PrettyPrint> {
+pub struct RxToken<Rx: phy::RxToken> {
     token:     Rx,
-    writer:    fn(Instant, PrettyPrinter<P>)
+    writer:    fn(Instant, Packet),
+    medium:    Medium,
 }
 
-impl<Rx: phy::RxToken, P: PrettyPrint> phy::RxToken for RxToken<Rx, P> {
+impl<Rx: phy::RxToken> phy::RxToken for RxToken<Rx> {
     fn consume<R, F>(self, timestamp: Instant, f: F) -> Result<R>
         where F: FnOnce(&mut [u8]) -> Result<R>
     {
-        let Self { token, writer } = self;
+        let Self { token, writer, medium } = self;
         token.consume(timestamp, |buffer| {
-            writer(timestamp, PrettyPrinter::<P>::new("<- ", &buffer));
+            writer(timestamp, Packet{
+                buffer,
+                medium,
+                prefix: "<- ",
+            });
             f(buffer)
         })
     }
 }
 
 #[doc(hidden)]
-pub struct TxToken<Tx: phy::TxToken, P: PrettyPrint> {
+pub struct TxToken<Tx: phy::TxToken> {
     token:     Tx,
-    writer:    fn(Instant, PrettyPrinter<P>)
+    writer:    fn(Instant, Packet),
+    medium:    Medium,
 }
 
-impl<Tx: phy::TxToken, P: PrettyPrint> phy::TxToken for TxToken<Tx, P> {
+impl<Tx: phy::TxToken> phy::TxToken for TxToken<Tx> {
     fn consume<R, F>(self, timestamp: Instant, len: usize, f: F) -> Result<R>
         where F: FnOnce(&mut [u8]) -> Result<R>
     {
-        let Self { token, writer } = self;
+        let Self { token, writer, medium } = self;
         token.consume(timestamp, len, |buffer| {
             let result = f(buffer);
-            writer(timestamp, PrettyPrinter::<P>::new("-> ", &buffer));
+            writer(timestamp, Packet{
+                buffer,
+                medium,
+                prefix: "-> ",
+            });
             result
         })
     }
 }
+
+pub struct Packet<'a> {
+    buffer: &'a [u8],
+    medium: Medium,
+    prefix: &'static str,
+}
+
+impl<'a> fmt::Display for Packet<'a> {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        let mut indent = PrettyIndent::new(self.prefix);
+        match self.medium {
+            #[cfg(feature = "medium-ethernet")]
+            Medium::Ethernet => crate::wire::EthernetFrame::<&'static [u8]>::pretty_print(&self.buffer, f, &mut indent),
+            #[cfg(feature = "medium-ip")]
+            Medium::Ip => match crate::wire::IpVersion::of_packet(&self.buffer) {
+                #[cfg(feature = "proto-ipv4")]
+                Ok(crate::wire::IpVersion::Ipv4) => crate::wire::Ipv4Packet::<&'static [u8]>::pretty_print(&self.buffer, f, &mut indent),
+                #[cfg(feature = "proto-ipv6")]
+                Ok(crate::wire::IpVersion::Ipv6) => crate::wire::Ipv6Packet::<&'static [u8]>::pretty_print(&self.buffer, f, &mut indent),
+                _ => f.write_str("unrecognized IP version")
+            }
+        }
+    }
+}