diff --git a/Cargo.lock b/Cargo.lock index 459bd67..d2b07d4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -142,6 +142,29 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" +[[package]] +name = "env_filter" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "humantime", + "log", +] + [[package]] name = "failure" version = "0.1.8" @@ -182,6 +205,12 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + [[package]] name = "ipnetwork" version = "0.20.0" @@ -269,9 +298,10 @@ dependencies = [ [[package]] name = "passivetcp-rs" -version = "0.1.0-alpha.0" +version = "0.1.0-alpha.1" dependencies = [ "clap", + "env_logger", "failure", "lazy_static", "log", diff --git a/Cargo.toml b/Cargo.toml index e752bcb..f547a76 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "passivetcp-rs" -version = "0.1.0-alpha.0" +version = "0.1.0-alpha.1" edition = "2021" description = "Passive traffic fingerprinting [p0f]" license = "MIT" @@ -18,6 +18,7 @@ failure = "0.1.8" log = "0.4.22" lazy_static = "1.5.0" ttl_cache = "0.5.1" +env_logger = "0.11.5" [[example]] name = "p0f" diff --git a/README.md b/README.md index dc770e7..f9620ed 100644 --- a/README.md +++ b/README.md @@ -65,38 +65,27 @@ Here’s a basic example of how to use passivetcp-rs: use passivetcp_rs::db::Database; use passivetcp_rs::P0f; +env_logger::init(); let args = Args::parse(); -let interface_name = args.interface; -let db = Database::default(); -let mut p0f = P0f::new(&db, 100); +let db = Box::leak(Box::new(Database::default())); +let (sender, receiver) = mpsc::channel(); -let interfaces: Vec = datalink::interfaces(); -let interface = interfaces - .into_iter() - .find(|iface| iface.name == interface_name) - .expect("Could not find the interface"); +thread::spawn(move || { + P0f::new(db, 100).analyze_network(&args.interface, sender); +}); -let config = Config { - promiscuous: true, - ..Config::default() -}; - -let (_tx, mut rx) = match datalink::channel(&interface, config) { - Ok(datalink::Channel::Ethernet(tx, rx)) => (tx, rx), - Ok(_) => panic!("Unhandled channel type"), - Err(e) => panic!("Unable to create channel: {}", e), -}; - -loop { - match rx.next() { - Ok(packet) => { - let p0f_output = p0f.analyze_tcp(packet); - p0f_output.syn.map(|syn| println!("{}", syn)); - p0f_output.syn_ack.map(|syn_ack| println!("{}", syn_ack)); - p0f_output.mtu.map(|mtu| println!("{}", mtu)); - p0f_output.uptime.map(|uptime| println!("{}", uptime)); - } - Err(e) => eprintln!("Failed to read packet: {}", e), +for output in receiver { + if let Some(syn) = output.syn { + info!("{}", syn); + } + if let Some(syn_ack) = output.syn_ack { + info!("{}", syn_ack); + } + if let Some(mtu) = output.mtu { + info!("{}", mtu); + } + if let Some(uptime) = output.uptime { + info!("{}", uptime); } } ``` diff --git a/examples/README.md b/examples/README.md index 379bba9..cea9a92 100644 --- a/examples/README.md +++ b/examples/README.md @@ -6,5 +6,5 @@ ip link show ### Process packages ``` cargo build --release --examples -sudo RUST_BACKTRACE=1 ./target/release/examples/p0f --interface +sudo RUST_LOG=info RUST_BACKTRACE=1 ./target/release/examples/p0f --interface ``` diff --git a/examples/p0f.rs b/examples/p0f.rs index 7ae931b..3d8a4cc 100644 --- a/examples/p0f.rs +++ b/examples/p0f.rs @@ -1,8 +1,9 @@ use clap::Parser; -use log::debug; +use log::{debug, info}; use passivetcp_rs::db::Database; use passivetcp_rs::P0f; -use pnet::datalink::{self, Config, NetworkInterface}; +use std::sync::mpsc; +use std::thread; #[derive(Parser, Debug)] #[command(version, about, long_about = None)] @@ -11,45 +12,31 @@ struct Args { interface: String, } -fn start_capture(interface_name: &str, p0f: &mut P0f) { - let interfaces: Vec = datalink::interfaces(); - let interface = interfaces - .into_iter() - .find(|iface| iface.name == interface_name) - .expect("Could not find the interface"); - - let config = Config { - promiscuous: true, - ..Config::default() - }; - - let (_tx, mut rx) = match datalink::channel(&interface, config) { - Ok(datalink::Channel::Ethernet(tx, rx)) => (tx, rx), - Ok(_) => panic!("Unhandled channel type"), - Err(e) => panic!("Unable to create channel: {}", e), - }; - - loop { - match rx.next() { - Ok(packet) => { - let p0f_output = p0f.analyze_tcp(packet); - p0f_output.syn.map(|syn| println!("{}", syn)); - p0f_output.syn_ack.map(|syn_ack| println!("{}", syn_ack)); - p0f_output.mtu.map(|mtu| println!("{}", mtu)); - p0f_output.uptime.map(|uptime| println!("{}", uptime)); - } - Err(e) => eprintln!("Failed to read packet: {}", e), - } - } -} - fn main() { + env_logger::init(); let args = Args::parse(); - let interface_name = args.interface; - let db = Database::default(); + let db = Box::leak(Box::new(Database::default())); debug!("Loaded database: {:?}", db); - let mut p0f = P0f::new(&db, 100); - start_capture(&interface_name, &mut p0f); + let (sender, receiver) = mpsc::channel(); + + thread::spawn(move || { + P0f::new(db, 100).analyze_network(&args.interface, sender); + }); + + for output in receiver { + if let Some(syn) = output.syn { + info!("{}", syn); + } + if let Some(syn_ack) = output.syn_ack { + info!("{}", syn_ack); + } + if let Some(mtu) = output.mtu { + info!("{}", mtu); + } + if let Some(uptime) = output.uptime { + info!("{}", uptime); + } + } } diff --git a/src/db.rs b/src/db.rs index 2bd10e6..4605b26 100644 --- a/src/db.rs +++ b/src/db.rs @@ -1,4 +1,5 @@ use crate::{http, tcp}; +use log::error; use std::fmt; /// Represents the database used by `P0f` to store signatures and associated metadata. @@ -56,7 +57,7 @@ impl Database { .ok() .and_then(|content| content.parse().ok()) .unwrap_or_else(|| { - eprintln!( + error!( "Failed to load configuration from {}. Falling back to default.", path ); diff --git a/src/lib.rs b/src/lib.rs index 1f6c80b..4f6a81e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,6 +14,10 @@ use crate::p0f_output::{MTUOutput, P0fOutput, SynAckTCPOutput, SynTCPOutput, Upt use crate::packet::ObservableSignature; use crate::signature_matcher::SignatureMatcher; use crate::uptime::{Connection, SynData}; +use log::{debug, error}; +use pnet::datalink; +use pnet::datalink::Config; +use std::sync::mpsc::Sender; use ttl_cache::TtlCache; pub struct P0f<'a> { @@ -40,99 +44,132 @@ impl<'a> P0f<'a> { Self { matcher, cache } } - /// Analyzes a TCP packet and returns the corresponding `P0fOutput`. + /// Captures and analyzes packets on the specified network interface. + /// + /// Sends `P0fOutput` through the provided channel. /// /// # Parameters - /// - `packet`: A byte slice representing the raw TCP packet to analyze. + /// - `interface_name`: The name of the network interface to analyze. + /// - `sender`: A `Sender` to send `P0fOutput` objects back to the caller. /// - /// # Returns - /// A `P0fOutput` containing the analysis results, including matched signatures, - /// observed MTU, uptime information, and other details. If no valid data is observed, an empty output is returned. - pub fn analyze_tcp(&mut self, packet: &[u8]) -> P0fOutput { - if let Ok(observable_signature) = ObservableSignature::extract(packet, &mut self.cache) { - if observable_signature.from_client { - //println!("MTU {:?}", observable_signature.mtu); - let mtu: Option = if let Some(mtu) = observable_signature.mtu { - if let Some((link, _matched_mtu)) = self.matcher.matching_by_mtu(&mtu) { - Some(MTUOutput { - source: observable_signature.source.clone(), - destination: observable_signature.destination.clone(), - link: link.clone(), - mtu, - }) - } else { - None - } - } else { - None + /// # Panics + /// - If the network interface cannot be found or a channel cannot be created. + pub fn analyze_network(&mut self, interface_name: &str, sender: Sender) { + let interfaces = datalink::interfaces(); + let interface = interfaces + .into_iter() + .find(|iface| iface.name == interface_name); + + match interface { + Some(iface) => { + debug!("Using network interface: {}", iface.name); + + let config = Config { + promiscuous: true, + ..Config::default() }; - let syn: Option = if let Some((label, _matched_signature)) = self - .matcher - .matching_by_tcp_request(&observable_signature.signature) - { - Some(SynTCPOutput { - source: observable_signature.source.clone(), - destination: observable_signature.destination.clone(), - label: Some(label.clone()), - sig: observable_signature.signature, - }) - } else { - Some(SynTCPOutput { - source: observable_signature.source.clone(), - destination: observable_signature.destination.clone(), - label: None, - sig: observable_signature.signature, - }) + let (_tx, mut rx) = match datalink::channel(&iface, config) { + Ok(datalink::Channel::Ethernet(tx, rx)) => (tx, rx), + Ok(_) => { + error!("Unhandled channel type for interface: {}", iface.name); + return; + } + Err(e) => { + error!( + "Unable to create channel for interface {}: {}", + iface.name, e + ); + return; + } }; - P0fOutput { - syn, - syn_ack: None, - mtu, - uptime: None, + loop { + match rx.next() { + Ok(packet) => { + let output = self.analyze_tcp(packet); + if sender.send(output).is_err() { + error!("Receiver dropped, stopping packet capture"); + break; + } + } + Err(e) => { + error!("Failed to read packet: {}", e); + } + } } - } else { - let syn_ack: Option = if let Some((label, _matched_signature)) = - self.matcher - .matching_by_tcp_response(&observable_signature.signature) - { - Some(SynAckTCPOutput { + } + None => { + error!("Could not find the network interface: {}", interface_name); + } + } + } + + fn analyze_tcp(&mut self, packet: &[u8]) -> P0fOutput { + match ObservableSignature::extract(packet, &mut self.cache) { + Ok(observable_signature) => { + let (syn, syn_ack, mtu, uptime) = if observable_signature.from_client { + let mtu = observable_signature.mtu.and_then(|mtu| { + self.matcher + .matching_by_mtu(&mtu) + .map(|(link, _)| MTUOutput { + source: observable_signature.source.clone(), + destination: observable_signature.destination.clone(), + link: link.clone(), + mtu, + }) + }); + + let syn = Some(SynTCPOutput { source: observable_signature.source.clone(), destination: observable_signature.destination.clone(), - label: Some(label.clone()), + label: self + .matcher + .matching_by_tcp_request(&observable_signature.signature) + .map(|(label, _)| label.clone()), sig: observable_signature.signature, - }) + }); + + (syn, None, mtu, None) } else { - Some(SynAckTCPOutput { + let syn_ack = Some(SynAckTCPOutput { source: observable_signature.source.clone(), destination: observable_signature.destination.clone(), - label: None, + label: self + .matcher + .matching_by_tcp_response(&observable_signature.signature) + .map(|(label, _)| label.clone()), sig: observable_signature.signature, - }) - }; + }); - P0fOutput { - syn: None, - syn_ack, - mtu: None, - uptime: observable_signature.uptime.map(|update| UptimeOutput { - source: observable_signature.source, - destination: observable_signature.destination, + let uptime = observable_signature.uptime.map(|update| UptimeOutput { + source: observable_signature.source.clone(), + destination: observable_signature.destination.clone(), days: update.days, hours: update.hours, min: update.min, up_mod_days: update.up_mod_days, freq: update.freq, - }), + }); + + (None, syn_ack, None, uptime) + }; + + P0fOutput { + syn, + syn_ack, + mtu, + uptime, } } - } else { - P0fOutput { - syn: None, - syn_ack: None, - mtu: None, - uptime: None, + Err(error) => { + debug!("Fail to process signature: {}", error); + P0fOutput { + syn: None, + syn_ack: None, + mtu: None, + uptime: None, + } } } }