Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion gping/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,18 @@ struct Args {
#[arg(long, default_value = "0")]
horizontal_margin: u16,

/// Use TCP pings instead of ICMP
#[arg(long, short = 't', default_value_t = false)]
tcping: bool,

/// Treat RST as a drop (default is to not consider RST as pong)
#[arg(long, short = 'r', default_value_t = false)]
no_rst: bool,

/// TCP port (only used for TCP pings)
#[arg(long, short = 'p', default_value_t = 80)]
port: u16,

#[arg(
name = "color",
short = 'c',
Expand Down Expand Up @@ -440,10 +452,16 @@ fn main() -> Result<()> {
} else {
PingOptions::new(host_or_cmd, interval, interface.clone())
};

if let Some(ping_args) = &ping_args {
ping_opts = ping_opts.with_raw_arguments(ping_args.clone());
}

if args.tcping {
ping_opts = ping_opts
.with_tcping(true)
.with_port(args.port)
.with_allow_rst(args.no_rst);
}
threads.push(start_ping_thread(
ping_opts,
host_id,
Expand Down
42 changes: 42 additions & 0 deletions pinger/examples/tcp_ping.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@

use pinger::{ping, PingOptions};
use std::env;
use std::time::Duration;

fn main() {
let args: Vec<String> = env::args().collect();
if args.len() < 2 {
eprintln!("Usage: tcp_ping <host> [port]");
return;
}

let host = &args[1];
let port: u16 = if args.len() >= 3 {
args[2].parse().expect("Port must be a number")
} else {
80 // default port
};

let opts = PingOptions::new(host, Duration::from_secs(1), None)
.with_tcping(true) // enable TCP ping
.with_port(port) // set port
.with_allow_rst(false); // treat RST as pong

let rx = ping(opts).expect("Failed to start TCP ping");

for result in rx {
match result {
pinger::PingResult::Pong(dur, target) => {
println!("PONG {} in {:?}", target, dur);
}
pinger::PingResult::Timeout(target) => {
println!("TIMEOUT {}", target);
}
pinger::PingResult::Unknown(target) => {
println!("UNKNOWN {}", target);
}
pinger::PingResult::PingExited(_, _) => {}
}
}
}

26 changes: 26 additions & 0 deletions pinger/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,17 @@ mod target;
#[cfg(test)]
mod test;

mod tcp;

#[derive(Debug, Clone)]
pub struct PingOptions {
pub target: Target,
pub interval: Duration,
pub interface: Option<String>,
pub raw_arguments: Option<Vec<String>>,
pub tcping: bool, // use TCP instead of ICMP
pub allow_rst: bool, // We can take leverage connection refused as a valid pong in some case
pub port: Option<u16>, // optional port for TCP ping
}

impl PingOptions {
Expand All @@ -68,6 +73,9 @@ impl PingOptions {
interval,
interface,
raw_arguments: None,
tcping: false,
allow_rst: false,
port: Some(80),
}
}
pub fn new(target: impl ToString, interval: Duration, interface: Option<String>) -> Self {
Expand All @@ -81,6 +89,20 @@ impl PingOptions {
pub fn new_ipv6(target: impl ToString, interval: Duration, interface: Option<String>) -> Self {
Self::from_target(Target::new_ipv6(target), interval, interface)
}
/// Enable or disable TCP ping
pub fn with_tcping(mut self, tcping: bool) -> Self {
self.tcping = tcping;
self
}
/// Allow or disallow treating RST as a valid response
pub fn with_allow_rst(mut self, allow: bool) -> Self {
self.allow_rst = allow;
self
}
pub fn with_port(mut self, port: u16) -> Self {
self.port = Some(port);
self
}
}

pub fn run_ping(
Expand Down Expand Up @@ -205,6 +227,10 @@ pub fn get_pinger(options: PingOptions) -> std::result::Result<Arc<dyn Pinger>,
return Ok(Arc::new(fake::FakePinger::from_options(options)?));
}

if options.tcping {
return Ok(Arc::new(crate::tcp::TcpPinger::from_options(options)?));
}

#[cfg(windows)]
{
return Ok(Arc::new(windows::WindowsPinger::from_options(options)?));
Expand Down
74 changes: 74 additions & 0 deletions pinger/src/tcp.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
use std::net::{TcpStream, ToSocketAddrs};
use std::sync::mpsc;
use std::thread;
use std::time::{Instant};
use std::io::ErrorKind;


use crate::{PingOptions, PingResult, Pinger};

pub struct TcpPinger {
options: PingOptions,
}

impl Pinger for TcpPinger {
fn from_options(options: PingOptions) -> Result<Self, crate::PingCreationError> {
Ok(TcpPinger { options })
}

fn parse_fn(&self) -> fn(String) -> Option<PingResult> {
|_| None // TCP doesn't parse lines
}

fn ping_args(&self) -> (&str, Vec<String>) {
("tcp", vec![]) // unused
}

fn start(&self) -> Result<mpsc::Receiver<PingResult>, crate::PingCreationError> {
let (tx, rx) = mpsc::channel();
let options = self.options.clone();

thread::spawn(move || {
for _ in 0.. {
let port = options.port.unwrap_or(80);
let socket_str = format!("{}:{}", options.target, port);
let addr = match socket_str.to_socket_addrs() {
Ok(mut addrs) => match addrs.next() {
Some(a) => a,
None => {
let _ = tx.send(PingResult::Unknown(
"Unable to resolve address".to_string()
));
continue;
}
},
Err(e) => {
let _ = tx.send(PingResult::Unknown(format!("Resolve error: {}", e)));
continue;
}
};

let start = Instant::now();
match TcpStream::connect_timeout(&addr, options.interval) {
Ok(_) => {
let _ = tx.send(PingResult::Pong(start.elapsed(), addr.to_string()));
}
Err(e) => {
//println!("DEBUG: error kind for {}: {:?}", addr, e.kind());
let is_rst = matches!(e.kind(), ErrorKind::ConnectionRefused);
if is_rst && options.allow_rst { // treat RST as pong default behavior
let _ = tx.send(PingResult::Pong(start.elapsed(), addr.to_string()));
} else {
let _ = tx.send(PingResult::Timeout(addr.to_string()));
}
}
}

thread::sleep(options.interval);
}
});

Ok(rx)
}
}