Add IP medium support to PcapWriter and Tracer.

master
Dario Nieuwenhuis 2021-03-25 00:20:58 +01:00
parent 9e3b373e36
commit 6e8c2a8455
5 changed files with 91 additions and 48 deletions

View File

@ -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):

View File

@ -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);

View File

@ -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 {

View File

@ -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 }
}

View File

@ -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")
}
}
}
}