towerops-agent/src/ping.rs
Graham McIntire cd45e9ff51
feat: add agent-side ICMP ping health monitoring
Implements health monitoring in the agent using ICMP ping, allowing
devices assigned to local (non-cloud) agents to be monitored.

Changes:
- Add surge-ping library for async ICMP ping operations
- Create ping.rs module with ping_device() function (5s timeout)
- Rename JobType::MONITOR to JobType::PING in protobuf
- Add monitoring_check channels for result communication
- Implement execute_ping_job() to handle PING jobs
- Update TUI to display ping result events

The agent now receives PING jobs from Phoenix, executes ICMP pings,
and sends MonitoringCheck results back for storage in the database.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-08 13:44:09 -06:00

68 lines
2 KiB
Rust

use anyhow::{Context, Result};
use std::net::IpAddr;
use std::time::Duration;
use surge_ping::{Client, Config, PingIdentifier, PingSequence};
use tokio::time::timeout;
/// Ping a device and return response time in milliseconds.
///
/// Returns Ok(response_time_ms) on success, Err on failure.
pub async fn ping_device(ip_address: &str, timeout_ms: u64) -> Result<f64> {
let ip: IpAddr = ip_address
.parse()
.context(format!("Invalid IP address: {}", ip_address))?;
// Create ICMP client
let client = Client::new(&Config::default())
.context("Failed to create ping client - may require root/admin privileges")?;
// Send ping with timeout
let payload = [0; 56]; // Standard ping payload size
let identifier = PingIdentifier(rand::random());
let sequence = PingSequence(1);
let ping_future = async {
match ip {
IpAddr::V4(addr) => {
client
.pinger(addr.into(), identifier)
.await
.ping(sequence, &payload)
.await
}
IpAddr::V6(addr) => {
client
.pinger(addr.into(), identifier)
.await
.ping(sequence, &payload)
.await
}
}
};
// Apply timeout
let (_, duration) = timeout(Duration::from_millis(timeout_ms), ping_future)
.await
.context("Ping timeout")?
.context("Ping failed")?;
// Convert Duration to milliseconds (f64 for sub-millisecond precision)
let ms = duration.as_secs_f64() * 1000.0;
Ok(ms)
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
#[ignore] // Requires network access and elevated privileges
async fn test_ping_localhost() {
let result = ping_device("127.0.0.1", 5000).await;
assert!(result.is_ok());
let response_time = result.unwrap();
assert!(response_time > 0.0);
assert!(response_time < 100.0); // Localhost should be fast
}
}