netscan 0.30.0

Cross-platform network scan library
Documentation
use std::collections::HashMap;
use std::net::IpAddr;
use std::str::FromStr;
use std::thread;
use std::time::Duration;

use futures::stream::{self, StreamExt};
use hickory_resolver::TokioResolver;

#[cfg(not(any(unix, target_os = "windows")))]
use hickory_resolver::config::ResolverConfig;
#[cfg(not(any(unix, target_os = "windows")))]
use hickory_resolver::name_server::TokioConnectionProvider;

#[cfg(not(target_os = "windows"))]
const DEFAULT_TIMEOUT: Duration = Duration::from_millis(200);
#[cfg(not(target_os = "windows"))]
const DEFAULT_TIMEOUT_GLOBAL: Duration = Duration::from_millis(1000);
#[cfg(target_os = "windows")]
const DEFAULT_TIMEOUT: Duration = Duration::from_millis(20);
#[cfg(target_os = "windows")]
const DEFAULT_TIMEOUT_GLOBAL: Duration = Duration::from_millis(1000);

#[cfg(any(unix, target_os = "windows"))]
fn get_resolver() -> Option<TokioResolver> {
    TokioResolver::builder_tokio()
        .ok()
        .map(|resolver| resolver.build())
}

#[cfg(not(any(unix, target_os = "windows")))]
fn get_resolver() -> Option<TokioResolver> {
    Some(
        TokioResolver::builder_with_config(
            ResolverConfig::default(),
            TokioConnectionProvider::default(),
        )
        .build(),
    )
}

pub fn lookup_host_name(host_name: &str) -> Option<IpAddr> {
    let ip_vec: Vec<IpAddr> = resolve_domain(host_name.to_string());
    let mut ipv6_vec: Vec<IpAddr> = vec![];
    for ip in ip_vec {
        match ip {
            IpAddr::V4(_) => {
                return Some(ip);
            }
            IpAddr::V6(_) => {
                ipv6_vec.push(ip);
            }
        }
    }
    if !ipv6_vec.is_empty() {
        Some(ipv6_vec[0])
    } else {
        None
    }
}

pub async fn lookup_host_name_async(host_name: String) -> Option<IpAddr> {
    let ip_vec: Vec<IpAddr> = resolve_domain_async(host_name).await;
    let mut ipv6_vec: Vec<IpAddr> = vec![];
    for ip in ip_vec {
        match ip {
            IpAddr::V4(_) => {
                return Some(ip);
            }
            IpAddr::V6(_) => {
                ipv6_vec.push(ip);
            }
        }
    }
    if !ipv6_vec.is_empty() {
        Some(ipv6_vec[0])
    } else {
        None
    }
}

pub fn lookup_ip_addr(ip_addr: &IpAddr) -> Option<String> {
    let names: Vec<String> = resolve_ip(ip_addr);
    if !names.is_empty() {
        Some(names[0].clone())
    } else {
        None
    }
}

pub async fn lookup_ip_addr_async(ip_addr: String) -> String {
    let ips: Vec<String> = resolve_ip_async(ip_addr).await;
    if !ips.is_empty() {
        ips[0].clone()
    } else {
        String::new()
    }
}

fn resolve_domain(host_name: String) -> Vec<IpAddr> {
    let rt = match tokio::runtime::Runtime::new() {
        Ok(rt) => rt,
        Err(_) => return Vec::new(),
    };
    rt.block_on(resolve_domain_async(host_name))
}

fn resolve_ip(ip_addr: &IpAddr) -> Vec<String> {
    let rt = match tokio::runtime::Runtime::new() {
        Ok(rt) => rt,
        Err(_) => return Vec::new(),
    };
    rt.block_on(resolve_ip_async(ip_addr.to_string()))
}

async fn resolve_domain_async(host_name: String) -> Vec<IpAddr> {
    let mut ips: Vec<IpAddr> = vec![];
    let Some(resolver) = get_resolver() else {
        return ips;
    };

    if let Ok(lip) = resolver.lookup_ip(host_name).await {
        for ip in lip.iter() {
            ips.push(ip);
        }
    }
    ips
}

async fn resolve_ip_async(ip_addr: String) -> Vec<String> {
    let ip_addr: IpAddr = match IpAddr::from_str(ip_addr.as_str()) {
        Ok(ip) => ip,
        Err(_) => return Vec::new(),
    };
    let mut names: Vec<String> = vec![];
    let Some(resolver) = get_resolver() else {
        return names;
    };

    let timeout = if crate::ip::is_global_addr(&ip_addr) {
        DEFAULT_TIMEOUT_GLOBAL
    } else {
        DEFAULT_TIMEOUT
    };

    let lookup_result = tokio::time::timeout(timeout, resolver.reverse_lookup(ip_addr)).await;
    if let Ok(Ok(rlookup)) = lookup_result {
        for name in rlookup.iter() {
            let s = name.to_string();
            if let Some(trimmed) = s.strip_suffix('.') {
                names.push(trimmed.to_string());
            } else {
                names.push(s);
            }
        }
    }

    names
}

pub async fn lookup_ips_async(ips: Vec<IpAddr>) -> HashMap<IpAddr, String> {
    let mut tasks = stream::iter(ips)
        .map(|ip| async move {
            let names = resolve_ip_async(ip.to_string()).await;
            (ip, names)
        })
        .buffer_unordered(10);
    let mut results: HashMap<IpAddr, String> = HashMap::new();
    while let Some(result) = tasks.next().await {
        results.insert(
            result.0,
            result.1.first().unwrap_or(&String::new()).to_string(),
        );
    }
    results
}

pub fn lookup_ips(ips: Vec<IpAddr>) -> HashMap<IpAddr, String> {
    let rt: tokio::runtime::Runtime = tokio::runtime::Runtime::new().unwrap();
    let handle = thread::spawn(move || rt.block_on(async { lookup_ips_async(ips).await }));
    handle.join().unwrap()
}

pub fn lookup_host(host: String) -> Vec<IpAddr> {
    resolve_domain(host)
}

pub fn lookup_addr(addr: &IpAddr) -> Vec<String> {
    resolve_ip(addr)
}