1use chrono::{DateTime, FixedOffset};
2use pcap_file::pcap;
3use pnet::util::MacAddr;
4use rand_distr::Uniform;
5use rand_distr::weighted::WeightedIndex;
6use serde::{Deserialize, Serialize};
7use std::cmp::Ordering;
8use std::fmt::{Debug, Display};
9use std::net::Ipv4Addr;
10use std::str::FromStr;
11use std::time::Duration;
12use strum::{Display, EnumIter, EnumString, IntoEnumIterator};
13use thingbuf::Recycle;
14
15#[derive(Debug, Clone)]
17pub struct SeededData<T: Clone> {
18 pub seed: u64,
19 pub data: T,
20}
21
22#[derive(Debug, Clone)]
24pub struct TimePoint {
25 pub unix_time: Duration,
26 pub date_time: DateTime<FixedOffset>,
27}
28
29#[allow(clippy::upper_case_acronyms)]
33#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash, EnumString)]
34#[serde(rename_all_fields = "UPPERCASE")]
35#[strum(
36 parse_err_fn = String::from,
37 parse_err_ty = String
38)]
39pub enum L4Proto {
40 #[serde(alias = "tcp")]
41 TCP,
42 #[serde(alias = "udp")]
43 UDP,
44 #[serde(alias = "icmp")]
45 ICMP,
46}
47
48#[allow(clippy::upper_case_acronyms)]
51#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash, EnumString)]
52#[strum(
53 parse_err_fn = String::from,
54 parse_err_ty = String
55)]
56pub enum TCPConnState {
57 SF,
59 SH,
61 RST,
63 S0,
65 REJ,
67 #[strum(serialize = "none")]
69 NoState,
70}
71
72impl Display for L4Proto {
73 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74 match self {
75 L4Proto::TCP => write!(f, "TCP"),
76 L4Proto::UDP => write!(f, "UDP"),
77 L4Proto::ICMP => write!(f, "ICMP"),
78 }
79 }
80}
81
82impl L4Proto {
83 pub fn iter() -> [L4Proto; 2] {
84 [L4Proto::TCP, L4Proto::UDP] }
87
88 pub fn get_protocol_number(&self) -> u8 {
89 match &self {
90 L4Proto::TCP => 6,
91 L4Proto::UDP => 17,
92 L4Proto::ICMP => 1,
93 }
94 }
95
96 pub fn wrap(&self, d: FlowData, c: Option<TCPConnState>) -> Flow {
97 match &self {
98 L4Proto::TCP => Flow::TCP(d, c.unwrap_or(TCPConnState::SF)), L4Proto::UDP => {
100 Flow::UDP(d)
102 }
103 L4Proto::ICMP => {
104 Flow::ICMP(d)
106 }
107 }
108 }
109}
110
111#[derive(Serialize, Deserialize, Debug, Clone, Copy, Eq, Hash, PartialEq, Display, EnumIter)]
112#[allow(clippy::upper_case_acronyms)]
113#[serde(rename_all = "lowercase")]
114pub enum L7Proto {
116 HTTP,
118 HTTPS,
119 QUIC,
120 SSH,
121 DNS,
122 DHCP,
123 SMTP,
124 Telnet,
125 IMAPS,
126 MQTT,
127 KMS,
128 MulticastDNS,
129 FTP,
130 FTPData,
131 LDAP,
132 NTP,
133 Kerberos,
134 DCERPC,
135 SMB,
136 NonDefault(&'static str),
141}
142
143impl FromStr for L7Proto {
144 type Err = String;
145
146 fn from_str(original_s: &str) -> Result<Self, String> {
149 let binding = original_s.to_uppercase().replace(' ', "");
150 let s = binding.as_str().trim();
151 Ok(if s.contains("HTTPS") || s.contains("SSL") {
152 L7Proto::HTTPS
153 } else if s.contains("HTTP") {
154 L7Proto::HTTP
156 } else if s.contains("QUIC") {
157 L7Proto::QUIC
158 } else if s.contains("SSH") {
159 L7Proto::SSH
160 } else if s.contains("MULTICASTDNS") {
161 L7Proto::MulticastDNS
162 } else if s.contains("DNS") {
163 L7Proto::DNS
165 } else if s.contains("DHCP") {
166 L7Proto::DHCP
167 } else if s.contains("SMTP") {
168 L7Proto::SMTP
170 } else if s.contains("TELNET") {
171 L7Proto::Telnet
172 } else if s.contains("IMAP") {
173 L7Proto::IMAPS
175 } else if s.contains("MQTT") {
176 L7Proto::MQTT
177 } else if s.contains("KMS") {
178 L7Proto::KMS
179 } else if s.contains("FTP-DATA") {
180 L7Proto::FTPData
181 } else if s.contains("FTP") {
182 L7Proto::FTP
184 } else if s.contains("LDAP") {
185 L7Proto::LDAP
186 } else if s.contains("NTP") {
187 L7Proto::NTP
188 } else if s.contains("DCE_RPC") || s.contains("DCERPC") {
189 L7Proto::DCERPC
190 } else if s.contains("KRB") || s.contains("KERBEROS") {
191 L7Proto::Kerberos
192 } else if s.contains("SMB") {
193 L7Proto::SMB
194 } else {
195 log::info!("Non-default protocol: {original_s}");
196 L7Proto::NonDefault(String::from(original_s).leak())
197 })
198 }
199}
200
201impl L7Proto {
202 pub fn all_names() -> Vec<String> {
204 L7Proto::iter().map(|p| p.to_string()).collect()
205 }
206
207 pub fn ui_label(&self) -> &'static str {
210 match self {
211 L7Proto::HTTP => "HTTP",
212 L7Proto::HTTPS => "HTTPS",
213 L7Proto::QUIC => "QUIC",
214 L7Proto::SSH => "SSH",
215 L7Proto::DNS => "DNS",
216 L7Proto::DHCP => "DHCP",
217 L7Proto::SMTP => "SMTP",
218 L7Proto::Telnet => "Telnet",
219 L7Proto::IMAPS => "IMAPS",
220 L7Proto::MQTT => "MQTT",
221 L7Proto::KMS => "KMS",
222 L7Proto::MulticastDNS => "mDNS",
223 L7Proto::FTP => "FTP",
224 L7Proto::FTPData => "FTP (data)",
225 L7Proto::LDAP => "LDAP",
226 L7Proto::NTP => "NTP",
227 L7Proto::Kerberos => "Kerberos",
228 L7Proto::SMB => "SMB",
229 L7Proto::DCERPC => "DCE/RPC",
230 L7Proto::NonDefault(s) => s,
231 }
232 }
233
234 pub fn get_default_src_port(&self) -> Port {
236 Port::Random
237 }
238
239 pub fn get_default_dst_port(&self) -> Option<Port> {
241 match self {
242 L7Proto::HTTP => Some(Port::Fixed(80)),
243 L7Proto::HTTPS | L7Proto::QUIC => Some(Port::Fixed(443)),
244 L7Proto::SSH => Some(Port::Fixed(22)),
245 L7Proto::DNS => Some(Port::Fixed(53)),
246 L7Proto::DHCP => Some(Port::Fixed(67)),
247 L7Proto::SMTP => Some(Port::Fixed(587)),
248 L7Proto::Telnet => Some(Port::Fixed(23)),
249 L7Proto::IMAPS => Some(Port::Fixed(993)),
250 L7Proto::MQTT => Some(Port::Fixed(1883)),
251 L7Proto::KMS => Some(Port::Fixed(1688)),
252 L7Proto::MulticastDNS => Some(Port::Fixed(5353)),
253 L7Proto::FTP => Some(Port::Fixed(21)),
254 L7Proto::FTPData => Some(Port::Random),
255 L7Proto::LDAP => Some(Port::Fixed(389)), L7Proto::NTP => Some(Port::Fixed(123)),
257 L7Proto::Kerberos => Some(Port::Fixed(88)),
258 L7Proto::SMB => Some(Port::Fixed(445)),
259 L7Proto::DCERPC => Some(Port::Fixed(135)),
260 L7Proto::NonDefault(_) => None,
261 }
262 }
263}
264
265#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
266pub enum Port {
268 Fixed(u16),
270 Random, }
273
274#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
275pub struct L7ProtoWithPort {
277 proto: L7Proto,
278 port: Port,
279}
280
281impl FromStr for L7ProtoWithPort {
282 type Err = String;
283
284 fn from_str(s: &str) -> Result<Self, Self::Err> {
285 let v: Vec<String> = s.split(':').map(ToString::to_string).collect();
286 assert!(!v.is_empty() && v.len() <= 2);
287 let port: Option<Port> = if v.len() == 2 {
288 if v[1].to_lowercase() == "random" {
289 Some(Port::Random)
291 } else {
292 Some(Port::Fixed(
293 v[1].parse::<u16>().expect("Cannot parse the port in {s}"),
294 ))
295 }
296 } else {
297 None
298 };
299 let proto: L7Proto =
300 L7Proto::from_str(v[0].to_uppercase().replace(' ', "").as_str().trim()).unwrap();
301 if port.is_none() && proto.get_default_dst_port().is_none() {
302 Err(format!(
303 "Non-default protocol {s} must include a port number"
304 ))
305 } else {
306 let port = port.unwrap_or_else(|| proto.get_default_dst_port().unwrap());
307 Ok(L7ProtoWithPort { proto, port })
308 }
309 }
310}
311
312impl L7ProtoWithPort {
313 pub fn get_proto(&self) -> L7Proto {
314 self.proto
315 }
316
317 pub fn get_port(&self) -> Port {
318 self.port
319 }
320}
321
322#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Default, EnumString, Hash)]
323#[strum(ascii_case_insensitive)]
324#[serde(rename_all = "lowercase")]
325pub enum OS {
327 #[default]
328 Linux,
329 Windows,
330 Router,
331}
332
333impl Display for OS {
334 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
335 match self {
336 OS::Linux => write!(f, "Linux"),
337 OS::Windows => write!(f, "Windows"),
338 OS::Router => write!(f, "Router"),
339 }
340 }
341}
342
343impl OS {
344 pub fn get_initial_ttl(&self) -> u8 {
345 match self {
346 OS::Linux => 64,
347 OS::Windows => 128,
348 OS::Router => 255,
349 }
350 }
351
352 pub fn get_ephemeral_port_distr(&self) -> Uniform<u16> {
354 match self {
355 OS::Linux | OS::Router => Uniform::new(32768, 60999).unwrap(),
356 OS::Windows => Uniform::new(49152, 65535).unwrap(),
357 }
358 }
359}
360
361#[allow(clippy::upper_case_acronyms)]
363#[derive(Debug, Clone, Copy)]
364pub enum Flow {
365 TCP(FlowData, TCPConnState),
366 UDP(FlowData),
367 ICMP(FlowData),
368}
369
370impl Flow {
371 pub fn get_data(&self) -> &FlowData {
372 match &self {
373 Flow::TCP(data, _) | Flow::UDP(data) | Flow::ICMP(data) => data,
374 }
375 }
376
377 pub fn get_data_mut(&mut self) -> &mut FlowData {
378 match self {
379 Flow::TCP(data, _) | Flow::UDP(data) | Flow::ICMP(data) => data,
380 }
381 }
382
383 pub fn get_flow_id(&self) -> FlowId {
384 let d = self.get_data();
385 FlowId {
386 protocol: self.get_proto(),
387 src_ip: d.src_ip,
388 dst_ip: d.dst_ip,
389 src_port: d.src_port,
390 dst_port: d.dst_port,
391 }
392 }
393
394 pub fn get_proto(&self) -> L4Proto {
395 match &self {
396 Flow::TCP(_, _) => L4Proto::TCP,
397 Flow::UDP(_) => L4Proto::UDP,
398 Flow::ICMP(_) => L4Proto::ICMP,
399 }
400 }
401}
402
403#[derive(Debug, Clone, Copy)]
405pub struct FlowData {
406 pub src_ip: Ipv4Addr,
408 pub dst_ip: Ipv4Addr,
409 pub src_os: OS,
410 pub dst_os: OS,
411 pub src_mac: MacAddr,
412 pub dst_mac: MacAddr,
413 pub src_port: u16,
414 pub dst_port: u16,
415 pub src_ttl: u8,
416 pub dst_ttl: u8,
417 pub packets_count_cluster: usize,
418 pub fwd_packets_count: usize,
419 pub bwd_packets_count: usize,
420 pub timestamp: Duration,
421 pub l7_proto: L7Proto,
422}
423
424impl From<Flow> for FlowData {
425 fn from(f: Flow) -> FlowData {
426 match f {
427 Flow::TCP(data, _) | Flow::UDP(data) | Flow::ICMP(data) => data,
428 }
429 }
430}
431
432#[derive(Debug, Clone)]
435pub enum PayloadType {
437 Empty,
439 Binary(&'static Vec<Vec<u8>>, WeightedIndex<u64>),
441}
442
443pub(crate) trait EdgeType: Debug + Clone {
444 fn get_payload_type(&self) -> &PayloadType;
445 fn get_direction(&self) -> PacketDirection;
446}
447
448#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
452pub enum PacketDirection {
453 Forward,
455 Backward,
457}
458
459impl PacketDirection {
460 pub fn into_reverse(self) -> PacketDirection {
461 match self {
462 PacketDirection::Forward => PacketDirection::Backward,
463 PacketDirection::Backward => PacketDirection::Forward,
464 }
465 }
466}
467
468#[derive(Debug, Clone)]
470pub enum Payload {
471 Empty,
473 Binary(&'static Vec<u8>),
475 Random(usize),
477}
478
479impl Payload {
480 pub fn get_payload_size(&self) -> usize {
481 match &self {
482 Payload::Empty => 0,
483 Payload::Binary(l) => l.len(),
484 Payload::Random(len) => *len,
485 }
486 }
487}
488
489pub trait PacketInfo: Clone + Debug {
491 #[allow(unused)]
492 fn get_direction(&self) -> PacketDirection;
493 fn get_ts(&self) -> Duration;
494 fn set_ts(&mut self, ts: Duration);
495}
496
497#[derive(Debug, Clone)]
498pub struct PacketsIR<T: PacketInfo> {
500 pub packets_info: Vec<T>,
501 pub flow: Flow,
502}
503
504#[derive(Debug, Clone, Eq, PartialEq)]
506pub struct Packet {
508 pub timestamp: Duration,
509 pub data: Vec<u8>,
510}
511
512impl From<pcap::PcapPacket<'_>> for Packet {
513 fn from(p: pcap::PcapPacket<'_>) -> Packet {
514 Packet {
515 timestamp: p.timestamp,
516 data: p.data.into_owned(),
517 }
518 }
519}
520
521impl Packet {
522 pub fn get_mutable_ip_packet(&mut self) -> Option<pnet_packet::ipv4::MutableIpv4Packet<'_>> {
523 let eth_offset = pnet_packet::ethernet::EthernetPacket::minimum_packet_size();
524 let ip_packet = pnet_packet::ipv4::MutableIpv4Packet::new(&mut self.data[eth_offset..])?;
525 Some(ip_packet)
526 }
527}
528
529impl Ord for Packet {
531 fn cmp(&self, other: &Self) -> Ordering {
532 if self.timestamp == other.timestamp {
533 self.data.cmp(&other.data) } else {
535 self.timestamp.cmp(&other.timestamp)
536 }
537 }
538}
539
540impl PartialOrd for Packet {
541 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
542 Some(self.cmp(other))
543 }
544}
545
546#[derive(Debug, Clone)]
547pub struct Packets {
549 pub packets: Vec<Packet>,
550 pub directions: Vec<PacketDirection>,
551 pub timestamps: Vec<Duration>,
552 pub flow: Flow,
553}
554
555impl Packets {
556 pub fn clear(&mut self) {
557 self.packets.clear();
558 self.directions.clear();
559 self.timestamps.clear();
560 }
561
562 pub fn reverse(&mut self) {
563 for d in &mut self.directions {
564 *d = d.into_reverse();
565 }
566 let data = self.flow.get_data_mut();
567 (data.src_ip, data.dst_ip) = (data.dst_ip, data.src_ip);
568 (data.src_port, data.dst_port) = (data.dst_port, data.src_port);
569 (data.src_ttl, data.dst_ttl) = (data.dst_ttl, data.src_ttl);
570 (data.fwd_packets_count, data.bwd_packets_count) =
571 (data.bwd_packets_count, data.fwd_packets_count);
572 }
573}
574
575impl Default for Packets {
576 fn default() -> Self {
577 Packets {
578 packets: Vec::with_capacity(150),
579 directions: Vec::with_capacity(150),
580 timestamps: Vec::with_capacity(150),
581 flow: Flow::UDP(FlowData {
582 src_ip: Ipv4Addr::UNSPECIFIED,
583 dst_ip: Ipv4Addr::UNSPECIFIED,
584 src_os: OS::Linux,
585 dst_os: OS::Linux,
586 src_mac: MacAddr::zero(),
587 dst_mac: MacAddr::zero(),
588 src_port: 0,
589 dst_port: 0,
590 src_ttl: 0,
591 dst_ttl: 0,
592 packets_count_cluster: 0,
593 fwd_packets_count: 0,
594 bwd_packets_count: 0,
595 timestamp: Duration::new(0, 0),
596 l7_proto: L7Proto::HTTP,
597 }),
598 }
599 }
600}
601
602pub struct PacketsRecycler {}
604
605impl Recycle<Packets> for PacketsRecycler {
606 fn new_element(&self) -> Packets {
608 Packets::default()
609 }
610 fn recycle(&self, element: &mut Packets) {
611 element.clear();
612 }
613}
614
615#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
616pub struct FlowId {
618 pub protocol: L4Proto,
619 pub src_ip: Ipv4Addr,
620 pub dst_ip: Ipv4Addr,
621 pub src_port: u16,
622 pub dst_port: u16,
623}
624
625impl FlowId {
626 pub fn is_compatible(&self, f: &Flow) -> bool {
629 let d = f.get_data();
630 self.src_ip == d.src_ip
631 && self.dst_ip == d.dst_ip
632 && self.src_port == d.src_port
633 && self.dst_port == d.dst_port
634 && self.protocol == f.get_proto()
635 }
636
637 pub fn normalize(&mut self) {
638 if self.src_ip > self.dst_ip
639 || (self.src_ip == self.dst_ip && self.src_port > self.dst_port)
640 {
641 std::mem::swap(&mut self.src_ip, &mut self.dst_ip);
642 std::mem::swap(&mut self.src_port, &mut self.dst_port);
643 }
644 }
645}