-
-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
275 additions
and
212 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
use ratatui::{ | ||
layout::{Alignment, Constraint, Direction, Layout, Margin, Rect}, | ||
style::{Color, Style, Stylize}, | ||
text::{Line, Span}, | ||
widgets::{Block, BorderType, Borders, Padding}, | ||
Frame, | ||
}; | ||
use std::sync::{atomic::Ordering, Arc, Mutex}; | ||
|
||
use crate::packets::packet::AppPacket; | ||
|
||
use super::syn_flood::SynFlood; | ||
|
||
#[derive(Debug)] | ||
pub struct Alert { | ||
syn_flood: SynFlood, | ||
pub flash_count: usize, | ||
pub detected: bool, | ||
} | ||
|
||
impl Alert { | ||
pub fn new(packets: Arc<Mutex<Vec<AppPacket>>>) -> Self { | ||
Self { | ||
syn_flood: SynFlood::new(packets), | ||
flash_count: 1, | ||
detected: false, | ||
} | ||
} | ||
|
||
pub fn check(&mut self) { | ||
if self.syn_flood.detected.load(Ordering::Relaxed) { | ||
self.detected = true; | ||
self.flash_count += 1; | ||
} else { | ||
self.detected = false; | ||
self.flash_count = 1; | ||
} | ||
} | ||
|
||
pub fn render(&self, frame: &mut Frame, block: Rect) { | ||
frame.render_widget( | ||
Block::default() | ||
.title({ | ||
Line::from(vec![ | ||
Span::from(" Packet ").fg(Color::DarkGray), | ||
Span::from(" Stats ").fg(Color::DarkGray), | ||
{ | ||
if self.detected { | ||
if self.flash_count % 12 == 0 { | ||
Span::from(" Alert ").fg(Color::White).bg(Color::Red) | ||
} else { | ||
Span::from(" Alert ").bg(Color::Red) | ||
} | ||
} else { | ||
Span::styled( | ||
" Alert ", | ||
Style::default().bg(Color::Green).fg(Color::White).bold(), | ||
) | ||
} | ||
}, | ||
]) | ||
}) | ||
.title_alignment(Alignment::Left) | ||
.padding(Padding::top(1)) | ||
.borders(Borders::ALL) | ||
.style(Style::default()) | ||
.border_type(BorderType::default()) | ||
.border_style(Style::default().green()), | ||
block.inner(Margin { | ||
horizontal: 1, | ||
vertical: 0, | ||
}), | ||
); | ||
|
||
if !self.detected { | ||
return; | ||
} | ||
|
||
let syn_flood_block = Layout::default() | ||
.direction(Direction::Vertical) | ||
.constraints([Constraint::Length(10), Constraint::Fill(1)]) | ||
.flex(ratatui::layout::Flex::SpaceBetween) | ||
.margin(2) | ||
.split(block)[0]; | ||
|
||
let syn_flood_block = Layout::default() | ||
.direction(Direction::Vertical) | ||
.constraints([ | ||
Constraint::Fill(1), | ||
Constraint::Max(60), | ||
Constraint::Fill(1), | ||
]) | ||
.flex(ratatui::layout::Flex::SpaceBetween) | ||
.margin(2) | ||
.split(syn_flood_block)[1]; | ||
|
||
self.syn_flood.render(frame, syn_flood_block); | ||
} | ||
|
||
pub fn title_span(&self) -> Span<'_> { | ||
if self.detected { | ||
if self.flash_count % 12 == 0 { | ||
Span::from(" Alert ").fg(Color::White).bg(Color::Red) | ||
} else { | ||
Span::from(" Alert ").fg(Color::Red) | ||
} | ||
} else { | ||
Span::from(" Alert ").fg(Color::DarkGray) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
use std::{ | ||
collections::HashMap, | ||
net::IpAddr, | ||
sync::{atomic::AtomicBool, Arc, Mutex}, | ||
thread, | ||
time::Duration, | ||
}; | ||
|
||
use ratatui::{ | ||
layout::{Alignment, Constraint, Flex, Rect}, | ||
style::{Style, Stylize}, | ||
text::Line, | ||
widgets::{Block, Borders, Row, Table}, | ||
Frame, | ||
}; | ||
|
||
use crate::packets::{ | ||
network::{IpPacket, IpProto}, | ||
packet::AppPacket, | ||
}; | ||
|
||
const WIN_SIZE: usize = 100_000; | ||
|
||
#[derive(Debug)] | ||
pub struct SynFlood { | ||
pub detected: Arc<AtomicBool>, | ||
pub map: Arc<Mutex<HashMap<IpAddr, usize>>>, | ||
} | ||
|
||
impl SynFlood { | ||
pub fn new(packets: Arc<Mutex<Vec<AppPacket>>>) -> Self { | ||
let map: Arc<Mutex<HashMap<IpAddr, usize>>> = Arc::new(Mutex::new(HashMap::new())); | ||
|
||
let detected = Arc::new(AtomicBool::new(false)); | ||
|
||
thread::spawn({ | ||
let packets = packets.clone(); | ||
let map = map.clone(); | ||
let detected = detected.clone(); | ||
move || loop { | ||
let start_index = { | ||
let packets = packets.lock().unwrap(); | ||
packets.len().saturating_sub(1) | ||
}; | ||
thread::sleep(Duration::from_secs(5)); | ||
let app_packets = { | ||
let packets = packets.lock().unwrap(); | ||
packets.clone() | ||
}; | ||
|
||
let mut map = map.lock().unwrap(); | ||
map.clear(); | ||
|
||
if app_packets.len() < WIN_SIZE { | ||
continue; | ||
} | ||
|
||
let mut nb_syn_packets = 0; | ||
|
||
app_packets[start_index..app_packets.len().saturating_sub(1)] | ||
.iter() | ||
.for_each(|packet| { | ||
if let AppPacket::Ip(ip_packet) = packet { | ||
if let IpPacket::V4(ipv4_packet) = ip_packet { | ||
if let IpProto::Tcp(tcp_packet) = ipv4_packet.proto { | ||
if tcp_packet.syn == 1 { | ||
nb_syn_packets += 1; | ||
if let Some(count) = | ||
map.get_mut(&IpAddr::V4(ipv4_packet.src_ip)) | ||
{ | ||
*count += 1; | ||
} else { | ||
map.insert(IpAddr::V4(ipv4_packet.src_ip), 1); | ||
} | ||
} | ||
} | ||
} | ||
if let IpPacket::V6(ipv6_packet) = ip_packet { | ||
if let IpProto::Tcp(tcp_packet) = ipv6_packet.proto { | ||
if tcp_packet.syn == 1 { | ||
nb_syn_packets += 1; | ||
if let Some(count) = | ||
map.get_mut(&IpAddr::V6(ipv6_packet.src_ip)) | ||
{ | ||
*count += 1; | ||
} else { | ||
map.insert(IpAddr::V6(ipv6_packet.src_ip), 1); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
}); | ||
|
||
if (nb_syn_packets as f64 / WIN_SIZE as f64) > 0.45 { | ||
detected.store(true, std::sync::atomic::Ordering::Relaxed); | ||
} else { | ||
detected.store(false, std::sync::atomic::Ordering::Relaxed); | ||
} | ||
} | ||
}); | ||
|
||
Self { map, detected } | ||
} | ||
|
||
pub fn render(&self, frame: &mut Frame, block: Rect) { | ||
let mut ips: Vec<(IpAddr, usize)> = { | ||
let map = self.map.lock().unwrap(); | ||
map.clone().into_iter().collect() | ||
}; | ||
ips.sort_by(|a, b| b.1.cmp(&a.1)); | ||
|
||
ips.retain(|(_, count)| *count > 10_000); | ||
|
||
let top_3_ips = ips.into_iter().take(3); | ||
|
||
let widths = [Constraint::Min(30), Constraint::Min(20)]; | ||
|
||
let rows = top_3_ips.map(|(ip, count)| { | ||
Row::new(vec![ | ||
Line::from(ip.to_string()).centered().bold(), | ||
Line::from(count.to_string()).centered(), | ||
]) | ||
}); | ||
let table = Table::new(rows, widths) | ||
.column_spacing(2) | ||
.flex(Flex::SpaceBetween) | ||
.header( | ||
Row::new(vec![ | ||
Line::from("IP Address").centered(), | ||
Line::from("Number of SYN packets").centered(), | ||
]) | ||
.style(Style::new().bold()) | ||
.bottom_margin(1), | ||
) | ||
.block( | ||
Block::new() | ||
.title(" SYN Flood Attack ") | ||
.borders(Borders::all()) | ||
.border_style(Style::new().yellow()) | ||
.title_alignment(Alignment::Center), | ||
); | ||
|
||
frame.render_widget(table, block); | ||
} | ||
} |
Oops, something went wrong.