huginn_net_tcp/
lib.rs

1#![forbid(unsafe_code)]
2
3pub use huginn_net_db as db;
4pub use huginn_net_db::tcp;
5
6pub mod ip_options;
7pub mod mtu;
8pub mod tcp_process;
9pub mod ttl;
10pub mod uptime;
11pub mod window_size;
12
13pub mod display;
14pub mod error;
15pub mod observable;
16pub mod output;
17pub mod process;
18pub mod signature_matcher;
19
20// Re-exports
21pub use error::*;
22pub use observable::*;
23pub use output::*;
24pub use process::*;
25pub use signature_matcher::*;
26pub use tcp_process::*;
27pub use uptime::{Connection, SynData};
28
29use pnet::datalink::{self, Channel};
30use pnet::packet::ethernet::{EtherTypes, EthernetPacket};
31use pnet::packet::ipv4::Ipv4Packet;
32use pnet::packet::ipv6::Ipv6Packet;
33use pnet::packet::Packet;
34use std::sync::atomic::{AtomicBool, Ordering};
35use std::sync::mpsc::Sender;
36use std::sync::Arc;
37use tracing::debug;
38use ttl_cache::TtlCache;
39
40/// A TCP-focused passive fingerprinting analyzer.
41///
42/// The `HuginnNetTcp` struct handles TCP packet analysis for OS fingerprinting,
43/// MTU detection, and uptime calculation using p0f-style methodologies.
44pub struct HuginnNetTcp<'a> {
45    pub matcher: Option<SignatureMatcher<'a>>,
46    max_connections: usize,
47}
48
49impl<'a> HuginnNetTcp<'a> {
50    /// Creates a new instance of `HuginnNetTcp`.
51    ///
52    /// # Parameters
53    /// - `database`: Optional signature database for OS matching
54    /// - `max_connections`: Maximum number of connections to track in the connection tracker
55    ///
56    /// # Returns
57    /// A new `HuginnNetTcp` instance ready for TCP analysis.
58    pub fn new(
59        database: Option<&'a db::Database>,
60        max_connections: usize,
61    ) -> Result<Self, HuginnNetTcpError> {
62        let matcher = database.map(SignatureMatcher::new);
63
64        Ok(Self {
65            matcher,
66            max_connections,
67        })
68    }
69
70    /// Analyzes network traffic from a live network interface for TCP packets.
71    ///
72    /// # Parameters
73    /// - `interface_name`: The name of the network interface to capture from.
74    /// - `sender`: A channel sender to send analysis results.
75    /// - `cancel_signal`: Optional atomic boolean to signal cancellation.
76    ///
77    /// # Returns
78    /// A `Result` indicating success or failure.
79    pub fn analyze_network(
80        &mut self,
81        interface_name: &str,
82        sender: Sender<TcpAnalysisResult>,
83        cancel_signal: Option<Arc<AtomicBool>>,
84    ) -> Result<(), HuginnNetTcpError> {
85        let interface = datalink::interfaces()
86            .into_iter()
87            .find(|iface| iface.name == interface_name)
88            .ok_or_else(|| {
89                HuginnNetTcpError::Parse(format!("Interface {interface_name} not found"))
90            })?;
91
92        let (_, mut rx) = match datalink::channel(&interface, Default::default()) {
93            Ok(Channel::Ethernet(tx, rx)) => (tx, rx),
94            Ok(_) => {
95                return Err(HuginnNetTcpError::Parse(
96                    "Unsupported channel type".to_string(),
97                ))
98            }
99            Err(e) => {
100                return Err(HuginnNetTcpError::Parse(format!(
101                    "Failed to create channel: {e}"
102                )))
103            }
104        };
105
106        // Connection tracker for TCP analysis
107        let mut connection_tracker = TtlCache::new(self.max_connections);
108
109        loop {
110            if let Some(ref signal) = cancel_signal {
111                if signal.load(Ordering::Relaxed) {
112                    break;
113                }
114            }
115
116            match rx.next() {
117                Ok(packet) => {
118                    // Process packet and handle errors gracefully
119                    match self.process_packet(packet, &mut connection_tracker) {
120                        Ok(result) => {
121                            if sender.send(result).is_err() {
122                                break;
123                            }
124                        }
125                        Err(huginn_error) => {
126                            debug!("Error processing packet: {}", huginn_error);
127                        }
128                    }
129                }
130                Err(e) => {
131                    return Err(HuginnNetTcpError::Parse(format!(
132                        "Error receiving packet: {e}"
133                    )));
134                }
135            }
136        }
137
138        Ok(())
139    }
140
141    /// Processes a single packet and extracts TCP information if present.
142    ///
143    /// # Parameters
144    /// - `packet`: The raw packet data.
145    /// - `connection_tracker`: Mutable reference to connection tracker.
146    ///
147    /// # Returns
148    /// A `Result` containing a `TcpAnalysisResult` or an error.
149    fn process_packet(
150        &self,
151        packet: &[u8],
152        connection_tracker: &mut TtlCache<Connection, SynData>,
153    ) -> Result<TcpAnalysisResult, HuginnNetTcpError> {
154        let ethernet = EthernetPacket::new(packet)
155            .ok_or_else(|| HuginnNetTcpError::Parse("Invalid Ethernet packet".to_string()))?;
156
157        match ethernet.get_ethertype() {
158            EtherTypes::Ipv4 => {
159                if let Some(ipv4) = Ipv4Packet::new(ethernet.payload()) {
160                    process::process_ipv4_packet(&ipv4, connection_tracker, self.matcher.as_ref())
161                } else {
162                    Ok(TcpAnalysisResult {
163                        syn: None,
164                        syn_ack: None,
165                        mtu: None,
166                        uptime: None,
167                    })
168                }
169            }
170            EtherTypes::Ipv6 => {
171                if let Some(ipv6) = Ipv6Packet::new(ethernet.payload()) {
172                    process::process_ipv6_packet(&ipv6, connection_tracker, self.matcher.as_ref())
173                } else {
174                    Ok(TcpAnalysisResult {
175                        syn: None,
176                        syn_ack: None,
177                        mtu: None,
178                        uptime: None,
179                    })
180                }
181            }
182            _ => Ok(TcpAnalysisResult {
183                syn: None,
184                syn_ack: None,
185                mtu: None,
186                uptime: None,
187            }),
188        }
189    }
190}