Skip to content

Commit 0bdd839

Browse files
Copilotnpv2k1
andcommitted
Add port explorer module with native and nmap scanning support
Co-authored-by: npv2k1 <73846954+npv2k1@users.noreply.github.com>
1 parent 8e6d286 commit 0bdd839

7 files changed

Lines changed: 397 additions & 6 deletions

File tree

src-tauri/src/commands/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::db;
22
pub mod blockchain;
3+
pub mod port_scanner;
34
#[tauri::command]
45
pub fn greet(name: &str) -> String {
56
println!("Hello, {}!", name);
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
use crate::modules::port_scanner::{scan_ports, PortScanResult, ScanOptions};
2+
3+
#[tauri::command]
4+
pub fn scan_ports_command(
5+
host: String,
6+
start_port: u16,
7+
end_port: u16,
8+
use_nmap: bool,
9+
timeout_ms: u64,
10+
) -> Result<Vec<PortScanResult>, String> {
11+
let options = ScanOptions {
12+
host,
13+
start_port,
14+
end_port,
15+
use_nmap,
16+
timeout_ms,
17+
};
18+
19+
scan_ports(options)
20+
}
21+
22+
#[tauri::command]
23+
pub fn check_nmap_available() -> bool {
24+
std::process::Command::new("nmap")
25+
.arg("--version")
26+
.output()
27+
.is_ok()
28+
}

src-tauri/src/main.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ fn main() {
3838
commands::blockchain::init_blockchain_command,
3939
commands::blockchain::add_block_command,
4040
commands::blockchain::get_blocks_command,
41+
commands::port_scanner::scan_ports_command,
42+
commands::port_scanner::check_nmap_available,
4143
])
4244
.setup({
4345
let python_server = Arc::clone(&python_server);

src-tauri/src/modules/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
pub mod blockchain;
2+
pub mod port_scanner;
23
pub mod python;
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
use serde::{Deserialize, Serialize};
2+
use std::net::TcpStream;
3+
use std::process::Command;
4+
use std::time::Duration;
5+
6+
#[derive(Debug, Clone, Serialize, Deserialize)]
7+
pub struct PortScanResult {
8+
pub port: u16,
9+
pub is_open: bool,
10+
pub service: Option<String>,
11+
}
12+
13+
#[derive(Debug, Clone, Serialize, Deserialize)]
14+
pub struct ScanOptions {
15+
pub host: String,
16+
pub start_port: u16,
17+
pub end_port: u16,
18+
pub use_nmap: bool,
19+
pub timeout_ms: u64,
20+
}
21+
22+
/// Scan a single port using native TCP connection
23+
pub fn scan_port_native(host: &str, port: u16, timeout_ms: u64) -> PortScanResult {
24+
let addr = format!("{}:{}", host, port);
25+
let timeout = Duration::from_millis(timeout_ms);
26+
27+
let is_open = TcpStream::connect_timeout(
28+
&addr.parse().unwrap_or_else(|_| {
29+
format!("127.0.0.1:{}", port).parse().unwrap()
30+
}),
31+
timeout,
32+
)
33+
.is_ok();
34+
35+
PortScanResult {
36+
port,
37+
is_open,
38+
service: if is_open { get_service_name(port) } else { None },
39+
}
40+
}
41+
42+
/// Get common service name for a port
43+
fn get_service_name(port: u16) -> Option<String> {
44+
let service = match port {
45+
20 => "FTP Data",
46+
21 => "FTP",
47+
22 => "SSH",
48+
23 => "Telnet",
49+
25 => "SMTP",
50+
53 => "DNS",
51+
80 => "HTTP",
52+
110 => "POP3",
53+
143 => "IMAP",
54+
443 => "HTTPS",
55+
445 => "SMB",
56+
3306 => "MySQL",
57+
3389 => "RDP",
58+
5432 => "PostgreSQL",
59+
5900 => "VNC",
60+
6379 => "Redis",
61+
8080 => "HTTP-Alt",
62+
8443 => "HTTPS-Alt",
63+
27017 => "MongoDB",
64+
_ => return None,
65+
};
66+
Some(service.to_string())
67+
}
68+
69+
/// Scan ports using native TCP method
70+
pub fn scan_ports_native(options: &ScanOptions) -> Vec<PortScanResult> {
71+
let mut results = Vec::new();
72+
73+
for port in options.start_port..=options.end_port {
74+
let result = scan_port_native(&options.host, port, options.timeout_ms);
75+
results.push(result);
76+
}
77+
78+
results
79+
}
80+
81+
/// Scan ports using nmap command
82+
pub fn scan_ports_nmap(options: &ScanOptions) -> Result<Vec<PortScanResult>, String> {
83+
let port_range = if options.start_port == options.end_port {
84+
format!("{}", options.start_port)
85+
} else {
86+
format!("{}-{}", options.start_port, options.end_port)
87+
};
88+
89+
let output = Command::new("nmap")
90+
.arg("-p")
91+
.arg(&port_range)
92+
.arg(&options.host)
93+
.output();
94+
95+
match output {
96+
Ok(output) => {
97+
let stdout = String::from_utf8_lossy(&output.stdout);
98+
parse_nmap_output(&stdout, options.start_port, options.end_port)
99+
}
100+
Err(e) => Err(format!("Failed to execute nmap: {}. Make sure nmap is installed.", e)),
101+
}
102+
}
103+
104+
/// Parse nmap output
105+
fn parse_nmap_output(output: &str, start_port: u16, end_port: u16) -> Result<Vec<PortScanResult>, String> {
106+
let mut results = Vec::new();
107+
108+
for line in output.lines() {
109+
if line.contains("/tcp") || line.contains("/udp") {
110+
let parts: Vec<&str> = line.split_whitespace().collect();
111+
if parts.len() >= 2 {
112+
if let Some(port_str) = parts[0].split('/').next() {
113+
if let Ok(port) = port_str.parse::<u16>() {
114+
let is_open = parts[1].contains("open");
115+
let service = if parts.len() >= 3 {
116+
Some(parts[2].to_string())
117+
} else {
118+
get_service_name(port)
119+
};
120+
121+
results.push(PortScanResult {
122+
port,
123+
is_open,
124+
service,
125+
});
126+
}
127+
}
128+
}
129+
}
130+
}
131+
132+
// If nmap didn't return results for all ports, fill in the gaps
133+
if results.is_empty() {
134+
for port in start_port..=end_port {
135+
results.push(PortScanResult {
136+
port,
137+
is_open: false,
138+
service: None,
139+
});
140+
}
141+
}
142+
143+
Ok(results)
144+
}
145+
146+
/// Main scan function that chooses between native and nmap
147+
pub fn scan_ports(options: ScanOptions) -> Result<Vec<PortScanResult>, String> {
148+
if options.use_nmap {
149+
scan_ports_nmap(&options)
150+
} else {
151+
Ok(scan_ports_native(&options))
152+
}
153+
}

0 commit comments

Comments
 (0)