nasoone_lib/lib.rs
1//! Nasoone-lib is a library for the NASOONE project.
2//!
3//! It provides an easy way for analyzing network traffic using pcap.
4//!
5//! The output is a CSV file with the following columns separated by a semicolon:
6//! - Source IP
7//! - Source port
8//! - Destination IP
9//! - Destination port
10//! - List of observed protocols
11//! - Timestamp of the first packet
12//! - Timestamp of the last packet
13//! - Number of bytes
14//! - Number of packets
15//!
16//! Example usage:
17//! ```
18//! use std::thread::sleep;
19//! use std::time::Duration;
20//! use nasoone_lib::Nasoone;
21//!
22//! let mut naso = Nasoone::new();
23//! // set the capture device from a physical interface
24//! naso.set_capture_device("en0").unwrap();
25//! naso.set_output("./report.csv").unwrap();
26//! // set the timeout between report updates (in seconds)
27//! naso.set_timeout(1).unwrap();
28//! // start the capture (non-blocking)
29//! naso.start().unwrap();
30//! sleep(Duration::from_secs(10));
31//! // pause the capture
32//! naso.pause().unwrap();
33//! sleep(Duration::from_secs(2));
34//! // resume the capture
35//! naso.resume().unwrap();
36//! sleep(Duration::from_secs(10));
37//! // stop the capture and get the stats
38//! let stats = naso.stop().unwrap();
39//! println!("{:?}", stats);
40//! ```
41
42mod filter;
43mod parser;
44mod producer;
45mod writer;
46
47pub use crate::filter::Filter;
48use crate::parser::parser_task;
49use crate::producer::producer_task;
50use crate::writer::writer_task;
51use crossbeam_channel::Sender;
52use pcap::{Active, Capture, Device, Inactive, Offline, Stat};
53use std::collections::HashSet;
54use std::error::Error;
55use std::fmt::{Debug, Display, Formatter};
56use std::fs::File;
57use std::net::IpAddr;
58use std::path::Path;
59use std::sync::{Arc, Mutex};
60use std::thread;
61
62enum Command {
63 Stop,
64 Pause,
65 Resume,
66}
67
68#[derive(Hash, Eq, PartialEq, Debug)]
69struct ReportKey {
70 source_ip: IpAddr,
71 source_port: u16,
72 destination_ip: IpAddr,
73 destination_port: u16,
74}
75
76impl Display for ReportKey {
77 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
78 let source_ip = self.source_ip;
79 let source_port = self.source_port.clone().to_string();
80 let dest_ip = self.destination_ip;
81 let dest_port = self.destination_port;
82 write!(
83 f,
84 "{}; {}; {}; {}",
85 source_ip, source_port, dest_ip, dest_port
86 )
87 }
88}
89
90#[derive(Debug)]
91struct ReportValue {
92 first_timestamp_ms: u64,
93 last_timestamp_ms: u64,
94 bytes: u64,
95 packets_count: usize,
96 protocols: HashSet<u8>,
97}
98
99impl Display for ReportValue {
100 fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
101 let protocols = self
102 .protocols
103 .iter()
104 .map(|p| match p {
105 6 => "TCP",
106 17 => "UDP",
107 _ => "",
108 })
109 .collect::<Vec<_>>()
110 .join(", ");
111 write!(
112 f,
113 "[{}]; {}; {}; {}; {}",
114 protocols,
115 self.first_timestamp_ms,
116 self.last_timestamp_ms,
117 self.bytes,
118 self.packets_count
119 )
120 }
121}
122
123#[derive(PartialEq, Eq, Debug, Clone)]
124/// Represents in which state the capture is.
125pub enum NasooneState {
126 /// The capture is not started and can be configured
127 Initial,
128 /// The capture is started and can be paused or stopped but not configured.
129 Running,
130 /// The capture is paused and can be resumed or stopped.
131 Paused,
132 /// The capture has finished, the user can retrieve stats calling stop.
133 Finished,
134 /// The capture is stopped and can only return to Initial.
135 Stopped,
136}
137
138#[derive(Debug)]
139/// Represents the pcap statistics about a capture (from <https://docs.rs/pcap/latest/pcap/index.html>.)
140pub struct NasooneStats {
141 /// Number of packets received
142 pub received: u32,
143 /// Number of packets dropped because there was no room in the operating system's buffer when
144 /// they arrived, because packets weren't being read fast enough
145 pub dropped: u32,
146 /// Number of packets dropped by the network interface or its driver
147 pub if_dropped: u32,
148}
149
150impl From<Stat> for NasooneStats {
151 fn from(stat: Stat) -> Self {
152 Self {
153 received: stat.received,
154 dropped: stat.dropped,
155 if_dropped: stat.if_dropped,
156 }
157 }
158}
159
160/// Abstraction of capture types before starting
161enum InactiveNasooneCapture {
162 /// The capture is performed on a pcap file.
163 FromFile(Capture<Offline>),
164 /// The capture is performed on a live network interface.
165 FromDevice(Capture<Inactive>),
166}
167
168/// Abstraction of the pcap capture.
169enum NasooneCapture {
170 /// The capture is performed on a pcap file.
171 FromFile(Capture<Offline>),
172 /// The capture is performed on a live network interface.
173 FromDevice(Capture<Active>),
174}
175
176impl NasooneCapture {
177 fn next(&mut self) -> Result<pcap::Packet, pcap::Error> {
178 match self {
179 NasooneCapture::FromFile(capture) => capture.next(),
180 NasooneCapture::FromDevice(capture) => capture.next(),
181 }
182 }
183}
184
185#[derive(Debug)]
186/// A network interface that can be used for capturing.
187pub struct NetworkInterface {
188 /// The name of the network interface.
189 name: String,
190 /// The optional friendly description of the network interface.
191 desc: Option<String>,
192}
193
194impl Display for NetworkInterface {
195 fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
196 let name = self.desc.clone().unwrap_or_else(|| self.name.clone());
197 write!(f, "{}", name)
198 }
199}
200
201impl NetworkInterface {
202 fn new(name: String, desc: Option<String>) -> NetworkInterface {
203 NetworkInterface { name, desc }
204 }
205 /// Returns the name of the network interface.
206 pub fn get_name(&self) -> &str {
207 &self.name
208 }
209 /// Returns the optional friendly description of the network interface.
210 pub fn get_desc(&self) -> Option<&str> {
211 self.desc.as_deref()
212 }
213}
214
215#[derive(Debug)]
216/// An error that can occur while using the library.
217pub enum NasooneError {
218 /// An error from the underlying pcap library.
219 PcapError(pcap::Error),
220 /// Invalid Nasoone state.
221 InvalidState(String),
222 /// The specified output path is not valid.
223 InvalidOutputPath(String),
224 /// The capture type is not set.
225 UnsetCapture,
226 /// The capture output file is not set.
227 UnsetOutput,
228 /// The timeout is not valid.
229 InvalidTimeout,
230 /// The capture has finished by itself.
231 CaptureOver,
232}
233
234impl Display for NasooneError {
235 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
236 match self {
237 NasooneError::PcapError(e) => write!(f, "Pcap error: {}", e),
238 NasooneError::InvalidState(s) => write!(f, "Invalid state: {}", s),
239 NasooneError::InvalidOutputPath(s) => write!(f, "Invalid output path: {}", s),
240 NasooneError::UnsetCapture => write!(f, "Capture is not set"),
241 NasooneError::UnsetOutput => write!(f, "Output is not set"),
242 NasooneError::InvalidTimeout => write!(f, "Invalid timeout"),
243 NasooneError::CaptureOver => write!(f, "Capture is over"),
244 }
245 }
246}
247
248impl Error for NasooneError {}
249
250#[derive(Clone, Eq, PartialEq)]
251struct PacketData {
252 timestamp_ms: u64,
253 data: Vec<u8>,
254 bytes: u32,
255}
256
257/// A struct for capturing network traffic.
258pub struct Nasoone {
259 /// The state of the capture.
260 state: NasooneState,
261 /// The channel sender for sending the state change to the producer thread.
262 tx_main_prod: Option<Sender<Command>>,
263 /// The periodical timeout after which the output file is updated.
264 timeout: u32,
265 /// The pcap capture.
266 capture: Option<InactiveNasooneCapture>,
267 /// The temprorary store for the BPF filter string
268 filter: String,
269 /// The path to the output file.
270 output: Option<File>,
271 /// Producer thread handle.
272 producer_handle: Option<thread::JoinHandle<Result<Stat, pcap::Error>>>,
273 /// Parser threads handles.
274 parser_handles: Vec<thread::JoinHandle<()>>,
275 /// Writer thread handle.
276 writer_handle: Option<thread::JoinHandle<()>>,
277 /// the amount of packets captured in the current session.
278 total_packets: Arc<Mutex<usize>>,
279}
280
281impl Nasoone {
282 pub fn new() -> Self {
283 Self {
284 state: NasooneState::Initial,
285 tx_main_prod: None,
286 timeout: 1,
287 capture: None,
288 output: None,
289 filter: String::new(),
290 producer_handle: None,
291 parser_handles: Vec::new(),
292 writer_handle: None,
293 total_packets: Arc::new(Mutex::new(0)),
294 }
295 }
296
297 /// Set the capture from a network interface.
298 /// It returns an error in the following cases:
299 /// - Nasoone is not in the Initial state
300 /// - the interface name is not valid
301 /// - the capture cannot be activated
302 ///
303 /// # Arguments
304 ///
305 /// * `device` - A string slice that holds the name of the interface to capture from.
306 ///
307 /// # Examples
308 ///
309 /// Create a nasoone instance and set the capture from the interface "en0"
310 /// ```
311 /// use nasoone_lib::Nasoone;
312 /// let mut nasoone = Nasoone::new();
313 /// let _ = nasoone.set_capture_device("en0");
314 /// ```
315 pub fn set_capture_device(&mut self, device: &str) -> Result<(), NasooneError> {
316 match self.state {
317 NasooneState::Initial => {
318 let capture = Capture::from_device(device).map_err(NasooneError::PcapError)?;
319 self.capture = Some(InactiveNasooneCapture::FromDevice(capture));
320 Ok(())
321 }
322 _ => Err(NasooneError::InvalidState(
323 "Nasoone is not in initial state".to_string(),
324 )),
325 }
326 }
327
328 /// Set the capture from a pcap file.
329 ///
330 /// It returns an error in the following cases:
331 /// - Nasoone is not in the Initial state
332 /// - the capture file is not valid
333 ///
334 /// # Arguments
335 ///
336 /// * `file` - A string slice with the file path.
337 ///
338 /// # Examples
339 ///
340 /// Create a nasoone instance and set the capture from the file "capture.pcap":
341 /// ```
342 /// use nasoone_lib::Nasoone;
343 /// let mut nasoone = Nasoone::new();
344 /// let _ = nasoone.set_capture_file("./capture.pcap");
345 /// ```
346 pub fn set_capture_file(&mut self, file: &str) -> Result<(), NasooneError> {
347 match self.state {
348 NasooneState::Initial => {
349 let capture = Capture::from_file(file).map_err(NasooneError::PcapError)?;
350 self.capture = Some(InactiveNasooneCapture::FromFile(capture));
351 Ok(())
352 }
353 _ => Err(NasooneError::InvalidState(
354 "Nasoone is not in initial state".to_string(),
355 )),
356 }
357 }
358
359 /// Set the timeout in seconds after which the output file is updated.
360 ///
361 /// The timeout must be greater than 0, it specifies the periodical update of the output file.
362 /// So, if the timeout is 1, the output file is updated every second.
363 ///
364 /// It returns an error in the following cases:
365 /// - Nasoone is not in the Initial state
366 /// - the timeout is 0
367 ///
368 /// # Arguments
369 ///
370 /// * `timeout` - The timeout in seconds.
371 ///
372 /// # Examples
373 ///
374 /// Create a nasoone instance and set the timeout to 1 second:
375 /// ```
376 /// use nasoone_lib::Nasoone;
377 /// let mut nasoone = Nasoone::new();
378 /// nasoone.set_timeout(1).expect("");
379 /// ```
380 pub fn set_timeout(&mut self, timeout: u32) -> Result<(), NasooneError> {
381 match self.state {
382 NasooneState::Initial => {
383 if timeout == 0 {
384 Err(NasooneError::InvalidTimeout)
385 } else {
386 self.timeout = timeout;
387 Ok(())
388 }
389 }
390 _ => Err(NasooneError::InvalidState(
391 "Nasoone is not in initial state".to_string(),
392 )),
393 }
394 }
395 /// Set the raw filter for the capture.
396 /// The raw filter is a [BPF](https://biot.com/capstats/bpf.html) string that is passed to pcap.
397 /// Multiple calls to this function or to set_filter will overwrite the previous filter.
398 ///
399 /// It returns an error in the following cases:
400 /// - Nasoone is not in the Initial state
401 /// - The capture is not set
402 /// - the filter is not valid
403 ///
404 /// # Arguments
405 ///
406 /// * `filter` - The filter string in BPF syntax.
407 ///
408 /// # Examples
409 ///
410 /// create a nasoone instance and set filter to accept only packets with source port 80 to 88:
411 /// ```
412 /// use nasoone_lib::Nasoone;
413 /// let mut nasoone = Nasoone::new();
414 /// nasoone.set_capture_device("en0").expect("");
415 /// nasoone.set_filter("src portrange 80-88").expect("");
416 /// ```
417 pub fn set_raw_filter(&mut self, filter: &str) -> Result<(), NasooneError> {
418 match self.state {
419 NasooneState::Initial => {
420 self.filter = filter.to_string();
421 Ok(())
422 }
423 _ => Err(NasooneError::InvalidState(
424 "Filters can be set only in initial state".to_string(),
425 )),
426 }
427 }
428 /// Set the filter for the capture.
429 /// The filter is a Filter struct that will be transformed in BPF and passed to pcap.
430 /// Multiple calls to this or set_raw_filter function will overwrite the previous filter.
431 ///
432 /// It returns an error in the following cases:
433 /// - Nasoone is not in the Initial state
434 /// - The capture is not set
435 ///
436 /// # Arguments
437 ///
438 /// * `filter` - The filter struct already setted up.
439 ///
440 /// # Examples
441 ///
442 /// create a nasoone instance and set filter to accept only tcp traffic
443 /// ```
444 /// use nasoone_lib::{Nasoone, Filter};
445 /// let filter = Filter::new().set_tcp_only();
446 /// let mut nasoone = Nasoone::new();
447 /// nasoone.set_capture_device("en0").unwrap();
448 /// nasoone.set_filter(&filter).unwrap();
449 /// ```
450 pub fn set_filter(&mut self, filter: &Filter) -> Result<(), NasooneError> {
451 match self.state {
452 NasooneState::Initial => {
453 self.filter = filter.to_string();
454 Ok(())
455 }
456 _ => Err(NasooneError::InvalidState(
457 "Filters can be set only in initial state".to_string(),
458 )),
459 }
460 }
461 /// Set the output file.
462 /// The output file is a CSV textual report of the capture.
463 ///
464 /// It returns an error in the following cases:
465 /// - Nasoone is not in the Initial state
466 /// - the file already exists
467 /// - the target directory does not exist
468 /// - the file cannot be created
469 ///
470 /// # Arguments
471 ///
472 /// * `output_file` - The path of the output file.
473 ///
474 /// # Examples
475 ///
476 /// create a nasoone instance and set the output file to "./output.csv":
477 /// ```
478 /// use nasoone_lib::Nasoone;
479 /// let mut nasoone = Nasoone::new();
480 /// nasoone.set_output("./output.csv").expect("");
481 /// ```
482 pub fn set_output(&mut self, output_file: &str) -> Result<(), NasooneError> {
483 match self.state {
484 NasooneState::Initial => {
485 let path = Path::new(output_file);
486 if path.exists() {
487 return Err(NasooneError::InvalidOutputPath(
488 "Output file already exists".to_string(),
489 ));
490 }
491 if path.parent().is_some() && !path.parent().unwrap().exists() {
492 return Err(NasooneError::InvalidOutputPath(
493 "Target directory does not exist".to_string(),
494 ));
495 }
496 match File::create(path) {
497 Ok(f) => {
498 self.output = Some(f);
499 Ok(())
500 }
501 Err(_) => Err(NasooneError::InvalidOutputPath(
502 "Could not create output file".to_string(),
503 )),
504 }
505 }
506 _ => Err(NasooneError::InvalidState(
507 "Output can be set only in initial state".to_string(),
508 )),
509 }
510 }
511
512 /// Start analyzing the network traffic.
513 ///
514 /// It returns an error in the following cases:
515 /// - Nasoone is not in the Initial state
516 /// - the capture is not set
517 /// - the output is not set
518 ///
519 /// # Examples
520 ///
521 /// create a nasoone instance, set the capture and the output file and start the analysis:
522 /// ```
523 /// use nasoone_lib::Nasoone;
524 /// let mut nasoone = Nasoone::new();
525 /// nasoone.set_capture_device("en0").expect("");
526 /// nasoone.set_output("./output.csv").expect("");
527 /// nasoone.start().expect("");
528 /// ```
529 pub fn start(&mut self) -> Result<(), NasooneError> {
530 match self.state {
531 NasooneState::Initial => {
532 // Create all the threads and start the capture.
533
534 if self.capture.is_none() {
535 return Err(NasooneError::UnsetCapture);
536 }
537 if self.output.is_none() {
538 return Err(NasooneError::UnsetOutput);
539 }
540
541 let (tx_main_prod, rx_main_prod) = crossbeam_channel::unbounded();
542 let (tx_prod_parser, rx_prod_parser) = crossbeam_channel::unbounded();
543 let (tx_parser_writer, rx_parser_writer) = crossbeam_channel::unbounded();
544
545 let capture = match self.capture.take().unwrap() {
546 InactiveNasooneCapture::FromFile(mut c) => {
547 if !self.filter.is_empty() {
548 c.filter(self.filter.as_str(), true)
549 .map_err(NasooneError::PcapError)?;
550 }
551 NasooneCapture::FromFile(c)
552 }
553 InactiveNasooneCapture::FromDevice(c) => {
554 let mut activated_c = c
555 .promisc(true)
556 .immediate_mode(true)
557 .timeout(200)
558 .open()
559 .map_err(NasooneError::PcapError)?;
560 if !self.filter.is_empty() {
561 activated_c
562 .filter(&self.filter, true)
563 .map_err(NasooneError::PcapError)?;
564 }
565 if cfg!(target_os = "linux") {
566 activated_c =
567 activated_c.setnonblock().map_err(NasooneError::PcapError)?;
568 }
569 NasooneCapture::FromDevice(activated_c)
570 }
571 };
572 self.tx_main_prod = Some(tx_main_prod);
573 self.producer_handle = Some(thread::spawn(move || {
574 producer_task(capture, tx_prod_parser, rx_main_prod)
575 }));
576
577 let num_cpus = num_cpus::get();
578
579 for _ in 0..num_cpus {
580 let rx_prod_parser = rx_prod_parser.clone();
581 let tx_parser_writer = tx_parser_writer.clone();
582 let timeout = self.timeout;
583 let total_packets = self.total_packets.clone();
584 self.parser_handles.push(thread::spawn(move || {
585 parser_task(rx_prod_parser, tx_parser_writer, timeout, total_packets)
586 }));
587 }
588
589 let mut output = self.output.take().unwrap();
590 let timeout = self.timeout;
591 self.writer_handle = Some(thread::spawn(move || {
592 writer_task(rx_parser_writer, &mut output, timeout);
593 }));
594
595 self.state = NasooneState::Running;
596 Ok(())
597 }
598 _ => Err(NasooneError::InvalidState(
599 "Nasoone is not in initial state".to_string(),
600 )),
601 }
602 }
603
604 /// Pause the analysis of the network traffic.
605 ///
606 /// If the capture is set from a file, the analysis stop reading the file.
607 /// Otherwise, it continues to receive packets from the network interface but it does not analyze them.
608 ///
609 /// It returns an error in the following cases:
610 /// - Nasoone is not in the Running state
611 /// - the capture is from a file and the file is over (the analysis is already finished)
612 ///
613 /// # Examples
614 ///
615 /// create a nasoone instance, start the analysis and then pause it:
616 /// ```
617 /// use std::thread::sleep;
618 /// use nasoone_lib::Nasoone;
619 /// let mut nasoone = Nasoone::new();
620 /// nasoone.set_capture_device("en0").expect("");
621 /// nasoone.set_output("./output.csv").expect("");
622 /// nasoone.start().expect("");
623 /// sleep(std::time::Duration::from_secs(5));
624 /// nasoone.pause().expect("");
625 /// ```
626 pub fn pause(&mut self) -> Result<(), NasooneError> {
627 match self.state {
628 NasooneState::Running => {
629 self.state = NasooneState::Paused;
630 match self.tx_main_prod.as_ref().unwrap().send(Command::Pause) {
631 Ok(_) => Ok(()),
632 Err(_) => Err(NasooneError::CaptureOver),
633 }
634 }
635 _ => Err(NasooneError::InvalidState(
636 "Nasoone is not running".to_string(),
637 )),
638 }
639 }
640
641 /// Resume the analysis if it was paused.
642 ///
643 /// It returns an error in the following cases:
644 /// - Nasoone is not in the Paused state
645 /// - the capture is from a file and the file is over (the analysis is already finished)
646 ///
647 /// # Examples
648 ///
649 /// create a nasoone instance, start the analysis, pause it and then resume it:
650 /// ```
651 /// use nasoone_lib::Nasoone;
652 /// let mut nasoone = Nasoone::new();
653 /// nasoone.set_capture_device("en0").expect("");
654 /// nasoone.set_output("./output.csv").expect("");
655 /// nasoone.start().expect("");
656 /// nasoone.pause().expect("");
657 /// nasoone.resume().expect("");
658 /// ```
659 pub fn resume(&mut self) -> Result<(), NasooneError> {
660 match self.state {
661 NasooneState::Paused => {
662 self.state = NasooneState::Running;
663 match self.tx_main_prod.as_ref().unwrap().send(Command::Resume) {
664 Ok(_) => Ok(()),
665 Err(_) => Err(NasooneError::CaptureOver),
666 }
667 }
668 _ => Err(NasooneError::InvalidState(
669 "Nasoone is not paused".to_string(),
670 )),
671 }
672 }
673
674 /// Stop the capture if it is running or paused. It will wait for the threads to finish,
675 /// so it could take some time (up to 200-250ms).
676 ///
677 /// It returns the statistics of the capture only if the capture is from a network interface.
678 /// Otherwise, it will return None.
679 ///
680 /// It returns an error in the following cases:
681 /// - Nasoone is not in the Running, Finished or Paused state
682 ///
683 /// # Examples
684 ///
685 /// create a nasoone instance, start the analysis and then stop it:
686 /// ```
687 /// use nasoone_lib::Nasoone;
688 /// let mut nasoone = Nasoone::new();
689 /// nasoone.set_capture_device("en0").expect("");
690 /// nasoone.set_output("./output.csv").expect("");
691 /// nasoone.start().expect("");
692 /// let stats = nasoone.stop().expect("");
693 /// ```
694 pub fn stop(&mut self) -> Result<Option<NasooneStats>, NasooneError> {
695 match self.state {
696 NasooneState::Running | NasooneState::Paused | NasooneState::Finished => {
697 self.state = NasooneState::Stopped;
698 println!("sending stop command");
699 let _ = self.tx_main_prod.as_ref().unwrap().send(Command::Stop); // ignore a possible error that would mean that the producer thread is already stopped
700 println!("stop command sent");
701 let stat = self.producer_handle.take().unwrap().join().unwrap();
702 println!("producer stopped");
703 for handle in self.parser_handles.drain(..) {
704 handle.join().unwrap();
705 }
706 self.writer_handle.take().unwrap().join().unwrap();
707 if stat.is_err() {
708 return Ok(None);
709 }
710 Ok(Some(NasooneStats::from(stat.unwrap())))
711 }
712 _ => Err(NasooneError::InvalidState(
713 "Nasoone is not running".to_string(),
714 )),
715 }
716 }
717
718 /// Get the total amount of packet received by the capture.
719 ///
720 /// # Examples
721 ///
722 /// Create a nasoone instance, start the analysis and then get the total amount of packet received:
723 /// ```
724 /// use std::thread::sleep;
725 /// use nasoone_lib::{Nasoone, NasooneState};
726 /// let mut nasoone = Nasoone::new();
727 /// nasoone.set_capture_device("en0").expect("");
728 /// nasoone.set_output("./output.csv").expect("");
729 /// nasoone.start().expect("");
730 /// sleep(std::time::Duration::from_secs(5));
731 /// assert!(nasoone.get_total_packets() > 0);
732 /// ```
733 pub fn get_total_packets(&mut self) -> usize {
734 *self.total_packets.lock().unwrap()
735 }
736
737 /// Get the current state of the capture.
738 ///
739 /// # Examples
740 ///
741 /// Create a nasoone instance, start the analysis, pause it and ask for the state:
742 /// ```
743 /// use nasoone_lib::{Nasoone, NasooneState};
744 /// let mut nasoone = Nasoone::new();
745 /// nasoone.set_capture_device("en0").expect("");
746 /// nasoone.set_output("./output.csv").expect("");
747 /// nasoone.start().expect("");
748 /// nasoone.pause().expect("");
749 /// assert_eq!(nasoone.get_state(), NasooneState::Paused);
750 /// ```
751 pub fn get_state(&mut self) -> NasooneState {
752 // control if the capture has finished by itself
753 if self.producer_handle.as_ref().is_some()
754 && self.producer_handle.as_ref().unwrap().is_finished()
755 {
756 self.state = NasooneState::Finished;
757 }
758 self.state.clone()
759 }
760
761 /// Get the list of available network interfaces.
762 /// It only returns interfaces that have a network address,
763 /// since the others can't receive any network packet.
764 ///
765 /// It could return underlined errors from the pcap library.
766 ///
767 /// # Examples
768 /// ```
769 /// use nasoone_lib::Nasoone;
770 /// let interfaces = Nasoone::list_devices().expect("");
771 /// ```
772 pub fn list_devices() -> Result<Vec<NetworkInterface>, NasooneError> {
773 let devices = Device::list()
774 .map_err(NasooneError::PcapError)?
775 .into_iter()
776 .filter(|d| !d.addresses.is_empty())
777 .map(|d| NetworkInterface::new(d.name, d.desc))
778 .collect();
779 Ok(devices)
780 }
781
782 /// Get the name of the default network interface.
783 pub fn get_default_device_name() -> Result<String, NasooneError> {
784 let device = Device::lookup().map_err(NasooneError::PcapError)?;
785 Ok(device.name)
786 }
787}
788
789impl Drop for Nasoone {
790 fn drop(&mut self) {
791 if self.state != NasooneState::Stopped {
792 // try to stop the capture
793 let _ = self.stop();
794 }
795 }
796}
797
798impl Default for Nasoone {
799 fn default() -> Self {
800 Self::new()
801 }
802}