From f0d54971a8a907e382e23471613d99efeb0287b3 Mon Sep 17 00:00:00 2001 From: Russell Cloran Date: Tue, 5 Sep 2023 09:50:42 -0700 Subject: [PATCH 1/6] Accept Duration for max_rtt instead of u64 --- README.md | 2 +- src/lib.rs | 13 +++++-------- src/ping.rs | 2 +- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 5acafb9..9f36ccf 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ fn main() { Note a Pinger is initialized with two arguments: the maximum round trip time before an address is considered "idle" (2 seconds by default) and the size of the ping data packet (16 bytes by default). To explicitly set these values Pinger would be initialized like so: ```rust -Pinger::new(Some(3000 as u64), Some(24 as usize)) +Pinger::new(Some(Duration::from_millis(3000)), Some(24 as usize)) ``` The public functions `stop_pinger()` to stop the continuous pinger and `ping_once()` to only run one round of pinging are also available. diff --git a/src/lib.rs b/src/lib.rs index ecb9e94..32e1e92 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -74,7 +74,7 @@ pub struct Pinger { impl Pinger { // initialize the pinger and start the icmp and icmpv6 listeners - pub fn new(_max_rtt: Option, _size: Option) -> NewPingerResult { + pub fn new(max_rtt: Option, _size: Option) -> NewPingerResult { let targets = BTreeMap::new(); let (sender, receiver) = channel(); @@ -93,7 +93,7 @@ impl Pinger { let (thread_tx, thread_rx) = channel(); let mut pinger = Pinger { - max_rtt: Arc::new(Duration::from_millis(2000)), + max_rtt: Arc::new(max_rtt.unwrap_or(Duration::from_millis(2000))), targets: Arc::new(Mutex::new(targets)), size: _size.unwrap_or(16), results_sender: sender, @@ -106,9 +106,6 @@ impl Pinger { timer: Arc::new(RwLock::new(Instant::now())), stop: Arc::new(Mutex::new(false)), }; - if let Some(rtt_value) = _max_rtt { - pinger.max_rtt = Arc::new(Duration::from_millis(rtt_value)); - } if let Some(size_value) = _size { pinger.size = size_value; } @@ -194,7 +191,7 @@ impl Pinger { tx, txv6, targets, - max_rtt, + &max_rtt, ); } else { thread::spawn(move || { @@ -207,7 +204,7 @@ impl Pinger { tx, txv6, targets, - max_rtt, + &max_rtt, ); }); } @@ -309,7 +306,7 @@ mod tests { // test we can create a new pinger with optional arguments, // test it returns the new pinger and a client channel // test we can use the client channel - let (pinger, channel) = Pinger::new(Some(3000), Some(24))?; + let (pinger, channel) = Pinger::new(Some(Duration::from_millis(3000)), Some(24))?; assert_eq!(pinger.max_rtt, Arc::new(Duration::new(3, 0))); assert_eq!(pinger.size, 24); diff --git a/src/ping.rs b/src/ping.rs index fbc5ebe..dd0a670 100644 --- a/src/ping.rs +++ b/src/ping.rs @@ -101,7 +101,7 @@ pub fn send_pings( tx: Arc>, txv6: Arc>, targets: Arc>>, - max_rtt: Arc, + max_rtt: &Duration, ) { loop { for (addr, ping) in targets.lock().unwrap().iter_mut() { From df106cc71027c9f4150df40ffb9e6ee645b39c15 Mon Sep 17 00:00:00 2001 From: Russell Cloran Date: Tue, 5 Sep 2023 10:14:35 -0700 Subject: [PATCH 2/6] Rename size argument to Pinger::new --- src/lib.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 32e1e92..168a6d2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -74,7 +74,7 @@ pub struct Pinger { impl Pinger { // initialize the pinger and start the icmp and icmpv6 listeners - pub fn new(max_rtt: Option, _size: Option) -> NewPingerResult { + pub fn new(max_rtt: Option, size: Option) -> NewPingerResult { let targets = BTreeMap::new(); let (sender, receiver) = channel(); @@ -92,10 +92,10 @@ impl Pinger { let (thread_tx, thread_rx) = channel(); - let mut pinger = Pinger { + let pinger = Pinger { max_rtt: Arc::new(max_rtt.unwrap_or(Duration::from_millis(2000))), targets: Arc::new(Mutex::new(targets)), - size: _size.unwrap_or(16), + size: size.unwrap_or(16), results_sender: sender, tx: Arc::new(Mutex::new(tx)), rx: Arc::new(Mutex::new(rx)), @@ -106,9 +106,6 @@ impl Pinger { timer: Arc::new(RwLock::new(Instant::now())), stop: Arc::new(Mutex::new(false)), }; - if let Some(size_value) = _size { - pinger.size = size_value; - } pinger.start_listener(); Ok((pinger, receiver)) From 63923bc7d59f6f69eb2507f75926e1e8c8e7951c Mon Sep 17 00:00:00 2001 From: Russell Cloran Date: Tue, 5 Sep 2023 10:05:06 -0700 Subject: [PATCH 3/6] Accept IpAddr in add/remove instead of string Fixes bparti/fastping-rs#30 Fixes bparli/fastping-rs#34 --- README.md | 8 ++++---- examples/ping.rs | 12 +++++++----- src/lib.rs | 36 ++++++++++-------------------------- 3 files changed, 21 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 9f36ccf..9985946 100644 --- a/README.md +++ b/README.md @@ -38,10 +38,10 @@ fn main() { Err(e) => panic!("Error creating pinger: {}", e), }; - pinger.add_ipaddr("8.8.8.8"); - pinger.add_ipaddr("1.1.1.1"); - pinger.add_ipaddr("7.7.7.7"); - pinger.add_ipaddr("2001:4860:4860::8888"); + pinger.add_ipaddr("8.8.8.8".parse().unwrap()); + pinger.add_ipaddr("1.1.1.1".parse().unwrap()); + pinger.add_ipaddr("7.7.7.7".parse().unwrap()); + pinger.add_ipaddr("2001:4860:4860::8888".parse().unwrap()); pinger.run_pinger(); loop { diff --git a/examples/ping.rs b/examples/ping.rs index 74858cc..c006ea0 100644 --- a/examples/ping.rs +++ b/examples/ping.rs @@ -3,20 +3,22 @@ extern crate pretty_env_logger; #[macro_use] extern crate log; +use std::error::Error; + use fastping_rs::PingResult::{Idle, Receive}; use fastping_rs::Pinger; -fn main() { +fn main() -> Result<(), Box> { pretty_env_logger::init(); let (pinger, results) = match Pinger::new(None, Some(64)) { Ok((pinger, results)) => (pinger, results), Err(e) => panic!("Error creating pinger: {}", e), }; - pinger.add_ipaddr("8.8.8.8"); - pinger.add_ipaddr("1.1.1.1"); - pinger.add_ipaddr("7.7.7.7"); - pinger.add_ipaddr("2001:4860:4860::8888"); + pinger.add_ipaddr("8.8.8.8".parse()?); + pinger.add_ipaddr("1.1.1.1".parse()?); + pinger.add_ipaddr("7.7.7.7".parse()?); + pinger.add_ipaddr("2001:4860:4860::8888".parse()?); pinger.run_pinger(); loop { diff --git a/src/lib.rs b/src/lib.rs index 168a6d2..2c8ceb2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -112,32 +112,16 @@ impl Pinger { } // add either an ipv4 or ipv6 target address for pinging - pub fn add_ipaddr(&self, ipaddr: &str) { - let addr = ipaddr.parse::(); - match addr { - Ok(valid_addr) => { - debug!("Address added {}", valid_addr); - let new_ping = Ping::new(valid_addr); - self.targets.lock().unwrap().insert(valid_addr, new_ping); - } - Err(e) => { - error!("Error adding ip address {}. Error: {}", ipaddr, e); - } - }; + pub fn add_ipaddr(&self, addr: IpAddr) { + debug!("Address added {}", addr); + let new_ping = Ping::new(addr); + self.targets.lock().unwrap().insert(addr, new_ping); } // remove a previously added ipv4 or ipv6 target address - pub fn remove_ipaddr(&self, ipaddr: &str) { - let addr = ipaddr.parse::(); - match addr { - Ok(valid_addr) => { - debug!("Address removed {}", valid_addr); - self.targets.lock().unwrap().remove(&valid_addr); - } - Err(e) => { - error!("Error removing ip address {}. Error: {}", ipaddr, e); - } - }; + pub fn remove_ipaddr(&self, addr: IpAddr) { + debug!("Address removed {}", addr); + self.targets.lock().unwrap().remove(&addr); } // stop running the continous pinger @@ -324,7 +308,7 @@ mod tests { #[test] fn test_add_remove_addrs() -> Result<(), Box> { let (pinger, _) = Pinger::new(None, None)?; - pinger.add_ipaddr("127.0.0.1"); + pinger.add_ipaddr([127, 0, 0, 1].into()); assert_eq!(pinger.targets.lock().unwrap().len(), 1); assert!(pinger .targets @@ -332,7 +316,7 @@ mod tests { .unwrap() .contains_key(&"127.0.0.1".parse::().unwrap())); - pinger.remove_ipaddr("127.0.0.1"); + pinger.remove_ipaddr([127, 0, 0, 1].into()); assert_eq!(pinger.targets.lock().unwrap().len(), 0); assert!(!pinger .targets @@ -359,7 +343,7 @@ mod tests { let test_addrs = ["127.0.0.1", "7.7.7.7", "::1"]; for target in test_addrs { - pinger.add_ipaddr(target); + pinger.add_ipaddr(target.parse()?); } pinger.ping_once(); From 8eecf48ba33afaabf49f86066754a0bb860e2db1 Mon Sep 17 00:00:00 2001 From: Russell Cloran Date: Tue, 12 Sep 2023 10:46:46 -0700 Subject: [PATCH 4/6] Return a Result with a proper Error from new() --- src/error.rs | 28 ++++++++++++++++++++++++++++ src/lib.rs | 20 ++++++++------------ 2 files changed, 36 insertions(+), 12 deletions(-) create mode 100644 src/error.rs diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..ac884b3 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,28 @@ +/// Errors that can be returned by the API +#[derive(Debug)] +pub enum Error { + /// Networking problems, source will be a std::io::Error + NetworkError { source: std::io::Error }, +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::NetworkError { source } => write!(f, "Network error: {}", source), + } + } +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::NetworkError { source } => Some(source), + } + } +} + +impl From for Error { + fn from(e: std::io::Error) -> Error { + Self::NetworkError { source: e } + } +} diff --git a/src/lib.rs b/src/lib.rs index 2c8ceb2..63284c3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,8 +3,10 @@ #[macro_use] extern crate log; +pub mod error; mod ping; +use crate::error::*; use ping::{send_pings, Ping, ReceivedPing}; use pnet::packet::icmp::echo_reply::EchoReplyPacket as IcmpEchoReplyPacket; use pnet::packet::icmpv6::echo_reply::EchoReplyPacket as Icmpv6EchoReplyPacket; @@ -23,9 +25,6 @@ use std::sync::{Arc, Mutex, RwLock}; use std::thread; use std::time::{Duration, Instant}; -// result type returned by fastping_rs::Pinger::new() -pub type NewPingerResult = Result<(Pinger, Receiver), String>; - // ping result type. Idle represents pings that have not received a repsonse within the max_rtt. // Receive represents pings which have received a repsonse pub enum PingResult { @@ -74,21 +73,18 @@ pub struct Pinger { impl Pinger { // initialize the pinger and start the icmp and icmpv6 listeners - pub fn new(max_rtt: Option, size: Option) -> NewPingerResult { + pub fn new( + max_rtt: Option, + size: Option, + ) -> Result<(Self, Receiver), Error> { let targets = BTreeMap::new(); let (sender, receiver) = channel(); let protocol = Layer4(Ipv4(IpNextHeaderProtocols::Icmp)); - let (tx, rx) = match transport_channel(4096, protocol) { - Ok((tx, rx)) => (tx, rx), - Err(e) => return Err(e.to_string()), - }; + let (tx, rx) = transport_channel(4096, protocol)?; let protocolv6 = Layer4(Ipv6(IpNextHeaderProtocols::Icmpv6)); - let (txv6, rxv6) = match transport_channel(4096, protocolv6) { - Ok((txv6, rxv6)) => (txv6, rxv6), - Err(e) => return Err(e.to_string()), - }; + let (txv6, rxv6) = transport_channel(4096, protocolv6)?; let (thread_tx, thread_rx) = channel(); From 8fd9e04356b24f648f12453d551c1fbe27968d06 Mon Sep 17 00:00:00 2001 From: Russell Cloran Date: Tue, 12 Sep 2023 16:20:29 -0700 Subject: [PATCH 5/6] Derive Debug for all basic structs --- src/lib.rs | 1 + src/ping.rs | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 63284c3..dd480d0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,6 +27,7 @@ use std::time::{Duration, Instant}; // ping result type. Idle represents pings that have not received a repsonse within the max_rtt. // Receive represents pings which have received a repsonse +#[derive(Debug)] pub enum PingResult { Idle { addr: IpAddr }, Receive { addr: IpAddr, rtt: Duration }, diff --git a/src/ping.rs b/src/ping.rs index dd0a670..a595414 100644 --- a/src/ping.rs +++ b/src/ping.rs @@ -10,6 +10,7 @@ use std::sync::mpsc::{Receiver, Sender}; use std::sync::{Arc, Mutex, RwLock}; use std::time::{Duration, Instant}; +#[derive(Debug)] pub struct Ping { addr: IpAddr, identifier: u16, @@ -17,6 +18,7 @@ pub struct Ping { pub seen: bool, } +#[derive(Debug)] pub struct ReceivedPing { pub addr: IpAddr, pub identifier: u16, From 519d3a6a88b935c2921b678f63ca898531caafdf Mon Sep 17 00:00:00 2001 From: Russell Cloran Date: Tue, 5 Sep 2023 13:59:33 -0700 Subject: [PATCH 6/6] Add some documentation to the API --- src/lib.rs | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index dd480d0..103423d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,13 @@ +//! ICMP ping library in Rust inspired by [go-fastping][1] and [AnyEvent::FastPing][2] +//! +//! fastping-rs is a Rust ICMP ping library for quickly sending and measuring batches of ICMP echo +//! request packets. The design prioritizes pinging a large number of hosts over a long time, +//! rather than pinging individual hosts once-off. +//! +//! [`Pinger`] provides the functionality for this module. +//! +//! [1]: https://pkg.go.dev/github.com/kanocz/go-fastping +//! [2]: https://metacpan.org/pod/AnyEvent::FastPing #![warn(rust_2018_idioms)] #[macro_use] @@ -25,14 +35,20 @@ use std::sync::{Arc, Mutex, RwLock}; use std::thread; use std::time::{Duration, Instant}; -// ping result type. Idle represents pings that have not received a repsonse within the max_rtt. -// Receive represents pings which have received a repsonse +/// The result of a single ping #[derive(Debug)] pub enum PingResult { + /// Pings that have not received a response within max_rtt Idle { addr: IpAddr }, + /// Pings which have received a response Receive { addr: IpAddr, rtt: Duration }, } +/// A long-lived pinger +/// +/// [`Pinger`]s create raw sockets for sending and receiving ICMP echo requests, which requires +/// special privileges on most operating systems. A thread is created to read from each (IPv4 and +/// IPv6) socket, and results are provided to the client on the channel in the `results` field. pub struct Pinger { // Number of milliseconds of an idle timeout. Once it passed, // the library calls an idle callback function. Default is 2000 @@ -73,7 +89,7 @@ pub struct Pinger { } impl Pinger { - // initialize the pinger and start the icmp and icmpv6 listeners + /// Create a [`Pinger`], create sockets, and start network listener threads pub fn new( max_rtt: Option, size: Option, @@ -108,31 +124,31 @@ impl Pinger { Ok((pinger, receiver)) } - // add either an ipv4 or ipv6 target address for pinging + /// Add a new target for pinging pub fn add_ipaddr(&self, addr: IpAddr) { debug!("Address added {}", addr); let new_ping = Ping::new(addr); self.targets.lock().unwrap().insert(addr, new_ping); } - // remove a previously added ipv4 or ipv6 target address + /// Remove a previously added target address pub fn remove_ipaddr(&self, addr: IpAddr) { debug!("Address removed {}", addr); self.targets.lock().unwrap().remove(&addr); } - // stop running the continous pinger + /// Stop running the continous pinger pub fn stop_pinger(&self) { let mut stop = self.stop.lock().unwrap(); *stop = true; } - // run one round of pinging and stop + /// Ping each target address once and stop pub fn ping_once(&self) { self.run_pings(true) } - // run the continuous pinger + /// Run the pinger continuously pub fn run_pinger(&self) { self.run_pings(false) }