pinger-2.1.1/.cargo_vcs_info.json0000644000000001440000000000100123200ustar { "git": { "sha1": "f4ff9f199974bce404390ccc6b084aa9c5bd8d4e" }, "path_in_vcs": "pinger" }pinger-2.1.1/Cargo.lock0000644000000372120000000000100103010ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "aho-corasick" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "anyhow" version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bitflags" version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" [[package]] name = "cfg-if" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" [[package]] name = "deranged" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" dependencies = [ "powerfmt", ] [[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "getrandom" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", "libc", "r-efi", "wasi", ] [[package]] name = "hashbrown" version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" [[package]] name = "indexmap" version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" dependencies = [ "equivalent", "hashbrown", ] [[package]] name = "itoa" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "lazy-regex" version = "3.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60c7310b93682b36b98fa7ea4de998d3463ccbebd94d935d6b48ba5b6ffa7126" dependencies = [ "lazy-regex-proc_macros", "once_cell", "regex", ] [[package]] name = "lazy-regex-proc_macros" version = "3.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ba01db5ef81e17eb10a5e0f2109d1b3a3e29bac3070fdbd7d156bf7dbd206a1" dependencies = [ "proc-macro2", "quote", "regex", "syn 2.0.105", ] [[package]] name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" [[package]] name = "log" version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "memchr" version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "ntest" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb183f0a1da7a937f672e5ee7b7edb727bf52b8a52d531374ba8ebb9345c0330" dependencies = [ "ntest_test_cases", "ntest_timeout", ] [[package]] name = "ntest_test_cases" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16d0d3f2a488592e5368ebbe996e7f1d44aa13156efad201f5b4d84e150eaa93" dependencies = [ "proc-macro2", "quote", "syn 1.0.109", ] [[package]] name = "ntest_timeout" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcc7c92f190c97f79b4a332f5e81dcf68c8420af2045c936c9be0bc9de6f63b5" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", "syn 1.0.109", ] [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "os_info" version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0e1ac5fde8d43c34139135df8ea9ee9465394b2d8d20f032d38998f64afffc3" dependencies = [ "log", "plist", "serde", "windows-sys", ] [[package]] name = "pinger" version = "2.1.1" dependencies = [ "anyhow", "lazy-regex", "ntest", "os_info", "rand", "thiserror", "winping", ] [[package]] name = "plist" version = "1.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3af6b589e163c5a788fab00ce0c0366f6efbb9959c2f9874b224936af7fce7e1" dependencies = [ "base64", "indexmap", "quick-xml", "serde", "time", ] [[package]] name = "powerfmt" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ "zerocopy", ] [[package]] name = "proc-macro-crate" version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" dependencies = [ "toml_edit", ] [[package]] name = "proc-macro2" version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d61789d7719defeb74ea5fe81f2fdfdbd28a803847077cecce2ff14e1472f6f1" dependencies = [ "unicode-ident", ] [[package]] name = "quick-xml" version = "0.38.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9845d9dccf565065824e69f9f235fafba1587031eda353c1f1561cd6a6be78f4" dependencies = [ "memchr", ] [[package]] name = "quote" version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] [[package]] name = "r-efi" version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rand" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha", "rand_core", ] [[package]] name = "rand_chacha" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", "rand_core", ] [[package]] name = "rand_core" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ "getrandom", ] [[package]] name = "regex" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "serde" version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", "syn 2.0.105", ] [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "syn" version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "syn" version = "2.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7bc3fcb250e53458e712715cf74285c1f889686520d79294a9ef3bd7aa1fc619" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "thiserror" version = "2.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b0949c3a6c842cbde3f1686d6eea5a010516deb7085f79db747562d4102f41e" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "2.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc5b44b4ab9c2fdd0e0512e6bece8388e214c0749f5862b114cc5b7a25daf227" dependencies = [ "proc-macro2", "quote", "syn 2.0.105", ] [[package]] name = "time" version = "0.3.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" dependencies = [ "deranged", "itoa", "num-conv", "powerfmt", "serde", "time-core", "time-macros", ] [[package]] name = "time-core" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" [[package]] name = "time-macros" version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" dependencies = [ "num-conv", "time-core", ] [[package]] name = "toml_datetime" version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" [[package]] name = "toml_edit" version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ "indexmap", "toml_datetime", "winnow", ] [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "wasi" version = "0.14.2+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" dependencies = [ "wit-bindgen-rt", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "winapi_forked_icmpapi" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42aecb895d6340af9ccc8dab9aeabfeab6d5d7266c5fd172c8be7e07db71c1e3" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" dependencies = [ "memchr", ] [[package]] name = "winping" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79ed0e3a789beb896b3de9fb7e93c76340f6f4adfab7770d6222b4b8625ef0aa" dependencies = [ "lazy_static", "static_assertions", "winapi_forked_icmpapi", ] [[package]] name = "wit-bindgen-rt" version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ "bitflags", ] [[package]] name = "zerocopy" version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", "syn 2.0.105", ] pinger-2.1.1/Cargo.toml0000644000000025150000000000100103220ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "pinger" version = "2.1.1" authors = ["Tom Forbes "] build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "A small cross-platform library to execute the ping command and parse the output" readme = "README.md" license = "MIT" repository = "https://github.com/orf/pinger/" [features] default = [] fake-ping = ["rand"] [lib] name = "pinger" path = "src/lib.rs" [[example]] name = "simple-ping" path = "examples/simple-ping.rs" [dependencies.lazy-regex] version = "3.4.1" [dependencies.rand] version = "0.9.0" optional = true [dependencies.thiserror] version = "2.0.11" [dev-dependencies.anyhow] version = "1.0.95" [dev-dependencies.ntest] version = "0.9.3" [dev-dependencies.os_info] version = "3.9.2" [target."cfg(windows)".dependencies.winping] version = "0.10.1" pinger-2.1.1/Cargo.toml.orig000064400000000000000000000010341046102023000137760ustar 00000000000000[package] name = "pinger" version = "2.1.1" authors = ["Tom Forbes "] edition = "2018" license = "MIT" description = "A small cross-platform library to execute the ping command and parse the output" repository = "https://github.com/orf/pinger/" [dependencies] thiserror = "2.0.11" lazy-regex = "3.4.1" rand = { version = "0.9.0", optional = true } [target.'cfg(windows)'.dependencies] winping = "0.10.1" [dev-dependencies] os_info = "3.9.2" ntest = "0.9.3" anyhow = "1.0.95" [features] default = [] fake-ping = ["rand"] pinger-2.1.1/README.md000064400000000000000000000017531046102023000123760ustar 00000000000000# pinger > A small cross-platform library to execute the ping command and parse the output. This crate is primarily built for use with `gping`, but it can also be used as a standalone library. This allows you to reliably ping hosts without having to worry about process permissions, in a cross-platform manner on Windows, Linux and macOS. ## Usage A full example of using the library can be found in the `examples/` directory, but the interface is quite simple: ```rust use std::time::Duration; use pinger::{ping, PingOptions}; fn ping_google() { let options = PingOptions::new("google.com", Duration::from_secs(1), None); let stream = ping(options).expect("Error pinging"); for message in stream { match message { pinger::PingResult::Pong(duration, _) => { println!("Duration: {:?}", duration) } _ => {} // Handle errors, log ping timeouts, etc. } } } ``` ## Adding pinger to your project. `cargo add pinger` pinger-2.1.1/examples/simple-ping.rs000064400000000000000000000015341046102023000155240ustar 00000000000000use pinger::{ping, PingOptions}; const LIMIT: usize = 3; pub fn main() { let target = "tomforb.es".to_string(); let interval = std::time::Duration::from_millis(500); let options = PingOptions::new(target, interval, None); let stream = ping(options).expect("Error pinging"); for message in stream.into_iter().take(LIMIT) { match message { pinger::PingResult::Pong(duration, line) => { println!("Duration: {:?}\t\t(raw: {:?})", duration, line) } pinger::PingResult::Timeout(line) => println!("Timeout! (raw: {line:?})"), pinger::PingResult::Unknown(line) => println!("Unknown line: {:?}", line), pinger::PingResult::PingExited(code, stderr) => { panic!("Ping exited! Code: {:?}. Stderr: {:?}", code, stderr) } } } } pinger-2.1.1/src/bsd.rs000064400000000000000000000024251046102023000130210ustar 00000000000000use crate::{extract_regex, PingCreationError, PingOptions, PingResult, Pinger}; use lazy_regex::*; pub static RE: Lazy = lazy_regex!(r"time=(?:(?P[0-9]+).(?P[0-9]+)\s+ms)"); pub struct BSDPinger { options: PingOptions, } pub(crate) fn parse_bsd(line: String) -> Option { if line.starts_with("PING ") { return None; } if line.starts_with("Request timeout") { return Some(PingResult::Timeout(line)); } extract_regex(&RE, line) } impl Pinger for BSDPinger { fn from_options(options: PingOptions) -> Result where Self: Sized, { Ok(Self { options }) } fn parse_fn(&self) -> fn(String) -> Option { parse_bsd } fn ping_args(&self) -> (&str, Vec) { let mut args = vec![format!( "-i{:.1}", self.options.interval.as_millis() as f32 / 1_000_f32 )]; if let Some(interface) = &self.options.interface { args.push("-I".into()); args.push(interface.clone()); } if let Some(raw_args) = &self.options.raw_arguments { args.extend(raw_args.iter().cloned()); } args.push(self.options.target.to_string()); ("ping", args) } } pinger-2.1.1/src/fake.rs000064400000000000000000000025521046102023000131600ustar 00000000000000use crate::{PingCreationError, PingOptions, PingResult, Pinger}; use rand::prelude::*; use rand::rng; use std::sync::mpsc; use std::sync::mpsc::Receiver; use std::thread; use std::time::Duration; pub struct FakePinger { options: PingOptions, } impl Pinger for FakePinger { fn from_options(options: PingOptions) -> Result where Self: Sized, { Ok(Self { options }) } fn parse_fn(&self) -> fn(String) -> Option { unimplemented!("parse for FakeParser not implemented") } fn ping_args(&self) -> (&str, Vec) { unimplemented!("ping_args not implemented for FakePinger") } fn start(&self) -> Result, PingCreationError> { let (tx, rx) = mpsc::channel(); let sleep_time = self.options.interval; thread::spawn(move || { let mut random = rng(); loop { let fake_seconds = random.random_range(50..150); let ping_result = PingResult::Pong( Duration::from_millis(fake_seconds), format!("Fake ping line: {fake_seconds} ms"), ); if tx.send(ping_result).is_err() { break; } std::thread::sleep(sleep_time); } }); Ok(rx) } } pinger-2.1.1/src/lib.rs000064400000000000000000000161171046102023000130220ustar 00000000000000/// Pinger /// This crate exposes a simple function to ping remote hosts across different operating systems. /// Example: /// ```no_run /// use std::time::Duration; /// use pinger::{ping, PingResult, PingOptions}; /// let options = PingOptions::new("tomforb.es".to_string(), Duration::from_secs(1), None); /// let stream = ping(options).expect("Error pinging"); /// for message in stream { /// match message { /// PingResult::Pong(duration, line) => println!("{:?} (line: {})", duration, line), /// PingResult::Timeout(_) => println!("Timeout!"), /// PingResult::Unknown(line) => println!("Unknown line: {}", line), /// PingResult::PingExited(_code, _stderr) => {} /// } /// } /// ``` use lazy_regex::Regex; use std::ffi::OsStr; use std::fmt::{Debug, Formatter}; use std::io::{BufRead, BufReader}; use std::process::{Child, Command, ExitStatus, Stdio}; use std::sync::{mpsc, Arc}; use std::time::Duration; use std::{fmt, io, thread}; use target::Target; use thiserror::Error; #[cfg(unix)] pub mod linux; #[cfg(unix)] pub mod macos; #[cfg(windows)] pub mod windows; #[cfg(unix)] mod bsd; #[cfg(feature = "fake-ping")] mod fake; mod target; #[cfg(test)] mod test; #[derive(Debug, Clone)] pub struct PingOptions { pub target: Target, pub interval: Duration, pub interface: Option, pub raw_arguments: Option>, } impl PingOptions { pub fn with_raw_arguments(mut self, raw_arguments: Vec) -> Self { self.raw_arguments = Some( raw_arguments .into_iter() .map(|item| item.to_string()) .collect(), ); self } } impl PingOptions { pub fn from_target(target: Target, interval: Duration, interface: Option) -> Self { Self { target, interval, interface, raw_arguments: None, } } pub fn new(target: impl ToString, interval: Duration, interface: Option) -> Self { Self::from_target(Target::new_any(target), interval, interface) } pub fn new_ipv4(target: impl ToString, interval: Duration, interface: Option) -> Self { Self::from_target(Target::new_ipv4(target), interval, interface) } pub fn new_ipv6(target: impl ToString, interval: Duration, interface: Option) -> Self { Self::from_target(Target::new_ipv6(target), interval, interface) } } pub fn run_ping( cmd: impl AsRef + Debug, args: Vec + Debug>, ) -> Result { Ok(Command::new(cmd.as_ref()) .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()?) } pub(crate) fn extract_regex(regex: &Regex, line: String) -> Option { let cap = regex.captures(&line)?; let ms = cap .name("ms") .expect("No capture group named 'ms'") .as_str() .parse::() .ok()?; let ns = match cap.name("ns") { None => 0, Some(cap) => { let matched_str = cap.as_str(); let number_of_digits = matched_str.len() as u32; let fractional_ms = matched_str.parse::().ok()?; fractional_ms * (10u64.pow(6 - number_of_digits)) } }; let duration = Duration::from_millis(ms) + Duration::from_nanos(ns); Some(PingResult::Pong(duration, line)) } pub trait Pinger: Send + Sync { fn from_options(options: PingOptions) -> std::result::Result where Self: Sized; fn parse_fn(&self) -> fn(String) -> Option; fn ping_args(&self) -> (&str, Vec); fn start(&self) -> Result, PingCreationError> { let (tx, rx) = mpsc::channel(); let (cmd, args) = self.ping_args(); let mut child = run_ping(cmd, args)?; let stdout = child.stdout.take().expect("child did not have a stdout"); let parse_fn = self.parse_fn(); thread::spawn(move || { let reader = BufReader::new(stdout).lines(); for line in reader { match line { Ok(msg) => { if let Some(result) = parse_fn(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)); }); Ok(rx) } } #[derive(Debug)] pub enum PingResult { Pong(Duration, String), Timeout(String), Unknown(String), PingExited(ExitStatus, String), } impl fmt::Display for PingResult { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match &self { PingResult::Pong(duration, _) => write!(f, "{duration:?}"), PingResult::Timeout(_) => write!(f, "Timeout"), PingResult::Unknown(_) => write!(f, "Unknown"), PingResult::PingExited(status, stderr) => write!(f, "Exited({status}, {stderr})"), } } } #[derive(Error, Debug)] pub enum PingCreationError { #[error("Could not detect ping. Stderr: {stderr:?}\nStdout: {stdout:?}")] UnknownPing { stderr: Vec, stdout: Vec, }, #[error("Error spawning ping: {0}")] SpawnError(#[from] io::Error), #[error("Installed ping is not supported: {alternative}")] NotSupported { alternative: String }, #[error("Invalid or unresolvable hostname {0}")] HostnameError(String), } pub fn get_pinger(options: PingOptions) -> std::result::Result, PingCreationError> { #[cfg(feature = "fake-ping")] if std::env::var("PINGER_FAKE_PING") .map(|e| e == "1") .unwrap_or_default() { return Ok(Arc::new(fake::FakePinger::from_options(options)?)); } #[cfg(windows)] { return Ok(Arc::new(windows::WindowsPinger::from_options(options)?)); } #[cfg(unix)] { if cfg!(target_os = "freebsd") || cfg!(target_os = "dragonfly") || cfg!(target_os = "openbsd") || cfg!(target_os = "netbsd") { Ok(Arc::new(bsd::BSDPinger::from_options(options)?)) } else if cfg!(target_os = "macos") { Ok(Arc::new(macos::MacOSPinger::from_options(options)?)) } else { Ok(Arc::new(linux::LinuxPinger::from_options(options)?)) } } } /// Start pinging a an address. The address can be either a hostname or an IP address. pub fn ping( options: PingOptions, ) -> std::result::Result, PingCreationError> { let pinger = get_pinger(options)?; pinger.start() } pinger-2.1.1/src/linux.rs000064400000000000000000000107601046102023000134110ustar 00000000000000use crate::{extract_regex, run_ping, PingCreationError, PingOptions, PingResult, Pinger}; use lazy_regex::*; pub static UBUNTU_RE: Lazy = lazy_regex!(r"(?i-u)time=(?P\d+)(?:\.(?P\d+))? *ms"); #[derive(Debug)] pub enum LinuxPinger { // Alpine BusyBox(PingOptions), // Debian, Ubuntu, etc IPTools(PingOptions), } impl LinuxPinger { pub fn detect_platform_ping(options: PingOptions) -> Result { let child = run_ping("ping", vec!["-V".to_string()])?; let output = child.wait_with_output()?; let stdout = String::from_utf8(output.stdout).expect("Error decoding ping stdout"); let stderr = String::from_utf8(output.stderr).expect("Error decoding ping stderr"); if stderr.contains("BusyBox") { Ok(LinuxPinger::BusyBox(options)) } else if stdout.contains("iputils") { Ok(LinuxPinger::IPTools(options)) } else if stdout.contains("inetutils") { Err(PingCreationError::NotSupported { alternative: "Please use iputils ping, not inetutils.".to_string(), }) } else { let first_two_lines_stderr: Vec = stderr.lines().take(2).map(str::to_string).collect(); let first_two_lines_stout: Vec = stdout.lines().take(2).map(str::to_string).collect(); Err(PingCreationError::UnknownPing { stdout: first_two_lines_stout, stderr: first_two_lines_stderr, }) } } } impl Pinger for LinuxPinger { fn from_options(options: PingOptions) -> Result where Self: Sized, { Self::detect_platform_ping(options) } fn parse_fn(&self) -> fn(String) -> Option { |line| { #[cfg(test)] eprintln!("Got line {line}"); if line.starts_with("64 bytes from") { return extract_regex(&UBUNTU_RE, line); } else if line.starts_with("no answer yet") { return Some(PingResult::Timeout(line)); } None } } fn ping_args(&self) -> (&str, Vec) { match self { // Alpine doesn't support timeout notifications, so we don't add the -O flag here. LinuxPinger::BusyBox(options) => { let cmd = if options.target.is_ipv6() { "ping6" } else { "ping" }; let mut args = vec![ options.target.to_string(), format!("-i{:.1}", options.interval.as_millis() as f32 / 1_000_f32), ]; if let Some(raw_args) = &options.raw_arguments { args.extend(raw_args.iter().cloned()); } (cmd, args) } LinuxPinger::IPTools(options) => { let cmd = if options.target.is_ipv6() { "ping6" } else { "ping" }; // The -O flag ensures we "no answer yet" messages from ping // See https://superuser.com/questions/270083/linux-ping-show-time-out let mut args = vec![ "-O".to_string(), format!("-i{:.1}", options.interval.as_millis() as f32 / 1_000_f32), ]; if let Some(interface) = &options.interface { args.push("-I".into()); args.push(interface.clone()); } if let Some(raw_args) = &options.raw_arguments { args.extend(raw_args.iter().cloned()); } args.push(options.target.to_string()); (cmd, args) } } } } #[cfg(test)] mod tests { #[test] #[cfg(target_os = "linux")] fn test_linux_detection() { use super::*; use os_info::Type; use std::time::Duration; let platform = LinuxPinger::detect_platform_ping(PingOptions::new( "foo.com".to_string(), Duration::from_secs(1), None, )) .unwrap(); match os_info::get().os_type() { Type::Alpine => { assert!(matches!(platform, LinuxPinger::BusyBox(_))) } Type::Ubuntu => { assert!(matches!(platform, LinuxPinger::IPTools(_))) } _ => {} } } } pinger-2.1.1/src/macos.rs000064400000000000000000000022711046102023000133520ustar 00000000000000use crate::bsd::parse_bsd; use crate::{PingCreationError, PingOptions, PingResult, Pinger}; use lazy_regex::*; pub static RE: Lazy = lazy_regex!(r"time=(?:(?P[0-9]+).(?P[0-9]+)\s+ms)"); pub struct MacOSPinger { options: PingOptions, } impl Pinger for MacOSPinger { fn from_options(options: PingOptions) -> Result where Self: Sized, { Ok(Self { options }) } fn parse_fn(&self) -> fn(String) -> Option { parse_bsd } fn ping_args(&self) -> (&str, Vec) { let cmd = if self.options.target.is_ipv6() { "ping6" } else { "ping" }; let mut args = vec![ format!( "-i{:.1}", self.options.interval.as_millis() as f32 / 1_000_f32 ), self.options.target.to_string(), ]; if let Some(interface) = &self.options.interface { args.push("-b".into()); args.push(interface.clone()); } if let Some(raw_args) = &self.options.raw_arguments { args.extend(raw_args.iter().cloned()); } (cmd, args) } } pinger-2.1.1/src/target.rs000064400000000000000000000032151046102023000135350ustar 00000000000000use std::fmt; use std::fmt::{Display, Formatter}; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum IPVersion { V4, V6, Any, } #[derive(Debug, Clone)] pub enum Target { IP(IpAddr), Hostname { domain: String, version: IPVersion }, } impl Target { pub fn is_ipv6(&self) -> bool { match self { Target::IP(ip) => ip.is_ipv6(), Target::Hostname { version, .. } => *version == IPVersion::V6, } } pub fn new_any(value: impl ToString) -> Self { let value = value.to_string(); if let Ok(ip) = value.parse::() { return Self::IP(ip); } Self::Hostname { domain: value, version: IPVersion::Any, } } pub fn new_ipv4(value: impl ToString) -> Self { let value = value.to_string(); if let Ok(ip) = value.parse::() { return Self::IP(IpAddr::V4(ip)); } Self::Hostname { domain: value.to_string(), version: IPVersion::V4, } } pub fn new_ipv6(value: impl ToString) -> Self { let value = value.to_string(); if let Ok(ip) = value.parse::() { return Self::IP(IpAddr::V6(ip)); } Self::Hostname { domain: value.to_string(), version: IPVersion::V6, } } } impl Display for Target { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { Target::IP(v) => Display::fmt(&v, f), Target::Hostname { domain, .. } => Display::fmt(&domain, f), } } } pinger-2.1.1/src/test.rs000064400000000000000000000115131046102023000132260ustar 00000000000000#[cfg(test)] mod tests { #[cfg(unix)] use crate::bsd::BSDPinger; #[cfg(unix)] use crate::linux::LinuxPinger; #[cfg(unix)] use crate::macos::MacOSPinger; #[cfg(windows)] use crate::windows::WindowsPinger; use crate::{PingOptions, PingResult, Pinger}; use anyhow::bail; use ntest::timeout; use std::time::Duration; const IS_GHA: bool = option_env!("GITHUB_ACTIONS").is_some(); #[test] #[timeout(20_000)] fn test_integration_any() { run_integration_test(PingOptions::new( "tomforb.es", Duration::from_millis(500), None, )) .unwrap(); } #[test] #[timeout(20_000)] fn test_integration_ipv4() { run_integration_test(PingOptions::new_ipv4( "tomforb.es", Duration::from_millis(500), None, )) .unwrap(); } #[test] #[timeout(20_000)] fn test_integration_ip6() { let res = run_integration_test(PingOptions::new_ipv6( "tomforb.es", Duration::from_millis(500), None, )); // ipv6 tests are allowed to fail on Gitlab CI, as it doesn't support ipv6, apparently. if !IS_GHA { res.unwrap(); } } fn run_integration_test(options: PingOptions) -> anyhow::Result<()> { let stream = crate::ping(options.clone())?; let mut success = 0; let mut errors = 0; for message in stream.into_iter().take(3) { match message { PingResult::Pong(_, m) | PingResult::Timeout(m) => { eprintln!("Message: {}", m); success += 1; } PingResult::Unknown(line) => { eprintln!("Unknown line: {}", line); errors += 1; } PingResult::PingExited(code, stderr) => { bail!("Ping exited with code: {}, stderr: {}", code, stderr); } } } assert_eq!(success, 3, "Success != 3 with opts {options:?}"); assert_eq!(errors, 0, "Errors != 0 with opts {options:?}"); Ok(()) } fn opts() -> PingOptions { PingOptions::new("foo".to_string(), Duration::from_secs(1), None) } fn test_parser(contents: &str) { let pinger = T::from_options(opts()).unwrap(); run_parser_test(contents, &pinger); } fn run_parser_test(contents: &str, pinger: &impl Pinger) { let parser = pinger.parse_fn(); let test_file: Vec<&str> = contents.split("-----").collect(); let input = test_file[0].trim().split('\n'); let expected: Vec<&str> = test_file[1].trim().split('\n').collect(); let parsed: Vec> = input.map(|l| parser(l.to_string())).collect(); assert_eq!( parsed.len(), expected.len(), "Parsed: {:?}, Expected: {:?}", &parsed, &expected ); for (idx, (output, expected)) in parsed.into_iter().zip(expected).enumerate() { if let Some(value) = output { assert_eq!( format!("{value}").trim(), expected.trim(), "Failed at idx {idx}" ) } else { assert_eq!("None", expected.trim(), "Failed at idx {idx}") } } } #[cfg(unix)] #[test] fn macos() { test_parser::(include_str!("tests/macos.txt")); } #[cfg(unix)] #[test] fn freebsd() { test_parser::(include_str!("tests/bsd.txt")); } #[cfg(unix)] #[test] fn dragonfly() { test_parser::(include_str!("tests/bsd.txt")); } #[cfg(unix)] #[test] fn openbsd() { test_parser::(include_str!("tests/bsd.txt")); } #[cfg(unix)] #[test] fn netbsd() { test_parser::(include_str!("tests/bsd.txt")); } #[cfg(unix)] #[test] fn ubuntu() { run_parser_test( include_str!("tests/ubuntu.txt"), &LinuxPinger::IPTools(opts()), ); } #[cfg(unix)] #[test] fn debian() { run_parser_test( include_str!("tests/debian.txt"), &LinuxPinger::IPTools(opts()), ); } #[cfg(windows)] #[test] fn windows() { test_parser::(include_str!("tests/windows.txt")); } #[cfg(unix)] #[test] fn android() { run_parser_test( include_str!("tests/android.txt"), &LinuxPinger::BusyBox(opts()), ); } #[cfg(unix)] #[test] fn alpine() { run_parser_test( include_str!("tests/alpine.txt"), &LinuxPinger::BusyBox(opts()), ); } } pinger-2.1.1/src/tests/alpine.txt000064400000000000000000000004541046102023000150560ustar 00000000000000PING google.com (142.250.178.14): 56 data bytes 64 bytes from 142.250.178.14: seq=0 ttl=37 time=19.236 ms 64 bytes from 142.250.178.14: seq=1 ttl=37 time=19.319 ms 64 bytes from 142.250.178.14: seq=2 ttl=37 time=17.944 ms ping: sendto: Network unreachable ----- None 19.236ms 19.319ms 17.944ms None pinger-2.1.1/src/tests/android.txt000064400000000000000000000014221046102023000152220ustar 00000000000000PING google.com (172.217.173.46) 56(84) bytes of data. 64 bytes from bog02s12-in-f14.1e100.net (172.217.173.46): icmp_seq=1 ttl=110 time=106 ms 64 bytes from bog02s12-in-f14.1e100.net (172.217.173.46): icmp_seq=2 ttl=110 time=142 ms 64 bytes from bog02s12-in-f14.1e100.net (172.217.173.46): icmp_seq=3 ttl=110 time=244 ms 64 bytes from bog02s12-in-f14.1e100.net (172.217.173.46): icmp_seq=4 ttl=110 time=120 ms 64 bytes from bog02s12-in-f14.1e100.net (172.217.173.46): icmp_seq=5 ttl=110 time=122 ms 64 bytes from 172.217.173.46: icmp_seq=6 ttl=110 time=246 ms --- google.com ping statistics --- 6 packets transmitted, 6 received, 0% packet loss, time 5018ms rtt min/avg/max/mdev = 106.252/163.821/246.851/58.823 ms ----- None 106ms 142ms 244ms 120ms 122ms 246ms None None None None pinger-2.1.1/src/tests/bsd.txt000064400000000000000000000004171046102023000143550ustar 00000000000000PING google.com (216.58.198.174): 56 data bytes 64 bytes from 96.47.72.84: icmp_seq=0 ttl=50 time=111.525 ms ping: sendto: Host is down 64 bytes from 96.47.72.84: icmp_seq=1 ttl=50 time=110.395 ms ping: sendto: No route to host ----- None 111.525ms None 110.395ms None pinger-2.1.1/src/tests/debian.txt000064400000000000000000000005661046102023000150340ustar 00000000000000PING google.com (216.58.209.78): 56 data bytes 64 bytes from 216.58.209.78: icmp_seq=0 ttl=37 time=21.308 ms 64 bytes from 216.58.209.78: icmp_seq=1 ttl=37 time=15.769 ms ^C--- google.com ping statistics --- 8 packets transmitted, 8 packets received, 0% packet loss round-trip min/avg/max/stddev = 15.282/20.347/41.775/8.344 ms ----- None 21.308ms 15.769ms None None None pinger-2.1.1/src/tests/macos.txt000064400000000000000000000012551046102023000147100ustar 00000000000000PING google.com (216.58.209.78): 56 data bytes 64 bytes from 216.58.209.78: icmp_seq=0 ttl=119 time=14.621 ms 64 bytes from 216.58.209.78: icmp_seq=1 ttl=119 time=33.898 ms 64 bytes from 216.58.209.78: icmp_seq=2 ttl=119 time=17.305 ms 64 bytes from 216.58.209.78: icmp_seq=3 ttl=119 time=24.235 ms 64 bytes from 216.58.209.78: icmp_seq=4 ttl=119 time=15.242 ms 64 bytes from 216.58.209.78: icmp_seq=5 ttl=119 time=16.639 ms Request timeout for icmp_seq 19 Request timeout for icmp_seq 20 Request timeout for icmp_seq 21 64 bytes from 216.58.209.78: icmp_seq=30 ttl=119 time=16.943 ms ----- None 14.621ms 33.898ms 17.305ms 24.235ms 15.242ms 16.639ms Timeout Timeout Timeout 16.943ms pinger-2.1.1/src/tests/ubuntu.txt000064400000000000000000000021021046102023000151200ustar 00000000000000PING google.com (216.58.209.78) 56(84) bytes of data. 64 bytes from mad07s22-in-f14.1e100.net (216.58.209.78): icmp_seq=1 ttl=37 time=25.1 ms 64 bytes from mad07s22-in-f14.1e100.net (216.58.209.78): icmp_seq=2 ttl=37 time=19.4 ms 64 bytes from mad07s22-in-f14.1e100.net (216.58.209.78): icmp_seq=3 ttl=37 time=14.9 ms 64 bytes from mad07s22-in-f14.1e100.net (216.58.209.78): icmp_seq=4 ttl=37 time=22.8 ms 64 bytes from mad07s22-in-f14.1e100.net (216.58.209.78): icmp_seq=5 ttl=37 time=13.9 ms 64 bytes from mad07s22-in-f14.1e100.net (216.58.209.78): icmp_seq=6 ttl=37 time=77.6 ms 64 bytes from mad07s22-in-f14.1e100.net (216.58.209.78): icmp_seq=7 ttl=37 time=158 ms no answer yet for icmp_seq=8 no answer yet for icmp_seq=9 64 bytes from mad07s22-in-f14.1e100.net (216.58.209.78): icmp_seq=18 ttl=37 time=357 ms 64 bytes from mad07s22-in-f14.1e100.net (216.58.209.78): icmp_seq=19 ttl=37 time=85.2 ms 64 bytes from mad07s22-in-f14.1e100.net (216.58.209.78): icmp_seq=20 ttl=37 time=17.8 ms ----- None 25.1ms 19.4ms 14.9ms 22.8ms 13.9ms 77.6ms 158ms Timeout Timeout 357ms 85.2ms 17.8ms pinger-2.1.1/src/tests/windows.txt000064400000000000000000000007001046102023000152720ustar 00000000000000pinging example.microsoft.com [192.168.239.132] with 32 bytes of data: Reply from 192.168.239.132: bytes=32 time=101ms TTL=124 Reply from 192.168.239.132: bytes=32 time=100ms TTL=124 Reply from 192.168.239.132: bytes=32 time=120ms TTL=124 Reply from 192.168.239.132: bytes=32 time=120ms TTL=124 Request timed out. Request timed out. Reply from 192.168.239.132: bytes=32 time=120ms TTL=124 ----- None 101ms 100ms 120ms 120ms Timeout Timeout 120ms pinger-2.1.1/src/windows.rs000064400000000000000000000061541046102023000137460ustar 00000000000000use crate::target::{IPVersion, Target}; use crate::PingCreationError; use crate::{extract_regex, PingOptions, PingResult, Pinger}; use lazy_regex::*; use std::net::{IpAddr, ToSocketAddrs}; use std::sync::mpsc; use std::thread; use std::time::Duration; use winping::{Buffer, Pinger as WinPinger}; pub static RE: Lazy = lazy_regex!(r"(?ix-u)time=(?P\d+)(?:\.(?P\d+))?"); pub struct WindowsPinger { options: PingOptions, } impl Pinger for WindowsPinger { fn from_options(options: PingOptions) -> Result { Ok(Self { options }) } fn parse_fn(&self) -> fn(String) -> Option { |line| { if line.contains("timed out") || line.contains("failure") { return Some(PingResult::Timeout(line)); } extract_regex(&RE, line) } } fn ping_args(&self) -> (&str, Vec) { unimplemented!("ping_args for WindowsPinger is not implemented") } fn start(&self) -> Result, PingCreationError> { let interval = self.options.interval; let parsed_ip = match &self.options.target { Target::IP(ip) => ip.clone(), Target::Hostname { domain, version } => { let ips = (domain.as_str(), 0).to_socket_addrs()?; let selected_ips: Vec<_> = if *version == IPVersion::Any { ips.collect() } else { ips.into_iter() .filter(|addr| { if *version == IPVersion::V6 { matches!(addr.ip(), IpAddr::V6(_)) } else { matches!(addr.ip(), IpAddr::V4(_)) } }) .collect() }; if selected_ips.is_empty() { return Err(PingCreationError::HostnameError(domain.clone()).into()); } selected_ips[0].ip() } }; let (tx, rx) = mpsc::channel(); thread::spawn(move || { let pinger = WinPinger::new().expect("Failed to create a WinPinger instance"); let mut buffer = Buffer::new(); loop { match pinger.send(parsed_ip.clone(), &mut buffer) { Ok(rtt) => { if tx .send(PingResult::Pong( Duration::from_millis(rtt as u64), "".to_string(), )) .is_err() { break; } } Err(_) => { // Fuck it. All errors are timeouts. Why not. if tx.send(PingResult::Timeout("".to_string())).is_err() { break; } } } thread::sleep(interval); } }); Ok(rx) } }