mirror of
https://github.com/mayanayza/netvisor.git
synced 2025-12-10 08:24:08 -06:00
working on discovery
This commit is contained in:
@@ -1,99 +1,99 @@
|
||||
// use axum::{
|
||||
// extract::{Path, State},
|
||||
// http::StatusCode,
|
||||
// response::{Json},
|
||||
// };
|
||||
// use std::sync::Arc;
|
||||
// use crate::AppState;
|
||||
// use crate::shared::handlers::ApiResponse;
|
||||
use axum::{
|
||||
extract::{Path, State},
|
||||
http::StatusCode,
|
||||
response::{Json},
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use crate::AppState;
|
||||
use crate::shared::handlers::ApiResponse;
|
||||
|
||||
// use crate::components::discovery::types::{DiscoveryConfig, StartDiscoveryRequest, DiscoveryProgress, DiscoveredDevice};
|
||||
// use crate::components::discovery::types::DiscoveryDepth::{Basic, Deep, Standard};
|
||||
// use crate::components::nodes::types::NetworkNode;
|
||||
use crate::components::discovery::types::{DiscoveryConfig, StartDiscoveryRequest, DiscoveryProgress, DiscoveredDevice};
|
||||
use crate::components::discovery::types::DiscoveryDepth::{Basic, Deep, Standard};
|
||||
use crate::components::nodes::types::NetworkNode;
|
||||
|
||||
// pub async fn start_discovery(
|
||||
// State(state): State<Arc<AppState>>,
|
||||
// Json(request): Json<StartDiscoveryRequest>,
|
||||
// ) -> Result<Json<ApiResponse<()>>, StatusCode> {
|
||||
// let config = DiscoveryConfig {
|
||||
// target_subnets: request.target_subnets,
|
||||
// discovery_depth: match request.discovery_depth.as_str() {
|
||||
// "basic" => Basic,
|
||||
// "deep" => Deep,
|
||||
// _ => Standard,
|
||||
// },
|
||||
// include_services: request.include_services,
|
||||
// snmp_communities: request.snmp_communities,
|
||||
// max_concurrent: request.max_concurrent,
|
||||
// timeout_ms: request.timeout,
|
||||
// port_scan_enabled: true,
|
||||
// common_ports: vec![22, 23, 25, 53, 80, 110, 143, 443, 993, 995, 3389, 5900, 8080, 8443],
|
||||
// };
|
||||
pub async fn start_discovery(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Json(request): Json<StartDiscoveryRequest>,
|
||||
) -> Result<Json<ApiResponse<()>>, StatusCode> {
|
||||
let config = DiscoveryConfig {
|
||||
target_subnets: request.target_subnets,
|
||||
discovery_depth: match request.discovery_depth.as_str() {
|
||||
"basic" => Basic,
|
||||
"deep" => Deep,
|
||||
_ => Standard,
|
||||
},
|
||||
include_services: request.include_services,
|
||||
snmp_communities: request.snmp_communities,
|
||||
max_concurrent: request.max_concurrent,
|
||||
timeout_ms: request.timeout,
|
||||
port_scan_enabled: true,
|
||||
common_ports: vec![22, 23, 25, 53, 80, 110, 143, 443, 993, 995, 3389, 5900, 8080, 8443],
|
||||
};
|
||||
|
||||
// let discovery = state.discovery.clone();
|
||||
// tokio::spawn(async move {
|
||||
// let discovery_guard = discovery.lock().await;
|
||||
// if let Err(e) = discovery_guard.start_discovery(config).await {
|
||||
// tracing::error!("Discovery failed: {}", e);
|
||||
// }
|
||||
// });
|
||||
let discovery = state.discovery.clone();
|
||||
tokio::spawn(async move {
|
||||
let discovery_guard = discovery.lock().await;
|
||||
if let Err(e) = discovery_guard.start_discovery(config).await {
|
||||
tracing::error!("Discovery failed: {}", e);
|
||||
}
|
||||
});
|
||||
|
||||
// Ok(Json(ApiResponse::success(())))
|
||||
// }
|
||||
Ok(Json(ApiResponse::success(())))
|
||||
}
|
||||
|
||||
// pub async fn stop_discovery(
|
||||
// State(state): State<Arc<AppState>>,
|
||||
// ) -> Result<Json<ApiResponse<()>>, StatusCode> {
|
||||
// let discovery = state.discovery.lock().await;
|
||||
// discovery.stop_discovery().await;
|
||||
// Ok(Json(ApiResponse::success(())))
|
||||
// }
|
||||
pub async fn stop_discovery(
|
||||
State(state): State<Arc<AppState>>,
|
||||
) -> Result<Json<ApiResponse<()>>, StatusCode> {
|
||||
let discovery = state.discovery.lock().await;
|
||||
discovery.stop_discovery().await;
|
||||
Ok(Json(ApiResponse::success(())))
|
||||
}
|
||||
|
||||
// pub async fn get_discovery_progress(
|
||||
// State(state): State<Arc<AppState>>,
|
||||
// ) -> Result<Json<ApiResponse<DiscoveryProgress>>, StatusCode> {
|
||||
// let discovery = state.discovery.lock().await;
|
||||
// let progress = discovery.get_progress().await;
|
||||
// Ok(Json(ApiResponse::success(progress)))
|
||||
// }
|
||||
pub async fn get_discovery_progress(
|
||||
State(state): State<Arc<AppState>>,
|
||||
) -> Result<Json<ApiResponse<DiscoveryProgress>>, StatusCode> {
|
||||
let discovery = state.discovery.lock().await;
|
||||
let progress = discovery.get_progress().await;
|
||||
Ok(Json(ApiResponse::success(progress)))
|
||||
}
|
||||
|
||||
// pub async fn get_discovered_devices(
|
||||
// State(state): State<Arc<AppState>>,
|
||||
// ) -> Result<Json<ApiResponse<Vec<DiscoveredDevice>>>, StatusCode> {
|
||||
// let discovery = state.discovery.lock().await;
|
||||
// let devices = discovery.get_discovered_devices().await;
|
||||
// Ok(Json(ApiResponse::success(devices)))
|
||||
// }
|
||||
pub async fn get_discovered_devices(
|
||||
State(state): State<Arc<AppState>>,
|
||||
) -> Result<Json<ApiResponse<Vec<DiscoveredDevice>>>, StatusCode> {
|
||||
let discovery = state.discovery.lock().await;
|
||||
let devices = discovery.get_discovered_devices().await;
|
||||
Ok(Json(ApiResponse::success(devices)))
|
||||
}
|
||||
|
||||
// pub async fn accept_discovered_device(
|
||||
// State(state): State<Arc<AppState>>,
|
||||
// Path(device_id): Path<String>,
|
||||
// ) -> Result<Json<ApiResponse<NetworkNode>>, StatusCode> {
|
||||
// let discovery = state.discovery.lock().await;
|
||||
pub async fn accept_discovered_device(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Path(device_id): Path<String>,
|
||||
) -> Result<Json<ApiResponse<NetworkNode>>, StatusCode> {
|
||||
let discovery = state.discovery.lock().await;
|
||||
|
||||
// match discovery.accept_device(&device_id).await {
|
||||
// Ok(node) => {
|
||||
// // Save the new node to storage
|
||||
// match state.storage.save_node(&node).await {
|
||||
// Ok(_) => Ok(Json(ApiResponse::success(node))),
|
||||
// Err(e) => {
|
||||
// tracing::error!("Failed to save accepted device as node: {}", e);
|
||||
// Ok(Json(ApiResponse::error(format!("Failed to save node: {}", e))))
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// Err(e) => Ok(Json(ApiResponse::error(e)))
|
||||
// }
|
||||
// }
|
||||
match discovery.accept_device(&device_id).await {
|
||||
Ok(node) => {
|
||||
// Save the new node to storage
|
||||
match state.storage.save_node(&node).await {
|
||||
Ok(_) => Ok(Json(ApiResponse::success(node))),
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to save accepted device as node: {}", e);
|
||||
Ok(Json(ApiResponse::error(format!("Failed to save node: {}", e))))
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(e) => Ok(Json(ApiResponse::error(e)))
|
||||
}
|
||||
}
|
||||
|
||||
// pub async fn reject_discovered_device(
|
||||
// State(state): State<Arc<AppState>>,
|
||||
// Path(device_id): Path<String>,
|
||||
// ) -> Result<Json<ApiResponse<()>>, StatusCode> {
|
||||
// let discovery = state.discovery.lock().await;
|
||||
pub async fn reject_discovered_device(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Path(device_id): Path<String>,
|
||||
) -> Result<Json<ApiResponse<()>>, StatusCode> {
|
||||
let discovery = state.discovery.lock().await;
|
||||
|
||||
// match discovery.reject_device(&device_id).await {
|
||||
// Ok(_) => Ok(Json(ApiResponse::success(()))),
|
||||
// Err(e) => Ok(Json(ApiResponse::error(e)))
|
||||
// }
|
||||
// }
|
||||
match discovery.reject_device(&device_id).await {
|
||||
Ok(_) => Ok(Json(ApiResponse::success(()))),
|
||||
Err(e) => Ok(Json(ApiResponse::error(e)))
|
||||
}
|
||||
}
|
||||
@@ -1,176 +1,160 @@
|
||||
// // src/discovery/scanner.rs
|
||||
// use super::types::*;
|
||||
// use std::collections::{HashMap, HashSet};
|
||||
// use std::net::{IpAddr, Ipv4Addr};
|
||||
// use std::sync::Arc;
|
||||
// use std::time::{Duration, Instant};
|
||||
// use tokio::net::TcpStream;
|
||||
// use tokio::sync::mpsc;
|
||||
// use tokio::time::timeout;
|
||||
// src/discovery/scanner.rs
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration};
|
||||
use tokio::net::TcpStream;
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::time::timeout;
|
||||
|
||||
// pub async fn ping_host(ip: &str, timeout_ms: u64) -> Result<bool, Box<dyn std::error::Error>> {
|
||||
// // Use TCP connect to port 80 as a proxy for "ping" since ICMP requires privileges
|
||||
// let addr = format!("{}:80", ip);
|
||||
// match timeout(Duration::from_millis(timeout_ms), TcpStream::connect(&addr)).await {
|
||||
// Ok(Ok(_)) => Ok(true),
|
||||
// _ => {
|
||||
// // Try port 22 as fallback
|
||||
// let addr = format!("{}:22", ip);
|
||||
// match timeout(Duration::from_millis(timeout_ms), TcpStream::connect(&addr)).await {
|
||||
// Ok(Ok(_)) => Ok(true),
|
||||
// _ => Ok(false)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
use crate::shared::utils::get_common_service_name;
|
||||
use crate::components::discovery::types::{DiscoveryConfig, DeviceType};
|
||||
|
||||
// pub async fn test_port(ip: &str, port: u16, timeout_ms: u64) -> Result<bool, Box<dyn std::error::Error>> {
|
||||
// let addr = format!("{}:{}", ip, port);
|
||||
// match timeout(Duration::from_millis(timeout_ms), TcpStream::connect(&addr)).await {
|
||||
// Ok(Ok(_)) => Ok(true),
|
||||
// _ => Ok(false)
|
||||
// }
|
||||
// }
|
||||
pub async fn ping_host(ip: &str, timeout_ms: u64) -> Result<bool, Box<dyn std::error::Error>> {
|
||||
// Use TCP connect to port 80 as a proxy for "ping" since ICMP requires privileges
|
||||
let addr = format!("{}:80", ip);
|
||||
match timeout(Duration::from_millis(timeout_ms), TcpStream::connect(&addr)).await {
|
||||
Ok(Ok(_)) => Ok(true),
|
||||
_ => {
|
||||
// Try port 22 as fallback
|
||||
let addr = format!("{}:22", ip);
|
||||
match timeout(Duration::from_millis(timeout_ms), TcpStream::connect(&addr)).await {
|
||||
Ok(Ok(_)) => Ok(true),
|
||||
_ => Ok(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// pub async fn ping_sweep(
|
||||
// target_ips: &[String],
|
||||
// config: &DiscoveryConfig,
|
||||
// stop_signal: Arc<tokio::sync::RwLock<bool>>
|
||||
// ) -> Vec<String> {
|
||||
// let (tx, mut rx) = mpsc::channel(100);
|
||||
// let mut alive_hosts = Vec::new();
|
||||
// let semaphore = Arc::new(tokio::sync::Semaphore::new(config.max_concurrent));
|
||||
pub async fn test_port(ip: &str, port: u16, timeout_ms: u64) -> Result<bool, Box<dyn std::error::Error>> {
|
||||
let addr = format!("{}:{}", ip, port);
|
||||
match timeout(Duration::from_millis(timeout_ms), TcpStream::connect(&addr)).await {
|
||||
Ok(Ok(_)) => Ok(true),
|
||||
_ => Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
// // Spawn tasks for each IP
|
||||
// for ip in target_ips {
|
||||
// let ip = ip.clone();
|
||||
// let tx = tx.clone();
|
||||
// let semaphore = semaphore.clone();
|
||||
// let timeout_ms = config.timeout_ms;
|
||||
// let stop_signal = stop_signal.clone();
|
||||
pub async fn ping_sweep(
|
||||
target_ips: &[String],
|
||||
config: &DiscoveryConfig,
|
||||
stop_signal: Arc<tokio::sync::RwLock<bool>>
|
||||
) -> Vec<String> {
|
||||
let (tx, mut rx) = mpsc::channel(100);
|
||||
let mut alive_hosts = Vec::new();
|
||||
let semaphore = Arc::new(tokio::sync::Semaphore::new(config.max_concurrent));
|
||||
|
||||
// tokio::spawn(async move {
|
||||
// let _permit = semaphore.acquire().await.unwrap();
|
||||
// Spawn tasks for each IP
|
||||
for ip in target_ips {
|
||||
let ip = ip.clone();
|
||||
let tx = tx.clone();
|
||||
let semaphore = semaphore.clone();
|
||||
let timeout_ms = config.timeout_ms;
|
||||
let stop_signal = stop_signal.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
let _permit = semaphore.acquire().await.unwrap();
|
||||
|
||||
// if *stop_signal.read().await {
|
||||
// return;
|
||||
// }
|
||||
if *stop_signal.read().await {
|
||||
return;
|
||||
}
|
||||
|
||||
// if let Ok(true) = ping_host(&ip, timeout_ms).await {
|
||||
// let _ = tx.send(ip).await;
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
if let Ok(true) = ping_host(&ip, timeout_ms).await {
|
||||
let _ = tx.send(ip).await;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// drop(tx); // Close the channel
|
||||
drop(tx); // Close the channel
|
||||
|
||||
// // Collect results
|
||||
// while let Some(ip) = rx.recv().await {
|
||||
// alive_hosts.push(ip);
|
||||
// Collect results
|
||||
while let Some(ip) = rx.recv().await {
|
||||
alive_hosts.push(ip);
|
||||
|
||||
// if *stop_signal.read().await {
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
if *stop_signal.read().await {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// alive_hosts
|
||||
// }
|
||||
alive_hosts
|
||||
}
|
||||
|
||||
// pub async fn port_scan(
|
||||
// hosts: &[String],
|
||||
// config: &DiscoveryConfig,
|
||||
// stop_signal: Arc<tokio::sync::RwLock<bool>>
|
||||
// ) -> HashMap<String, Vec<u16>> {
|
||||
// let mut results = HashMap::new();
|
||||
// let semaphore = Arc::new(tokio::sync::Semaphore::new(config.max_concurrent));
|
||||
pub async fn port_scan(
|
||||
hosts: &[String],
|
||||
config: &DiscoveryConfig,
|
||||
stop_signal: Arc<tokio::sync::RwLock<bool>>
|
||||
) -> HashMap<String, Vec<u16>> {
|
||||
let mut results = HashMap::new();
|
||||
let semaphore = Arc::new(tokio::sync::Semaphore::new(config.max_concurrent));
|
||||
|
||||
// for host in hosts {
|
||||
// if *stop_signal.read().await {
|
||||
// break;
|
||||
// }
|
||||
for host in hosts {
|
||||
if *stop_signal.read().await {
|
||||
break;
|
||||
}
|
||||
|
||||
// let mut open_ports = Vec::new();
|
||||
let mut open_ports = Vec::new();
|
||||
|
||||
// for &port in &config.common_ports {
|
||||
// let _permit = semaphore.acquire().await.unwrap();
|
||||
for &port in &config.common_ports {
|
||||
let _permit = semaphore.acquire().await.unwrap();
|
||||
|
||||
// if *stop_signal.read().await {
|
||||
// break;
|
||||
// }
|
||||
if *stop_signal.read().await {
|
||||
break;
|
||||
}
|
||||
|
||||
// if let Ok(true) = test_port(host, port, config.timeout_ms).await {
|
||||
// open_ports.push(port);
|
||||
// }
|
||||
// }
|
||||
if let Ok(true) = test_port(host, port, config.timeout_ms).await {
|
||||
open_ports.push(port);
|
||||
}
|
||||
}
|
||||
|
||||
// if !open_ports.is_empty() {
|
||||
// results.insert(host.clone(), open_ports);
|
||||
// }
|
||||
// }
|
||||
if !open_ports.is_empty() {
|
||||
results.insert(host.clone(), open_ports);
|
||||
}
|
||||
}
|
||||
|
||||
// results
|
||||
// }
|
||||
results
|
||||
}
|
||||
|
||||
// pub fn classify_device_type(ports: &[u16]) -> DeviceType {
|
||||
// let port_set: HashSet<u16> = ports.iter().cloned().collect();
|
||||
pub fn classify_device_type(ports: &[u16]) -> DeviceType {
|
||||
let port_set: HashSet<u16> = ports.iter().cloned().collect();
|
||||
|
||||
// // Router/Switch indicators
|
||||
// if port_set.contains(&161) && (port_set.contains(&22) || port_set.contains(&23)) {
|
||||
// return DeviceType::Router;
|
||||
// }
|
||||
// Router/Switch indicators
|
||||
if port_set.contains(&161) && (port_set.contains(&22) || port_set.contains(&23)) {
|
||||
return DeviceType::Router;
|
||||
}
|
||||
|
||||
// // Server indicators
|
||||
// if port_set.contains(&22) && (port_set.contains(&80) || port_set.contains(&443)) {
|
||||
// return DeviceType::Server;
|
||||
// }
|
||||
// Server indicators
|
||||
if port_set.contains(&22) && (port_set.contains(&80) || port_set.contains(&443)) {
|
||||
return DeviceType::Server;
|
||||
}
|
||||
|
||||
// // Printer indicators
|
||||
// if port_set.contains(&631) || port_set.contains(&9100) {
|
||||
// return DeviceType::Printer;
|
||||
// }
|
||||
// Printer indicators
|
||||
if port_set.contains(&631) || port_set.contains(&9100) {
|
||||
return DeviceType::Printer;
|
||||
}
|
||||
|
||||
// // NAS indicators
|
||||
// if port_set.contains(&139) && port_set.contains(&445) {
|
||||
// return DeviceType::NAS;
|
||||
// }
|
||||
// NAS indicators
|
||||
if port_set.contains(&139) && port_set.contains(&445) {
|
||||
return DeviceType::NAS;
|
||||
}
|
||||
|
||||
// // IoT indicators (common IoT ports)
|
||||
// if port_set.contains(&8080) || port_set.contains(&8443) {
|
||||
// return DeviceType::IoT;
|
||||
// }
|
||||
// IoT indicators (common IoT ports)
|
||||
if port_set.contains(&8080) || port_set.contains(&8443) {
|
||||
return DeviceType::IoT;
|
||||
}
|
||||
|
||||
// // Access Point indicators
|
||||
// if port_set.contains(&80) && port_set.contains(&443) && ports.len() <= 3 {
|
||||
// return DeviceType::AccessPoint;
|
||||
// }
|
||||
// Access Point indicators
|
||||
if port_set.contains(&80) && port_set.contains(&443) && ports.len() <= 3 {
|
||||
return DeviceType::AccessPoint;
|
||||
}
|
||||
|
||||
// DeviceType::Unknown
|
||||
// }
|
||||
DeviceType::Unknown
|
||||
}
|
||||
|
||||
// pub async fn detect_services_on_ports(ip: &str, ports: &[u16], timeout_ms: u64) -> Vec<String> {
|
||||
// let mut services = Vec::new();
|
||||
pub async fn detect_services_on_ports(ip: &str, ports: &[u16], _timeout_ms: u64) -> Vec<String> {
|
||||
let mut services = Vec::new();
|
||||
|
||||
// for &port in ports {
|
||||
// let service = match port {
|
||||
// 22 => "SSH",
|
||||
// 23 => "Telnet",
|
||||
// 25 => "SMTP",
|
||||
// 53 => "DNS",
|
||||
// 80 => "HTTP",
|
||||
// 110 => "POP3",
|
||||
// 143 => "IMAP",
|
||||
// 443 => "HTTPS",
|
||||
// 993 => "IMAPS",
|
||||
// 995 => "POP3S",
|
||||
// 3389 => "RDP",
|
||||
// 5900 => "VNC",
|
||||
// 8080 => "HTTP-Alt",
|
||||
// 8443 => "HTTPS-Alt",
|
||||
// _ => continue,
|
||||
// };
|
||||
|
||||
// services.push(service.to_string());
|
||||
// }
|
||||
for &port in ports {
|
||||
let service = get_common_service_name(port);
|
||||
services.push(service.to_string());
|
||||
}
|
||||
|
||||
// services
|
||||
// }
|
||||
services
|
||||
}
|
||||
@@ -1,171 +1,172 @@
|
||||
// // src/discovery/snmp.rs
|
||||
// use snmp2::{SyncSession, Value as SnmpValue};
|
||||
// use std::collections::HashMap;
|
||||
// use std::time::Duration;
|
||||
// use tokio::net::TcpStream;
|
||||
// use tokio::time::timeout;
|
||||
// src/discovery/snmp.rs
|
||||
use snmp2::{SyncSession, Value as SnmpValue};
|
||||
use std::collections::HashMap;
|
||||
use std::time::Duration;
|
||||
use tokio::net::TcpStream;
|
||||
use tokio::time::timeout;
|
||||
use crate::components::discovery::types::DiscoveryConfig;
|
||||
|
||||
// const SYSTEM_DESCR: &str = "1.3.6.1.2.1.1.1.0";
|
||||
// const SYSTEM_NAME: &str = "1.3.6.1.2.1.1.5.0";
|
||||
// const SYSTEM_CONTACT: &str = "1.3.6.1.2.1.1.4.0";
|
||||
// const SYSTEM_LOCATION: &str = "1.3.6.1.2.1.1.6.0";
|
||||
const SYSTEM_DESCR: &str = "1.3.6.1.2.1.1.1.0";
|
||||
const SYSTEM_NAME: &str = "1.3.6.1.2.1.1.5.0";
|
||||
const SYSTEM_CONTACT: &str = "1.3.6.1.2.1.1.4.0";
|
||||
const SYSTEM_LOCATION: &str = "1.3.6.1.2.1.1.6.0";
|
||||
|
||||
// fn create_snmp_session(target: &str, community: &str, timeout_ms: u64) -> Result<SyncSession, String> {
|
||||
// let session = SyncSession::new(
|
||||
// target,
|
||||
// community.as_bytes(),
|
||||
// Some(Duration::from_millis(timeout_ms)),
|
||||
// 0
|
||||
// ).map_err(|e| format!("Failed to create SNMP session: {}", e))?;
|
||||
fn create_snmp_session(target: &str, community: &str, timeout_ms: u64) -> Result<SyncSession, String> {
|
||||
let session = SyncSession::new(
|
||||
target,
|
||||
community.as_bytes(),
|
||||
Some(Duration::from_millis(timeout_ms)),
|
||||
0
|
||||
).map_err(|e| format!("Failed to create SNMP session: {}", e))?;
|
||||
|
||||
// Ok(session)
|
||||
// }
|
||||
Ok(session)
|
||||
}
|
||||
|
||||
// pub async fn test_snmp_connectivity(ip: &str, community: &str, timeout_ms: u64) -> Result<bool, Box<dyn std::error::Error>> {
|
||||
// // Test SNMP connectivity by querying system description
|
||||
// match query_snmp_get(ip, community, SYSTEM_DESCR, timeout_ms).await {
|
||||
// Ok(_) => Ok(true),
|
||||
// Err(_) => {
|
||||
// // Fallback: try to connect to SNMP port
|
||||
// let addr = format!("{}:161", ip);
|
||||
// match timeout(Duration::from_millis(timeout_ms), TcpStream::connect(&addr)).await {
|
||||
// Ok(Ok(_)) => Ok(true),
|
||||
// _ => Ok(false)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
pub async fn test_snmp_connectivity(ip: &str, community: &str, timeout_ms: u64) -> Result<bool, Box<dyn std::error::Error>> {
|
||||
// Test SNMP connectivity by querying system description
|
||||
match query_snmp_get(ip, community, SYSTEM_DESCR, timeout_ms).await {
|
||||
Ok(_) => Ok(true),
|
||||
Err(_) => {
|
||||
// Fallback: try to connect to SNMP port
|
||||
let addr = format!("{}:161", ip);
|
||||
match timeout(Duration::from_millis(timeout_ms), TcpStream::connect(&addr)).await {
|
||||
Ok(Ok(_)) => Ok(true),
|
||||
_ => Ok(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// async fn query_snmp_get(target: &str, community: &str, oid: &str, timeout_ms: u64) -> Result<String, String> {
|
||||
// let session = create_snmp_session(target, community, timeout_ms)?;
|
||||
async fn query_snmp_get(target: &str, community: &str, oid: &str, timeout_ms: u64) -> Result<String, String> {
|
||||
let session = create_snmp_session(target, community, timeout_ms)?;
|
||||
|
||||
// match session.get(oid.as_bytes()) {
|
||||
// Ok(response) => {
|
||||
// if let Some((_, value)) = response.first() {
|
||||
// let value_str = snmp_value_to_string(value);
|
||||
// Ok(value_str)
|
||||
// } else {
|
||||
// Err("No response received".to_string())
|
||||
// }
|
||||
// },
|
||||
// Err(e) => Err(format!("SNMP get failed: {}", e))
|
||||
// }
|
||||
// }
|
||||
match session.get(oid.as_bytes()) {
|
||||
Ok(response) => {
|
||||
if let Some((_, value)) = response.first() {
|
||||
let value_str = snmp_value_to_string(value);
|
||||
Ok(value_str)
|
||||
} else {
|
||||
Err("No response received".to_string())
|
||||
}
|
||||
},
|
||||
Err(e) => Err(format!("SNMP get failed: {}", e))
|
||||
}
|
||||
}
|
||||
|
||||
// pub async fn query_snmp_system_info(ip: &str, community: &str, timeout_ms: u64) -> Result<HashMap<String, String>, Box<dyn std::error::Error>> {
|
||||
// let mut info = HashMap::new();
|
||||
pub async fn query_snmp_system_info(ip: &str, community: &str, timeout_ms: u64) -> Result<HashMap<String, String>, Box<dyn std::error::Error>> {
|
||||
let mut info = HashMap::new();
|
||||
|
||||
// // Query system name (hostname)
|
||||
// if let Ok(hostname) = query_snmp_get(ip, community, SYSTEM_NAME, timeout_ms).await {
|
||||
// if !hostname.is_empty() && hostname != "Unknown" {
|
||||
// info.insert("hostname".to_string(), hostname);
|
||||
// }
|
||||
// }
|
||||
// Query system name (hostname)
|
||||
if let Ok(hostname) = query_snmp_get(ip, community, SYSTEM_NAME, timeout_ms).await {
|
||||
if !hostname.is_empty() && hostname != "Unknown" {
|
||||
info.insert("hostname".to_string(), hostname);
|
||||
}
|
||||
}
|
||||
|
||||
// // Query system description (often contains vendor info)
|
||||
// if let Ok(description) = query_snmp_get(ip, community, SYSTEM_DESCR, timeout_ms).await {
|
||||
// if !description.is_empty() && description != "Unknown" {
|
||||
// info.insert("description".to_string(), description);
|
||||
// Query system description (often contains vendor info)
|
||||
if let Ok(description) = query_snmp_get(ip, community, SYSTEM_DESCR, timeout_ms).await {
|
||||
if !description.is_empty() && description != "Unknown" {
|
||||
info.insert("description".to_string(), description);
|
||||
|
||||
// // Try to extract vendor from description
|
||||
// let vendor = extract_vendor_from_description(&description);
|
||||
// if !vendor.is_empty() {
|
||||
// info.insert("vendor".to_string(), vendor);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// Try to extract vendor from description
|
||||
let vendor = extract_vendor_from_description(&description);
|
||||
if !vendor.is_empty() {
|
||||
info.insert("vendor".to_string(), vendor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// // Query system contact
|
||||
// if let Ok(contact) = query_snmp_get(ip, community, SYSTEM_CONTACT, timeout_ms).await {
|
||||
// if !contact.is_empty() && contact != "Unknown" {
|
||||
// info.insert("contact".to_string(), contact);
|
||||
// }
|
||||
// }
|
||||
// Query system contact
|
||||
if let Ok(contact) = query_snmp_get(ip, community, SYSTEM_CONTACT, timeout_ms).await {
|
||||
if !contact.is_empty() && contact != "Unknown" {
|
||||
info.insert("contact".to_string(), contact);
|
||||
}
|
||||
}
|
||||
|
||||
// // Query system location
|
||||
// if let Ok(location) = query_snmp_get(ip, community, SYSTEM_LOCATION, timeout_ms).await {
|
||||
// if !location.is_empty() && location != "Unknown" {
|
||||
// info.insert("location".to_string(), location);
|
||||
// }
|
||||
// }
|
||||
// Query system location
|
||||
if let Ok(location) = query_snmp_get(ip, community, SYSTEM_LOCATION, timeout_ms).await {
|
||||
if !location.is_empty() && location != "Unknown" {
|
||||
info.insert("location".to_string(), location);
|
||||
}
|
||||
}
|
||||
|
||||
// Ok(info)
|
||||
// }
|
||||
Ok(info)
|
||||
}
|
||||
|
||||
// pub async fn snmp_discovery(
|
||||
// hosts: &[String],
|
||||
// config: &DiscoveryConfig,
|
||||
// stop_signal: Arc<tokio::sync::RwLock<bool>>
|
||||
// ) -> HashMap<String, HashMap<String, String>> {
|
||||
// let mut results = HashMap::new();
|
||||
pub async fn snmp_discovery(
|
||||
hosts: &[String],
|
||||
config: &DiscoveryConfig,
|
||||
stop_signal: Arc<tokio::sync::RwLock<bool>>
|
||||
) -> HashMap<String, HashMap<String, String>> {
|
||||
let mut results = HashMap::new();
|
||||
|
||||
// for host in hosts {
|
||||
// if *stop_signal.read().await {
|
||||
// break;
|
||||
// }
|
||||
for host in hosts {
|
||||
if *stop_signal.read().await {
|
||||
break;
|
||||
}
|
||||
|
||||
// for community in &config.snmp_communities {
|
||||
// // Test SNMP connectivity
|
||||
// if let Ok(true) = test_snmp_connectivity(host, community, config.timeout_ms).await {
|
||||
// // Query system information
|
||||
// if let Ok(sys_info) = query_snmp_system_info(host, community, config.timeout_ms).await {
|
||||
// if !sys_info.is_empty() {
|
||||
// results.insert(host.clone(), sys_info);
|
||||
// break; // Found working community, no need to try others
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
for community in &config.snmp_communities {
|
||||
// Test SNMP connectivity
|
||||
if let Ok(true) = test_snmp_connectivity(host, community, config.timeout_ms).await {
|
||||
// Query system information
|
||||
if let Ok(sys_info) = query_snmp_system_info(host, community, config.timeout_ms).await {
|
||||
if !sys_info.is_empty() {
|
||||
results.insert(host.clone(), sys_info);
|
||||
break; // Found working community, no need to try others
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// results
|
||||
// }
|
||||
results
|
||||
}
|
||||
|
||||
// fn snmp_value_to_string(value: &SnmpValue) -> String {
|
||||
// match value {
|
||||
// SnmpValue::Integer(i) => i.to_string(),
|
||||
// SnmpValue::OctetString(s) => String::from_utf8_lossy(&s).to_string(),
|
||||
// SnmpValue::ObjectIdentifier(o) => String::from_utf8_lossy(&o).to_string(),
|
||||
// SnmpValue::IpAddress(ip) => format!("{}.{}.{}.{}", ip[0], ip[1], ip[2], ip[3]),
|
||||
// SnmpValue::Counter32(c) => c.to_string(),
|
||||
// SnmpValue::Gauge32(g) => g.to_string(),
|
||||
// SnmpValue::TimeTicks(t) => t.to_string(),
|
||||
// SnmpValue::Counter64(c) => c.to_string(),
|
||||
// _ => "Unknown".to_string(),
|
||||
// }
|
||||
// }
|
||||
fn snmp_value_to_string(value: &SnmpValue) -> String {
|
||||
match value {
|
||||
SnmpValue::Integer(i) => i.to_string(),
|
||||
SnmpValue::OctetString(s) => String::from_utf8_lossy(&s).to_string(),
|
||||
SnmpValue::ObjectIdentifier(o) => String::from_utf8_lossy(&o).to_string(),
|
||||
SnmpValue::IpAddress(ip) => format!("{}.{}.{}.{}", ip[0], ip[1], ip[2], ip[3]),
|
||||
SnmpValue::Counter32(c) => c.to_string(),
|
||||
SnmpValue::Gauge32(g) => g.to_string(),
|
||||
SnmpValue::TimeTicks(t) => t.to_string(),
|
||||
SnmpValue::Counter64(c) => c.to_string(),
|
||||
_ => "Unknown".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
// fn extract_vendor_from_description(description: &str) -> String {
|
||||
// let desc_lower = description.to_lowercase();
|
||||
fn extract_vendor_from_description(description: &str) -> String {
|
||||
let desc_lower = description.to_lowercase();
|
||||
|
||||
// // Common vendor patterns in SNMP system descriptions
|
||||
// let vendors = vec![
|
||||
// ("cisco", "Cisco"),
|
||||
// ("juniper", "Juniper"),
|
||||
// ("hp ", "HP"),
|
||||
// ("hewlett", "HP"),
|
||||
// ("dell", "Dell"),
|
||||
// ("netgear", "Netgear"),
|
||||
// ("d-link", "D-Link"),
|
||||
// ("tp-link", "TP-Link"),
|
||||
// ("ubiquiti", "Ubiquiti"),
|
||||
// ("mikrotik", "MikroTik"),
|
||||
// ("fortinet", "Fortinet"),
|
||||
// ("palo alto", "Palo Alto"),
|
||||
// ("aruba", "Aruba"),
|
||||
// ("huawei", "Huawei"),
|
||||
// ("zyxel", "ZyXEL"),
|
||||
// ("linux", "Linux"),
|
||||
// ("windows", "Microsoft"),
|
||||
// ("freebsd", "FreeBSD"),
|
||||
// ("openbsd", "OpenBSD"),
|
||||
// ];
|
||||
// Common vendor patterns in SNMP system descriptions
|
||||
let vendors = vec![
|
||||
("cisco", "Cisco"),
|
||||
("juniper", "Juniper"),
|
||||
("hp ", "HP"),
|
||||
("hewlett", "HP"),
|
||||
("dell", "Dell"),
|
||||
("netgear", "Netgear"),
|
||||
("d-link", "D-Link"),
|
||||
("tp-link", "TP-Link"),
|
||||
("ubiquiti", "Ubiquiti"),
|
||||
("mikrotik", "MikroTik"),
|
||||
("fortinet", "Fortinet"),
|
||||
("palo alto", "Palo Alto"),
|
||||
("aruba", "Aruba"),
|
||||
("huawei", "Huawei"),
|
||||
("zyxel", "ZyXEL"),
|
||||
("linux", "Linux"),
|
||||
("windows", "Microsoft"),
|
||||
("freebsd", "FreeBSD"),
|
||||
("openbsd", "OpenBSD"),
|
||||
];
|
||||
|
||||
// for (pattern, vendor) in vendors {
|
||||
// if desc_lower.contains(pattern) {
|
||||
// return vendor.to_string();
|
||||
// }
|
||||
// }
|
||||
for (pattern, vendor) in vendors {
|
||||
if desc_lower.contains(pattern) {
|
||||
return vendor.to_string();
|
||||
}
|
||||
}
|
||||
|
||||
// "Unknown".to_string()
|
||||
// }
|
||||
"Unknown".to_string()
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use std::time::{SystemTime, UNIX_EPOCH, Duration};
|
||||
use crate::components::tests::types::CheckConfig;
|
||||
use crate::components::diagnostics::types::CheckResult;
|
||||
use reqwest::Client;
|
||||
|
||||
|
||||
|
||||
mod basic;
|
||||
mod vpn;
|
||||
@@ -12,7 +15,6 @@ mod security;
|
||||
mod performance;
|
||||
mod analysis;
|
||||
mod cdn;
|
||||
mod utils;
|
||||
mod vlan;
|
||||
|
||||
use basic::*;
|
||||
@@ -26,10 +28,22 @@ use performance::*;
|
||||
use analysis::*;
|
||||
use cdn::*;
|
||||
|
||||
pub use utils::{
|
||||
create_http_client,
|
||||
get_common_service_name,
|
||||
};
|
||||
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(5);
|
||||
const DEFAULT_USER_AGENT: &str = "Netzoot/1.0";
|
||||
|
||||
// Create HTTP client with reasonable defaults
|
||||
pub fn create_http_client(timeout_ms: Option<u64>) -> Result<Client, String> {
|
||||
let timeout = timeout_ms
|
||||
.map(|ms| Duration::from_millis(ms))
|
||||
.unwrap_or(DEFAULT_TIMEOUT);
|
||||
|
||||
Client::builder()
|
||||
.timeout(timeout)
|
||||
.user_agent(DEFAULT_USER_AGENT)
|
||||
.danger_accept_invalid_certs(false)
|
||||
.build()
|
||||
.map_err(|e| format!("Failed to create HTTP client: {}", e))
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum CheckError {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use super::{create_http_client, get_common_service_name};
|
||||
use super::{create_http_client};
|
||||
use serde_json::{json, Value};
|
||||
use std::net::ToSocketAddrs;
|
||||
use std::time::{Duration, Instant};
|
||||
use tokio::net::TcpStream;
|
||||
use tokio::time::timeout;
|
||||
use crate::components::tests::checks::CheckConfig;
|
||||
use crate::shared::utils::get_common_service_name;
|
||||
|
||||
pub async fn ssl_certificate_check(config: &CheckConfig) -> Result<Value, String> {
|
||||
let target = config.target.as_ref().ok_or("Target hostname is required")?;
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
use std::time::Duration;
|
||||
use reqwest::Client;
|
||||
|
||||
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(5);
|
||||
const DEFAULT_USER_AGENT: &str = "Netzoot/1.0";
|
||||
|
||||
|
||||
// Create HTTP client with reasonable defaults
|
||||
pub fn create_http_client(timeout_ms: Option<u64>) -> Result<Client, String> {
|
||||
let timeout = timeout_ms
|
||||
.map(|ms| Duration::from_millis(ms))
|
||||
.unwrap_or(DEFAULT_TIMEOUT);
|
||||
|
||||
Client::builder()
|
||||
.timeout(timeout)
|
||||
.user_agent(DEFAULT_USER_AGENT)
|
||||
.danger_accept_invalid_certs(false)
|
||||
.build()
|
||||
.map_err(|e| format!("Failed to create HTTP client: {}", e))
|
||||
}
|
||||
|
||||
// Helper function for common service names
|
||||
pub fn get_common_service_name(port: u16) -> &'static str {
|
||||
match port {
|
||||
21 => "FTP",
|
||||
22 => "SSH",
|
||||
23 => "Telnet",
|
||||
25 => "SMTP",
|
||||
53 => "DNS",
|
||||
80 => "HTTP",
|
||||
110 => "POP3",
|
||||
143 => "IMAP",
|
||||
443 => "HTTPS",
|
||||
587 => "SMTP-Submission",
|
||||
993 => "IMAP-SSL",
|
||||
995 => "POP3-SSL",
|
||||
3306 => "MySQL",
|
||||
3389 => "RDP",
|
||||
5432 => "PostgreSQL",
|
||||
6379 => "Redis",
|
||||
27017 => "MongoDB",
|
||||
_ => "Unknown",
|
||||
}
|
||||
}
|
||||
+14
-7
@@ -10,6 +10,7 @@ use tower_http::{
|
||||
cors::{Any, CorsLayer},
|
||||
trace::TraceLayer,
|
||||
};
|
||||
use tokio::sync::Mutex;
|
||||
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
||||
|
||||
mod config;
|
||||
@@ -19,16 +20,22 @@ mod shared;
|
||||
use config::ServerConfig;
|
||||
use shared::handlers::create_router;
|
||||
|
||||
use crate::{components::diagnostics::storage::DiagnosticStorage, shared::storage::StorageFactory};
|
||||
use crate::components::tests::storage::{TestStorage};
|
||||
use crate::components::nodes::storage::{NodeStorage};
|
||||
use crate::{
|
||||
components::{
|
||||
diagnostics::storage::DiagnosticStorage,
|
||||
tests::storage::{TestStorage},
|
||||
nodes::storage::{NodeStorage},
|
||||
discovery::types::NetworkDiscovery
|
||||
},
|
||||
shared::storage::StorageFactory
|
||||
};
|
||||
|
||||
pub struct AppState {
|
||||
pub config: ServerConfig,
|
||||
pub node_storage: Box<dyn NodeStorage>,
|
||||
pub test_storage: Box<dyn TestStorage>,
|
||||
pub diagnostic_storage: Box<dyn DiagnosticStorage>
|
||||
// pub discovery: Arc<Mutex<NetworkDiscovery>>,
|
||||
pub diagnostic_storage: Box<dyn DiagnosticStorage>,
|
||||
pub discovery: Arc<Mutex<NetworkDiscovery>>,
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
@@ -91,7 +98,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
let sqlite_storage= StorageFactory::new_sqlite(&config.database_url()).await?;
|
||||
|
||||
// Initialize discovery
|
||||
// let discovery = NetworkDiscovery::new()?;
|
||||
let discovery = NetworkDiscovery::new()?;
|
||||
|
||||
// Create app state
|
||||
let state = Arc::new(AppState {
|
||||
@@ -99,7 +106,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
node_storage: sqlite_storage.nodes,
|
||||
test_storage: sqlite_storage.tests,
|
||||
diagnostic_storage: sqlite_storage.diagnostics,
|
||||
// discovery: Arc::new(Mutex::new(discovery))
|
||||
discovery: Arc::new(Mutex::new(discovery))
|
||||
});
|
||||
|
||||
// Create router
|
||||
|
||||
+2
-1
@@ -1,2 +1,3 @@
|
||||
pub mod handlers;
|
||||
pub mod storage;
|
||||
pub mod storage;
|
||||
pub mod utils;
|
||||
@@ -0,0 +1,26 @@
|
||||
// Helper function for common service names
|
||||
pub fn get_common_service_name(port: u16) -> &'static str {
|
||||
match port {
|
||||
21 => "FTP",
|
||||
22 => "SSH",
|
||||
23 => "Telnet",
|
||||
25 => "SMTP",
|
||||
53 => "DNS",
|
||||
80 => "HTTP",
|
||||
110 => "POP3",
|
||||
143 => "IMAP",
|
||||
443 => "HTTPS",
|
||||
587 => "SMTP-Submission",
|
||||
993 => "IMAP-SSL",
|
||||
995 => "POP3-SSL",
|
||||
3306 => "MySQL",
|
||||
3389 => "RDP",
|
||||
5432 => "PostgreSQL",
|
||||
5900 => "VNC",
|
||||
6379 => "Redis",
|
||||
8080 => "HTTP-Alt",
|
||||
8443 => "HTTPS-Alt",
|
||||
27017 => "MongoDB",
|
||||
_ => "Unknown",
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user