22#![ cfg_attr( not( debug_assertions) , windows_subsystem = "windows" ) ]
33
44use crate :: result:: { PortStatus , ScanResult } ;
5- use std:: net:: { IpAddr , Shutdown , TcpStream , ToSocketAddrs , UdpSocket } ;
5+ use std:: net:: { IpAddr , Shutdown , TcpStream , ToSocketAddrs } ;
66use std:: ops:: Deref ;
7+ use std:: sync:: atomic:: { AtomicBool , Ordering } ;
78use std:: sync:: { Arc , Mutex } ;
89use std:: thread;
10+ use std:: thread:: available_parallelism;
911use std:: time:: Duration ;
1012
1113mod result;
1214
15+ struct SharedState {
16+ is_scanning : Arc < AtomicBool > ,
17+ cancellation_token : Arc < AtomicBool > ,
18+ last_error : Arc < Mutex < String > > ,
19+ }
20+
1321fn main ( ) {
22+ let shared_state = SharedState {
23+ is_scanning : Arc :: new ( AtomicBool :: new ( false ) ) ,
24+ cancellation_token : Arc :: new ( AtomicBool :: new ( false ) ) ,
25+ last_error : Arc :: new ( Mutex :: new ( String :: from ( "" ) ) ) ,
26+ } ;
27+
1428 tauri:: Builder :: default ( )
15- . invoke_handler ( tauri:: generate_handler![ open_website, scan_port_range] )
29+ . manage ( shared_state)
30+ . invoke_handler ( tauri:: generate_handler![
31+ open_website,
32+ scan_port_range,
33+ cancel_scan,
34+ get_number_of_threads
35+ ] )
1636 . run ( tauri:: generate_context!( ) )
1737 . expect ( "error while running tauri application" ) ;
1838}
1939
40+ /// Get the number of threads that can be used for port scanning
41+ ///
42+ /// # Returns
43+ ///
44+ /// * `u32` - The number of threads that can be used for port scanning
45+ #[ tauri:: command]
46+ fn get_number_of_threads ( ) -> usize {
47+ let default_parallelism_approx = available_parallelism ( ) . unwrap ( ) . get ( ) ;
48+ default_parallelism_approx
49+ }
50+
51+ /// Open a website using the default browser
52+ ///
53+ /// # Arguments
54+ ///
55+ /// * `website` - The website that needs to be opened
56+ ///
57+ /// # Returns
58+ ///
59+ /// * `Ok(())` - If the website was opened successfully
60+ /// * `Err(String)` - If the website could not be opened
2061#[ tauri:: command]
2162fn open_website ( website : & str ) -> Result < ( ) , String > {
2263 match open:: that ( website) {
@@ -25,15 +66,61 @@ fn open_website(website: &str) -> Result<(), String> {
2566 }
2667}
2768
69+ /// Cancel a port scan
70+ ///
71+ /// # Arguments
72+ ///
73+ /// * `state` - The shared state that contains the cancellation token
74+ ///
75+ /// # Returns
76+ ///
77+ /// * `Ok(())` - If the cancellation token was set successfully
78+ /// * `Err(String)` - If the cancellation token could not be set
79+ #[ tauri:: command]
80+ async fn cancel_scan ( state : tauri:: State < ' _ , SharedState > ) -> Result < ( ) , String > {
81+ if !state. is_scanning . load ( Ordering :: SeqCst ) {
82+ return Err ( String :: from ( "No scan is currently running" ) ) ;
83+ }
84+
85+ state. cancellation_token . store ( true , Ordering :: SeqCst ) ;
86+ state. is_scanning . store ( false , Ordering :: SeqCst ) ;
87+
88+ Ok ( ( ) )
89+ }
90+
91+ /// Scan a range of ports for a specified host
92+ ///
93+ /// # Arguments
94+ ///
95+ /// * `state` - The shared state that contains the cancellation token
96+ /// * `address` - The host that needs to be scanned
97+ /// * `start_port` - The initial port that needs to be scanned
98+ /// * `end_port` - The final port that needs to be scanned
99+ /// * `timeout` - The connection timeout (in milliseconds) before a port is marked as closed
100+ /// * `threads` - The number of threads that should be used to scan the ports
101+ /// * `sort` - Whether the results should be sorted by port number
102+ ///
103+ /// # Returns
104+ ///
105+ /// * `Ok(Vec<ScanResult>)` - If the scan was successful
106+ /// * `Err(String)` - If the scan was unsuccessful
28107#[ tauri:: command]
29108async fn scan_port_range (
109+ state : tauri:: State < ' _ , SharedState > ,
30110 address : & str ,
31111 start_port : u16 ,
32112 end_port : u16 ,
33113 timeout : u64 ,
34114 threads : u32 ,
35115 sort : bool ,
36116) -> Result < Vec < ScanResult > , String > {
117+ if state. is_scanning . load ( Ordering :: SeqCst ) {
118+ return Err ( String :: from ( "A scan is already running" ) ) ;
119+ }
120+
121+ state. is_scanning . store ( true , Ordering :: SeqCst ) ;
122+ state. last_error . lock ( ) . unwrap ( ) . clear ( ) ;
123+
37124 let mut threads = threads;
38125 let all_results: Arc < Mutex < Vec < ScanResult > > > = Arc :: new ( Mutex :: new ( vec ! [ ] ) ) ;
39126
@@ -57,8 +144,24 @@ async fn scan_port_range(
57144 let local_host = String :: from ( address) ;
58145
59146 let all_results = Arc :: clone ( & all_results) ;
147+ let cancellation_token = Arc :: clone ( & state. cancellation_token ) ;
148+ let last_error = Arc :: clone ( & state. last_error ) ;
149+
60150 let handle = thread:: spawn ( move || {
61- let res = scan_tcp_range ( & local_host, local_start, local_end, timeout) ;
151+ let res = scan_tcp_range (
152+ & local_host,
153+ local_start,
154+ local_end,
155+ timeout,
156+ cancellation_token,
157+ ) ;
158+
159+ let res = res. unwrap_or_else ( |e| {
160+ let mut last_error = last_error. lock ( ) . unwrap ( ) ;
161+ * last_error = e. to_string ( ) ;
162+
163+ vec ! [ ]
164+ } ) ;
62165
63166 let mut results = all_results. lock ( ) . unwrap ( ) ;
64167 for l in res {
@@ -96,7 +199,16 @@ async fn scan_port_range(
96199 handle. join ( ) . unwrap ( ) ;
97200 }
98201 } else {
99- let res = scan_tcp_range ( address, start_port, end_port, timeout) ;
202+ let cancellation_token = Arc :: clone ( & state. cancellation_token ) ;
203+
204+ let res = scan_tcp_range ( address, start_port, end_port, timeout, cancellation_token) ;
205+ let res = match res {
206+ Ok ( res) => res,
207+ Err ( e) => {
208+ return Err ( e. to_string ( ) ) ;
209+ }
210+ } ;
211+
100212 let mut all = all_results. lock ( ) . unwrap ( ) ;
101213 for l in res {
102214 all. push ( l) ;
@@ -110,6 +222,16 @@ async fn scan_port_range(
110222 res. sort_by ( |a, b| a. port . cmp ( & b. port ) ) ;
111223 }
112224
225+ state. is_scanning . store ( false , Ordering :: SeqCst ) ;
226+ state. cancellation_token . store ( false , Ordering :: SeqCst ) ;
227+
228+ if res. is_empty ( ) {
229+ let last_error = state. last_error . lock ( ) . unwrap ( ) ;
230+ if !last_error. is_empty ( ) {
231+ return Err ( last_error. deref ( ) . to_string ( ) ) ;
232+ }
233+ }
234+
113235 Ok ( res. deref ( ) . to_vec ( ) )
114236}
115237
@@ -121,17 +243,30 @@ async fn scan_port_range(
121243/// * `start` - The initial port that needs to be scanned
122244/// * `end` - The final port that needs to be scanned
123245/// * `timeout` - The connection timeout (in milliseconds) before a port is marked as closed
124- /// * `no_closed ` - Sets whether closed ports should be added to the return list or not
246+ /// * `cancellation_token ` - A cancellation token that can be used to cancel the scan
125247fn scan_tcp_range (
126248 host : & str ,
127249 start : u16 ,
128250 end : u16 ,
129251 timeout : u64 ,
130- ) -> Vec < ScanResult > {
252+ cancellation_token : Arc < AtomicBool > ,
253+ ) -> Result < Vec < ScanResult > , String > {
131254 let mut scan_result = vec ! [ ] ;
132255
133256 for n in start..=end {
134- let mut address = format ! ( "{}:{}" , host, n) . to_socket_addrs ( ) . unwrap ( ) ;
257+ // Check the cancellation token and return if it's true
258+ if cancellation_token. load ( Ordering :: Relaxed ) {
259+ return Ok ( scan_result) ;
260+ }
261+
262+ let address = format ! ( "{}:{}" , host, n) . to_socket_addrs ( ) ;
263+ let mut address = match address {
264+ Ok ( res) => res,
265+ Err ( e) => {
266+ return Err ( e. to_string ( ) ) ;
267+ }
268+ } ;
269+
135270 let socket_address = address. next ( ) . unwrap ( ) ;
136271 let ip_addr: IpAddr = socket_address. ip ( ) ;
137272 let host_name = ip_addr. to_string ( ) ;
@@ -146,7 +281,7 @@ fn scan_tcp_range(
146281 match res {
147282 Ok ( _) => { }
148283 Err ( e) => {
149- panic ! ( "Unable to shut down TcpStream: {}" , e)
284+ println ! ( "Unable to shut down TcpStream: {}" , e)
150285 }
151286 }
152287 } else {
@@ -155,5 +290,5 @@ fn scan_tcp_range(
155290 }
156291 }
157292
158- scan_result
293+ Ok ( scan_result)
159294}
0 commit comments