use std::net::{SocketAddr, TcpStream, ToSocketAddrs};
use crate::error::AgentError;
use crate::transport::DaemonProbe;
pub fn probe_daemon(spec: &DaemonProbe) -> Result<(), AgentError> {
let addr_str = format!("{}:{}", spec.host, spec.port);
let addr: SocketAddr = match addr_str.to_socket_addrs() {
Ok(mut iter) => match iter.next() {
Some(a) => a,
None => {
return Err(AgentError::DaemonNotRunning {
host: spec.host.clone(),
port: spec.port,
detail: "no resolvable address".into(),
});
}
},
Err(e) => {
return Err(AgentError::DaemonNotRunning {
host: spec.host.clone(),
port: spec.port,
detail: format!("address resolution failed: {}", e),
});
}
};
match TcpStream::connect_timeout(&addr, spec.timeout()) {
Ok(_stream) => Ok(()),
Err(e)
if e.kind() == std::io::ErrorKind::TimedOut
|| e.kind() == std::io::ErrorKind::WouldBlock =>
{
Err(AgentError::DaemonProbeTimeout {
host: spec.host.clone(),
port: spec.port,
timeout_ms: spec.timeout_ms,
})
}
Err(e) => Err(AgentError::DaemonNotRunning {
host: spec.host.clone(),
port: spec.port,
detail: format!("connect failed: {}", e),
}),
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::net::TcpListener;
#[test]
fn probe_succeeds_on_open_port() {
let listener = TcpListener::bind("127.0.0.1:0").expect("bind failed");
let port = listener.local_addr().expect("local_addr").port();
let spec = DaemonProbe::new("127.0.0.1", port, 2000);
let result = probe_daemon(&spec);
drop(listener);
assert!(
result.is_ok(),
"probe must succeed when a listener is bound: {:?}",
result
);
}
#[test]
fn probe_returns_not_running_on_closed_port() {
let port = {
let listener = TcpListener::bind("127.0.0.1:0").expect("bind failed");
listener.local_addr().expect("local_addr").port()
};
let spec = DaemonProbe::new("127.0.0.1", port, 500);
let result = probe_daemon(&spec);
assert!(
matches!(
result,
Err(AgentError::DaemonNotRunning { .. })
| Err(AgentError::DaemonProbeTimeout { .. })
),
"probe on closed port must return DaemonNotRunning or DaemonProbeTimeout, got: {:?}",
result
);
}
#[test]
#[cfg(not(target_os = "windows"))]
fn probe_returns_error_on_blackhole() {
let spec = DaemonProbe::new("192.0.2.1", 1, 200);
let result = probe_daemon(&spec);
assert!(
matches!(
result,
Err(AgentError::DaemonProbeTimeout { .. })
| Err(AgentError::DaemonNotRunning { .. })
),
"probe on blackhole address must return an error, got: {:?}",
result
);
}
}