Skip to content

Commit f7af4dd

Browse files
committed
refactor alert
1 parent b2ff062 commit f7af4dd

File tree

4 files changed

+275
-212
lines changed

4 files changed

+275
-212
lines changed

oryx-tui/src/alert/alert.rs

+111
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
use ratatui::{
2+
layout::{Alignment, Constraint, Direction, Layout, Margin, Rect},
3+
style::{Color, Style, Stylize},
4+
text::{Line, Span},
5+
widgets::{Block, BorderType, Borders, Padding},
6+
Frame,
7+
};
8+
use std::sync::{atomic::Ordering, Arc, Mutex};
9+
10+
use crate::packets::packet::AppPacket;
11+
12+
use super::syn_flood::SynFlood;
13+
14+
#[derive(Debug)]
15+
pub struct Alert {
16+
syn_flood: SynFlood,
17+
pub flash_count: usize,
18+
pub detected: bool,
19+
}
20+
21+
impl Alert {
22+
pub fn new(packets: Arc<Mutex<Vec<AppPacket>>>) -> Self {
23+
Self {
24+
syn_flood: SynFlood::new(packets),
25+
flash_count: 1,
26+
detected: false,
27+
}
28+
}
29+
30+
pub fn check(&mut self) {
31+
if self.syn_flood.detected.load(Ordering::Relaxed) {
32+
self.detected = true;
33+
self.flash_count += 1;
34+
} else {
35+
self.detected = false;
36+
self.flash_count = 1;
37+
}
38+
}
39+
40+
pub fn render(&self, frame: &mut Frame, block: Rect) {
41+
frame.render_widget(
42+
Block::default()
43+
.title({
44+
Line::from(vec![
45+
Span::from(" Packet ").fg(Color::DarkGray),
46+
Span::from(" Stats ").fg(Color::DarkGray),
47+
{
48+
if self.detected {
49+
if self.flash_count % 12 == 0 {
50+
Span::from(" Alert 󰐼 ").fg(Color::White).bg(Color::Red)
51+
} else {
52+
Span::from(" Alert 󰐼 ").bg(Color::Red)
53+
}
54+
} else {
55+
Span::styled(
56+
" Alert ",
57+
Style::default().bg(Color::Green).fg(Color::White).bold(),
58+
)
59+
}
60+
},
61+
])
62+
})
63+
.title_alignment(Alignment::Left)
64+
.padding(Padding::top(1))
65+
.borders(Borders::ALL)
66+
.style(Style::default())
67+
.border_type(BorderType::default())
68+
.border_style(Style::default().green()),
69+
block.inner(Margin {
70+
horizontal: 1,
71+
vertical: 0,
72+
}),
73+
);
74+
75+
if !self.detected {
76+
return;
77+
}
78+
79+
let syn_flood_block = Layout::default()
80+
.direction(Direction::Vertical)
81+
.constraints([Constraint::Length(10), Constraint::Fill(1)])
82+
.flex(ratatui::layout::Flex::SpaceBetween)
83+
.margin(2)
84+
.split(block)[0];
85+
86+
let syn_flood_block = Layout::default()
87+
.direction(Direction::Vertical)
88+
.constraints([
89+
Constraint::Fill(1),
90+
Constraint::Max(60),
91+
Constraint::Fill(1),
92+
])
93+
.flex(ratatui::layout::Flex::SpaceBetween)
94+
.margin(2)
95+
.split(syn_flood_block)[1];
96+
97+
self.syn_flood.render(frame, syn_flood_block);
98+
}
99+
100+
pub fn title_span(&self) -> Span<'_> {
101+
if self.detected {
102+
if self.flash_count % 12 == 0 {
103+
Span::from(" Alert 󰐼 ").fg(Color::White).bg(Color::Red)
104+
} else {
105+
Span::from(" Alert 󰐼 ").fg(Color::Red)
106+
}
107+
} else {
108+
Span::from(" Alert ").fg(Color::DarkGray)
109+
}
110+
}
111+
}

oryx-tui/src/alert/syn_flood.rs

