Skip to main content

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}