From 8d7fa94a6d5c5de22d4a081f65c675da5af77f02 Mon Sep 17 00:00:00 2001 From: Egor Karavaev Date: Sun, 18 Jun 2017 13:14:46 +0300 Subject: [PATCH] Add the `ping` example. --- Cargo.toml | 3 + README.md | 24 +++++++- examples/ping.rs | 141 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 167 insertions(+), 1 deletion(-) create mode 100644 examples/ping.rs diff --git a/Cargo.toml b/Cargo.toml index f9b7a05..7e30d31 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,3 +39,6 @@ name = "server" [[example]] name = "client" + +[[example]] +name = "ping" diff --git a/README.md b/README.md index e5704b2..4ba34f8 100644 --- a/README.md +++ b/README.md @@ -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 ------- diff --git a/examples/ping.rs b/examples/ping.rs new file mode 100644 index 0000000..47467c9 --- /dev/null +++ b/examples/ping.rs @@ -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, + 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); +}