+146
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
use std::{
2+
collections::HashMap,
3+
net::IpAddr,
4+
sync::{atomic::AtomicBool, Arc, Mutex},
5+
thread,
6+
time::Duration,
7+
};
8+
9+
use ratatui::{
10+
layout::{Alignment, Constraint, Flex, Rect},
11+
style::{Style, Stylize},
12+
text::Line,
13+
widgets::{Block, Borders, Row, Table},
14+
Frame,
15+
};
16+
17+
use crate::packets::{
18+
network::{IpPacket, IpProto},
19+
packet::AppPacket,
20+
};
21+
22+
const WIN_SIZE: usize = 100_000;
23+
24+
#[derive(Debug)]
25+
pub struct SynFlood {
26+
pub detected: Arc<AtomicBool>,
27+
pub map: Arc<Mutex<HashMap<IpAddr, usize>>>,
28+
}
29+
30+
impl SynFlood {
31+
pub fn new(packets: Arc<Mutex<Vec<AppPacket>>>) -> Self {
32+
let map: Arc<Mutex<HashMap<IpAddr, usize>>> = Arc::new(Mutex::new(HashMap::new()));
33+
34+
let detected = Arc::new(AtomicBool::new(false));
35+
36+
thread::spawn({
37+
let packets = packets.clone();
38+
let map = map.clone();
39+
let detected = detected.clone();
40+
move || loop {
41+
let start_index = {
42+
let packets = packets.lock().unwrap();
43+
packets.len().saturating_sub(1)
44+
};
45+
thread::sleep(Duration::from_secs(5));
46+
let app_packets = {
47+
let packets = packets.lock().unwrap();
48+
packets.clone()
49+
};
50+
51+
let mut map = map.lock().unwrap();
52+
map.clear();
53+
54+
if app_packets.len() < WIN_SIZE {
55+
continue;
56+
}
57+
58+
let mut nb_syn_packets = 0;
59+
60+
app_packets[start_index..app_packets.len().saturating_sub(1)]
61+
.iter()
62+
.for_each(|packet| {
63+
if let AppPacket::Ip(ip_packet) = packet {
64+
if let IpPacket::V4(ipv4_packet) = ip_packet {
65+
if let IpProto::Tcp(tcp_packet) = ipv4_packet.proto {
66+
if tcp_packet.syn == 1 {
67+
nb_syn_packets += 1;
68+
if let Some(count) =
69+
map.get_mut(&IpAddr::V4(ipv4_packet.src_ip))
70+
{
71+
*count += 1;
72+
} else {
73+
map.insert(IpAddr::V4(ipv4_packet.src_ip), 1);
74+
}
75+
}
76+
}
77+
}
78+
if let IpPacket::V6(ipv6_packet) = ip_packet {
79+
if let IpProto::Tcp(tcp_packet) = ipv6_packet.proto {
80+
if tcp_packet.syn == 1 {
81+
nb_syn_packets += 1;
82+
if let Some(count) =
83+
map.get_mut(&IpAddr::V6(ipv6_packet.src_ip))
84+
{
85+
*count += 1;
86+
} else {
87+
map.insert(IpAddr::V6(ipv6_packet.src_ip), 1);
88+
}
89+
}
90+
}
91+
}
92+
}
93+
});
94+
95+
if (nb_syn_packets as f64 / WIN_SIZE as f64) > 0.45 {
96+
detected.store(true, std::sync::atomic::Ordering::Relaxed);
97+
} else {
98+
detected.store(false, std::sync::atomic::Ordering::Relaxed);
99+
}
100+
}
101+
});
102+
103+
Self { map, detected }
104+
}
105+
106+
pub fn render(&self, frame: &mut Frame, block: Rect) {
107+
let mut ips: Vec<(IpAddr, usize)> = {
108+
let map = self.map.lock().unwrap();
109+
map.clone().into_iter().collect()
110+
};
111+
ips.sort_by(|a, b| b.1.cmp(&a.1));
112+
113+
ips.retain(|(_, count)| *count > 10_000);
114+
115+
let top_3_ips = ips.into_iter().take(3);
116+
117+
let widths = [Constraint::Min(30), Constraint::Min(20)];
118+
119+
let rows = top_3_ips.map(|(ip, count)| {
120+
Row::new(vec![
121+
Line::from(ip.to_string()).centered().bold(),
122+
Line::from(count.to_string()).centered(),
123+
])
124+
});
125+
let table = Table::new(rows, widths)
126+
.column_spacing(2)
127+
.flex(Flex::SpaceBetween)
128+
.header(
129+
Row::new(vec![
130+
Line::from("IP Address").centered(),
131+
Line::from("Number of SYN packets").centered(),
132+
])
133+
.style(Style::new().bold())
134+
.bottom_margin(1),
135+
)
136+
.block(
137+
Block::new()
138+
.title(" SYN Flood Attack ")
139+
.borders(Borders::all())
140+
.border_style(Style::new().yellow())
141+
.title_alignment(Alignment::Center),
142+
);
143+
144+
frame.render_widget(table, block);
145+
}
146+
}

0 commit comments

Comments
 (0)