use std::net::{IpAddr, Ipv6Addr, TcpStream, ToSocketAddrs};
use std::time::Duration;
pub const CONNECT_TIMEOUT: Duration = Duration::from_secs(2);
fn host_port(host: &str, port: u16) -> String {
if host.parse::<Ipv6Addr>().is_ok() {
format!("[{}]:{}", host, port)
} else {
format!("{}:{}", host, port)
}
}
pub fn resolve_host(host: &str) -> Option<IpAddr> {
host_port(host, 80)
.to_socket_addrs()
.ok()?
.next()
.map(|addr| addr.ip())
}
pub fn is_online(host: &str) -> bool {
resolve_host(host).is_some()
}
pub fn scan_port(host: String, port: u16, timeout: Option<Duration>) -> Option<u16> {
let socket_addr = host_port(&host, port).to_socket_addrs().ok()?.next()?;
match TcpStream::connect_timeout(&socket_addr, timeout.unwrap_or(CONNECT_TIMEOUT)) {
Ok(_) => Some(port),
Err(_) => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
const TEST_TIMEOUT: Option<Duration> = Some(Duration::from_millis(100));
#[test]
fn test_scan_port_success() {
let result = scan_port("127.0.0.1".to_string(), 0, TEST_TIMEOUT);
assert!(result.is_none());
}
#[test]
fn test_scan_port_failure() {
let result = scan_port("127.0.0.1".to_string(), 1, TEST_TIMEOUT); assert!(result.is_none());
}
#[test]
fn test_is_online_numeric_ip() {
assert!(is_online("127.0.0.1"));
}
#[test]
fn test_is_online_invalid_host() {
assert!(!is_online(""));
}
#[test]
fn test_host_port_ipv6_is_bracketed() {
assert_eq!(host_port("::1", 80), "[::1]:80");
assert_eq!(host_port("2001:db8::1", 443), "[2001:db8::1]:443");
}
#[test]
fn test_host_port_ipv4_and_hostname_plain() {
assert_eq!(host_port("127.0.0.1", 80), "127.0.0.1:80");
assert_eq!(host_port("example.com", 8080), "example.com:8080");
}
}