Add the `ping` example.

v0.7.x
Egor Karavaev 2017-06-18 13:14:46 +03:00 committed by whitequark
parent ca56baca65
commit 8d7fa94a6d
3 changed files with 167 additions and 1 deletions

View File

@ -39,3 +39,6 @@ name = "server"
[[example]]
name = "client"
[[example]]
name = "ping"

View File

@ -7,7 +7,7 @@ include complicated compile-time computations, such as macro or type tricks, eve
at cost of performance degradation.
_smoltcp_ does not need heap allocation *at all*, is [extensively documented][docs],
and compiles on stable Rust 1.15 and later.
and compiles on stable Rust 1.18 and later.
[docs]: https://docs.rs/smoltcp/
@ -214,6 +214,28 @@ cargo run --example client -- tap0 ADDRESS PORT
It connects to the given address (not a hostname) and port (e.g. `socat stdio tcp4-listen 1234`),
and will respond with reversed chunks of the input indefinitely.
### examples/ping.rs
_examples/ping.rs_ implements a minimal version of the `ping` utility using raw sockets.
The host is assigned the hardware address `02-00-00-00-00-02` and IPv4 address `192.168.69.1`.
Read its [source code](/examples/ping.rs), then run it as:
```sh
cargo run --example ping -- tap0 ADDRESS
```
It sends a series of 4 ICMP ECHO\_REQUEST packets to the given address at one second intervals and
prints out a status line on each valid ECHO\_RESPONSE received.
The first ECHO\_REQUEST packet is expected to be lost since arp\_cache is empty after startup;
the ECHO\_REQUEST packet is dropped and an ARP request is sent instead.
Currently, netmasks are not implemented, and so the only address this example can reach
is the other endpoint of the tap interface, `192.168.1.100`. It cannot reach itself because
packets entering a tap interface do not loop back.
License
-------

141
examples/ping.rs Normal file
View File

@ -0,0 +1,141 @@
#[macro_use]
extern crate log;
extern crate env_logger;
extern crate getopts;
extern crate smoltcp;
extern crate byteorder;
mod utils;
use std::str::{self, FromStr};
use std::time::{Duration, Instant};
use smoltcp::Error;
use smoltcp::wire::{EthernetAddress, IpVersion, IpProtocol, IpAddress,
Ipv4Address, Ipv4Packet, Ipv4Repr,
Icmpv4Repr, Icmpv4Packet};
use smoltcp::iface::{ArpCache, SliceArpCache, EthernetInterface};
use smoltcp::socket::{AsSocket, SocketSet};
use smoltcp::socket::{RawSocket, RawSocketBuffer, RawPacketBuffer};
use std::collections::HashMap;
use byteorder::{ByteOrder, NetworkEndian};
const PING_INTERVAL_S: u64 = 1;
const PING_TIMEOUT_S: u64 = 5;
const PINGS_TO_SEND: usize = 4;
fn main() {
utils::setup_logging();
let (device, args) = utils::setup_device(&["ADDRESS"]);
let startup_time = Instant::now();
let arp_cache = SliceArpCache::new(vec![Default::default(); 8]);
let remote_addr = Ipv4Address::from_str(&args[0]).unwrap();
let local_addr = Ipv4Address::new(192, 168, 69, 1);
let raw_rx_buffer = RawSocketBuffer::new(vec![RawPacketBuffer::new(vec![0; 256])]);
let raw_tx_buffer = RawSocketBuffer::new(vec![RawPacketBuffer::new(vec![0; 256])]);
let raw_socket = RawSocket::new(IpVersion::Ipv4, IpProtocol::Icmp,
raw_rx_buffer, raw_tx_buffer);
let hardware_addr = EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x02]);
let mut iface = EthernetInterface::new(
Box::new(device), Box::new(arp_cache) as Box<ArpCache>,
hardware_addr, [IpAddress::from(local_addr)]);
let mut sockets = SocketSet::new(vec![]);
let raw_handle = sockets.add(raw_socket);
let mut send_next = Duration::default();
let mut seq_no = 0;
let mut received = 0;
let mut echo_payload = [0xffu8; 40];
let mut waiting_queue = HashMap::new();
loop {
{
let socket: &mut RawSocket = sockets.get_mut(raw_handle).as_socket();
let timestamp = Instant::now().duration_since(startup_time);
let timestamp_us = (timestamp.as_secs() * 1000000) +
(timestamp.subsec_nanos() / 1000) as u64;
if seq_no == PINGS_TO_SEND as u16 && waiting_queue.is_empty() {
break;
}
if socket.can_send() && seq_no < PINGS_TO_SEND as u16 && send_next <= timestamp {
NetworkEndian::write_u64(&mut echo_payload, timestamp_us);
let icmp_repr = Icmpv4Repr::EchoRequest {
ident: 1,
seq_no,
data: &echo_payload,
};
let ipv4_repr = Ipv4Repr {
/*src_addr: Ipv4Address::UNSPECIFIED,*/
src_addr: Ipv4Address::new(0, 0, 0, 0),
dst_addr: remote_addr,
protocol: IpProtocol::Icmp,
payload_len: icmp_repr.buffer_len(),
};
let raw_payload = socket
.send(ipv4_repr.buffer_len() + icmp_repr.buffer_len())
.unwrap();
let mut ipv4_packet = Ipv4Packet::new(raw_payload).unwrap();
ipv4_repr.emit(&mut ipv4_packet);
let mut icmp_packet = Icmpv4Packet::new(ipv4_packet.payload_mut()).unwrap();
icmp_repr.emit(&mut icmp_packet);
waiting_queue.insert(seq_no, timestamp);
seq_no += 1;
send_next += Duration::new(PING_INTERVAL_S, 0);
}
if socket.can_recv() {
let payload = socket.recv().unwrap();
let ipv4_packet = Ipv4Packet::new(payload).unwrap();
let ipv4_repr = Ipv4Repr::parse(&ipv4_packet).unwrap();
if ipv4_repr.src_addr == remote_addr && ipv4_repr.dst_addr == local_addr {
let icmp_packet = Icmpv4Packet::new(ipv4_packet.payload()).unwrap();
let icmp_repr = Icmpv4Repr::parse(&icmp_packet);
if let Ok(Icmpv4Repr::EchoReply { seq_no, data, .. }) = icmp_repr {
if let Some(_) = waiting_queue.get(&seq_no) {
let packet_timestamp_us = NetworkEndian::read_u64(data);
println!("{} bytes from {}: icmp_seq={}, time={:.3}ms",
data.len(), remote_addr, seq_no,
(timestamp_us - packet_timestamp_us) as f64 / 1000.0);
waiting_queue.remove(&seq_no);
received += 1;
}
}
}
}
waiting_queue.retain(|seq, from| {
if (timestamp - *from).as_secs() < PING_TIMEOUT_S {
true
} else {
println!("From {} icmp_seq={} timeout", remote_addr, seq);
false
}
})
}
let timestamp = Instant::now().duration_since(startup_time);
let timestamp_ms = (timestamp.as_secs() * 1000) +
(timestamp.subsec_nanos() / 1000000) as u64;
match iface.poll(&mut sockets, timestamp_ms) {
Ok(()) | Err(Error::Exhausted) => (),
Err(e) => debug!("poll error: {}", e),
}
}
println!("--- {} ping statistics ---", remote_addr);
println!("{} packets transmitted, {} received, {:.0}% packet loss",
seq_no, received, 100.0 * (seq_no - received) as f64 / seq_no as f64);
}