huginn_net_http/
lib.rs

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