use std::net::{IpAddr, Ipv4Addr};
use std::sync::Arc;
use std::time::Duration;
use futures_util::stream::{self, StreamExt};
use lanflare_proto::discovery::ProtocolType;
use crate::client::{LsClient, LsClientConfig};
use crate::crypto::cert::TlsIdentity;
use crate::discovery::registry::{DiscoveredPeer, PeerRegistry};
pub const SCAN_CONCURRENCY: usize = 128;
pub const SCAN_PROBE_TIMEOUT: Duration = Duration::from_millis(500);
pub const SCAN_CONNECT_TIMEOUT: Duration = Duration::from_millis(300);
pub async fn scan_subnet(
interface_ip: Ipv4Addr,
port: u16,
identity: &TlsIdentity,
registry: &Arc<PeerRegistry>,
) -> Vec<DiscoveredPeer> {
let base = interface_ip.octets();
let client_config = LsClientConfig {
timeout: SCAN_PROBE_TIMEOUT,
connect_timeout: SCAN_CONNECT_TIMEOUT,
tls_identity: Some(identity.clone()),
target_fingerprint: None, };
let client = match LsClient::new(client_config) {
Ok(c) => c,
Err(e) => {
tracing::warn!("Failed to create client for subnet scanner: {e}");
return Vec::new();
}
};
let candidates: Vec<Ipv4Addr> = (1..=254u8)
.map(|host| Ipv4Addr::new(base[0], base[1], base[2], host))
.filter(|&ip| ip != interface_ip)
.collect();
let client_ref = &client;
let own_fp = identity.fingerprint.clone();
let reg_ref = registry;
let discovered_peers: Vec<DiscoveredPeer> = stream::iter(candidates)
.map(|target_ip| {
let own_fp = own_fp.clone();
async move {
probe_host(client_ref, target_ip, port, &own_fp, reg_ref).await
}
})
.buffer_unordered(SCAN_CONCURRENCY)
.filter_map(std::future::ready)
.collect()
.await;
discovered_peers
}
async fn probe_host(
client: &LsClient,
target_ip: Ipv4Addr,
port: u16,
own_fingerprint: &str,
registry: &Arc<PeerRegistry>,
) -> Option<DiscoveredPeer> {
let protocols = [ProtocolType::Https, ProtocolType::Http];
for proto in protocols {
let proto_str = match proto {
ProtocolType::Https => "https",
ProtocolType::Http => "http",
};
let base_url = format!("{proto_str}://{target_ip}:{port}");
match client.get_info(&base_url).await {
Ok(info) => {
if !info.fingerprint.is_empty()
&& info.fingerprint.eq_ignore_ascii_case(own_fingerprint)
{
return None;
}
let peer = registry
.handle_info(IpAddr::V4(target_ip), port, proto, &info)
.await;
return peer;
}
Err(err) => {
let should_retry_http = match &err {
crate::client::ClientError::Reqwest(re) => {
!re.is_connect() && !re.is_timeout()
}
crate::client::ClientError::Tls(_) => true,
_ => false,
};
if !should_retry_http {
return None;
}
continue;
}
}
}
None
}