passivetcp_rs/fingerprint_result.rs
1use crate::db::{Label, Type};
2use crate::http::HttpDiagnosis;
3use crate::observable_signals::{ObservableHttpRequest, ObservableHttpResponse};
4use crate::observable_signals::{ObservableTcp, ObservableTlsClient};
5use crate::process::IpPort;
6use crate::tcp::Ttl;
7use std::fmt;
8use std::fmt::Formatter;
9
10/// Represents the output from the passive TCP fingerprinting tool.
11///
12/// This struct contains various optional outputs that can be derived
13/// from analyzing TCP packets, such as SYN, SYN-ACK, MTU, uptime, and HTTP data.
14pub struct FingerprintResult {
15 /// Information derived from SYN packets.
16 pub syn: Option<SynTCPOutput>,
17
18 /// Information derived from SYN-ACK packets.
19 pub syn_ack: Option<SynAckTCPOutput>,
20
21 /// Information about the Maximum Transmission Unit (MTU).
22 pub mtu: Option<MTUOutput>,
23
24 /// Information about the system uptime.
25 pub uptime: Option<UptimeOutput>,
26
27 /// Information derived from HTTP request headers.
28 pub http_request: Option<HttpRequestOutput>,
29
30 /// Information derived from HTTP response headers.
31 pub http_response: Option<HttpResponseOutput>,
32
33 /// Information derived from TLS ClientHello analysis using JA4 fingerprinting.
34 /// JA4 methodology by FoxIO, LLC - implementation from scratch for passivetcp-rs.
35 pub tls_client: Option<TlsClientOutput>,
36}
37
38/// Represents an operative system.
39///
40/// This struct contains the name, family, variant, and kind of operative system.
41/// Examples:
42/// - name: "Linux", family: "unix", variant: "2.2.x-3.x", kind: Type::Specified
43/// - name: "Windows", family: "win", variant: "NT kernel 6.x", kind: Type::Specified
44/// - name: "iOS", family: "unix", variant: "iPhone or iPad", kind: Type::Specified
45pub struct OperativeSystem {
46 pub name: String,
47 pub family: Option<String>,
48 pub variant: Option<String>,
49 pub kind: Type,
50}
51
52impl From<&Label> for OperativeSystem {
53 fn from(label: &Label) -> Self {
54 OperativeSystem {
55 name: label.name.clone(),
56 family: label.class.clone(),
57 variant: label.flavor.clone(),
58 kind: label.ty.clone(),
59 }
60 }
61}
62
63/// The operative system with the highest quality that matches the packet. Quality is a value between 0.0 and 1.0. 1.0 is a perfect match.
64pub struct OSQualityMatched {
65 pub os: OperativeSystem,
66 pub quality: f32,
67}
68
69/// Holds information derived from analyzing a TCP SYN packet (client initiation).
70///
71/// This structure contains details about the client system based on its SYN packet,
72/// including the identified OS/application label and the raw TCP signature.
73pub struct SynTCPOutput {
74 /// The source IP address and port of the client sending the SYN.
75 pub source: IpPort,
76 /// The destination IP address and port of the server receiving the SYN.
77 pub destination: IpPort,
78 /// The operative system with the highest quality that matches the SYN packet.
79 pub os_matched: Option<OSQualityMatched>,
80 /// The raw TCP signature extracted from the SYN packet.
81 pub sig: ObservableTcp,
82}
83
84impl fmt::Display for SynTCPOutput {
85 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
86 write!(
87 f,
88 ".-[ {}/{} -> {}/{} (syn) ]-\n\
89 |\n\
90 | client = {}/{}\n\
91 | os = {}\n\
92 | dist = {}\n\
93 | params = {}\n\
94 | raw_sig = {}\n\
95 `----\n",
96 self.source.ip,
97 self.source.port,
98 self.destination.ip,
99 self.destination.port,
100 self.source.ip,
101 self.source.port,
102 self.os_matched.as_ref().map_or("???".to_string(), |l| {
103 format!(
104 "{}/{}/{}",
105 l.os.name,
106 l.os.family.as_deref().unwrap_or("???"),
107 l.os.variant.as_deref().unwrap_or("??")
108 )
109 }),
110 match self.sig.ittl {
111 Ttl::Distance(_, distance) => distance,
112 Ttl::Bad(value) => value,
113 Ttl::Value(value) => value,
114 Ttl::Guess(value) => value,
115 },
116 self.os_matched
117 .as_ref()
118 .map_or("none".to_string(), |l| l.os.kind.to_string()),
119 self.sig,
120 )
121 }
122}
123
124/// Holds information derived from analyzing a TCP SYN+ACK packet (server response).
125///
126/// This structure contains details about the server system based on its SYN+ACK packet,
127/// including the identified OS/application label and the raw TCP signature.
128pub struct SynAckTCPOutput {
129 /// The source IP address and port of the server sending the SYN+ACK.
130 pub source: IpPort,
131 /// The destination IP address and port of the client receiving the SYN+ACK.
132 pub destination: IpPort,
133 /// The operative system with the highest quality that matches the SYN+ACK packet.
134 pub os_matched: Option<OSQualityMatched>,
135 /// The raw TCP signature extracted from the SYN+ACK packet.
136 pub sig: ObservableTcp,
137}
138
139impl fmt::Display for SynAckTCPOutput {
140 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
141 write!(
142 f,
143 ".-[ {}/{} -> {}/{} (syn+ack) ]-\n\
144 |\n\
145 | server = {}/{}\n\
146 | os = {}\n\
147 | dist = {}\n\
148 | params = {}\n\
149 | raw_sig = {}\n\
150 `----\n",
151 self.source.ip,
152 self.source.port,
153 self.destination.ip,
154 self.destination.port,
155 self.destination.ip,
156 self.destination.port,
157 self.os_matched.as_ref().map_or("???".to_string(), |l| {
158 format!(
159 "{}/{}/{}",
160 l.os.name,
161 l.os.family.as_deref().unwrap_or("???"),
162 l.os.variant.as_deref().unwrap_or("??")
163 )
164 }),
165 match self.sig.ittl {
166 Ttl::Distance(_, distance) => distance,
167 Ttl::Bad(value) => value,
168 Ttl::Value(value) => value,
169 Ttl::Guess(value) => value,
170 },
171 self.os_matched
172 .as_ref()
173 .map_or("none".to_string(), |l| l.os.kind.to_string()),
174 self.sig,
175 )
176 }
177}
178
179/// Holds information about the estimated Maximum Transmission Unit (MTU) of a link.
180///
181/// This structure contains the source and destination addresses, an estimation
182/// of the link type based on common MTU values, and the calculated raw MTU value.
183pub struct MTUOutput {
184 /// The source IP address and port (usually the client).
185 pub source: IpPort,
186 /// The destination IP address and port (usually the server).
187 pub destination: IpPort,
188 /// An estimated link type (e.g., "Ethernet", "PPPoE") based on the calculated MTU.
189 pub link: String,
190 /// The calculated Maximum Transmission Unit (MTU) value in bytes.
191 pub mtu: u16,
192}
193
194impl fmt::Display for MTUOutput {
195 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
196 write!(
197 f,
198 ".-[ {}/{} -> {}/{} (mtu) ]-\n\
199 |\n\
200 | server = {}/{}\n\
201 | link = {}\n\
202 | raw_mtu = {}\n\
203 `----\n",
204 self.source.ip,
205 self.source.port,
206 self.destination.ip,
207 self.destination.port,
208 self.destination.ip,
209 self.destination.port,
210 self.link,
211 self.mtu,
212 )
213 }
214}
215
216/// Holds uptime information derived from TCP timestamp analysis.
217///
218/// This structure contains the estimated uptime components (days, hours, minutes),
219/// the timestamp clock's wraparound period (`up_mod_days`), and the calculated
220/// clock frequency (`freq`). Note that the days/hours/minutes calculation based
221/// on the timestamp value might be approximate.
222pub struct UptimeOutput {
223 /// The source IP address and port of the connection.
224 pub source: IpPort,
225 /// The destination IP address and port of the connection.
226 pub destination: IpPort,
227 /// Estimated uptime in days, derived from the TCP timestamp value. Potentially approximate.
228 pub days: u32,
229 /// Estimated uptime component in hours. Potentially approximate.
230 pub hours: u32,
231 /// Estimated uptime component in minutes. Potentially approximate.
232 pub min: u32,
233 /// The calculated period in days after which the timestamp counter wraps around (2^32 ticks).
234 pub up_mod_days: u32,
235 /// The calculated frequency of the remote system's timestamp clock in Hertz (Hz).
236 pub freq: f64,
237}
238
239impl fmt::Display for UptimeOutput {
240 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
241 write!(
242 f,
243 ".-[ {}/{} -> {}/{} (uptime) ]-\n\
244 |\n\
245 | client = {}/{}\n\
246 | uptime = {} days, {} hrs, {} min (modulo {} days)\n\
247 | raw_freq = {:.2} Hz\n\
248 `----\n",
249 self.destination.ip,
250 self.destination.port,
251 self.source.ip,
252 self.source.port,
253 self.destination.ip,
254 self.destination.port,
255 self.days,
256 self.hours,
257 self.min,
258 self.up_mod_days,
259 self.freq,
260 )
261 }
262}
263
264pub struct BrowserQualityMatched {
265 pub browser: Browser,
266 pub quality: f32,
267}
268
269/// Represents a browser.
270///
271/// This struct contains the name, family, variant, and kind of browser.
272/// Examples:
273/// - name: "", family: "chrome", variant: "11.x to 26.x", kind: Type::Specified
274/// - name: "", family: "firefox", variant: "3.x", kind: Type::Specified
275pub struct Browser {
276 pub name: String,
277 pub family: Option<String>,
278 pub variant: Option<String>,
279 pub kind: Type,
280}
281
282impl From<&Label> for Browser {
283 fn from(label: &Label) -> Self {
284 Browser {
285 name: label.name.clone(),
286 family: label.class.clone(),
287 variant: label.flavor.clone(),
288 kind: label.ty.clone(),
289 }
290 }
291}
292
293/// Holds information derived from analyzing HTTP request headers.
294///
295/// This structure contains details about the client, the detected application
296/// (if any), the preferred language, diagnostic parameters related to HTTP behavior,
297/// and the raw HTTP signature.
298pub struct HttpRequestOutput {
299 /// The source IP address and port of the client making the request.
300 pub source: IpPort,
301 /// The destination IP address and port of the server receiving the request.
302 pub destination: IpPort,
303 /// The preferred language indicated in the `Accept-Language` header, if present.
304 pub lang: Option<String>,
305 /// Diagnostic information about potential HTTP specification violations or common practices.
306 pub diagnosis: HttpDiagnosis,
307 /// The browser with the highest quality that matches the HTTP request.
308 pub browser_matched: Option<BrowserQualityMatched>,
309 /// The raw signature representing the HTTP headers and their order.
310 pub sig: ObservableHttpRequest,
311}
312
313impl fmt::Display for HttpRequestOutput {
314 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
315 write!(
316 f,
317 ".-[ {}/{} -> {}/{} (http request) ]-\n\
318 |\n\
319 | client = {}/{}\n\
320 | app = {}\n\
321 | lang = {}\n\
322 | params = {}\n\
323 | raw_sig = {}\n\
324 `----\n",
325 self.source.ip,
326 self.source.port,
327 self.destination.ip,
328 self.destination.port,
329 self.source.ip,
330 self.source.port,
331 self.browser_matched
332 .as_ref()
333 .map_or("???".to_string(), |l| {
334 format!(
335 "{}/{}/{}",
336 l.browser.name,
337 l.browser.family.as_deref().unwrap_or("???"),
338 l.browser.variant.as_deref().unwrap_or("???")
339 )
340 }),
341 self.lang.as_deref().unwrap_or("???"),
342 self.diagnosis,
343 self.sig,
344 )
345 }
346}
347
348pub struct WebServerQualityMatched {
349 pub web_server: WebServer,
350 pub quality: f32,
351}
352
353/// Represents a web server.
354///
355/// This struct contains the name, family, variant, and kind of browser.
356/// Examples:
357/// - name: "", family: "apache", variant: "2.x", kind: Type::Specified
358/// - name: "", family: "nginx", variant: "1.x", kind: Type::Specified
359pub struct WebServer {
360 pub name: String,
361 pub family: Option<String>,
362 pub variant: Option<String>,
363 pub kind: Type,
364}
365
366impl From<&Label> for WebServer {
367 fn from(label: &Label) -> Self {
368 WebServer {
369 name: label.name.clone(),
370 family: label.class.clone(),
371 variant: label.flavor.clone(),
372 kind: label.ty.clone(),
373 }
374 }
375}
376
377/// Holds information derived from analyzing HTTP response headers.
378///
379/// This structure contains details about the server, the detected application
380/// (if any), diagnostic parameters related to HTTP behavior, and the raw HTTP signature.
381pub struct HttpResponseOutput {
382 /// The source IP address and port of the server sending the response.
383 pub source: IpPort,
384 /// The destination IP address and port of the client receiving the response.
385 pub destination: IpPort,
386 /// Diagnostic information about potential HTTP specification violations or common practices.
387 pub diagnosis: HttpDiagnosis,
388 /// The label identifying the likely server application (e.g., Apache, Nginx) and the quality.
389 pub web_server_matched: Option<WebServerQualityMatched>,
390 /// The raw signature representing the HTTP headers and their order.
391 pub sig: ObservableHttpResponse,
392}
393
394impl fmt::Display for HttpResponseOutput {
395 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
396 write!(
397 f,
398 ".-[ {}/{} -> {}/{} (http response) ]-\n\
399 |\n\
400 | server = {}/{}\n\
401 | app = {}\n\
402 | params = {}\n\
403 | raw_sig = {}\n\
404 `----\n",
405 self.source.ip,
406 self.source.port,
407 self.destination.ip,
408 self.destination.port,
409 self.destination.ip,
410 self.destination.port,
411 self.web_server_matched
412 .as_ref()
413 .map_or("???".to_string(), |l| {
414 format!(
415 "{}/{}/{}",
416 l.web_server.name,
417 l.web_server.family.as_deref().unwrap_or("???"),
418 l.web_server.variant.as_deref().unwrap_or("???")
419 )
420 }),
421 self.diagnosis,
422 self.sig,
423 )
424 }
425}
426
427/// Holds information derived from analyzing TLS ClientHello packets.
428///
429/// This structure contains details about the TLS client based on its ClientHello packet,
430/// including the JA4 Payload and extracted TLS parameters.
431pub struct TlsClientOutput {
432 /// The source IP address and port of the client sending the ClientHello.
433 pub source: IpPort,
434 /// The destination IP address and port of the server receiving the ClientHello.
435 pub destination: IpPort,
436 /// The raw TLS signature extracted from the ClientHello packet.
437 pub sig: ObservableTlsClient,
438}
439
440impl fmt::Display for TlsClientOutput {
441 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
442 write!(
443 f,
444 ".-[ {}/{} -> {}/{} (tls client) ]-\n\
445 |\n\
446 | client = {}/{}\n\
447 | ja4 = {}\n\
448 | ja4_r = {}\n\
449 | ja4_o = {}\n\
450 | ja4_or = {}\n\
451 | sni = {}\n\
452 | version = {}\n\
453 `----\n",
454 self.source.ip,
455 self.source.port,
456 self.destination.ip,
457 self.destination.port,
458 self.source.ip,
459 self.source.port,
460 self.sig.ja4.full.value(),
461 self.sig.ja4.raw.value(),
462 self.sig.ja4_original.full.value(),
463 self.sig.ja4_original.raw.value(),
464 self.sig.sni.as_deref().unwrap_or("none"),
465 self.sig.version,
466 )
467 }
468}