diff --git a/README.md b/README.md index d7191597..114a2e8f 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![Version](https://img.shields.io/crates/v/sniffnet.svg)](https://crates.io/crates/sniffnet) ![License](https://img.shields.io/crates/l/sniffnet.svg) -Application to analyze and filter network traffic. +Multithreaded application to analyze and filter network traffic. Several command line options are available to select the network adapter to inspect, to set a desired reports update frequency and to specify filters on the observed traffic. diff --git a/src/main.rs b/src/main.rs index 772fe521..804959ab 100644 --- a/src/main.rs +++ b/src/main.rs @@ -60,7 +60,7 @@ fn main() { let lowest_port = args.lowest_port; let highest_port = args.highest_port; let min_packets = args.minimum_packets; - let interval = args.interval; + let interval: u64 = args.interval; let network_layer: String = args.network_layer_filter.to_ascii_lowercase(); let network_layer_2: String = network_layer.clone(); let transport_layer: String = args.transport_layer_filter.to_ascii_lowercase(); @@ -124,7 +124,11 @@ fn main() { println!("{}{}{}", "\tThe folder '".cyan().italic(), output_folder.cyan().italic(), "' will be periodically updated\r".cyan().italic()); println!("{}{}{}{}", "\r\n\tPress the key\r".cyan().bold(), "\r\n\t\t- 'p' to pause\r".yellow().bold(), - "\r\n\t\t- 's' to stop\r".red().bold(), "\r\n\tthe application\n\n\r".cyan().bold()); + "\r\n\t\t- 's' to stop\r".red().bold(), "\r\n\tthe application\n\r".cyan().bold()); + print!("\t{}{}{}\r", "Updating reports every ".cyan().blink().italic(), + interval.to_string().cyan().blink().italic(), + " seconds...".cyan().blink().italic()); + io::stdout().flush().unwrap(); // to kill the main thread even if a secondary thread panics let orig_hook = panic::take_hook(); @@ -151,7 +155,7 @@ fn main() { }); // Main thread: updates application status - set_status_by_key(status_pair2); + set_status_by_key(status_pair2, interval); // Wait for the final report update, to not kill the application while the report is being written thread_write_report.join().expect("Thread in charge of writing report panicked!\r\n"); @@ -268,7 +272,7 @@ fn is_valid_transport_layer(transport_layer: String) -> bool { /// # Arguments /// /// * `status_pair` - Shared variable to change the application current status. -fn set_status_by_key(status_pair: Arc<(Mutex, Condvar)>) { +fn set_status_by_key(status_pair: Arc<(Mutex, Condvar)>, interval: u64) { let _raw = RawScreen::into_raw_mode(); let mut reader = input().read_sync(); @@ -279,6 +283,8 @@ fn set_status_by_key(status_pair: Arc<(Mutex, Condvar)>) { match event { InputEvent::Keyboard(KeyEvent::Char('p')) => { if *status == Status::Running { + print!(" \r"); + io::stdout().flush().unwrap(); println!("\t{}", "Sniffnet paused...\r".yellow().bold()); print!("\t{}", "Press 'r' to resume\r".yellow().blink().bold()); io::stdout().flush().unwrap(); @@ -287,17 +293,21 @@ fn set_status_by_key(status_pair: Arc<(Mutex, Condvar)>) { } InputEvent::Keyboard(KeyEvent::Char('r')) => { if *status == Status::Pause { - print!(" \r"); + print!(" \r"); io::stdout().flush().unwrap(); println!("\t{}", "Sniffnet resumed\r".green().bold()); + print!("\t{}{}{}\r", "Updating reports every ".cyan().blink().italic(), + interval.to_string().cyan().blink().italic(), + " seconds...".cyan().blink().italic()); + io::stdout().flush().unwrap(); *status = Status::Running; cvar.notify_all(); } } InputEvent::Keyboard(KeyEvent::Char('s')) => { - print!(" \r"); + print!(" \r"); io::stdout().flush().unwrap(); - println!("\r\t{}", "Sniffnet stopped... waiting for the last report update\n\n\r".red().bold()); + println!("\r\t{}", "Sniffnet stopped... waiting for the last reports update\n\n\r".red().bold()); *status = Status::Stop; cvar.notify_all(); return; diff --git a/src/thread_write_report_functions.rs b/src/thread_write_report_functions.rs index abfdde7b..f6d0856c 100644 --- a/src/thread_write_report_functions.rs +++ b/src/thread_write_report_functions.rs @@ -22,9 +22,6 @@ use crate::{address_port_pair::AddressPortPair, AppProtocol, info_address_port_p use std::time::{Instant}; use plotters::style::full_palette::{GREEN_600, GREY}; -#[cfg(feature = "draw_graph")] -use charts::{Chart, ScaleLinear, MarkerType, LineSeriesView, AreaSeriesView, AxisPosition, Color}; - /// The calling thread enters in a loop in which it waits for ```interval``` seconds and then re-write /// from scratch the output report file, with updated values. @@ -73,8 +70,7 @@ pub fn sleep_and_write_report_loop(lowest_port: u16, highest_port: u16, interval let path_graph = &*format!("{}/bandwidth.svg", output_folder); - let mut times_report_updated: u128 = 0; - let mut last_report_updated_console: u128 = 0; + let mut tot_intervals: u128 = 0; let time_origin = Local::now(); let first_timestamp = time_origin.format("%d/%m/%Y %H:%M:%S").to_string(); #[cfg(feature = "elapsed_time")] @@ -97,10 +93,10 @@ pub fn sleep_and_write_report_loop(lowest_port: u16, highest_port: u16, interval let mut max_received_packets_second: i128 = 0; loop { + // sleep interval seconds thread::sleep(Duration::from_secs(interval)); - times_report_updated += 1; - let mut output = BufWriter::new(File::create(format!("{}/report.txt", output_folder.clone())).expect("Error creating output file\n\r")); + tot_intervals += 1; let info_traffic = info_traffic_mutex.lock().expect("Error acquiring mutex\n\r"); @@ -113,33 +109,46 @@ pub fn sleep_and_write_report_loop(lowest_port: u16, highest_port: u16, interval #[cfg(feature = "elapsed_time")] let start = Instant::now(); - write_report_file_header(output.get_mut().try_clone().expect("Error cloning file handler\n\r"), - device_name.clone(), first_timestamp.clone(), - times_report_updated, lowest_port, highest_port, min_packets, - network_layer.clone(), transport_layer.clone(), app_layer.clone(), - info_traffic.map.len(), all_packets, - tot_received_packets+tot_sent_packets, info_traffic.app_protocols.clone()); + if *status_pair.0.lock().expect("Error acquiring mutex\n\r") != Status::Pause { // write textual report - #[cfg(feature = "elapsed_time")] - let time_header = start.elapsed().as_millis(); + let mut output = BufWriter::new(File::create(format!("{}/report.txt", output_folder.clone())).expect("Error creating output file\n\r")); - let mut sorted_vec: Vec<(&AddressPortPair, &InfoAddressPortPair)> = info_traffic.map.iter().collect(); - sorted_vec.sort_by(|&(_, a), &(_, b)| - b.transmitted_packets.cmp(&a.transmitted_packets)); + write_report_file_header(output.get_mut().try_clone().expect("Error cloning file handler\n\r"), + device_name.clone(), first_timestamp.clone(), + lowest_port, highest_port, min_packets, + network_layer.clone(), transport_layer.clone(), app_layer.clone(), + info_traffic.map.len(), all_packets, + tot_received_packets+tot_sent_packets, info_traffic.app_protocols.clone()); - #[cfg(feature = "elapsed_time")] - let time_header_sort = start.elapsed().as_millis(); + #[cfg(feature = "elapsed_time")] + let time_header = start.elapsed().as_millis(); - for (key, val) in sorted_vec.iter() { - if val.transmitted_packets >= min_packets { - write!(output, "{}\n{}\n\n", key, val).expect("Error writing output file\n\r"); + let mut sorted_vec: Vec<(&AddressPortPair, &InfoAddressPortPair)> = info_traffic.map.iter().collect(); + sorted_vec.sort_by(|&(_, a), &(_, b)| + b.transmitted_packets.cmp(&a.transmitted_packets)); + + #[cfg(feature = "elapsed_time")] + let time_header_sort = start.elapsed().as_millis(); + + for (key, val) in sorted_vec.iter() { + if val.transmitted_packets >= min_packets { + write!(output, "{}\n{}\n\n", key, val).expect("Error writing output file\n\r"); + } } + + output.flush().expect("Error writing output file\n\r"); + + #[cfg(feature = "elapsed_time")] + let time_header_sort_print = start.elapsed().as_millis(); + } drop(info_traffic); // graphs + #[cfg(feature = "elapsed_time")] + let start_drawing = Instant::now(); let root_area = SVGBackend::new(path_graph, (1250, 700)).into_drawing_area(); root_area.fill(&GREY).expect("Error drawing graph"); @@ -153,12 +162,12 @@ pub fn sleep_and_write_report_loop(lowest_port: u16, highest_port: u16, interval // bits graph - sent_bits_graph.push((interval as u128 * times_report_updated,(-1*(tot_sent_bytes*8) as i128 + tot_sent_bits_prev)/interval as i128 )); + sent_bits_graph.push((interval as u128 * tot_intervals,(-1*(tot_sent_bytes*8) as i128 + tot_sent_bits_prev)/interval as i128 )); if -1*(tot_sent_bytes*8) as i128 + tot_sent_bits_prev < min_sent_bits_second { min_sent_bits_second = -1*(tot_sent_bytes*8) as i128 + tot_sent_bits_prev; } tot_sent_bits_prev = (tot_sent_bytes * 8) as i128; - received_bits_graph.push((interval as u128 * times_report_updated, (tot_received_bytes as i128 * 8 - tot_received_bits_prev)/interval as i128 )); + received_bits_graph.push((interval as u128 * tot_intervals, (tot_received_bytes as i128 * 8 - tot_received_bits_prev)/interval as i128 )); if tot_received_bytes as i128 * 8 - tot_received_bits_prev > max_received_bits_second { max_received_bits_second = tot_received_bytes as i128 * 8 - tot_received_bits_prev ; } @@ -167,7 +176,7 @@ pub fn sleep_and_write_report_loop(lowest_port: u16, highest_port: u16, interval .set_label_area_size(LabelAreaPosition::Left, 60) .set_label_area_size(LabelAreaPosition::Bottom, 60) .caption("Bit traffic per second", ("sans-serif", 30)) - .build_cartesian_2d(0..interval as u128 * times_report_updated, min_sent_bits_second/interval as i128..max_received_bits_second/interval as i128) + .build_cartesian_2d(0..interval as u128 * tot_intervals, min_sent_bits_second/interval as i128..max_received_bits_second/interval as i128) .expect("Error drawing graph"); chart_bits.configure_mesh() .y_desc("bit/s") @@ -205,12 +214,12 @@ pub fn sleep_and_write_report_loop(lowest_port: u16, highest_port: u16, interval // packets graph - sent_packets_graph.push((interval as u128 * times_report_updated, (-1*(tot_sent_packets as i128) + tot_sent_packets_prev)/interval as i128 )); + sent_packets_graph.push((interval as u128 * tot_intervals, (-1*(tot_sent_packets as i128) + tot_sent_packets_prev)/interval as i128 )); if -1*(tot_sent_packets as i128) + tot_sent_packets_prev < min_sent_packets_second { min_sent_packets_second = -1*(tot_sent_packets as i128) + tot_sent_packets_prev; } tot_sent_packets_prev = tot_sent_packets as i128; - received_packets_graph.push((interval as u128 * times_report_updated, (tot_received_packets as i128 - tot_received_packets_prev)/interval as i128 )); + received_packets_graph.push((interval as u128 * tot_intervals, (tot_received_packets as i128 - tot_received_packets_prev)/interval as i128 )); if tot_received_packets as i128 - tot_received_packets_prev > max_received_packets_second { max_received_packets_second = tot_received_packets as i128 - tot_received_packets_prev; } @@ -219,7 +228,7 @@ pub fn sleep_and_write_report_loop(lowest_port: u16, highest_port: u16, interval .set_label_area_size(LabelAreaPosition::Left, 60) .set_label_area_size(LabelAreaPosition::Bottom, 60) .caption("Packet traffic per second", ("sans-serif", 30)) - .build_cartesian_2d(0..interval as u128*times_report_updated, min_sent_packets_second/interval as i128..max_received_packets_second/interval as i128) + .build_cartesian_2d(0..interval as u128*tot_intervals, min_sent_packets_second/interval as i128..max_received_packets_second/interval as i128) .expect("Error drawing graph"); chart_packets.configure_mesh() .y_desc("packet/s") @@ -249,6 +258,11 @@ pub fn sleep_and_write_report_loop(lowest_port: u16, highest_port: u16, interval root_area.present().expect("Error drawing graph"); + #[cfg(feature = "elapsed_time")] + let time_drawing = start_drawing.elapsed().as_millis(); + #[cfg(feature = "elapsed_time")] + println!("Drawing time: {} ms", time_drawing); + #[cfg(feature = "elapsed_time")] { let time_header_sort_print = start.elapsed().as_millis(); @@ -264,33 +278,17 @@ pub fn sleep_and_write_report_loop(lowest_port: u16, highest_port: u16, interval time_header_sort_print-time_header_sort, time_header_sort_print).expect("Error writing output file\n\r"); - if times_report_updated >= 10 { + if tot_intervals >= 10 { write!(output, "\t\tTimings (average on the last 10 report writes):\n\ \t\t\tTot time mutex held: {}ms\n", last_10_write_times.iter().sum::()/10).expect("Error writing output file\n\r"); last_10_write_times.remove(0); } - + output.flush().expect("Error writing output file\n\r"); } - output.flush().expect("Error writing output file\n\r"); - - - let status = status_pair.0.lock().expect("Error acquiring mutex\n\r"); - if *status == Status::Running { - if times_report_updated - last_report_updated_console != 1 { - println!("{}{}{}{}\r", "\tReport updated (".cyan().italic(), - times_report_updated.to_string().cyan().italic(), ")".cyan().italic(), - " - reports have also been updated during pause".cyan().italic()); - } - else { - println!("{}{}{}\r", "\tReports updated (".cyan().italic(), - times_report_updated.to_string().cyan().italic(), ")".cyan().italic()); - } - last_report_updated_console = times_report_updated; - } - else if *status == Status::Stop { - println!("{}{}{}\r", "\tThe final report is available in the file '".cyan().italic(), + if *status_pair.0.lock().expect("Error acquiring mutex\n\r") == Status::Stop { + println!("{}{}{}\r", "\tThe final reports are available in the folder '".cyan().italic(), output_folder.clone().cyan().bold(), "'\n\n\r".cyan().italic()); return; } @@ -500,8 +498,6 @@ fn get_app_count_string(app_count: HashMap, tot_packets: u128 /// /// * `first_timestamp` - A not formatted String representing the initial timestamp of the sniffing process. /// -/// * `times_report_updated` - An integer representing the amount of times the report has been updated. -/// /// * `lowest_port` - The lowest port number to be considered in the report. Specified by the user /// through the ```-l``` option. /// @@ -547,7 +543,7 @@ fn get_app_count_string(app_count: HashMap, tot_packets: u128 /// <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> /// ``` fn write_report_file_header(mut output: File, device_name: String, first_timestamp: String, - times_report_updated: u128, lowest_port: u16, highest_port: u16, + lowest_port: u16, highest_port: u16, min_packets: u128, network_layer: String, transport_layer: String, app_layer: AppProtocol, num_pairs: usize, num_sniffed_packets: u128, num_filtered_packets: u128, app_count: HashMap) { @@ -556,7 +552,6 @@ fn write_report_file_header(mut output: File, device_name: String, first_timesta let adapter_string = format!("<><>\t\tPackets are sniffed from adapter '{}'\n", device_name); let first_timestamp_string = format!("<><>\t\t\tReport start time: {}\n", first_timestamp); let last_timestamp_string = format!("<><>\t\t\tReport last update: {}\n", Local::now().format("%d/%m/%Y %H:%M:%S").to_string()); - let number_updates_string = format!("<><>\t\t\tNumber of times report was updated: {}\n", times_report_updated.separate_with_underscores()); let ports_string = get_ports_string(lowest_port,highest_port); let network_layer_string = get_network_layer_string(network_layer); let transport_layer_string = get_transport_layer_string(transport_layer); @@ -572,7 +567,6 @@ fn write_report_file_header(mut output: File, device_name: String, first_timesta write!(output, "<><>\t\tReport updates info\n").expect("Error writing output file\n"); write!(output, "{}", first_timestamp_string).expect("Error writing output file\n"); write!(output, "{}", last_timestamp_string).expect("Error writing output file\n"); - write!(output, "{}", number_updates_string).expect("Error writing output file\n"); write!(output, "<><>\n").expect("Error writing output file\n"); write!(output, "<><>\t\tFilters\n").expect("Error writing output file\n");