Git Product home page Git Product logo

fastping-rs's Introduction

fastping-rs

ICMP ping library in Rust inspired by go-fastping and AnyEvent::FastPing Perl module

fastping-rs is a Rust ICMP ping library, inspired by go-fastping and the AnyEvent::FastPing Perl module, for quickly sending and measuring batches of ICMP ECHO REQUEST packets.

Usage

Pinger::new returns a tuple containing the actual pinger, and the channel to listen for ping results on. The ping results will either be a PingResult::Receive (if the ping response was received prior to the maximum allowed roud trip time) or a PingResult::Idle (if the response was not in time).

run with example

git clone https://github.com/bparli/fastping-rs
cd fastping-rs
sudo RUST_LOG=info cargo run --example ping

Example

Add some crate to your dependencies:

log = "0.4"
pretty_env_logger = "0.4"
fastping-rs = "0.2"

And then get started in your main.rs:

extern crate pretty_env_logger;
#[macro_use]
extern crate log;

use fastping_rs::PingResult::{Idle, Receive};
use fastping_rs::Pinger;

fn main() {
    pretty_env_logger::init();
    let (pinger, results) = match Pinger::new(None, Some(56)) {
        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.run_pinger();

    loop {
        match results.recv() {
            Ok(result) => match result {
                Idle { addr } => {
                    error!("Idle Address {}.", addr);
                }
                Receive { addr, rtt } => {
                    info!("Receive from Address {} in {:?}.", addr, rtt);
                }
            },
            Err(_) => panic!("Worker threads disconnected before the solution was found!"),
        }
    }
}

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:

Pinger::new(Some(3000 as u64), 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.

Additional Notes

This library requires the ability to create raw sockets. Either explicitly set for your program (sudo setcap cap_net_raw=eip /usr/bin/testping for example) or run as root.

Only supported on linux and osx for now (Windows will likely not work).

fastping-rs's People

Contributors

bnjjj avatar bparli avatar fredrik-jansson-se avatar rcloran avatar timbuchwaldt avatar tuetuopay avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

fastping-rs's Issues

Getting stdout errors Error sending ping result on channel: sending on a closed channel

I am running something very similar to your test_integration()

I have a struct called PingScanner that has a HashMap of ip addresses.
My intention is run its run_pings in a thread and have it update the Hashmap of ip adresses which are reachable or not.

Trouble is I keep getting the errors

Error sending ping result on channel: sending on a closed channel
repeating. Why do I get those errors ?

My very simplified code is below. (I removed setting the hashmap stuff)

Thanks

use fastping_rs::Pinger;
use fastping_rs::PingResult::{Idle, Receive};
use std::collections::{BTreeMap, HashMap};
use std::sync::{Mutex, RwLock, Arc};
use std::sync::atomic::{AtomicBool, Ordering};
use std::thread;
use std::time::Duration;

#[derive(Debug, Clone)]
pub struct PingScanner {
    results : Arc<RwLock<HashMap<String, Mutex<bool>>>>,
    addresses : Arc<Mutex<Vec<String>>>,
    running: Arc<AtomicBool>,
}

impl PingScanner {
    pub fn new() -> Self {

        PingScanner { results: Arc::new(RwLock::new(HashMap::new())), 
                      addresses: Arc::new(Mutex::new(Vec::new())),
                      running: Arc::new(AtomicBool::new(false)) }
    }

    pub fn contains(&self, addr: &str) -> bool {
        let map = self.results.read().expect("RwLock poisoned");
        map.contains_key(&addr.to_string())
    }

    pub fn add_ipaddr(&self, addr: &str) {

        let mut addresses = self.addresses.lock().expect("addresses locked");

        if addresses.iter().any(|x| x == addr) {
            return;
        }

        addresses.push(addr.to_string());
    }

    pub fn run_pings(&self) {
   
        let running = self.running.clone();

        let is_running = running.clone().load(Ordering::Relaxed);

        if is_running {
            return;
        }

        running.store(true, Ordering::Relaxed);

        info!("checking ping channels");

        match Pinger::new(None, None) {
            Ok((pinger, test_channel)) => {

                //let mut addresses = self.addresses.lock().expect("addresses locked");
                let mut addresses = vec!["10.0.128.174"];

                for addr in addresses.iter() {
                    pinger.add_ipaddr(addr);
                }
                
                //pinger.ping_once();
                pinger.run_pinger();
                
                for ip in addresses.iter() {

                    println!("matching ip {:?}", ip);

                    match test_channel.recv() {
                        Ok(result) => {
                            match result {
                                Idle{addr} => {
                                    error!("Idle Address {}.", addr);
                                },
                                Receive{addr, rtt: _} => {
                                    info!("Receive from Address {}.", addr);
                                }
                            }
                        },
                        Err(_) => assert!(false),
                    }
                }

                pinger.stop_pinger();
            }
            Err(e) => {
                println!("Test failed: {}", e);
            }
        }

        running.store(false, Ordering::Relaxed);
    }
}

Creating multiple Pinger instances on a recurring task/thread results in a threads "leak".

I have an app that occasionally spawns a new Pinger instance in a new task to do a few pings and I've noticed that this app keeps on facing a memory issue that originated from the code that creates the Pinger instances, resulting in my app being unable to spawn new threads.
When I've further investigated it, I found out what I believe to be the root cause of the problem - the Pinger's listeners.

When a new Pinger instance is created using the Pinger::new method, two new threads are created, an IPv4 Listener and an IPv6 Listener. from looking at their code, those threads are infinite, and the only. time they return is if there is an error with the std::sync::mpsc::channel and the Pinger is marked as stopped (Which applies for both a continuous pinger and a ping_once pinger).

If I understood everything correctly and didn't miss anything (The threads in my app kept increasing by 2 each time which seems like pointing directly toward those listeners), I believe that there should be a way to stop those listeners - either with stop_pinger actually stopping them or having a pinger_shutdown method that make those threads stop (And probably pair that method with a start/restart method to restart those listeners).

Thank you!

Allow adjusting pnet transport_channel buffer sizes

When using fastping-rs with a high number of targets (10k+), the hard-coded transport_channel buffer sizes are too small and one hits the following issue:

 ERROR fastping_rs::ping > Failed to send ping to 208.127.100.128: No buffer space available (os error 105)
 ERROR fastping_rs::ping > Failed to send ping to 208.127.100.157: No buffer space available (os error 105)
 ERROR fastping_rs::ping > Failed to send ping to 208.127.100.161: No buffer space available (os error 105)
 ERROR fastping_rs::ping > Failed to send ping to 208.127.101.128: No buffer space available (os error 105)
 ERROR fastping_rs::ping > Failed to send ping to 208.127.101.142: No buffer space available (os error 105)
 ERROR fastping_rs::ping > Failed to send ping to 208.127.101.155: No buffer space available (os error 105)
 ERROR fastping_rs::ping > Failed to send ping to 208.127.101.157: No buffer space available (os error 105)
 ERROR fastping_rs::ping > Failed to send ping to 208.127.117.131: No buffer space available (os error 105)
 ERROR fastping_rs::ping > Failed to send ping to 208.127.124.241: No buffer space available (os error 105)

Ideally this would be configurable:

let (tx, rx) = match transport_channel(4096, protocol) {

Support lazy creation of icmpv4 vs icmpv6 transports

Ran into a bit of a hiccup when using your library on a kernel with ipv6 disabled. (I bet you can see where this is going).

You can repro this using your example and disabling ipv6 support in your kernel with something like,

sysctl -w net. ipv6. conf. all. disable_ipv6=1.

Your library rigthfully returns (Rust)

Error: Failed to crete ICMP pinger: Address family not supported by protocol (os error 97)

(strace)

....
close(5)                                = 0
socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6) = -1 EAFNOSUPPORT (Address family not supported by protocol)
close(4)                                = 0
close(3)                                = 0
write(2, "Error: ", 7Error: )                  = 7
write(2, "Failed to crete ICMP pinger: Add"..., 83Failed to crete ICMP pinger: Address family not supported by protocol (os error 97)) = 83
write(2, "\n", 1
)                       = 1
sigaltstack({ss_sp=NULL, ss_flags=SS_DISABLE, ss_size=16384}, NULL) = 0
munmap(0x7f83cbf000, 20480)             = 0
exit_group(1)                           = ?

It looks like I could use fastping_rs::send_ping manually but the ergonomics of this is a bit off in comparison to the rest of the library. Is the send_ping function the way to do only pings only over ipv4 or am I missing something here?

Cheers

(Also?) expose IpAddr in the API directly

Currently the API only accepts &strs, but that causes some ineffiencies in caller code if

  1. you are already working with IpAddrs at the callsite, or
  2. you want to process the results (which are IpAddr) and look up some things at the callsite which are key'd on IPs, at which point you'd need to convert the results back to strs, or convert the IP keys at the callsite to IpAddr, doubling the amount of work either way.

Error 10022 on windows 11

on my windows 11 machine I get this error when running the example code provided:

ERROR fastping_rs > An error occurred while reading: An invalid argument was supplied. (os error 10022)

I am running the code in admin mode. In wireshark I can see the request is done correctly and I get a reply but I get the error in the termina.

add_ipaddr and remove_ipaddr should return a Result

First off, this is a very nice and simple pinging crate, so thank you for writing this. However, it would be nice if the functions add_ipaddr and remove_ipaddr returned a result of their IP parsing instead of handling it on their own. Them parsing it on their own can result in the pinger running indefinitely when a wrong IP is specified and therefore no IPs are present.

ping_once should return immediately when packet recieved

The idea is already described in another issue, so I will paste an adapted quote:

Regardless of the latency of the ICMP packets, Pinger.run_once() currently only returns after MaxRTT.

I understanding using MaxRTT to space pings when running with run_pinger(), but when i explicitly call Pinger.ping_once(), I want it to return as soon as the response ICMP packet is received.

build error

error: linking with `link.exe` failed: exit code: 1181
  |
  = note: "d:\\Program Files\\Microsoft Visual Studio\\2022\\Preview\\VC\\Tools\\MSVC\\14.31.30818\\bin\\HostX64\\x64\\link.exe" "/NOLOGO" ... ...
= note: Non-UTF-8 output: LINK : fatal error LNK1181: \xce\xde\xb7\xa8\xb4\xf2\xbf\xaa\xca\xe4\xc8\xeb\xce\xc4\xbc\xfe\xa1\xb0Packet.lib\xa1\xb1\r\n

sorry , I find this by searching maybe that is the cause, so I have to provide pcap library?

You must have WinPcap or npcap installed (tested with version WinPcap 4.1.3) (If using npcap, make sure to install with the "Install Npcap in WinPcap API-compatible Mode")
You must place Packet.lib from the WinPcap Developers pack in a directory named lib, in the root of this repository. Alternatively, you can use any of the locations listed in the %LIB%/$Env:LIB environment variables. For the 64 bit toolchain it is in WpdPack/Lib/x64/Packet.lib, for the 32 bit toolchain, it is in WpdPack/Lib/Packet.lib.

Remove all IPs

I think it'd be a nice API improvement (both ergonomically and efficiency-wise) if you could remove all the IPs in one go, for cases where you want to load in a new set of IPs.

(Alternatively a shutdown method would work as well, but in #25 you seem to argue against that architecture.)

Incorrectly implemented identifier and sequence

The Wireshark capture on the left shows 10 ICMP echo requests from Linux's built-in ping utility. Notice that the identifier is fixed and the sequence number increments by one for every request sent. On the right hand side fastping-rs's capture. Both fields are randomized.

image

This causes some issues. In a situation where the latency is high but pinging interval is low, the responses will be matched to the wrong request. The screenshot below demonstrates what I mean. The actual latency is around 200ms.

image

The two values on line 22 and 23 should not be randomized.

fastping-rs/src/ping.rs

Lines 16 to 30 in b645db0

fn send_echo(tx: &mut TransportSender, addr: IpAddr, size: usize) -> Result<usize, std::io::Error> {
// Allocate enough space for a new packet
let mut vec: Vec<u8> = vec![0; size];
// Use echo_request so we can set the identifier and sequence number
let mut echo_packet = echo_request::MutableEchoRequestPacket::new(&mut vec[..]).unwrap();
echo_packet.set_sequence_number(random::<u16>());
echo_packet.set_identifier(random::<u16>());
echo_packet.set_icmp_type(IcmpTypes::EchoRequest);
let csum = icmp_checksum(&echo_packet);
echo_packet.set_checksum(csum);
tx.send_to(echo_packet, addr)
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.