Skip to main content

huginn_net/process/
mod.rs

1use crate::error::HuginnNetError;
2use crate::parser::packet::{parse_packet, IpPacket};
3use crate::AnalysisConfig;
4use huginn_net_http::error::HuginnNetHttpError;
5use huginn_net_http::http_process::{FlowKey, HttpProcessors, ObservableHttpPackage, TcpFlow};
6#[cfg(feature = "http-p0f-request")]
7use huginn_net_http::observable::ObservableHttpRequest;
8#[cfg(feature = "http-p0f-response")]
9use huginn_net_http::observable::ObservableHttpResponse;
10use huginn_net_tcp::error::HuginnNetTcpError;
11#[cfg(feature = "tcp-mtu")]
12use huginn_net_tcp::observable::ObservableMtu;
13#[cfg(any(feature = "tcp-syn", feature = "tcp-syn-ack"))]
14use huginn_net_tcp::observable::ObservableTcp;
15#[cfg(feature = "tcp-uptime")]
16use huginn_net_tcp::observable::ObservableUptime;
17use huginn_net_tcp::tcp_process::ObservableTCPPackage;
18use huginn_net_tcp::ConnectionTracker;
19use huginn_net_tls::error::HuginnNetTlsError;
20use huginn_net_tls::{ObservableTlsClient, ObservableTlsPackage};
21use pnet::packet::ip::IpNextHeaderProtocols;
22use pnet::packet::{ipv4::Ipv4Packet, ipv6::Ipv6Packet, tcp::TcpPacket, Packet};
23use std::net::IpAddr;
24use ttl_cache::TtlCache;
25
26#[derive(Clone)]
27pub struct IpPort {
28    pub ip: IpAddr,
29    pub port: u16,
30}
31
32pub struct ObservablePackage {
33    pub source: IpPort,
34    pub destination: IpPort,
35    #[cfg(feature = "tcp-syn")]
36    pub tcp_request: Option<ObservableTcp>,
37    #[cfg(feature = "tcp-syn-ack")]
38    pub tcp_response: Option<ObservableTcp>,
39    #[cfg(feature = "tcp-mtu")]
40    pub mtu: Option<ObservableMtu>,
41    #[cfg(feature = "tcp-uptime")]
42    pub client_uptime: Option<ObservableUptime>,
43    #[cfg(feature = "tcp-uptime")]
44    pub server_uptime: Option<ObservableUptime>,
45    #[cfg(feature = "http-p0f-request")]
46    pub http_request: Option<ObservableHttpRequest>,
47    #[cfg(feature = "http-p0f-response")]
48    pub http_response: Option<ObservableHttpResponse>,
49    pub tls_client: Option<ObservableTlsClient>,
50}
51
52impl ObservablePackage {
53    pub fn extract(
54        packet: &[u8],
55        connection_tracker: &mut ConnectionTracker,
56        http_flows: &mut TtlCache<FlowKey, TcpFlow>,
57        http_processors: &HttpProcessors,
58        config: &AnalysisConfig,
59    ) -> Result<Self, HuginnNetError> {
60        match parse_packet(packet) {
61            IpPacket::Ipv4(ip_data) => {
62                if let Some(ipv4) = Ipv4Packet::new(ip_data) {
63                    process_ipv4(connection_tracker, http_flows, http_processors, ipv4, config)
64                } else {
65                    Err(HuginnNetError::UnexpectedPackage("Invalid IPv4 packet".to_string()))
66                }
67            }
68            IpPacket::Ipv6(ip_data) => {
69                if let Some(ipv6) = Ipv6Packet::new(ip_data) {
70                    process_ipv6(connection_tracker, http_flows, http_processors, ipv6, config)
71                } else {
72                    Err(HuginnNetError::UnexpectedPackage("Invalid IPv6 packet".to_string()))
73                }
74            }
75            IpPacket::None => {
76                Err(HuginnNetError::UnexpectedPackage("No valid IP packet found".to_string()))
77            }
78        }
79    }
80}
81
82trait IpPacketProcessor: Packet {
83    fn is_tcp(&self) -> bool;
84    fn get_protocol_error(&self) -> String;
85    fn get_addresses(&self) -> (IpAddr, IpAddr);
86    fn process_http_with_data(
87        data: &[u8],
88        http_flows: &mut TtlCache<FlowKey, TcpFlow>,
89        http_processors: &HttpProcessors,
90    ) -> Result<ObservableHttpPackage, HuginnNetError>;
91    fn process_tcp_with_data(
92        data: &[u8],
93        connection_tracker: &mut ConnectionTracker,
94    ) -> Result<ObservableTCPPackage, HuginnNetError>;
95    fn process_tls_with_data(data: &[u8]) -> Result<ObservableTlsPackage, HuginnNetError>;
96}
97
98impl IpPacketProcessor for Ipv4Packet<'_> {
99    fn is_tcp(&self) -> bool {
100        self.get_next_level_protocol() == IpNextHeaderProtocols::Tcp
101    }
102
103    fn get_protocol_error(&self) -> String {
104        format!(
105            "unsupported IPv4 packet with non-TCP payload: {}",
106            self.get_next_level_protocol()
107        )
108    }
109
110    fn get_addresses(&self) -> (IpAddr, IpAddr) {
111        (IpAddr::V4(self.get_source()), IpAddr::V4(self.get_destination()))
112    }
113
114    fn process_http_with_data(
115        data: &[u8],
116        http_flows: &mut TtlCache<FlowKey, TcpFlow>,
117        http_processors: &HttpProcessors,
118    ) -> Result<ObservableHttpPackage, HuginnNetError> {
119        if let Some(packet) = Ipv4Packet::new(data) {
120            huginn_net_http::http_process::process_http_ipv4(&packet, http_flows, http_processors)
121                .map_err(|e| match e {
122                    HuginnNetHttpError::Parse(msg) => HuginnNetError::Parse(msg),
123                    HuginnNetHttpError::UnsupportedProtocol(msg) => {
124                        HuginnNetError::UnsupportedProtocol(msg)
125                    }
126                    HuginnNetHttpError::Misconfiguration(msg) => HuginnNetError::Parse(msg),
127                    HuginnNetHttpError::NoSettingsFrame => {
128                        HuginnNetError::Parse("No SETTINGS frame found".to_string())
129                    }
130                    HuginnNetHttpError::MalformedPseudoHeaders(msg) => HuginnNetError::Parse(msg),
131                })
132        } else {
133            Err(HuginnNetError::UnexpectedPackage("Invalid IPv4 packet data".to_string()))
134        }
135    }
136
137    fn process_tcp_with_data(
138        data: &[u8],
139        connection_tracker: &mut ConnectionTracker,
140    ) -> Result<ObservableTCPPackage, HuginnNetError> {
141        if let Some(packet) = Ipv4Packet::new(data) {
142            huginn_net_tcp::tcp_process::process_tcp_ipv4(&packet, connection_tracker).map_err(
143                |e| match e {
144                    HuginnNetTcpError::Parse(msg) => HuginnNetError::Parse(msg),
145                    HuginnNetTcpError::UnsupportedProtocol(msg) => {
146                        HuginnNetError::UnsupportedProtocol(msg)
147                    }
148                    HuginnNetTcpError::InvalidTcpFlags(flags) => {
149                        HuginnNetError::InvalidTcpFlags(flags)
150                    }
151                    HuginnNetTcpError::UnexpectedPackage(msg) => {
152                        HuginnNetError::UnexpectedPackage(msg)
153                    }
154                    HuginnNetTcpError::Misconfiguration(msg) => HuginnNetError::Parse(msg),
155                },
156            )
157        } else {
158            Err(HuginnNetError::UnexpectedPackage("Invalid IPv4 packet data".to_string()))
159        }
160    }
161
162    fn process_tls_with_data(data: &[u8]) -> Result<ObservableTlsPackage, HuginnNetError> {
163        if let Some(packet) = Ipv4Packet::new(data) {
164            huginn_net_tls::process_tls_ipv4(&packet).map_err(|e| match e {
165                HuginnNetTlsError::Parse(msg) => HuginnNetError::Parse(msg),
166                HuginnNetTlsError::UnsupportedProtocol(msg) => {
167                    HuginnNetError::UnsupportedProtocol(msg)
168                }
169                HuginnNetTlsError::Misconfiguration(msg) => HuginnNetError::Parse(msg),
170                HuginnNetTlsError::NotClientHello => {
171                    HuginnNetError::Parse("TLS record is not a ClientHello".to_string())
172                }
173                HuginnNetTlsError::Unknown => {
174                    HuginnNetError::Parse("Unknown TLS error".to_string())
175                }
176            })
177        } else {
178            Err(HuginnNetError::UnexpectedPackage("Invalid IPv4 packet data".to_string()))
179        }
180    }
181}
182
183impl IpPacketProcessor for Ipv6Packet<'_> {
184    fn is_tcp(&self) -> bool {
185        self.get_next_header() == IpNextHeaderProtocols::Tcp
186    }
187
188    fn get_protocol_error(&self) -> String {
189        format!("IPv6 packet with non-TCP payload: {}", self.get_next_header())
190    }
191
192    fn get_addresses(&self) -> (IpAddr, IpAddr) {
193        (IpAddr::V6(self.get_source()), IpAddr::V6(self.get_destination()))
194    }
195
196    fn process_http_with_data(
197        data: &[u8],
198        http_flows: &mut TtlCache<FlowKey, TcpFlow>,
199        http_processors: &HttpProcessors,
200    ) -> Result<ObservableHttpPackage, HuginnNetError> {
201        if let Some(packet) = Ipv6Packet::new(data) {
202            huginn_net_http::http_process::process_http_ipv6(&packet, http_flows, http_processors)
203                .map_err(|e| match e {
204                    HuginnNetHttpError::Parse(msg) => HuginnNetError::Parse(msg),
205                    HuginnNetHttpError::UnsupportedProtocol(msg) => {
206                        HuginnNetError::UnsupportedProtocol(msg)
207                    }
208                    HuginnNetHttpError::Misconfiguration(msg) => HuginnNetError::Parse(msg),
209                    HuginnNetHttpError::NoSettingsFrame => {
210                        HuginnNetError::Parse("No SETTINGS frame found".to_string())
211                    }
212                    HuginnNetHttpError::MalformedPseudoHeaders(msg) => HuginnNetError::Parse(msg),
213                })
214        } else {
215            Err(HuginnNetError::UnexpectedPackage("Invalid IPv6 packet data".to_string()))
216        }
217    }
218
219    fn process_tcp_with_data(
220        data: &[u8],
221        connection_tracker: &mut ConnectionTracker,
222    ) -> Result<ObservableTCPPackage, HuginnNetError> {
223        if let Some(packet) = Ipv6Packet::new(data) {
224            huginn_net_tcp::tcp_process::process_tcp_ipv6(&packet, connection_tracker).map_err(
225                |e| match e {
226                    HuginnNetTcpError::Parse(msg) => HuginnNetError::Parse(msg),
227                    HuginnNetTcpError::UnsupportedProtocol(msg) => {
228                        HuginnNetError::UnsupportedProtocol(msg)
229                    }
230                    HuginnNetTcpError::InvalidTcpFlags(flags) => {
231                        HuginnNetError::InvalidTcpFlags(flags)
232                    }
233                    HuginnNetTcpError::UnexpectedPackage(msg) => {
234                        HuginnNetError::UnexpectedPackage(msg)
235                    }
236                    HuginnNetTcpError::Misconfiguration(msg) => HuginnNetError::Parse(msg),
237                },
238            )
239        } else {
240            Err(HuginnNetError::UnexpectedPackage("Invalid IPv6 packet data".to_string()))
241        }
242    }
243
244    fn process_tls_with_data(data: &[u8]) -> Result<ObservableTlsPackage, HuginnNetError> {
245        if let Some(packet) = Ipv6Packet::new(data) {
246            huginn_net_tls::process_tls_ipv6(&packet).map_err(|e| match e {
247                huginn_net_tls::error::HuginnNetTlsError::Parse(msg) => HuginnNetError::Parse(msg),
248                huginn_net_tls::error::HuginnNetTlsError::UnsupportedProtocol(msg) => {
249                    HuginnNetError::UnsupportedProtocol(msg)
250                }
251                huginn_net_tls::error::HuginnNetTlsError::Misconfiguration(msg) => {
252                    HuginnNetError::Parse(msg)
253                }
254                huginn_net_tls::error::HuginnNetTlsError::NotClientHello => {
255                    HuginnNetError::Parse("TLS record is not a ClientHello".to_string())
256                }
257                huginn_net_tls::error::HuginnNetTlsError::Unknown => {
258                    HuginnNetError::Parse("Unknown TLS error".to_string())
259                }
260            })
261        } else {
262            Err(HuginnNetError::UnexpectedPackage("Invalid IPv6 packet data".to_string()))
263        }
264    }
265}
266
267fn execute_analysis<P: IpPacketProcessor>(
268    packet_data: &[u8],
269    connection_tracker: &mut ConnectionTracker,
270    http_flows: &mut TtlCache<FlowKey, TcpFlow>,
271    http_processors: &HttpProcessors,
272    config: &AnalysisConfig,
273    source: IpPort,
274    destination: IpPort,
275) -> Result<ObservablePackage, HuginnNetError> {
276    let http_response = if config.http_enabled {
277        P::process_http_with_data(packet_data, http_flows, http_processors)?
278    } else {
279        ObservableHttpPackage::empty()
280    };
281
282    let tcp_response: ObservableTCPPackage = if config.tcp_enabled {
283        P::process_tcp_with_data(packet_data, connection_tracker)?
284    } else {
285        ObservableTCPPackage::empty()
286    };
287
288    let tls_response = if config.tls_enabled {
289        P::process_tls_with_data(packet_data)?
290    } else {
291        ObservableTlsPackage { tls_client: None }
292    };
293
294    handle_http_tcp_tlc(Ok(http_response), Ok(tcp_response), Ok(tls_response), source, destination)
295}
296
297fn process_ip<P: IpPacketProcessor>(
298    connection_tracker: &mut ConnectionTracker,
299    http_flows: &mut TtlCache<FlowKey, TcpFlow>,
300    http_processors: &HttpProcessors,
301    packet: P,
302    config: &AnalysisConfig,
303) -> Result<ObservablePackage, HuginnNetError> {
304    if !packet.is_tcp() {
305        return Err(HuginnNetError::UnsupportedProtocol(packet.get_protocol_error()));
306    }
307
308    let (source_ip, destination_ip) = packet.get_addresses();
309    let tcp_ports = TcpPacket::new(packet.payload())
310        .ok_or_else(|| HuginnNetError::UnexpectedPackage("Invalid TCP packet".to_string()))?;
311
312    let source = IpPort { ip: source_ip, port: tcp_ports.get_source() };
313    let destination = IpPort { ip: destination_ip, port: tcp_ports.get_destination() };
314
315    let packet_data = packet.packet();
316
317    execute_analysis::<P>(
318        packet_data,
319        connection_tracker,
320        http_flows,
321        http_processors,
322        config,
323        source,
324        destination,
325    )
326}
327
328pub fn process_ipv4(
329    connection_tracker: &mut ConnectionTracker,
330    http_flows: &mut TtlCache<FlowKey, TcpFlow>,
331    http_processors: &HttpProcessors,
332    packet: Ipv4Packet,
333    config: &AnalysisConfig,
334) -> Result<ObservablePackage, HuginnNetError> {
335    process_ip(connection_tracker, http_flows, http_processors, packet, config)
336}
337
338pub fn process_ipv6(
339    connection_tracker: &mut ConnectionTracker,
340    http_flows: &mut TtlCache<FlowKey, TcpFlow>,
341    http_processors: &HttpProcessors,
342    packet: Ipv6Packet,
343    config: &AnalysisConfig,
344) -> Result<ObservablePackage, HuginnNetError> {
345    process_ip(connection_tracker, http_flows, http_processors, packet, config)
346}
347
348fn handle_http_tcp_tlc(
349    http_response: Result<ObservableHttpPackage, HuginnNetError>,
350    tcp_response: Result<ObservableTCPPackage, HuginnNetError>,
351    tls_response: Result<ObservableTlsPackage, HuginnNetError>,
352    source: IpPort,
353    destination: IpPort,
354) -> Result<ObservablePackage, HuginnNetError> {
355    match (http_response, tcp_response, tls_response) {
356        #[cfg_attr(
357            not(any(
358                feature = "tcp-syn",
359                feature = "tcp-syn-ack",
360                feature = "tcp-mtu",
361                feature = "tcp-uptime",
362                feature = "http-p0f-request",
363                feature = "http-p0f-response"
364            )),
365            allow(unused_variables)
366        )]
367        (Ok(http_package), Ok(tcp_package), Ok(tls_package)) => {
368            #[cfg(not(any(
369                feature = "tcp-syn",
370                feature = "tcp-syn-ack",
371                feature = "tcp-mtu",
372                feature = "tcp-uptime"
373            )))]
374            let _ = &tcp_package;
375            #[cfg(not(any(feature = "http-p0f-request", feature = "http-p0f-response")))]
376            let _ = &http_package;
377            Ok(ObservablePackage {
378                source,
379                destination,
380                #[cfg(feature = "tcp-syn")]
381                tcp_request: tcp_package.tcp_request,
382                #[cfg(feature = "tcp-syn-ack")]
383                tcp_response: tcp_package.tcp_response,
384                #[cfg(feature = "tcp-mtu")]
385                mtu: tcp_package.mtu,
386                #[cfg(feature = "tcp-uptime")]
387                client_uptime: tcp_package.client_uptime,
388                #[cfg(feature = "tcp-uptime")]
389                server_uptime: tcp_package.server_uptime,
390                #[cfg(feature = "http-p0f-request")]
391                http_request: http_package.http_request,
392                #[cfg(feature = "http-p0f-response")]
393                http_response: http_package.http_response,
394                tls_client: tls_package.tls_client,
395            })
396        }
397        (Err(http_err), _, _) => Err(http_err),
398        (_, Err(tcp_err), _) => Err(tcp_err),
399        (_, _, Err(tls_err)) => Err(tls_err),
400    }
401}