diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 6aa06da..7a16dd8 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -133,7 +133,7 @@ async fn scan_port_range( start_port: u16, end_port: u16, timeout: u64, - threads: u32, + threads: usize, sort: bool, ) -> Result, String> { if state.is_scanning.load(Ordering::SeqCst) { @@ -254,7 +254,7 @@ async fn scan_port_range( state.cancellation_token.store(false, Ordering::SeqCst); return Ok(vec![]); } - + let ip_addr = Ipv6Addr::from(ip_int.to_be_bytes()); addresses.push(ip_addr.to_string()); } @@ -267,9 +267,10 @@ async fn scan_port_range( let mut threads = threads; let all_results: Arc>> = Arc::new(Mutex::new(vec![])); + let mut scan_results: Vec = vec![]; for address in addresses { - let cancellation_token = Arc::clone(&state.cancellation_token); // Check the cancellation token and return if it's true + let cancellation_token = Arc::clone(&state.cancellation_token); if cancellation_token.load(Ordering::Relaxed) { state.is_scanning.store(false, Ordering::SeqCst); state.cancellation_token.store(false, Ordering::SeqCst); @@ -278,84 +279,89 @@ async fn scan_port_range( return Ok(res.deref().to_vec()); } - if threads > 1 { - let total_ports = u32::from(end_port) - u32::from(start_port) + 1; + for port in start_port..=end_port { + let scan_result = ScanResult::initialize(&address, port); + scan_results.push(scan_result); + } + } + + if threads > 1 { + if threads > scan_results.len() { + threads = scan_results.len(); + } - if threads > total_ports { - threads = total_ports; + // Divide the scan results into equal parts for each thread + let range = scan_results.len() / threads; + let remainder = scan_results.len() % threads; + + let mut current_start = 0; + let mut current_end = range - 1; + + let mut handles = vec![]; + for t in 0..threads { + // Make sure the remainder is included in the last thread + if t == threads - 1 && remainder > 0 { + current_end += remainder; } - let range = (total_ports / threads) as u16; - let remainder = (total_ports % threads) as u16; - - let mut current_start = start_port; - let mut current_end = start_port + (range - 1); - - let mut handles = vec![]; - for n in 0..threads { - let local_start = current_start; - let local_end = current_end; - - let all_results = Arc::clone(&all_results); - let cancellation_token = Arc::clone(&state.cancellation_token); - let last_error = Arc::clone(&state.last_error); - - let addr_clone = address.clone(); - let handle = thread::spawn(move || { - let res = scan_tcp_range( - &addr_clone, - local_start, - local_end, - timeout, - cancellation_token, - ); - - let res = res.unwrap_or_else(|e| { - let mut last_error = last_error.lock().unwrap(); - *last_error = e.to_string(); - - vec![] - }); - - let mut results = all_results.lock().unwrap(); - for l in res { - results.push(l); - } - }); - handles.push(handle); + let local_start = current_start; + let local_end = current_end; - if current_end != u16::MAX { - current_start = current_end + 1; - } + let all_results = Arc::clone(&all_results); + let cancellation_token = Arc::clone(&state.cancellation_token); + let last_error = Arc::clone(&state.last_error); - match current_end.checked_add(range) { - Some(v) => { - current_end = v; - } - None => { - current_end = u16::MAX; + // Get a slice of the scan results for the current thread + let scan_results_slice = scan_results[local_start..=local_end].to_vec(); + + let handle = thread::spawn(move || { + let mut local_results = vec![]; + for scan_result in scan_results_slice { + if cancellation_token.load(Ordering::Relaxed) { + break; } - }; - if remainder > 0 && n == threads - 2 { - match current_end.checked_add(remainder) { - None => { - current_end = u16::MAX; - } - Some(v) => { - current_end = v; + let address = scan_result.address.clone(); + let port = scan_result.port; + + let res = match scan_request(scan_result, timeout) { + Ok(r) => r, + Err(e) => { + let mut last_error = last_error.lock().unwrap(); + *last_error = e.to_string(); + ScanResult::new(&address, port, "", PortStatus::Unknown) } - } + }; + + local_results.push(res); } - } - for handle in handles { - handle.join().unwrap(); - } - } else { + // Append the results to the global results at the end of the thread + let mut results = all_results.lock().unwrap(); + results.append(&mut local_results); + }); + handles.push(handle); + + current_start = current_end + 1; + current_end += range; + } + + for handle in handles { + handle.join().unwrap(); + } + } else { + for scan_result in scan_results { + // Check the cancellation token and return if it's true let cancellation_token = Arc::clone(&state.cancellation_token); + if cancellation_token.load(Ordering::Relaxed) { + state.is_scanning.store(false, Ordering::SeqCst); + state.cancellation_token.store(false, Ordering::SeqCst); - let res = scan_tcp_range(&address, start_port, end_port, timeout, cancellation_token); + let res = all_results.lock().unwrap(); + return Ok(res.deref().to_vec()); + } + + let res = scan_request(scan_result, timeout); let res = match res { Ok(res) => res, Err(e) => { @@ -364,9 +370,7 @@ async fn scan_port_range( }; let mut all = all_results.lock().unwrap(); - for l in res { - all.push(l); - } + all.push(res); } } @@ -390,59 +394,44 @@ async fn scan_port_range( Ok(res.deref().to_vec()) } -/// Scan a range of ports for a specified host +/// Scan a single port on a specified host /// /// # Arguments /// -/// * `host` - The host that needs to be scanned -/// * `start` - The initial port that needs to be scanned -/// * `end` - The final port that needs to be scanned +/// * `request` - The request that needs to be scanned /// * `timeout` - The connection timeout (in milliseconds) before a port is marked as closed -/// * `cancellation_token` - A cancellation token that can be used to cancel the scan -fn scan_tcp_range( - host: &str, - start: u16, - end: u16, - timeout: u64, - cancellation_token: Arc, -) -> Result, String> { - let mut scan_result = vec![]; - for n in start..=end { - // Check the cancellation token and return if it's true - if cancellation_token.load(Ordering::Relaxed) { - return Ok(scan_result); +/// +/// # Returns +/// +/// * `Ok(ScanResult)` - If the scan was successful +/// * `Err(String)` - If the scan was unsuccessful +fn scan_request(mut request: ScanResult, timeout: u64) -> Result { + let address = format!("{}:{}", request.address, request.port).to_socket_addrs(); + let mut address = match address { + Ok(res) => res, + Err(e) => { + return Err(e.to_string()); } + }; - let address = format!("{}:{}", host, n).to_socket_addrs(); - let mut address = match address { - Ok(res) => res, - Err(e) => { - return Err(e.to_string()); - } - }; - - let socket_address = address.next().unwrap(); - let ip_addr: IpAddr = socket_address.ip(); - let host_name = ip_addr.to_string(); + let socket_address = address.next().unwrap(); + let ip_addr: IpAddr = socket_address.ip(); + let host_name = ip_addr.to_string(); - if let Ok(stream) = - TcpStream::connect_timeout(&socket_address, Duration::from_millis(timeout)) - { - let sr = ScanResult::new(host, n, &host_name, PortStatus::Open); - scan_result.push(sr); + if let Ok(stream) = TcpStream::connect_timeout(&socket_address, Duration::from_millis(timeout)) + { + request.set_scan_result(&host_name, PortStatus::Open); - let res = stream.shutdown(Shutdown::Both); - match res { - Ok(_) => {} - Err(e) => { - println!("Unable to shut down TcpStream: {}", e) - } + let res = stream.shutdown(Shutdown::Both); + match res { + Ok(_) => {} + Err(e) => { + println!("Unable to shut down TcpStream: {}", e) } - } else { - let sr = ScanResult::new(host, n, &host_name, PortStatus::Closed); - scan_result.push(sr); } + } else { + request.set_scan_result(&host_name, PortStatus::Closed); } - Ok(scan_result) + Ok(request) } diff --git a/src-tauri/src/result.rs b/src-tauri/src/result.rs index 9f5e22d..b1b8bec 100644 --- a/src-tauri/src/result.rs +++ b/src-tauri/src/result.rs @@ -6,6 +6,7 @@ use std::time::SystemTime; pub enum PortStatus { Open, Closed, + Unknown, } #[derive(Serialize, Deserialize, Clone)] @@ -13,11 +14,11 @@ pub struct ScanResult { pub address: String, pub port: u16, #[serde(rename = "hostName")] - pub host_name: String, + pub host_name: Option, #[serde(rename = "portStatus")] pub port_status: PortStatus, #[serde(rename = "scanDate")] - pub scan_date: String, + pub scan_date: Option, } impl ScanResult { @@ -41,9 +42,46 @@ impl ScanResult { ScanResult { address: String::from(address), port, - host_name: String::from(host_name), + host_name: Some(String::from(host_name)), port_status, - scan_date: now, + scan_date: Some(now), } } + + /// Initialize a ScanResult + /// + /// # Arguments + /// + /// * `address` - The address that was scanned + /// * `port` - The port that was scanned + /// * `host_name` - The host name of the address that was scanned + /// + /// # Returns + /// + /// A new ScanResult + pub fn initialize(address: &str, port: u16) -> ScanResult { + ScanResult { + address: String::from(address), + port, + host_name: None, + port_status: PortStatus::Unknown, + scan_date: None, + } + } + + /// Set the host name,port status and scan date + /// + /// # Arguments + /// + /// * `host_name` - The host name of the address + /// * `port_status` - The status of the port + pub fn set_scan_result(&mut self, host_name: &str, port_status: PortStatus) { + let now = SystemTime::now(); + let now: DateTime = now.into(); + let now = now.to_rfc3339(); + + self.scan_date = Some(now); + self.port_status = port_status; + self.host_name = Some(String::from(host_name)); + } } diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 29fd61b..c4dec39 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -50,7 +50,7 @@ "windows": [ { "fullscreen": false, - "height": 750, + "height": 780, "resizable": true, "title": "Advanced PortChecker", "width": 1100 diff --git a/src/contexts/MainContextProvider/index.jsx b/src/contexts/MainContextProvider/index.jsx index ee4c801..c2a2d48 100644 --- a/src/contexts/MainContextProvider/index.jsx +++ b/src/contexts/MainContextProvider/index.jsx @@ -43,6 +43,7 @@ const initState = { startPort: 0, endPort: 65535, isScanning: false, + isCancelling: false, threads, timeout, noClosed, diff --git a/src/languages/en_us.json b/src/languages/en_us.json index d053b4a..112ab2a 100644 --- a/src/languages/en_us.json +++ b/src/languages/en_us.json @@ -82,5 +82,6 @@ "exportSuccessful": "Export successful", "runningLatestVersion": "You are running the latest version.", "themeToggleInTopBar": "Toggle theme in top bar", - "exportIncludeClosedPorts": "Include closed ports in exports" + "exportIncludeClosedPorts": "Include closed ports in exports", + "unknown": "Unknown" } diff --git a/src/languages/es_es.json b/src/languages/es_es.json index 8e699dd..d175bc8 100644 --- a/src/languages/es_es.json +++ b/src/languages/es_es.json @@ -82,5 +82,6 @@ "exportSuccessful": "Exportación exitosa", "runningLatestVersion": "Estás ejecutando la última versión.", "themeToggleInTopBar": "Alternar tema en la barra superior", - "exportIncludeClosedPorts": "Incluir puertos cerrados en las exportaciones" + "exportIncludeClosedPorts": "Incluir puertos cerrados en las exportaciones", + "unknown": "Desconocido" } diff --git a/src/languages/fr_fr.json b/src/languages/fr_fr.json index dfa2747..9bf4e11 100644 --- a/src/languages/fr_fr.json +++ b/src/languages/fr_fr.json @@ -82,5 +82,6 @@ "exportSuccessful": "Exportation réussie", "runningLatestVersion": "Vous utilisez la dernière version.", "themeToggleInTopBar": "Basculer le thème dans la barre supérieure", - "exportIncludeClosedPorts": "Inclure les ports fermés dans les exportations" + "exportIncludeClosedPorts": "Inclure les ports fermés dans les exportations", + "unknown": "Inconnu" } diff --git a/src/languages/it_it.json b/src/languages/it_it.json index b228f71..5dec180 100644 --- a/src/languages/it_it.json +++ b/src/languages/it_it.json @@ -82,5 +82,6 @@ "exportSuccessful": "Esportazione completata", "runningLatestVersion": "La versione in uso è aggiornata.", "themeToggleInTopBar": "Attiva/disattiva tema nella barra superiore", - "exportIncludeClosedPorts": "Includi porte chiuse nelle esportazioni" + "exportIncludeClosedPorts": "Includi porte chiuse nelle esportazioni", + "unknown": "Sconosciuto" } diff --git a/src/languages/nl_nl.json b/src/languages/nl_nl.json index 12a4eda..4377ba0 100644 --- a/src/languages/nl_nl.json +++ b/src/languages/nl_nl.json @@ -82,5 +82,6 @@ "exportSuccessful": "Exporteren succesvol", "runningLatestVersion": "U gebruikt de laatste versie.", "themeToggleInTopBar": "Thema toggle in de bovenste balk", - "exportIncludeClosedPorts": "Gesloten poorten opnemen in export" + "exportIncludeClosedPorts": "Gesloten poorten opnemen in export", + "unknown": "Onbekend" } diff --git a/src/languages/zh_cn.json b/src/languages/zh_cn.json index fae8bf8..3e0550e 100644 --- a/src/languages/zh_cn.json +++ b/src/languages/zh_cn.json @@ -82,5 +82,6 @@ "exportSuccessful": "导出成功", "runningLatestVersion": "已是最新版本!", "themeToggleInTopBar": "在顶部栏显示明暗主题切换", - "exportIncludeClosedPorts": "导出时包含关闭的端口" + "exportIncludeClosedPorts": "导出时包含关闭的端口", + "unknown": "未知" } diff --git a/src/reducers/MainReducer/Actions/actionTypes.js b/src/reducers/MainReducer/Actions/actionTypes.js index eb50950..100860a 100644 --- a/src/reducers/MainReducer/Actions/actionTypes.js +++ b/src/reducers/MainReducer/Actions/actionTypes.js @@ -20,3 +20,4 @@ export const SET_SORT = 'SET_SORT'; export const SET_SCAN_RESULTS = 'SET_SCAN_RESULTS'; export const SET_THEME_TOGGLE = 'SET_THEME_TOGGLE'; export const SET_EXPORT_NO_CLOSED = 'SET_EXPORT_NO_CLOSED'; +export const SET_IS_CANCELLING = 'SET_IS_CANCELLING'; diff --git a/src/reducers/MainReducer/Actions/index.js b/src/reducers/MainReducer/Actions/index.js index 3d75a8b..ecd7b09 100644 --- a/src/reducers/MainReducer/Actions/index.js +++ b/src/reducers/MainReducer/Actions/index.js @@ -8,6 +8,7 @@ import { SET_END_PORT, SET_ERROR, SET_EXPORT_NO_CLOSED, + SET_IS_CANCELLING, SET_IS_SCANNING, SET_LANGUAGE_INDEX, SET_LOADING, @@ -157,3 +158,8 @@ export const setExportNoClosed = (value) => ({ type: SET_EXPORT_NO_CLOSED, payload: value, }); + +export const setIsCancelling = (value) => ({ + type: SET_IS_CANCELLING, + payload: value, +}); diff --git a/src/reducers/MainReducer/index.js b/src/reducers/MainReducer/index.js index 06c2eb0..9cfc15d 100644 --- a/src/reducers/MainReducer/index.js +++ b/src/reducers/MainReducer/index.js @@ -19,7 +19,9 @@ import { SET_THREADS, SET_TIMEOUT, SET_UPDATE, - SET_THEME_TOGGLE, SET_EXPORT_NO_CLOSED, + SET_THEME_TOGGLE, + SET_EXPORT_NO_CLOSED, + SET_IS_CANCELLING, } from './Actions/actionTypes'; const MainReducer = (state, action) => { @@ -166,6 +168,11 @@ const MainReducer = (state, action) => { ...state, exportNoClosed: action.payload, }; + case SET_IS_CANCELLING: + return { + ...state, + isCancelling: action.payload, + }; default: return state; } diff --git a/src/routes/Home/index.jsx b/src/routes/Home/index.jsx index 401ef15..45ca9db 100644 --- a/src/routes/Home/index.jsx +++ b/src/routes/Home/index.jsx @@ -22,6 +22,7 @@ import { setAddress, setEndPort, setError, + setIsCancelling, setIsScanning, setPageIndex, setScanResults, @@ -36,6 +37,7 @@ const Home = () => { const { languages, languageIndex, address, startPort, endPort, timeout, threads, noClosed, sort, isScanning, scanResults, exportNoClosed, + isCancelling, } = state; const language = languages[languageIndex]; @@ -86,9 +88,13 @@ const Home = () => { */ const startStopScan = () => { if (isScanning) { + d1(setIsCancelling(true)); cancelScan() .catch((err) => { d1(setError(err)); + }) + .finally(() => { + d1(setIsCancelling(false)); }); } else { if (address === '' || startPort < 0 @@ -98,6 +104,7 @@ const Home = () => { || startPort > endPort) return; d1(setIsScanning(true)); + d1(setScanResults(null)); scanAddress(address, startPort, endPort, timeout, threads, sort) .then((res) => { @@ -284,7 +291,13 @@ const Home = () => { continue; } - const portStatus = res.portStatus === 'Open' ? language.open : language.closed; + let portStatus = language.closed; + if (res.portStatus === 'Open') { + portStatus = language.open; + } else if (res.portStatus === 'Unknown') { + portStatus = language.unknown; + } + scanResultRows.push( createData( res.address + res.port, @@ -312,6 +325,7 @@ const Home = () => { id="address-basic" label={language.address} variant="outlined" + placeholder="127.0.0.1/24" value={address} disabled={isScanning} fullWidth @@ -363,7 +377,7 @@ const Home = () => { variant="contained" color="primary" sx={{ mt: 2, float: 'right' }} - disabled={address === ''} + disabled={address === '' || isCancelling} onClick={startStopScan} > {isScanning ? language.cancel : language.scan}