use crate::config::ProjectConfig;
use crate::detectors::DetectorTrait;
use crate::models::{DetectionSource, Service};
use crate::utils::ServiceSignatures;
use anyhow::Result;
use async_trait::async_trait;
use std::net::{TcpStream, SocketAddr};
use std::path::Path;
use std::time::Duration;
use tracing::{debug, trace};
pub struct PortDetector {
start_port: u16,
end_port: u16,
}
impl PortDetector {
pub fn new(start_port: u16, end_port: u16) -> Self {
Self {
start_port,
end_port,
}
}
fn is_port_open(&self, port: u16) -> bool {
let addr = SocketAddr::from(([127, 0, 0, 1], port));
match TcpStream::connect_timeout(&addr, Duration::from_millis(100)) {
Ok(_) => true,
Err(_) => false,
}
}
}
#[async_trait]
impl DetectorTrait for PortDetector {
fn name(&self) -> &str {
"PortDetector"
}
fn priority(&self) -> i32 {
30
}
async fn detect(&self, project_path: &Path, _config: Option<&ProjectConfig>) -> Result<Vec<Service>> {
let mut services = Vec::new();
debug!("Scanning ports {} to {}", self.start_port, self.end_port);
for port in self.start_port..=self.end_port {
if self.is_port_open(port) {
trace!("Port {} is open", port);
if let Some(signature) = ServiceSignatures::find_by_port(port) {
let role = signature.role;
let mut service = Service::new_with_role(
signature.name.to_string(),
signature.service_type.clone(),
"localhost".to_string(),
port,
DetectionSource::PortScan,
project_path.to_string_lossy().to_string(),
role,
);
service.tags.push("auto-detected".to_string());
service.systemd_unit = signature.systemd_unit.map(|s| s.to_string());
services.push(service);
debug!("Detected {} on port {} (role: {:?})", signature.name, port, role);
} else {
let service_type = crate::models::ServiceType::Custom;
let role = ServiceSignatures::infer_role(port, None, &service_type);
let service = Service::new_with_role(
format!("Unknown Service (Port {})", port),
service_type,
"localhost".to_string(),
port,
DetectionSource::PortScan,
project_path.to_string_lossy().to_string(),
role,
);
services.push(service);
debug!("Detected unknown service on port {} (inferred role: {:?})", port, role);
}
}
}
Ok(services)
}
}