Skip to content
Merged
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
4 changes: 2 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ mod ping;

pub use crate::errors::Error;
pub use crate::ping::{
Ping, PingResult, SocketType, SocketType::DGRAM, SocketType::RAW, SocketType::SYSTEM,
dgramsock, new, ping, rawsock,
Ping, PingResult, SocketType, SocketType::DGRAM, SocketType::RAW, dgramsock, new, ping,
rawsock,
};
136 changes: 0 additions & 136 deletions src/ping.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ type Token = [u8; TOKEN_SIZE];
pub enum SocketType {
RAW,
DGRAM,
/// Call the system `ping`/`ping6` command instead of a raw socket.
SYSTEM,
}

#[derive(Debug)]
Expand Down Expand Up @@ -303,7 +301,6 @@ impl<'a> Ping<'a> {

pub fn send(&self) -> Result<PingResult, Error> {
match self.socket_type {
SocketType::SYSTEM => ping_with_system_cmd(self.addr, self.timeout, self.ttl),
SocketType::RAW => self.ping_with_socket(Type::RAW),
SocketType::DGRAM => self.ping_with_socket(Type::DGRAM),
}
Expand All @@ -313,136 +310,3 @@ impl<'a> Ping<'a> {
pub fn new<'a>(addr: IpAddr) -> Ping<'a> {
return Ping::new(addr);
}

#[cfg(target_os = "macos")]
fn extract_float_field(line: &str, prefix: &str) -> Option<f64> {
let start = line.find(prefix)? + prefix.len();
let rest = &line[start..];
let end = rest.find(|c: char| !c.is_ascii_digit() && c != '.').unwrap_or(rest.len());
rest[..end].parse().ok()
}

#[cfg(target_os = "macos")]
fn extract_u8_field(line: &str, prefix: &str) -> Option<u8> {
let start = line.find(prefix)? + prefix.len();
let rest = &line[start..];
let end = rest.find(|c: char| !c.is_ascii_digit()).unwrap_or(rest.len());
rest[..end].parse().ok()
}

#[cfg(target_os = "macos")]
fn extract_u16_field(line: &str, prefix: &str) -> Option<u16> {
let start = line.find(prefix)? + prefix.len();
let rest = &line[start..];
let end = rest.find(|c: char| !c.is_ascii_digit()).unwrap_or(rest.len());
rest[..end].parse().ok()
}

#[cfg(target_os = "macos")]
fn extract_source_ip(line: &str) -> Option<IpAddr> {
// Format variants:
// "64 bytes from 8.8.8.8: icmp_seq=..."
// "64 bytes from dns.google (8.8.8.8): icmp_seq=..."
// "16 bytes from ::1%lo0: icmp_seq=..."
let after_from = line.find("bytes from ")? + "bytes from ".len();
let rest = &line[after_from..];

// If there's a parenthesised IP, prefer that.
let candidate = if let Some(open) = rest.find('(') {
let close = rest.find(')')?;
&rest[open + 1..close]
} else {
// Strip trailing ':'
let end = rest.find([':', ' ']).unwrap_or(rest.len());
&rest[..end]
};

// Strip IPv6 zone ID (e.g. "::1%lo0")
let candidate = match candidate.find('%') {
Some(pct) => &candidate[..pct],
None => candidate,
};

candidate.parse().ok()
}

#[cfg(target_os = "macos")]
#[allow(deprecated)]
fn ping_with_system_cmd(
addr: IpAddr,
timeout: Option<Duration>,
ttl: Option<u32>,
) -> Result<PingResult, Error> {
use std::process::Command;

let time_start = SystemTime::now();

let output = if addr.is_ipv4() {
let mut cmd = Command::new("ping");
cmd.arg("-c").arg("1");
if let Some(t) = timeout {
cmd.arg("-W").arg(t.as_millis().to_string());
}
if let Some(t) = ttl {
cmd.arg("-m").arg(t.to_string());
}
cmd.arg(addr.to_string()).output()
} else {
let mut cmd = Command::new("ping6");
cmd.arg("-c").arg("1");
if let Some(t) = ttl {
cmd.arg("-h").arg(t.to_string());
}
cmd.arg(addr.to_string()).output()
}
.map_err(|e| Error::IoError { error: e })?;

if !output.status.success() {
let error = std::io::Error::new(std::io::ErrorKind::TimedOut, "ping command failed");
return Err(Error::IoError { error });
}

let stdout = String::from_utf8_lossy(&output.stdout);

let reply_line = stdout
.lines()
.find(|line| line.contains("bytes from") && line.contains("time="))
.ok_or_else(|| {
let error =
std::io::Error::new(std::io::ErrorKind::InvalidData, "failed to parse ping output");
Error::IoError { error }
})?;

let rtt_ms = extract_float_field(reply_line, "time=").ok_or_else(|| {
let error = std::io::Error::new(std::io::ErrorKind::InvalidData, "failed to parse RTT");
Error::IoError { error }
})?;

let seq_cnt = extract_u16_field(reply_line, "icmp_seq=").unwrap_or(0);
let reply_ttl = extract_u8_field(reply_line, "ttl=")
.or_else(|| extract_u8_field(reply_line, "hlim="));
let source = extract_source_ip(reply_line).unwrap_or(addr);

let elapsed = SystemTime::now()
.duration_since(time_start)
.unwrap_or(Duration::from_micros((rtt_ms * 1000.0) as u64));

Ok(PingResult {
rtt: elapsed,
ident: 0,
seq_cnt,
payload: vec![],
source,
target: addr,
ttl: reply_ttl,
})
}

#[cfg(not(target_os = "macos"))]
fn ping_with_system_cmd(
_addr: IpAddr,
_timeout: Option<Duration>,
_ttl: Option<u32>,
) -> Result<PingResult, Error> {
Err(Error::InvalidProtocol)
}
26 changes: 0 additions & 26 deletions tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,29 +228,3 @@ fn ping_result_raw_socket() {
// Verify payload exists
assert!(result.payload.len() >= 24); // TOKEN_SIZE
}

#[cfg(target_os = "macos")]
#[test]
fn system_ping_v4() {
let addr = "127.0.0.1".parse().unwrap();
let result = ping::new(addr)
.timeout(Duration::from_secs(2))
.socket_type(ping::SYSTEM)
.send()
.unwrap();
assert_eq!(result.source, addr);
assert!(result.rtt > Duration::from_secs(0));
}

#[cfg(target_os = "macos")]
#[test]
fn system_ping_v6() {
let addr = "::1".parse().unwrap();
let result = ping::new(addr)
.timeout(Duration::from_secs(2))
.socket_type(ping::SYSTEM)
.send()
.unwrap();
assert_eq!(result.source, addr);
assert!(result.rtt > Duration::from_secs(0));
}
Loading