passivetcp_rs/lib.rs
1// ============================================================================
2// DEPRECATION WARNING
3// ============================================================================
4#![deprecated(
5 since = "1.3.1",
6 note = "This crate has been renamed to 'huginn-net'. Please use 'huginn-net' instead. See https://crates.io/crates/huginn-net for migration details."
7)]
8
9// ============================================================================
10// CORE IMPORTS (database, errors, results - always required)
11// ============================================================================
12use crate::db::{Database, Label};
13use crate::fingerprint_result::{FingerprintResult, OSQualityMatched};
14
15// ============================================================================
16// TCP PROTOCOL IMPORTS (base protocol)
17// ============================================================================
18use crate::fingerprint_result::{
19 MTUOutput, OperativeSystem, SynAckTCPOutput, SynTCPOutput, UptimeOutput,
20};
21use crate::uptime::{Connection, SynData};
22pub use tcp::Ttl;
23
24// ============================================================================
25// HTTP PROTOCOL IMPORTS (depends on TCP)
26// ============================================================================
27use crate::fingerprint_result::{
28 Browser, BrowserQualityMatched, HttpRequestOutput, HttpResponseOutput, WebServer,
29 WebServerQualityMatched,
30};
31use crate::http::{HttpDiagnosis, Signature};
32use crate::http_process::{FlowKey, TcpFlow};
33
34// ============================================================================
35// TLS PROTOCOL IMPORTS (depends on TCP)
36// ============================================================================
37use crate::fingerprint_result::TlsClientOutput;
38
39// ============================================================================
40// SHARED PROCESSING IMPORTS (used across protocols)
41// ============================================================================
42use crate::process::ObservablePackage;
43use crate::signature_matcher::SignatureMatcher;
44
45// ============================================================================
46// OBSERVABLE SIGNALS EXPORTS (conditional in future)
47// ============================================================================
48pub use observable_signals::{
49 ObservableHttpRequest, // HTTP signals
50 ObservableHttpResponse, // HTTP signals
51 ObservableTcp, // TCP signals
52 ObservableTlsClient, // TLS signals
53};
54
55// ============================================================================
56// EXTERNAL CRATE IMPORTS
57// ============================================================================
58use pcap_file::pcap::PcapReader;
59use pnet::datalink;
60use pnet::datalink::Config;
61use std::error::Error;
62use std::fs::File;
63use std::sync::mpsc::Sender;
64use tracing::{debug, error};
65use ttl_cache::TtlCache;
66
67// ============================================================================
68// CORE MODULES (always required - database, matching, errors, results)
69// ============================================================================
70pub mod db;
71pub mod db_matching_trait;
72pub mod db_parse;
73mod display;
74pub mod error;
75pub mod fingerprint_result;
76
77// ============================================================================
78// TCP PROTOCOL MODULES (base protocol - required by HTTP and TLS)
79// ============================================================================
80pub mod mtu;
81mod observable_tcp_signals_matching;
82pub mod tcp;
83pub mod tcp_process;
84pub mod ttl;
85pub mod uptime;
86pub mod window_size;
87
88// ============================================================================
89// HTTP PROTOCOL MODULES (depends on TCP)
90// ============================================================================
91pub mod http;
92pub mod http_languages;
93pub mod http_process;
94mod observable_http_signals_matching;
95
96// ============================================================================
97// TLS PROTOCOL MODULES (depends on TCP)
98// ============================================================================
99pub mod tls;
100pub mod tls_process;
101
102// ============================================================================
103// SHARED PROCESSING MODULES (used by multiple protocols)
104// ============================================================================
105pub mod ip_options;
106pub mod observable_signals;
107pub mod process;
108pub mod signature_matcher;
109
110/// Configuration for protocol analysis
111#[derive(Debug, Clone)]
112pub struct AnalysisConfig {
113 pub http_enabled: bool,
114 pub tcp_enabled: bool,
115 pub tls_enabled: bool,
116}
117
118impl Default for AnalysisConfig {
119 fn default() -> Self {
120 Self {
121 http_enabled: true,
122 tcp_enabled: true,
123 tls_enabled: true,
124 }
125 }
126}
127
128/// A passive TCP fingerprinting engine inspired by `p0f` with JA4 TLS client fingerprinting.
129///
130/// The `PassiveTcp` struct acts as the core component of the library, handling TCP packet
131/// analysis and matching signatures using a database of known fingerprints, plus JA4 TLS
132/// TLS client analysis following the official FoxIO specification.
133pub struct PassiveTcp<'a> {
134 pub matcher: Option<SignatureMatcher<'a>>,
135 tcp_cache: TtlCache<Connection, SynData>,
136 http_cache: TtlCache<FlowKey, TcpFlow>,
137 config: AnalysisConfig,
138}
139
140impl<'a> PassiveTcp<'a> {
141 /// Creates a new instance of `PassiveTcp`.
142 ///
143 /// # Parameters
144 /// - `database`: Optional reference to the database containing known TCP/Http signatures from p0f.
145 /// Required if HTTP or TCP analysis is enabled. Not needed for TLS-only analysis.
146 /// - `cache_capacity`: The maximum number of connections to maintain in the TTL cache.
147 /// - `config`: Optional configuration specifying which protocols to analyze. If None, uses default (all enabled).
148 ///
149 /// # Returns
150 /// A new `PassiveTcp` instance initialized with the given database, cache capacity, and configuration.
151 pub fn new(
152 database: Option<&'a Database>,
153 cache_capacity: usize,
154 config: Option<AnalysisConfig>,
155 ) -> Self {
156 let config = config.unwrap_or_default();
157
158 let matcher = if config.tcp_enabled || config.http_enabled {
159 database.map(SignatureMatcher::new)
160 } else {
161 None
162 };
163
164 let tcp_cache_size = if config.tcp_enabled {
165 cache_capacity
166 } else {
167 0
168 };
169 let http_cache_size = if config.http_enabled {
170 cache_capacity
171 } else {
172 0
173 };
174
175 Self {
176 matcher,
177 tcp_cache: TtlCache::new(tcp_cache_size),
178 http_cache: TtlCache::new(http_cache_size),
179 config,
180 }
181 }
182
183 fn process_with<F>(
184 &mut self,
185 mut packet_fn: F,
186 sender: Sender<FingerprintResult>,
187 ) -> Result<(), Box<dyn Error>>
188 where
189 F: FnMut() -> Option<Result<Vec<u8>, Box<dyn Error>>>,
190 {
191 while let Some(packet_result) = packet_fn() {
192 match packet_result {
193 Ok(packet) => {
194 let output = self.analyze_tcp(&packet);
195 if sender.send(output).is_err() {
196 error!("Receiver dropped, stopping packet processing");
197 break;
198 }
199 }
200 Err(e) => {
201 error!("Failed to read packet: {}", e);
202 }
203 }
204 }
205 Ok(())
206 }
207
208 /// Captures and analyzes packets on the specified network interface.
209 ///
210 /// Sends `FingerprintResult` through the provided channel.
211 ///
212 /// # Parameters
213 /// - `interface_name`: The name of the network interface to analyze.
214 /// - `sender`: A `Sender` to send `FingerprintResult` objects back to the caller.
215 ///
216 /// # Panics
217 /// - If the network interface cannot be found or a channel cannot be created.
218 pub fn analyze_network(
219 &mut self,
220 interface_name: &str,
221 sender: Sender<FingerprintResult>,
222 ) -> Result<(), Box<dyn Error>> {
223 let interfaces = datalink::interfaces();
224 let interface = interfaces
225 .into_iter()
226 .find(|iface| iface.name == interface_name)
227 .ok_or_else(|| format!("Could not find network interface: {}", interface_name))?;
228
229 debug!("Using network interface: {}", interface.name);
230
231 let config = Config {
232 promiscuous: true,
233 ..Config::default()
234 };
235
236 let (_tx, mut rx) = match datalink::channel(&interface, config) {
237 Ok(datalink::Channel::Ethernet(tx, rx)) => (tx, rx),
238 Ok(_) => return Err("Unhandled channel type".into()),
239 Err(e) => return Err(format!("Unable to create channel: {}", e).into()),
240 };
241
242 self.process_with(
243 move || match rx.next() {
244 Ok(packet) => Some(Ok(packet.to_vec())),
245 Err(e) => Some(Err(e.into())),
246 },
247 sender,
248 )
249 }
250
251 /// Analyzes packets from a PCAP file.
252 ///
253 /// # Parameters
254 /// - `pcap_path`: The path to the PCAP file to analyze.
255 /// - `sender`: A `Sender` to send `FingerprintResult` objects back to the caller.
256 ///
257 /// # Panics
258 /// - If the PCAP file cannot be opened or read.
259 pub fn analyze_pcap(
260 &mut self,
261 pcap_path: &str,
262 sender: Sender<FingerprintResult>,
263 ) -> Result<(), Box<dyn Error>> {
264 let file = File::open(pcap_path)?;
265 let mut pcap_reader = PcapReader::new(file)?;
266
267 self.process_with(
268 move || match pcap_reader.next_packet() {
269 Some(Ok(packet)) => Some(Ok(packet.data.to_vec())),
270 Some(Err(e)) => Some(Err(e.into())),
271 None => None,
272 },
273 sender,
274 )
275 }
276
277 /// Analyzes a TCP packet and returns a `FingerprintResult` object.
278 ///
279 /// # Parameters
280 /// - `packet`: A reference to the TCP packet to analyze.
281 ///
282 /// # Returns
283 /// A `FingerprintResult` object containing the analysis results.
284 pub fn analyze_tcp(&mut self, packet: &[u8]) -> FingerprintResult {
285 match ObservablePackage::extract(
286 packet,
287 &mut self.tcp_cache,
288 &mut self.http_cache,
289 &self.config,
290 ) {
291 Ok(observable_package) => {
292 let (syn, syn_ack, mtu, uptime, http_request, http_response, tls_client) = {
293 let mtu: Option<MTUOutput> =
294 observable_package.mtu.and_then(|observable_mtu| {
295 self.matcher.as_ref().and_then(|matcher| {
296 matcher.matching_by_mtu(&observable_mtu.value).map(
297 |(link, _mtu_result)| MTUOutput {
298 source: observable_package.source.clone(),
299 destination: observable_package.destination.clone(),
300 link: link.clone(),
301 mtu: observable_mtu.value,
302 },
303 )
304 })
305 });
306
307 let syn: Option<SynTCPOutput> =
308 observable_package
309 .tcp_request
310 .map(|observable_tcp| SynTCPOutput {
311 source: observable_package.source.clone(),
312 destination: observable_package.destination.clone(),
313 os_matched: self
314 .matcher
315 .as_ref()
316 .and_then(|matcher| {
317 matcher.matching_by_tcp_request(&observable_tcp)
318 })
319 .map(|(label, _signature, quality)| OSQualityMatched {
320 os: OperativeSystem::from(label),
321 quality,
322 }),
323 sig: observable_tcp,
324 });
325
326 let syn_ack: Option<SynAckTCPOutput> =
327 observable_package
328 .tcp_response
329 .map(|observable_tcp| SynAckTCPOutput {
330 source: observable_package.source.clone(),
331 destination: observable_package.destination.clone(),
332 os_matched: self
333 .matcher
334 .as_ref()
335 .and_then(|matcher| {
336 matcher.matching_by_tcp_response(&observable_tcp)
337 })
338 .map(|(label, _signature, quality)| OSQualityMatched {
339 os: OperativeSystem::from(label),
340 quality,
341 }),
342 sig: observable_tcp,
343 });
344
345 let uptime: Option<UptimeOutput> =
346 observable_package.uptime.map(|update| UptimeOutput {
347 source: observable_package.source.clone(),
348 destination: observable_package.destination.clone(),
349 days: update.days,
350 hours: update.hours,
351 min: update.min,
352 up_mod_days: update.up_mod_days,
353 freq: update.freq,
354 });
355
356 let http_request: Option<HttpRequestOutput> = observable_package
357 .http_request
358 .map(|observable_http_request| {
359 let signature_matcher: Option<(&Label, &Signature, f32)> =
360 self.matcher.as_ref().and_then(|matcher| {
361 matcher.matching_by_http_request(&observable_http_request)
362 });
363
364 let ua_matcher: Option<(&String, &Option<String>)> =
365 observable_http_request.user_agent.clone().and_then(|ua| {
366 self.matcher
367 .as_ref()
368 .and_then(|matcher| matcher.matching_by_user_agent(ua))
369 });
370
371 let http_diagnosis = http_process::get_diagnostic(
372 observable_http_request.user_agent.clone(),
373 ua_matcher,
374 signature_matcher.map(|(label, _signature, _quality)| label),
375 );
376
377 HttpRequestOutput {
378 source: observable_package.source.clone(),
379 destination: observable_package.destination.clone(),
380 lang: observable_http_request.lang.clone(),
381 browser_matched: signature_matcher.map(
382 |(label, _signature, quality)| BrowserQualityMatched {
383 browser: Browser::from(label),
384 quality,
385 },
386 ),
387 diagnosis: http_diagnosis,
388 sig: observable_http_request,
389 }
390 });
391
392 let http_response: Option<HttpResponseOutput> = observable_package
393 .http_response
394 .map(|observable_http_response| HttpResponseOutput {
395 source: observable_package.source.clone(),
396 destination: observable_package.destination.clone(),
397 web_server_matched: self.matcher.as_ref().and_then(|matcher| {
398 matcher
399 .matching_by_http_response(&observable_http_response)
400 .map(|(label, _signature, quality)| WebServerQualityMatched {
401 web_server: WebServer::from(label),
402 quality,
403 })
404 }),
405 diagnosis: HttpDiagnosis::None,
406 sig: observable_http_response,
407 });
408
409 let tls_client: Option<TlsClientOutput> =
410 observable_package
411 .tls_client
412 .map(|observable_tls| TlsClientOutput {
413 source: observable_package.source.clone(),
414 destination: observable_package.destination.clone(),
415 sig: observable_tls,
416 });
417
418 (
419 syn,
420 syn_ack,
421 mtu,
422 uptime,
423 http_request,
424 http_response,
425 tls_client,
426 )
427 };
428
429 FingerprintResult {
430 syn,
431 syn_ack,
432 mtu,
433 uptime,
434 http_request,
435 http_response,
436 tls_client,
437 }
438 }
439 Err(error) => {
440 debug!("Fail to process signature: {}", error);
441 FingerprintResult {
442 syn: None,
443 syn_ack: None,
444 mtu: None,
445 uptime: None,
446 http_request: None,
447 http_response: None,
448 tls_client: None,
449 }
450 }
451 }
452 }
453}