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
20pub 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
40pub struct HuginnNetTcp<'a> {
45 pub matcher: Option<SignatureMatcher<'a>>,
46 max_connections: usize,
47}
48
49impl<'a> HuginnNetTcp<'a> {
50 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 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 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 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 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}