1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
use anyhow::Result;
use os_info::Type;
use regex::Regex;
use std::io::{BufRead, BufReader};
use std::process::{Command, Stdio};
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
use thiserror::Error;
#[macro_use]
extern crate lazy_static;
pub mod linux;
pub mod macos;
#[cfg(test)]
mod test;
pub mod windows;
pub trait Pinger: Default {
fn start<P>(&self, target: String) -> Result<mpsc::Receiver<PingResult>>
where
P: Parser,
{
let (tx, rx) = mpsc::channel();
let args = self.ping_args(target);
thread::spawn(move || {
let mut child = Command::new("ping")
.args(args)
.stdout(Stdio::piped())
.stderr(Stdio::null())
.spawn()
.expect("Failed to run ping");
let parser = P::default();
let stdout = child.stdout.take().expect("child did not have a stdout");
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,
}
}
});
Ok(rx)
}
fn ping_args(&self, target: String) -> Vec<String> {
return vec![target];
}
}
#[derive(Default)]
pub struct SimplePinger {}
impl Pinger for SimplePinger {}
pub trait Parser: Default {
fn parse(&self, line: String) -> Option<PingResult>;
fn extract_regex(&self, regex: &Regex, line: String) -> Option<PingResult> {
let cap = regex.captures(&line)?;
let time = cap
.name("time")
.expect("No capture group named 'time'")
.as_str()
.parse::<f32>()
.expect("time cannot be parsed as f32");
Some(PingResult::Pong(Duration::from_micros(
(time * 100f32) as u64,
)))
}
}
#[derive(Debug)]
pub enum PingResult {
Pong(Duration),
Timeout,
}
#[derive(Error, Debug)]
pub enum PingError {
#[error("Unsupported OS {0}")]
UnsupportedOS(String),
}
pub fn ping(addr: String) -> Result<mpsc::Receiver<PingResult>> {
let os_type = os_info::get().os_type();
match os_type {
Type::Windows => {
let p = SimplePinger::default();
p.start::<windows::WindowsParser>(addr)
}
Type::Linux | Type::Debian | Type::Ubuntu | Type::Alpine => {
let p = linux::LinuxPinger::default();
p.start::<linux::LinuxParser>(addr)
}
Type::Macos => {
let p = macos::MacOSPinger::default();
p.start::<macos::MacOSParser>(addr)
}
_ => Err(PingError::UnsupportedOS(os_type.to_string()).into()),
}
}