Add IP medium support to PcapWriter and Tracer.
parent
9e3b373e36
commit
6e8c2a8455
20
README.md
20
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:
|
Read its [source code](/examples/httpclient.rs), then run it as:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cargo run --example httpclient -- tap0 ADDRESS URL
|
cargo run --example httpclient -- --tap tap0 ADDRESS URL
|
||||||
```
|
```
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
```sh
|
```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:
|
or:
|
||||||
|
|
||||||
```sh
|
```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.
|
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:
|
Read its [source code](/examples/ping.rs), then run it as:
|
||||||
|
|
||||||
```sh
|
```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
|
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:
|
Read its [source code](/examples/server.rs), then run it as:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cargo run --example server -- tap0
|
cargo run --example server -- --tap tap0
|
||||||
```
|
```
|
||||||
|
|
||||||
It responds to:
|
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:
|
Read its [source code](/examples/client.rs), then run it as:
|
||||||
|
|
||||||
```sh
|
```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`),
|
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:
|
Read its [source code](/examples/benchmark.rs), then run it as:
|
||||||
|
|
||||||
```sh
|
```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
|
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:
|
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
|
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
|
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`:
|
Read its [source code](/examples/loopback.rs), then run it without `std`:
|
||||||
|
|
||||||
```sh
|
```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):
|
... or with `std` (in this case the features don't have to be explicitly listed):
|
||||||
|
|
|
@ -14,10 +14,10 @@ use log::{Level, LevelFilter, trace};
|
||||||
use env_logger::Builder;
|
use env_logger::Builder;
|
||||||
use getopts::{Options, Matches};
|
use getopts::{Options, Matches};
|
||||||
|
|
||||||
use smoltcp::phy::{Device, EthernetTracer, FaultInjector, Medium};
|
use smoltcp::phy::{Device, Tracer, FaultInjector, Medium};
|
||||||
#[cfg(feature = "phy-tuntap_interface")]
|
#[cfg(feature = "phy-tuntap_interface")]
|
||||||
use smoltcp::phy::TunTapInterface;
|
use smoltcp::phy::TunTapInterface;
|
||||||
use smoltcp::phy::{PcapWriter, PcapSink, PcapMode, PcapLinkType};
|
use smoltcp::phy::{PcapWriter, PcapSink, PcapMode};
|
||||||
use smoltcp::phy::RawSocket;
|
use smoltcp::phy::RawSocket;
|
||||||
use smoltcp::time::{Duration, Instant};
|
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)
|
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>
|
where D: for<'a> Device<'a>
|
||||||
{
|
{
|
||||||
let drop_chance = matches.opt_str("drop-chance").map(|s| u8::from_str(&s).unwrap())
|
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 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>,
|
let device = PcapWriter::new(
|
||||||
if loopback { PcapMode::TxOnly } else { PcapMode::Both },
|
device,
|
||||||
PcapLinkType::Ethernet);
|
Rc::new(RefCell::new(pcap_writer)) as Rc<dyn PcapSink>,
|
||||||
let device = EthernetTracer::new(device, |_timestamp, _printer| {
|
if loopback { PcapMode::TxOnly } else { PcapMode::Both },
|
||||||
|
);
|
||||||
|
|
||||||
|
let device = Tracer::new(device, |_timestamp, _printer| {
|
||||||
#[cfg(feature = "log")]
|
#[cfg(feature = "log")]
|
||||||
trace!("{}", _printer);
|
trace!("{}", _printer);
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut device = FaultInjector::new(device, seed);
|
let mut device = FaultInjector::new(device, seed);
|
||||||
device.set_drop_chance(drop_chance);
|
device.set_drop_chance(drop_chance);
|
||||||
device.set_corrupt_chance(corrupt_chance);
|
device.set_corrupt_chance(corrupt_chance);
|
||||||
|
|
|
@ -117,11 +117,6 @@ pub use self::raw_socket::RawSocket;
|
||||||
#[cfg(all(feature = "phy-tuntap_interface", any(target_os = "linux", target_os = "android")))]
|
#[cfg(all(feature = "phy-tuntap_interface", any(target_os = "linux", target_os = "android")))]
|
||||||
pub use self::tuntap_interface::TunTapInterface;
|
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.
|
/// A description of checksum behavior for a particular protocol.
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum Checksum {
|
pub enum Checksum {
|
||||||
|
|
|
@ -3,6 +3,7 @@ use std::cell::RefCell;
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use byteorder::{ByteOrder, NativeEndian};
|
use byteorder::{ByteOrder, NativeEndian};
|
||||||
|
use phy::Medium;
|
||||||
|
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
use crate::phy::{self, DeviceCapabilities, Device};
|
use crate::phy::{self, DeviceCapabilities, Device};
|
||||||
|
@ -14,7 +15,7 @@ enum_with_unknown! {
|
||||||
/// Ethernet frames
|
/// Ethernet frames
|
||||||
Ethernet = 1,
|
Ethernet = 1,
|
||||||
/// IPv4 or IPv6 packets (depending on the version field)
|
/// 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> {
|
impl<D: for<'a> Device<'a>, S: PcapSink + Clone> PcapWriter<D, S> {
|
||||||
/// Creates a packet capture writer.
|
/// 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);
|
sink.global_header(link_type);
|
||||||
PcapWriter { lower, sink, mode }
|
PcapWriter { lower, sink, mode }
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::Result;
|
use core::fmt;
|
||||||
use crate::wire::pretty_print::{PrettyPrint, PrettyPrinter};
|
|
||||||
use crate::phy::{self, DeviceCapabilities, Device};
|
use crate::{Result, wire::pretty_print::{PrettyIndent, PrettyPrint}};
|
||||||
|
use crate::phy::{self, DeviceCapabilities, Device, Medium};
|
||||||
use crate::time::Instant;
|
use crate::time::Instant;
|
||||||
|
|
||||||
/// A tracer device.
|
/// A tracer device.
|
||||||
|
@ -8,14 +9,14 @@ use crate::time::Instant;
|
||||||
/// A tracer is a device that pretty prints all packets traversing it
|
/// A tracer is a device that pretty prints all packets traversing it
|
||||||
/// using the provided writer function, and then passes them to another
|
/// using the provided writer function, and then passes them to another
|
||||||
/// device.
|
/// device.
|
||||||
pub struct Tracer<D: for<'a> Device<'a>, P: PrettyPrint> {
|
pub struct Tracer<D: for<'a> Device<'a>> {
|
||||||
inner: D,
|
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.
|
/// 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 }
|
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>,
|
where D: for<'b> Device<'b>,
|
||||||
P: PrettyPrint + 'a,
|
|
||||||
{
|
{
|
||||||
type RxToken = RxToken<<D as Device<'a>>::RxToken, P>;
|
type RxToken = RxToken<<D as Device<'a>>::RxToken>;
|
||||||
type TxToken = TxToken<<D as Device<'a>>::TxToken, P>;
|
type TxToken = TxToken<<D as Device<'a>>::TxToken>;
|
||||||
|
|
||||||
fn capabilities(&self) -> DeviceCapabilities { self.inner.capabilities() }
|
fn capabilities(&self) -> DeviceCapabilities { self.inner.capabilities() }
|
||||||
|
|
||||||
fn receive(&'a mut self) -> Option<(Self::RxToken, Self::TxToken)> {
|
fn receive(&'a mut self) -> Option<(Self::RxToken, Self::TxToken)> {
|
||||||
let &mut Self { ref mut inner, writer, .. } = self;
|
let &mut Self { ref mut inner, writer, .. } = self;
|
||||||
|
let medium = inner.capabilities().medium;
|
||||||
inner.receive().map(|(rx_token, tx_token)| {
|
inner.receive().map(|(rx_token, tx_token)| {
|
||||||
let rx = RxToken { token: rx_token, writer };
|
let rx = RxToken { token: rx_token, writer, medium };
|
||||||
let tx = TxToken { token: tx_token, writer };
|
let tx = TxToken { token: tx_token, writer, medium };
|
||||||
(rx, tx)
|
(rx, tx)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn transmit(&'a mut self) -> Option<Self::TxToken> {
|
fn transmit(&'a mut self) -> Option<Self::TxToken> {
|
||||||
let &mut Self { ref mut inner, writer } = self;
|
let &mut Self { ref mut inner, writer } = self;
|
||||||
|
let medium = inner.capabilities().medium;
|
||||||
inner.transmit().map(|tx_token| {
|
inner.transmit().map(|tx_token| {
|
||||||
TxToken { token: tx_token, writer }
|
TxToken { token: tx_token, medium, writer }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub struct RxToken<Rx: phy::RxToken, P: PrettyPrint> {
|
pub struct RxToken<Rx: phy::RxToken> {
|
||||||
token: Rx,
|
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>
|
fn consume<R, F>(self, timestamp: Instant, f: F) -> Result<R>
|
||||||
where F: FnOnce(&mut [u8]) -> Result<R>
|
where F: FnOnce(&mut [u8]) -> Result<R>
|
||||||
{
|
{
|
||||||
let Self { token, writer } = self;
|
let Self { token, writer, medium } = self;
|
||||||
token.consume(timestamp, |buffer| {
|
token.consume(timestamp, |buffer| {
|
||||||
writer(timestamp, PrettyPrinter::<P>::new("<- ", &buffer));
|
writer(timestamp, Packet{
|
||||||
|
buffer,
|
||||||
|
medium,
|
||||||
|
prefix: "<- ",
|
||||||
|
});
|
||||||
f(buffer)
|
f(buffer)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub struct TxToken<Tx: phy::TxToken, P: PrettyPrint> {
|
pub struct TxToken<Tx: phy::TxToken> {
|
||||||
token: Tx,
|
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>
|
fn consume<R, F>(self, timestamp: Instant, len: usize, f: F) -> Result<R>
|
||||||
where F: FnOnce(&mut [u8]) -> 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| {
|
token.consume(timestamp, len, |buffer| {
|
||||||
let result = f(buffer);
|
let result = f(buffer);
|
||||||
writer(timestamp, PrettyPrinter::<P>::new("-> ", &buffer));
|
writer(timestamp, Packet{
|
||||||
|
buffer,
|
||||||
|
medium,
|
||||||
|
prefix: "-> ",
|
||||||
|
});
|
||||||
result
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue