1pub mod service_detection;
36pub mod os_fingerprint;
37pub mod vulnerability;
38pub mod compliance;
39
40pub use service_detection::{BannerGrabber, ServiceInfo, ServiceSignatures};
41pub use os_fingerprint::{OSFingerprint, OSDetector, OperatingSystem};
42pub use vulnerability::{VulnerabilityScanner, CVE, VulnerabilityReport};
43pub use compliance::{ComplianceScanner, ComplianceResult, ComplianceFramework as NetworkComplianceFramework};
44
45use chrono::{DateTime, Utc};
46use futures::future::join_all;
47use serde::{Deserialize, Serialize};
48use std::net::{IpAddr, Ipv4Addr, SocketAddr};
49use std::time::Duration;
50use thiserror::Error;
51use tokio::io::{AsyncReadExt, AsyncWriteExt};
52use tokio::net::TcpStream;
53use tokio::time::timeout;
54
55#[derive(Error, Debug)]
57pub enum ScanError {
58 #[error("Connection timeout")]
59 Timeout,
60
61 #[error("Connection failed: {0}")]
62 ConnectionFailed(String),
63
64 #[error("Invalid IP address")]
65 InvalidIpAddress,
66
67 #[error("Invalid port range")]
68 InvalidPortRange,
69
70 #[error("Invalid subnet mask")]
71 InvalidSubnetMask,
72
73 #[error("Banner grab failed: {0}")]
74 BannerGrabFailed(String),
75}
76
77#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
79pub enum PortRiskLevel {
80 Critical,
82 High,
84 Medium,
86 Low,
88 Unknown,
90}
91
92#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
94pub enum PortStatus {
95 Open,
96 Closed,
97 Filtered,
98}
99
100#[derive(Debug, Clone, Serialize, Deserialize)]
102pub struct PortScanResult {
103 pub port: u16,
104 pub status: PortStatus,
105 pub service: Option<String>,
106 pub banner: Option<String>,
107 pub risk_level: PortRiskLevel,
108 pub timestamp: DateTime<Utc>,
109 pub response_time_ms: Option<u64>,
110}
111
112#[derive(Debug, Clone, Serialize, Deserialize)]
114pub struct ScanResult {
115 pub target: String,
116 pub scan_start: DateTime<Utc>,
117 pub scan_end: DateTime<Utc>,
118 pub ports_scanned: usize,
119 pub open_ports: Vec<PortScanResult>,
120 pub closed_ports: usize,
121 pub filtered_ports: usize,
122}
123
124impl ScanResult {
125 pub fn to_json(&self) -> Result<String, serde_json::Error> {
127 serde_json::to_string_pretty(self)
128 }
129
130 pub fn summary(&self) -> String {
132 format!(
133 "Target: {} | Scanned: {} ports | Open: {} | Closed: {} | Filtered: {}",
134 self.target,
135 self.ports_scanned,
136 self.open_ports.len(),
137 self.closed_ports,
138 self.filtered_ports
139 )
140 }
141
142 pub fn get_high_risk_ports(&self) -> Vec<&PortScanResult> {
144 self.open_ports
145 .iter()
146 .filter(|p| matches!(p.risk_level, PortRiskLevel::Critical | PortRiskLevel::High))
147 .collect()
148 }
149
150 pub fn get_ports_by_risk(&self, risk: PortRiskLevel) -> Vec<&PortScanResult> {
152 self.open_ports
153 .iter()
154 .filter(|p| p.risk_level == risk)
155 .collect()
156 }
157
158 pub fn scan_duration_secs(&self) -> f64 {
160 (self.scan_end - self.scan_start).num_milliseconds() as f64 / 1000.0
161 }
162}
163
164#[derive(Debug, Clone)]
166pub struct ScannerConfig {
167 pub timeout_ms: u64,
168 pub concurrent_scans: usize,
169 pub detect_services: bool,
170 pub grab_banners: bool,
171}
172
173impl Default for ScannerConfig {
174 fn default() -> Self {
175 Self {
176 timeout_ms: 1000,
177 concurrent_scans: 100,
178 detect_services: true,
179 grab_banners: false, }
181 }
182}
183
184pub struct NetworkScanner {
186 config: ScannerConfig,
187}
188
189impl NetworkScanner {
190 pub fn new() -> Self {
192 Self {
193 config: ScannerConfig::default(),
194 }
195 }
196
197 pub fn with_config(config: ScannerConfig) -> Self {
199 Self { config }
200 }
201
202 pub async fn scan_port(&self, ip: IpAddr, port: u16) -> PortScanResult {
204 let start = std::time::Instant::now();
205 let addr = SocketAddr::new(ip, port);
206
207 let mut stream_opt = None;
208 let status = match timeout(
209 Duration::from_millis(self.config.timeout_ms),
210 TcpStream::connect(addr),
211 )
212 .await
213 {
214 Ok(Ok(stream)) => {
215 stream_opt = Some(stream);
216 PortStatus::Open
217 }
218 Ok(Err(_)) => PortStatus::Closed,
219 Err(_) => PortStatus::Filtered,
220 };
221
222 let response_time = if status == PortStatus::Open {
223 Some(start.elapsed().as_millis() as u64)
224 } else {
225 None
226 };
227
228 let service = if status == PortStatus::Open && self.config.detect_services {
229 Self::detect_service(port)
230 } else {
231 None
232 };
233
234 let banner = if status == PortStatus::Open && self.config.grab_banners {
235 if let Some(mut stream) = stream_opt {
236 Self::grab_banner(&mut stream, port).await.ok()
237 } else {
238 None
239 }
240 } else {
241 None
242 };
243
244 let risk_level = Self::assess_port_risk(port);
245
246 PortScanResult {
247 port,
248 status,
249 service,
250 banner,
251 risk_level,
252 timestamp: Utc::now(),
253 response_time_ms: response_time,
254 }
255 }
256
257 pub async fn scan_ports(
259 &self,
260 ip: IpAddr,
261 start_port: u16,
262 end_port: u16,
263 ) -> Result<ScanResult, ScanError> {
264 if start_port > end_port {
265 return Err(ScanError::InvalidPortRange);
266 }
267
268 let scan_start = Utc::now();
269 let target = ip.to_string();
270
271 let mut tasks = Vec::new();
273 for port in start_port..=end_port {
274 let task = self.scan_port(ip, port);
275 tasks.push(task);
276
277 if tasks.len() >= self.config.concurrent_scans {
279 let results = join_all(tasks).await;
280 tasks = Vec::new();
281 for _ in results {
283 }
285 }
286 }
287
288 let all_results = join_all(tasks).await;
290
291 let scan_end = Utc::now();
292
293 let open_ports: Vec<PortScanResult> = all_results
295 .iter()
296 .filter(|r| r.status == PortStatus::Open)
297 .cloned()
298 .collect();
299
300 let closed_ports = all_results
301 .iter()
302 .filter(|r| r.status == PortStatus::Closed)
303 .count();
304
305 let filtered_ports = all_results
306 .iter()
307 .filter(|r| r.status == PortStatus::Filtered)
308 .count();
309
310 Ok(ScanResult {
311 target,
312 scan_start,
313 scan_end,
314 ports_scanned: all_results.len(),
315 open_ports,
316 closed_ports,
317 filtered_ports,
318 })
319 }
320
321 pub async fn scan_common_ports(&self, ip: IpAddr) -> Result<ScanResult, ScanError> {
323 let common_ports = vec![
324 20, 21, 22, 23, 25, 53, 80, 110, 143, 443, 445, 993, 995, 3306, 3389, 5432, 5900, 8080,
325 8443, 27017,
326 ];
327
328 let scan_start = Utc::now();
329 let target = ip.to_string();
330
331 let tasks: Vec<_> = common_ports
332 .iter()
333 .map(|&port| self.scan_port(ip, port))
334 .collect();
335
336 let results = join_all(tasks).await;
337 let scan_end = Utc::now();
338
339 let open_ports: Vec<PortScanResult> = results
340 .iter()
341 .filter(|r| r.status == PortStatus::Open)
342 .cloned()
343 .collect();
344
345 let closed_ports = results
346 .iter()
347 .filter(|r| r.status == PortStatus::Closed)
348 .count();
349
350 let filtered_ports = results
351 .iter()
352 .filter(|r| r.status == PortStatus::Filtered)
353 .count();
354
355 Ok(ScanResult {
356 target,
357 scan_start,
358 scan_end,
359 ports_scanned: results.len(),
360 open_ports,
361 closed_ports,
362 filtered_ports,
363 })
364 }
365
366 fn detect_service(port: u16) -> Option<String> {
368 let service = match port {
369 20 => "FTP-DATA",
370 21 => "FTP",
371 22 => "SSH",
372 23 => "Telnet",
373 25 => "SMTP",
374 53 => "DNS",
375 80 => "HTTP",
376 110 => "POP3",
377 143 => "IMAP",
378 443 => "HTTPS",
379 445 => "SMB",
380 993 => "IMAPS",
381 995 => "POP3S",
382 3306 => "MySQL",
383 3389 => "RDP",
384 5432 => "PostgreSQL",
385 5900 => "VNC",
386 8080 => "HTTP-Proxy",
387 8443 => "HTTPS-Alt",
388 27017 => "MongoDB",
389 _ => "Unknown",
390 };
391
392 Some(service.to_string())
393 }
394
395 async fn grab_banner(stream: &mut TcpStream, port: u16) -> Result<String, ScanError> {
397 let probe: &[u8] = match port {
399 80 | 8080 => b"HEAD / HTTP/1.0\r\n\r\n",
400 21 | 22 | 23 | 25 => b"", _ => b"", };
403
404 if !probe.is_empty() {
405 let _ = timeout(Duration::from_millis(500), stream.write_all(probe)).await;
406 }
407
408 let mut buffer = vec![0u8; 1024];
409 match timeout(Duration::from_millis(500), stream.read(&mut buffer)).await {
410 Ok(Ok(n)) if n > 0 => {
411 let banner = String::from_utf8_lossy(&buffer[..n]).trim().to_string();
412 if !banner.is_empty() {
413 Ok(banner)
414 } else {
415 Err(ScanError::BannerGrabFailed("Empty response".to_string()))
416 }
417 }
418 _ => Err(ScanError::BannerGrabFailed("No response".to_string())),
419 }
420 }
421
422 fn assess_port_risk(port: u16) -> PortRiskLevel {
424 match port {
425 21 | 23 | 69 | 512..=514 => PortRiskLevel::Critical, 3306 | 5432 | 27017 | 6379 | 3389 | 5900 | 445 | 139 | 135 | 1433 | 1521 => PortRiskLevel::High, 80 | 8080 | 8000 | 25 | 110 | 143 => PortRiskLevel::Medium, 22 | 443 | 8443 | 465 | 587 | 993 | 995 => PortRiskLevel::Low, _ => PortRiskLevel::Unknown,
442 }
443 }
444
445 pub async fn scan_subnet(
447 &self,
448 subnet: &str,
449 ports: Vec<u16>,
450 ) -> Result<Vec<ScanResult>, ScanError> {
451 let (base_ip, mask) = Self::parse_cidr(subnet)?;
452 let hosts = Self::generate_host_ips(base_ip, mask);
453
454 let mut results = Vec::new();
455 for host_ip in hosts {
456 let scan_start = Utc::now();
457 let target = host_ip.to_string();
458
459 let tasks: Vec<_> = ports
460 .iter()
461 .map(|&port| self.scan_port(IpAddr::V4(host_ip), port))
462 .collect();
463
464 let port_results = join_all(tasks).await;
465 let scan_end = Utc::now();
466
467 let open_ports: Vec<PortScanResult> = port_results
468 .iter()
469 .filter(|r| r.status == PortStatus::Open)
470 .cloned()
471 .collect();
472
473 if !open_ports.is_empty() {
475 let closed_ports = port_results
476 .iter()
477 .filter(|r| r.status == PortStatus::Closed)
478 .count();
479
480 let filtered_ports = port_results
481 .iter()
482 .filter(|r| r.status == PortStatus::Filtered)
483 .count();
484
485 results.push(ScanResult {
486 target,
487 scan_start,
488 scan_end,
489 ports_scanned: port_results.len(),
490 open_ports,
491 closed_ports,
492 filtered_ports,
493 });
494 }
495 }
496
497 Ok(results)
498 }
499
500 fn parse_cidr(cidr: &str) -> Result<(Ipv4Addr, u8), ScanError> {
502 let parts: Vec<&str> = cidr.split('/').collect();
503 if parts.len() != 2 {
504 return Err(ScanError::InvalidSubnetMask);
505 }
506
507 let ip = parts[0]
508 .parse::<Ipv4Addr>()
509 .map_err(|_| ScanError::InvalidIpAddress)?;
510
511 let mask = parts[1]
512 .parse::<u8>()
513 .map_err(|_| ScanError::InvalidSubnetMask)?;
514
515 if mask > 32 {
516 return Err(ScanError::InvalidSubnetMask);
517 }
518
519 Ok((ip, mask))
520 }
521
522 fn generate_host_ips(base_ip: Ipv4Addr, mask: u8) -> Vec<Ipv4Addr> {
524 let ip_u32 = u32::from(base_ip);
525 let network_mask = !((1u32 << (32 - mask)) - 1);
526 let network_addr = ip_u32 & network_mask;
527 let host_count = (1u32 << (32 - mask)).saturating_sub(2); let mut ips = Vec::new();
530 for i in 1..=host_count.min(254) {
531 let host_ip = Ipv4Addr::from(network_addr + i);
533 ips.push(host_ip);
534 }
535
536 ips
537 }
538}
539
540impl Default for NetworkScanner {
541 fn default() -> Self {
542 Self::new()
543 }
544}
545
546#[cfg(test)]
547mod tests {
548 use super::*;
549 use std::str::FromStr;
550
551 #[tokio::test]
552 async fn test_scan_single_port() {
553 let scanner = NetworkScanner::new();
554 let ip = IpAddr::from_str("127.0.0.1").unwrap();
555
556 let result = scanner.scan_port(ip, 9999).await;
558
559 assert!(result.port == 9999);
560 assert!(result.status == PortStatus::Closed || result.status == PortStatus::Filtered);
562 }
563
564 #[tokio::test]
565 async fn test_service_detection() {
566 assert_eq!(NetworkScanner::detect_service(80), Some("HTTP".to_string()));
567 assert_eq!(
568 NetworkScanner::detect_service(443),
569 Some("HTTPS".to_string())
570 );
571 assert_eq!(NetworkScanner::detect_service(22), Some("SSH".to_string()));
572 }
573
574 #[tokio::test]
575 async fn test_scan_result_summary() {
576 let result = ScanResult {
577 target: "192.168.1.1".to_string(),
578 scan_start: Utc::now(),
579 scan_end: Utc::now(),
580 ports_scanned: 100,
581 open_ports: vec![],
582 closed_ports: 95,
583 filtered_ports: 5,
584 };
585
586 let summary = result.summary();
587 assert!(summary.contains("192.168.1.1"));
588 assert!(summary.contains("100"));
589 }
590
591 #[tokio::test]
592 async fn test_invalid_port_range() {
593 let scanner = NetworkScanner::new();
594 let ip = IpAddr::from_str("127.0.0.1").unwrap();
595
596 let result = scanner.scan_ports(ip, 100, 50).await;
597 assert!(result.is_err());
598 }
599
600 #[test]
601 fn test_port_risk_assessment() {
602 assert_eq!(
604 NetworkScanner::assess_port_risk(23),
605 PortRiskLevel::Critical
606 ); assert_eq!(
608 NetworkScanner::assess_port_risk(21),
609 PortRiskLevel::Critical
610 ); assert_eq!(NetworkScanner::assess_port_risk(3389), PortRiskLevel::High); assert_eq!(NetworkScanner::assess_port_risk(3306), PortRiskLevel::High); assert_eq!(NetworkScanner::assess_port_risk(80), PortRiskLevel::Medium); assert_eq!(NetworkScanner::assess_port_risk(443), PortRiskLevel::Low); assert_eq!(NetworkScanner::assess_port_risk(22), PortRiskLevel::Low); }
623
624 #[test]
625 fn test_cidr_parsing() {
626 let result = NetworkScanner::parse_cidr("192.168.1.0/24");
627 assert!(result.is_ok());
628 let (ip, mask) = result.unwrap();
629 assert_eq!(ip.to_string(), "192.168.1.0");
630 assert_eq!(mask, 24);
631
632 assert!(NetworkScanner::parse_cidr("192.168.1.0").is_err());
634 assert!(NetworkScanner::parse_cidr("192.168.1.0/33").is_err());
635 assert!(NetworkScanner::parse_cidr("invalid/24").is_err());
636 }
637
638 #[test]
639 fn test_host_ip_generation() {
640 let base_ip = Ipv4Addr::new(192, 168, 1, 0);
641 let ips = NetworkScanner::generate_host_ips(base_ip, 30); assert_eq!(ips.len(), 2);
644 assert_eq!(ips[0], Ipv4Addr::new(192, 168, 1, 1));
645 assert_eq!(ips[1], Ipv4Addr::new(192, 168, 1, 2));
646 }
647
648 #[tokio::test]
649 async fn test_scan_result_high_risk_ports() {
650 let result = ScanResult {
651 target: "192.168.1.1".to_string(),
652 scan_start: Utc::now(),
653 scan_end: Utc::now(),
654 ports_scanned: 5,
655 open_ports: vec![
656 PortScanResult {
657 port: 23,
658 status: PortStatus::Open,
659 service: Some("Telnet".to_string()),
660 banner: None,
661 risk_level: PortRiskLevel::Critical,
662 timestamp: Utc::now(),
663 response_time_ms: Some(10),
664 },
665 PortScanResult {
666 port: 3389,
667 status: PortStatus::Open,
668 service: Some("RDP".to_string()),
669 banner: None,
670 risk_level: PortRiskLevel::High,
671 timestamp: Utc::now(),
672 response_time_ms: Some(15),
673 },
674 PortScanResult {
675 port: 443,
676 status: PortStatus::Open,
677 service: Some("HTTPS".to_string()),
678 banner: None,
679 risk_level: PortRiskLevel::Low,
680 timestamp: Utc::now(),
681 response_time_ms: Some(5),
682 },
683 ],
684 closed_ports: 2,
685 filtered_ports: 0,
686 };
687
688 let high_risk = result.get_high_risk_ports();
689 assert_eq!(high_risk.len(), 2); let critical_ports = result.get_ports_by_risk(PortRiskLevel::Critical);
692 assert_eq!(critical_ports.len(), 1);
693 assert_eq!(critical_ports[0].port, 23);
694 }
695
696 #[tokio::test]
697 async fn test_scan_duration_calculation() {
698 let start = Utc::now();
699 tokio::time::sleep(Duration::from_millis(100)).await;
700 let end = Utc::now();
701
702 let result = ScanResult {
703 target: "127.0.0.1".to_string(),
704 scan_start: start,
705 scan_end: end,
706 ports_scanned: 10,
707 open_ports: vec![],
708 closed_ports: 10,
709 filtered_ports: 0,
710 };
711
712 let duration = result.scan_duration_secs();
713 assert!((0.1..1.0).contains(&duration));
714 }
715
716 #[tokio::test]
717 async fn test_json_export() {
718 let result = ScanResult {
719 target: "192.168.1.100".to_string(),
720 scan_start: Utc::now(),
721 scan_end: Utc::now(),
722 ports_scanned: 3,
723 open_ports: vec![PortScanResult {
724 port: 80,
725 status: PortStatus::Open,
726 service: Some("HTTP".to_string()),
727 banner: Some("Server: nginx/1.18.0".to_string()),
728 risk_level: PortRiskLevel::Medium,
729 timestamp: Utc::now(),
730 response_time_ms: Some(12),
731 }],
732 closed_ports: 2,
733 filtered_ports: 0,
734 };
735
736 let json = result.to_json();
737 assert!(json.is_ok());
738 let json_str = json.unwrap();
739 assert!(json_str.contains("192.168.1.100"));
740 assert!(json_str.contains("HTTP"));
741 assert!(json_str.contains("nginx"));
742 }
743
744 #[test]
745 fn test_scanner_config_defaults() {
746 let config = ScannerConfig::default();
747 assert_eq!(config.timeout_ms, 1000);
748 assert_eq!(config.concurrent_scans, 100);
749 assert!(config.detect_services);
750 assert!(!config.grab_banners); }
752
753 #[tokio::test]
754 async fn test_scanner_with_custom_config() {
755 let config = ScannerConfig {
756 timeout_ms: 500,
757 concurrent_scans: 50,
758 detect_services: true,
759 grab_banners: false,
760 };
761
762 let scanner = NetworkScanner::with_config(config);
763 let ip = IpAddr::from_str("127.0.0.1").unwrap();
764
765 let result = scanner.scan_port(ip, 9999).await;
766 assert!(result.port == 9999);
767 }
768}