Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft: change pinger to work in "better" manner #273

Open
wants to merge 20 commits into
base: master
Choose a base branch
from
Prev Previous commit
Next Next commit
hi
nitay committed Mar 2, 2023
commit f921a7e92dcf784d8431d7eac4d41db0b8c504ac
63 changes: 8 additions & 55 deletions pinger/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#[cfg(unix)]
use crate::linux::{detect_linux_ping, LinuxPingType};
use crate::linux::{detect_linux_ping};
/// Pinger
/// This crate exposes a simple function to ping remote hosts across different operating systems.
/// Example:
@@ -19,8 +19,7 @@ use crate::linux::{detect_linux_ping, LinuxPingType};
use anyhow::{Context, Result};
use regex::Regex;
use std::fmt::Formatter;
use std::io::{BufRead, BufReader};
use std::process::{Child, Command, ExitStatus, Stdio};
use std::process::{Child, Command, ExitStatus, Stdio, Output};
use std::sync::mpsc;
use std::time::Duration;
use std::{fmt, thread};
@@ -38,53 +37,23 @@ pub mod windows;
#[cfg(test)]
mod test;

pub fn run_ping(cmd: &str, args: Vec<String>) -> Result<Child> {
pub fn run_ping(cmd: &str, args: Vec<String>) -> Result<Output> {
Command::new(cmd)
.args(&args)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
// Required to ensure that the output is formatted in the way we expect, not
// using locale specific delimiters.
.env("LANG", "C")
.env("LC_ALL", "C")
.spawn()
.with_context(|| format!("Failed to run ping with args {:?}", &args))
.output()
.context(|| format!("Failed to run ping with args {:?}", &args))
}

pub trait Pinger: Default {
fn start<P>(&self, target: String) -> Result<mpsc::Receiver<PingResult>>
where
P: Parser,
{
let (tx, rx) = mpsc::channel();
let (cmd, args) = self.ping_args(target);
let mut child = run_ping(cmd, args)?;
let stdout = child.stdout.take().context("child did not have a stdout")?;

thread::spawn(move || {
let parser = P::default();
let reader = BufReader::new(stdout).lines();
for line in reader {
match line {
Ok(msg) => {
if let Some(result) = parser.parse(msg) {
if tx.send(result).is_err() {
break;
}
}
}
Err(_) => break,
}
}
let result = child.wait_with_output().expect("Child wasn't started?");
let decoded_stderr = String::from_utf8(result.stderr).expect("Error decoding stderr");
let _ = tx.send(PingResult::PingExited(result.status, decoded_stderr));
});
fn start<P: Parser>(&self, target: String) -> Result<mpsc::Receiver<PingResult>>;

Ok(rx)
}

fn set_interval(&mut self, interval: Duration);
fn get_interval(&mut self);

fn set_interface(&mut self, interface: Option<String>);

@@ -93,16 +62,6 @@ pub trait Pinger: Default {
}
}

// Default empty implementation of a pinger.
#[derive(Default)]
pub struct SimplePinger {}

impl Pinger for SimplePinger {
fn set_interval(&mut self, _interval: Duration) {}

fn set_interface(&mut self, _interface: Option<String>) {}
}

pub trait Parser: Default {
fn parse(&self, line: String) -> Option<PingResult>;

@@ -190,18 +149,12 @@ pub fn ping_with_interval(
#[cfg(unix)]
{
match detect_linux_ping() {
Ok(LinuxPingType::IPTools) => {
Ok(_) => {
let mut p = linux::LinuxPinger::default();
p.set_interval(interval);
p.set_interface(interface);
p.start::<linux::LinuxParser>(addr)
}
Ok(LinuxPingType::BusyBox) => {
let mut p = linux::AlpinePinger::default();
p.set_interval(interval);
p.set_interface(interface);
p.start::<linux::LinuxParser>(addr)
}
Err(e) => Err(PingError::UnsupportedPing(e))?,
}
}
94 changes: 41 additions & 53 deletions pinger/src/linux.rs
Original file line number Diff line number Diff line change
@@ -1,30 +1,19 @@
use crate::{run_ping, Parser, PingDetectionError, PingResult, Pinger};
use anyhow::Context;
use anyhow::{Context, Result};
use regex::Regex;
use std::time::Duration;
use std::{time::Duration, io::{BufRead, BufReader}, thread, sync::mpsc, process::Output};

#[derive(Debug, Eq, PartialEq)]
pub enum LinuxPingType {
BusyBox,
IPTools,
}

pub fn detect_linux_ping() -> Result<LinuxPingType, PingDetectionError> {
pub fn detect_linux_ping() -> Result<(), PingDetectionError> {
let child = run_ping("ping", vec!["-V".to_string()])?;
let output = child
.wait_with_output()
.context("Error getting ping stdout/stderr")?;
let stdout = String::from_utf8(output.stdout).context("Error decoding ping stdout")?;
let stderr = String::from_utf8(output.stderr).context("Error decoding ping stderr")?;

if stderr.contains("BusyBox") {
Ok(LinuxPingType::BusyBox)
} else if stdout.contains("iputils") {
Ok(LinuxPingType::IPTools)
} else if stdout.contains("inetutils") {
Err(PingDetectionError::NotSupported {
alternative: "Please use iputils ping, not inetutils.".to_string(),
})
if stdout.contains("iputils") {
Ok(())
} else {
let first_two_lines_stderr: Vec<String> =
stderr.lines().take(2).map(str::to_string).collect();
@@ -44,10 +33,46 @@ pub struct LinuxPinger {
}

impl Pinger for LinuxPinger {

fn start<P>(&self, target: String) -> Result<mpsc::Receiver<PingResult>>
where
P: Parser,
{
let (tx, rx) = mpsc::channel();
let parser = P::default();

thread::spawn(move || {
let (cmd, args) = self.ping_args(target);
match run_ping(cmd, args) {
Ok(output) => {
if output.status.success() {
if let Some(result) = parser.parse(output.stdout) {
if tx.send(result).is_err() {

}
}
} else {
let decoded_stderr = String::from_utf8(output.stderr).expect("Error decoding stderr");
let _ = tx.send(PingResult::PingExited(output.status, decoded_stderr));
}
}
Err(error) => {
}
};
thread::sleep(self.interval);
});

Ok(rx)
}

fn set_interval(&mut self, interval: Duration) {
self.interval = interval;
}

fn get_interval(&mut self) {
self.interval.clone();
}

fn set_interface(&mut self, interface: Option<String>) {
self.interface = interface;
}
@@ -69,23 +94,6 @@ impl Pinger for LinuxPinger {
}
}

#[derive(Default)]
pub struct AlpinePinger {
interval: Duration,
interface: Option<String>,
}

// Alpine doesn't support timeout notifications, so we don't add the -O flag here
impl Pinger for AlpinePinger {
fn set_interval(&mut self, interval: Duration) {
self.interval = interval;
}

fn set_interface(&mut self, interface: Option<String>) {
self.interface = interface;
}
}

lazy_static! {
static ref UBUNTU_RE: Regex =
Regex::new(r"(?i-u)time=(?P<ms>\d+)(?:\.(?P<ns>\d+))? *ms").unwrap();
@@ -104,23 +112,3 @@ impl Parser for LinuxParser {
None
}
}

#[cfg(test)]
mod tests {
#[test]
#[cfg(target_os = "linux")]
fn test_linux_detection() {
use super::*;
use os_info::Type;
let ping_type = detect_linux_ping().expect("Error getting ping");
match os_info::get().os_type() {
Type::Alpine => {
assert_eq!(ping_type, LinuxPingType::BusyBox)
}
Type::Ubuntu => {
assert_eq!(ping_type, LinuxPingType::IPTools)
}
_ => {}
}
}
}
3 changes: 3 additions & 0 deletions pinger/src/windows.rs
Original file line number Diff line number Diff line change
@@ -75,6 +75,9 @@ impl Pinger for WindowsPinger {
fn set_interface(&mut self, interface: Option<String>) {
self.interface = interface;
}
fn get_interval(&mut self) {
self.interval.clone();
}
}

#[derive(Default)]