From c7d5fe0f5a370d6d763a6593e5de8bb70f30430f Mon Sep 17 00:00:00 2001 From: Badr Date: Thu, 26 Sep 2024 21:03:23 +0200 Subject: [PATCH] refactor: restructre app tui components (#21) --- oryx-tui/src/alerts/alert.rs | 124 ++++++ oryx-tui/src/alerts/syn_flood.rs | 146 +++++++ oryx-tui/src/app.rs | 619 +++--------------------------- oryx-tui/src/bandwidth.rs | 124 +++--- oryx-tui/src/ebpf.rs | 40 +- oryx-tui/src/event.rs | 9 +- oryx-tui/src/export.rs | 18 +- oryx-tui/src/filters/direction.rs | 36 +- oryx-tui/src/filters/filter.rs | 272 +++++++++++++ oryx-tui/src/filters/fuzzy.rs | 40 +- oryx-tui/src/filters/link.rs | 43 ++- oryx-tui/src/filters/network.rs | 48 ++- oryx-tui/src/filters/transport.rs | 48 ++- oryx-tui/src/handler.rs | 561 +++++++++------------------ oryx-tui/src/infos.rs | 0 oryx-tui/src/interface.rs | 101 ++++- oryx-tui/src/lib.rs | 6 + oryx-tui/src/main.rs | 13 +- oryx-tui/src/packets/network.rs | 7 +- oryx-tui/src/packets/packet.rs | 13 +- oryx-tui/src/stats.rs | 18 +- oryx-tui/src/tui.rs | 20 +- 22 files changed, 1235 insertions(+), 1071 deletions(-) create mode 100644 oryx-tui/src/alerts/alert.rs create mode 100644 oryx-tui/src/alerts/syn_flood.rs create mode 100644 oryx-tui/src/filters/filter.rs delete mode 100644 oryx-tui/src/infos.rs diff --git a/oryx-tui/src/alerts/alert.rs b/oryx-tui/src/alerts/alert.rs new file mode 100644 index 0000000..e305902 --- /dev/null +++ b/oryx-tui/src/alerts/alert.rs @@ -0,0 +1,124 @@ +use ratatui::{ + layout::{Alignment, Constraint, Direction, Layout, Margin, Rect}, + style::{Color, Style, Stylize}, + text::{Line, Span, Text}, + 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>>) -> 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 { + let text_block = Layout::default() + .direction(Direction::Vertical) + .constraints([ + Constraint::Fill(1), + Constraint::Length(3), + Constraint::Fill(1), + ]) + .flex(ratatui::layout::Flex::SpaceBetween) + .margin(2) + .split(block)[1]; + + let text = Text::from("No alerts").bold().centered(); + frame.render_widget(text, text_block); + 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) + } + } +} diff --git a/oryx-tui/src/alerts/syn_flood.rs b/oryx-tui/src/alerts/syn_flood.rs new file mode 100644 index 0000000..b50c1f1 --- /dev/null +++ b/oryx-tui/src/alerts/syn_flood.rs @@ -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, + pub map: Arc>>, +} + +impl SynFlood { + pub fn new(packets: Arc>>) -> Self { + let map: Arc>> = 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); + } +} diff --git a/oryx-tui/src/app.rs b/oryx-tui/src/app.rs index 399ba97..c9d57b7 100644 --- a/oryx-tui/src/app.rs +++ b/oryx-tui/src/app.rs @@ -1,37 +1,34 @@ -use oryx_common::protocols::{ - Protocol, NB_LINK_PROTOCOL, NB_NETWORK_PROTOCOL, NB_TRANSPORT_PROTOCOL, -}; use oryx_common::RawPacket; -use ratatui::layout::{Alignment, Constraint, Direction, Flex, Layout, Margin, Rect}; -use ratatui::style::{Color, Style, Stylize}; -use ratatui::text::{Line, Span}; -use ratatui::widgets::Clear; use ratatui::{ + layout::{Alignment, Constraint, Direction, Flex, Layout, Margin, Rect}, + style::{Color, Style, Stylize}, + text::{Line, Span}, widgets::{ - Block, BorderType, Borders, Cell, HighlightSpacing, Padding, Paragraph, Row, Scrollbar, - ScrollbarOrientation, ScrollbarState, Table, TableState, + Block, BorderType, Borders, Cell, Clear, HighlightSpacing, Padding, Paragraph, Row, + Scrollbar, ScrollbarOrientation, ScrollbarState, Table, TableState, }, Frame, }; -use std::collections::HashMap; -use std::net::IpAddr; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::{Arc, Mutex}; -use std::time::Duration; -use std::{error, thread}; +use std::{ + error, + sync::{Arc, Mutex}, + thread, +}; use tui_big_text::{BigText, PixelSize}; +use crate::alerts::alert::Alert; use crate::bandwidth::Bandwidth; -use crate::filters::direction::TrafficDirectionFilter; -use crate::filters::fuzzy::{self, Fuzzy}; -use crate::filters::link::LinkFilter; -use crate::filters::network::NetworkFilter; -use crate::filters::transport::TransportFilter; +use crate::filters::{ + filter::Filter, + fuzzy::{self, Fuzzy}, +}; use crate::help::Help; use crate::interface::Interface; use crate::notification::Notification; -use crate::packets::network::{IpPacket, IpProto}; -use crate::packets::packet::AppPacket; +use crate::packets::{ + network::{IpPacket, IpProto}, + packet::AppPacket, +}; use crate::stats::Stats; pub type AppResult = std::result::Result>; @@ -63,25 +60,6 @@ pub struct DataEventHandler { pub handler: thread::JoinHandle<()>, } -#[derive(Debug, Clone)] -pub struct FilterChannel { - pub sender: kanal::Sender<(Protocol, bool)>, - pub receiver: kanal::Receiver<(Protocol, bool)>, -} - -impl FilterChannel { - pub fn new() -> Self { - let (sender, receiver) = kanal::unbounded(); - Self { sender, receiver } - } -} - -impl Default for FilterChannel { - fn default() -> Self { - Self::new() - } -} - #[derive(Debug)] pub struct App { pub running: bool, @@ -90,12 +68,7 @@ pub struct App { // used in setup to know which block to fall into after discarding help pub previous_focused_block: FocusedBlock, pub interface: Interface, - pub network_filter: NetworkFilter, - pub transport_filter: TransportFilter, - pub link_filter: LinkFilter, - pub traffic_direction_filter: TrafficDirectionFilter, - pub ingress_filter_channel: FilterChannel, - pub egress_filter_channel: FilterChannel, + pub filter: Filter, pub start_sniffing: bool, pub packets: Arc>>, pub packets_table_state: TableState, @@ -108,12 +81,10 @@ pub struct App { pub packet_window_size: usize, pub update_filters: bool, pub data_channel_sender: kanal::Sender<[u8; RawPacket::LEN]>, - pub bandwidth: Arc>>, + pub bandwidth: Bandwidth, pub show_packet_infos_popup: bool, pub packet_index: Option, - pub syn_flood_map: Arc>>, - pub syn_flood_attck_detected: Arc, - pub alert_flash_count: usize, + pub alert: Alert, } impl Default for App { @@ -126,15 +97,6 @@ impl App { pub fn new() -> Self { let packets = Arc::new(Mutex::new(Vec::with_capacity(AppPacket::LEN * 1024 * 1024))); let stats = Arc::new(Mutex::new(Stats::default())); - let fuzzy = Arc::new(Mutex::new(Fuzzy::default())); - let syn_flood_map: Arc>> = - Arc::new(Mutex::new(HashMap::new())); - - let syn_flood_attck_detected = Arc::new(AtomicBool::new(false)); - - let network_filter = NetworkFilter::new(); - let transport_filter = TransportFilter::new(); - let link_filter = LinkFilter::new(); let (sender, receiver) = kanal::unbounded(); @@ -149,131 +111,17 @@ impl App { } }); - let bandwidth = Arc::new(Mutex::new(Bandwidth::new().ok())); - - thread::spawn({ - let bandwidth = bandwidth.clone(); - move || loop { - thread::sleep(Duration::from_secs(1)); - { - let mut bandwidth = bandwidth.lock().unwrap(); - if bandwidth.is_some() { - let _ = bandwidth.as_mut().unwrap().refresh(); - } - } - } - }); - - thread::spawn({ - let fuzzy = fuzzy.clone(); - let packets = packets.clone(); - move || { - let mut last_index = 0; - let mut pattern = String::new(); - loop { - thread::sleep(Duration::from_millis(TICK_RATE)); - let packets = packets.lock().unwrap(); - let mut fuzzy = fuzzy.lock().unwrap(); - - if fuzzy.is_enabled() && !fuzzy.filter.value().is_empty() { - let current_pattern = fuzzy.filter.value().to_owned(); - if current_pattern != pattern { - fuzzy.find(packets.as_slice()); - pattern = current_pattern; - last_index = packets.len(); - } else { - fuzzy.append(&packets.as_slice()[last_index..]); - last_index = packets.len(); - } - } - } - } - }); - - thread::spawn({ - let packets = packets.clone(); - let syn_flood_map = syn_flood_map.clone(); - let syn_flood_attck_detected = syn_flood_attck_detected.clone(); - let win_size = 100_000; - 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 = syn_flood_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 { - syn_flood_attck_detected.store(true, std::sync::atomic::Ordering::Relaxed); - } else { - syn_flood_attck_detected.store(false, std::sync::atomic::Ordering::Relaxed); - } - } - }); - Self { running: true, help: Help::new(), focused_block: FocusedBlock::Interface, previous_focused_block: FocusedBlock::Interface, interface: Interface::default(), - network_filter, - transport_filter, - link_filter, - traffic_direction_filter: TrafficDirectionFilter::default(), - ingress_filter_channel: FilterChannel::new(), - egress_filter_channel: FilterChannel::new(), + filter: Filter::new(), start_sniffing: false, - packets, + packets: packets.clone(), packets_table_state: TableState::default(), - fuzzy, + fuzzy: Fuzzy::new(packets.clone()), notifications: Vec::new(), manuall_scroll: false, mode: Mode::Packet, @@ -282,63 +130,37 @@ impl App { packet_window_size: 0, update_filters: false, data_channel_sender: sender, - bandwidth, + bandwidth: Bandwidth::new(), show_packet_infos_popup: false, packet_index: None, - syn_flood_map, - syn_flood_attck_detected, - alert_flash_count: 1, + alert: Alert::new(packets.clone()), } } pub fn render(&mut self, frame: &mut Frame) { // Setup if !self.start_sniffing { - let ( - interface_block, - transport_filter_block, - network_filter_block, - link_filter_block, - traffic_direction_block, - start_block, - ) = { + let (interface_block, filter_block, start_block) = { let chunks = Layout::default() .direction(Direction::Vertical) .constraints([ Constraint::Length(self.interface.interfaces.len() as u16 + 6), - Constraint::Length(NB_TRANSPORT_PROTOCOL + 4), - Constraint::Length(NB_NETWORK_PROTOCOL + 4), - Constraint::Length(NB_LINK_PROTOCOL + 4), - Constraint::Length(6), + Constraint::Fill(1), Constraint::Length(4), ]) .margin(1) .flex(Flex::SpaceAround) .split(frame.area()); - ( - chunks[0], chunks[1], chunks[2], chunks[3], chunks[4], chunks[5], - ) + (chunks[0], chunks[1], chunks[2]) }; // interfaces self.interface - .render(frame, interface_block, &self.focused_block); + .render_on_setup(frame, interface_block, &self.focused_block); // Filters - self.network_filter - .render(frame, network_filter_block, &self.focused_block); - - self.transport_filter - .render(frame, transport_filter_block, &self.focused_block); - - self.link_filter - .render(frame, link_filter_block, &self.focused_block); - - self.traffic_direction_filter.render( - frame, - traffic_direction_block, - &self.focused_block, - ); + self.filter + .render_on_setup(frame, filter_block, &self.focused_block); // Start Button let start = BigText::builder() @@ -372,164 +194,10 @@ impl App { }; // Interface - let widths = [Constraint::Length(4), Constraint::Fill(1)]; - - let interface_infos = [ - Row::new(vec![ - Span::styled("Name", Style::new().bold()), - Span::from(self.interface.selected_interface.name.clone()), - ]), - Row::new(vec![ - Span::styled("Mac", Style::new().bold()), - Span::from( - self.interface - .selected_interface - .mac_address - .clone() - .unwrap_or("-".to_string()), - ), - ]), - Row::new(vec![ - Span::styled("IPv4", Style::new().bold()), - Span::from( - self.interface - .selected_interface - .addresses - .iter() - .find(|a| matches!(a, IpAddr::V4(_) | IpAddr::V6(_))) - .unwrap() - .to_string(), - ), - ]), - Row::new(vec![ - Span::styled("IPv6", Style::new().bold()), - Span::from({ - match self - .interface - .selected_interface - .addresses - .iter() - .find(|a| matches!(a, IpAddr::V6(_))) - { - Some(ip) => ip.to_string(), - None => "-".to_string(), - } - }), - ]), - ]; - - let interface_table = Table::new(interface_infos, widths).column_spacing(3).block( - Block::default() - .title(" Interface 󰲝 ") - .title_style(Style::default().bold().green()) - .title_alignment(Alignment::Center) - .padding(Padding::horizontal(2)) - .borders(Borders::ALL) - .style(Style::default()) - .border_type(BorderType::default()) - .border_style(Style::default().green()), - ); + self.interface.render_on_sniffing(frame, interface_block); // Filters - let widths = [Constraint::Length(10), Constraint::Fill(1)]; - let filters = { - [ - Row::new(vec![ - Line::styled("Transport", Style::new().bold()), - Line::from_iter(TransportFilter::new().selected_protocols.iter().map( - |filter| { - if self.transport_filter.applied_protocols.contains(filter) { - Span::styled( - format!(" {} ", filter), - Style::default().light_green(), - ) - } else { - Span::styled( - format!(" {} ", filter), - Style::default().light_red(), - ) - } - }, - )), - ]), - Row::new(vec![ - Line::styled("Network", Style::new().bold()), - Line::from_iter(NetworkFilter::new().selected_protocols.iter().map( - |filter| { - if self.network_filter.applied_protocols.contains(filter) { - Span::styled( - format!(" {} ", filter), - Style::default().light_green(), - ) - } else { - Span::styled( - format!(" {} ", filter), - Style::default().light_red(), - ) - } - }, - )), - ]), - Row::new(vec![ - Line::styled("Link", Style::new().bold()), - Line::from_iter(LinkFilter::new().selected_protocols.iter().map( - |filter| { - if self.link_filter.applied_protocols.contains(filter) { - Span::styled( - format!(" {} ", filter), - Style::default().light_green(), - ) - } else { - Span::styled( - format!(" {} ", filter), - Style::default().light_red(), - ) - } - }, - )), - ]), - Row::new(vec![ - Line::styled("Direction", Style::new().bold()), - Line::from_iter( - TrafficDirectionFilter::default() - .selected_direction - .iter() - .map(|filter| { - if self - .traffic_direction_filter - .applied_direction - .contains(filter) - { - Span::styled( - format!("󰞁 {} ", filter), - Style::default().light_green(), - ) - } else { - Span::styled( - format!("󰿝 {} ", filter), - Style::default().light_red(), - ) - } - }), - ), - ]), - ] - }; - - let filter_table = Table::new(filters, widths).column_spacing(3).block( - Block::default() - .title(" Filters 󱪤 ") - .title_style(Style::default().bold().green()) - .title_alignment(Alignment::Center) - .padding(Padding::horizontal(2)) - .borders(Borders::ALL) - .style(Style::default()) - .border_type(BorderType::default()) - .border_style(Style::default().green()), - ); - - frame.render_widget(interface_table, interface_block); - frame.render_widget(filter_table, filter_block); + self.filter.render_on_sniffing(frame, filter_block); // Packets/Stats match self.mode { @@ -540,89 +208,13 @@ impl App { } } Mode::Stats => self.render_stats_mode(frame, mode_block), - Mode::Alerts => self.render_alerts_mode(frame, mode_block), + Mode::Alerts => self.alert.render(frame, mode_block), } // Update filters if self.update_filters { - let layout = Layout::default() - .direction(Direction::Vertical) - .constraints([ - Constraint::Fill(1), - Constraint::Length(40), - Constraint::Fill(1), - ]) - .flex(ratatui::layout::Flex::SpaceBetween) - .split(mode_block); - - let block = Layout::default() - .direction(Direction::Horizontal) - .constraints([ - Constraint::Fill(1), - Constraint::Length(60), - Constraint::Fill(1), - ]) - .flex(ratatui::layout::Flex::SpaceBetween) - .split(layout[1])[1]; - - let ( - transport_filter_block, - network_filter_block, - link_filter_block, - traffic_direction_block, - apply_block, - ) = { - let chunks = Layout::default() - .direction(Direction::Vertical) - .constraints([ - Constraint::Length(NB_TRANSPORT_PROTOCOL + 4), - Constraint::Length(NB_NETWORK_PROTOCOL + 4), - Constraint::Length(NB_LINK_PROTOCOL + 4), - Constraint::Length(6), - Constraint::Length(4), - ]) - .margin(1) - .flex(Flex::SpaceBetween) - .split(block); - (chunks[0], chunks[1], chunks[2], chunks[3], chunks[4]) - }; - - frame.render_widget(Clear, block); - frame.render_widget( - Block::new() - .borders(Borders::all()) - .border_type(BorderType::Thick) - .border_style(Style::default().green()), - block, - ); - - self.transport_filter - .render(frame, transport_filter_block, &self.focused_block); - - self.network_filter - .render(frame, network_filter_block, &self.focused_block); - - self.link_filter - .render(frame, link_filter_block, &self.focused_block); - - self.traffic_direction_filter.render( - frame, - traffic_direction_block, - &self.focused_block, - ); - - let apply = BigText::builder() - .pixel_size(PixelSize::Sextant) - .style(if self.focused_block == FocusedBlock::Start { - Style::default().white().bold() - } else { - Style::default().dark_gray() - }) - .lines(vec!["APPLY".into()]) - .centered() - .build(); - frame.render_widget(apply, apply_block); + self.filter.update(frame, mode_block, &self.focused_block); } } } @@ -940,17 +532,7 @@ impl App { Style::default().bg(Color::Green).fg(Color::White).bold(), ), Span::from(" Stats ").fg(Color::DarkGray), - { - if self.syn_flood_attck_detected.load(Ordering::Relaxed) { - if self.alert_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) - } - }, + self.alert.title_span(), ]) }) .title_alignment(Alignment::Left) @@ -1040,7 +622,6 @@ impl App { pub fn render_stats_mode(&mut self, frame: &mut Frame, block: Rect) { let stats = self.stats.lock().unwrap(); - let mut bandwidth = self.bandwidth.lock().unwrap(); let (bandwidth_block, stats_block) = { let chunks = Layout::default() @@ -1060,17 +641,7 @@ impl App { " Stats ", Style::default().bg(Color::Green).fg(Color::White).bold(), ), - { - if self.syn_flood_attck_detected.load(Ordering::Relaxed) { - if self.alert_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) - } - }, + self.alert.title_span(), ]) }) .title_alignment(Alignment::Left) @@ -1087,109 +658,11 @@ impl App { stats.render(frame, stats_block); - if bandwidth.is_some() { - bandwidth.as_mut().unwrap().render( - frame, - bandwidth_block, - &self.interface.selected_interface.name.clone(), - ); - } - } - - fn render_alerts_mode(&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.syn_flood_attck_detected.load(Ordering::Relaxed) { - if self.alert_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, - }), + self.bandwidth.render( + frame, + bandwidth_block, + &self.interface.selected_interface.name.clone(), ); - - if !self.syn_flood_attck_detected.load(Ordering::Relaxed) { - 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]; - - let mut attacker_ips: Vec<(IpAddr, usize)> = { - let map = self.syn_flood_map.lock().unwrap(); - map.clone().into_iter().collect() - }; - attacker_ips.sort_by(|a, b| b.1.cmp(&a.1)); - - attacker_ips.retain(|(_, count)| *count > 10_000); - - let top_3 = attacker_ips.into_iter().take(3); - - let widths = [Constraint::Min(30), Constraint::Min(20)]; - - let rows = top_3.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, syn_flood_block); } fn render_packet_infos_popup(&self, frame: &mut Frame) { @@ -1260,11 +733,7 @@ impl App { self.notifications.iter_mut().for_each(|n| n.ttl -= 1); self.notifications.retain(|n| n.ttl > 0); - if self.syn_flood_attck_detected.load(Ordering::Relaxed) { - self.alert_flash_count += 1; - } else { - self.alert_flash_count = 1; - } + self.alert.check(); } pub fn quit(&mut self) { diff --git a/oryx-tui/src/bandwidth.rs b/oryx-tui/src/bandwidth.rs index d492f0f..0d2efcb 100644 --- a/oryx-tui/src/bandwidth.rs +++ b/oryx-tui/src/bandwidth.rs @@ -1,6 +1,9 @@ use std::collections::{HashMap, VecDeque}; use std::fs::File; use std::io::{Read, Seek}; +use std::sync::{Arc, Mutex}; +use std::thread; +use std::time::Duration; use ratatui::layout::{Alignment, Constraint, Direction, Layout, Rect}; use ratatui::style::{Style, Stylize}; @@ -11,8 +14,6 @@ use ratatui::{ Frame, }; -use crate::app::AppResult; - #[derive(Clone, Debug)] pub struct BandwidthBuffer { incoming_max: usize, @@ -46,76 +47,93 @@ impl BandwidthBuffer { #[derive(Debug)] pub struct Bandwidth { - fd: File, - current: HashMap, - pub map: HashMap, + map: Arc>>, +} + +impl Default for Bandwidth { + fn default() -> Self { + Self::new() + } } impl Bandwidth { - pub fn new() -> AppResult { - let mut fd = File::open("/proc/net/dev")?; - let mut current: HashMap = HashMap::new(); - let mut map: HashMap = HashMap::new(); + pub fn new() -> Self { + let map: Arc>> = + Arc::new(Mutex::new(HashMap::new())); - let mut buffer = String::new(); - fd.read_to_string(&mut buffer)?; - let mut lines = buffer.lines(); + thread::spawn({ + let map = map.clone(); + move || { + //TODO: handle error + let mut fd = File::open("/proc/net/dev").unwrap(); + let mut current: HashMap = HashMap::new(); - lines.next(); - lines.next(); + let mut buffer = String::new(); + fd.read_to_string(&mut buffer).unwrap(); + let mut lines = buffer.lines(); - for line in lines { - let splits: Vec<&str> = line.split_whitespace().collect(); + lines.next(); + lines.next(); - let mut interface_name = splits[0].to_string(); - interface_name.pop(); + for line in lines { + let splits: Vec<&str> = line.split_whitespace().collect(); - let bandwidth_buffer = BandwidthBuffer::new(20); + let mut interface_name = splits[0].to_string(); + interface_name.pop(); - let received: usize = splits[1].parse()?; - let sent: usize = splits[9].parse()?; + let bandwidth_buffer = BandwidthBuffer::new(20); - current.insert(interface_name.clone(), (received, sent)); + let received: usize = splits[1].parse().unwrap(); + let sent: usize = splits[9].parse().unwrap(); - map.insert(interface_name, bandwidth_buffer); - } + current.insert(interface_name.clone(), (received, sent)); - Ok(Self { fd, current, map }) - } + { + let mut map = map.lock().unwrap(); + map.insert(interface_name, bandwidth_buffer); + } + } - pub fn refresh(&mut self) -> AppResult<()> { - self.fd.seek(std::io::SeekFrom::Start(0))?; - let mut buffer = String::new(); - self.fd.read_to_string(&mut buffer)?; + loop { + thread::sleep(Duration::from_secs(1)); + fd.seek(std::io::SeekFrom::Start(0)).unwrap(); + let mut buffer = String::new(); + fd.read_to_string(&mut buffer).unwrap(); - let mut lines = buffer.lines(); + let mut lines = buffer.lines(); - lines.next(); - lines.next(); + lines.next(); + lines.next(); - for line in lines { - let splits: Vec<&str> = line.split_whitespace().collect(); + for line in lines { + let splits: Vec<&str> = line.split_whitespace().collect(); - let mut interface_name = splits[0].to_string(); - interface_name.pop(); + let mut interface_name = splits[0].to_string(); + interface_name.pop(); - let received: usize = splits[1].parse()?; - let sent: usize = splits[9].parse()?; + let received: usize = splits[1].parse().unwrap(); + let sent: usize = splits[9].parse().unwrap(); - if let Some(bandwidth_buffer) = self.map.get_mut(&interface_name) { - let current = self.current.get_mut(&interface_name).unwrap(); - bandwidth_buffer.push(( - received.saturating_sub(current.0) / 1024, - sent.saturating_sub(current.1) / 1024, - )); - current.0 = received; - current.1 = sent; + let mut map = map.lock().unwrap(); + if let Some(bandwidth_buffer) = map.get_mut(&interface_name) { + let current = current.get_mut(&interface_name).unwrap(); + bandwidth_buffer.push(( + received.saturating_sub(current.0) / 1024, + sent.saturating_sub(current.1) / 1024, + )); + current.0 = received; + current.1 = sent; + } + } + } } - } - Ok(()) + }); + + Self { map } } pub fn render(&self, frame: &mut Frame, bandwidth_block: Rect, network_interface: &str) { + let map = self.map.lock().unwrap(); let (incoming_block, outgoing_block) = { let chunks = Layout::default() .direction(Direction::Horizontal) @@ -125,7 +143,7 @@ impl Bandwidth { (chunks[0], chunks[1]) }; let (incoming_max_val, incoming_unit) = - if let Some(bandwidth_buffer) = self.map.get(network_interface) { + if let Some(bandwidth_buffer) = map.get(network_interface) { match bandwidth_buffer.incoming_max { n if (1024usize.pow(2)..1024usize.pow(3)).contains(&n) => { ((n / 1024usize.pow(2)) as f64, "GB") @@ -138,7 +156,7 @@ impl Bandwidth { }; let (outgoing_max_val, outgoing_unit) = - if let Some(bandwidth_buffer) = self.map.get(network_interface) { + if let Some(bandwidth_buffer) = map.get(network_interface) { match bandwidth_buffer.outgoing_max { n if (1024usize.pow(2)..1024usize.pow(3)).contains(&n) => { ((n / 1024usize.pow(2)) as f64, "GB") @@ -151,7 +169,7 @@ impl Bandwidth { }; let incoming_data = { - if let Some(v) = self.map.get(network_interface) { + if let Some(v) = map.get(network_interface) { let values = v.get(); let x: Vec<(f64, f64)> = values .iter() @@ -174,7 +192,7 @@ impl Bandwidth { }; let outgoing_data = { - if let Some(v) = self.map.get(network_interface) { + if let Some(v) = map.get(network_interface) { let values = v.get(); let x: Vec<(f64, f64)> = values .iter() diff --git a/oryx-tui/src/ebpf.rs b/oryx-tui/src/ebpf.rs index 029945d..edf08c8 100644 --- a/oryx-tui/src/ebpf.rs +++ b/oryx-tui/src/ebpf.rs @@ -1,22 +1,24 @@ -use std::os::fd::AsRawFd; -use std::sync::atomic::AtomicBool; -use std::sync::Arc; -use std::thread::spawn; -use std::time::Duration; -use std::{io, thread}; - -use aya::maps::ring_buf::RingBufItem; -use aya::maps::{Array, MapData, RingBuf}; -use aya::programs::{tc, SchedClassifier, TcAttachType}; -use aya::{include_bytes_aligned, Bpf}; -use mio::{Events, Interest, Poll, Registry, Token}; -use oryx_common::protocols::Protocol; -use oryx_common::RawPacket; - -use crate::event::Event; -use crate::notification::{Notification, NotificationLevel}; -use mio::event::Source; -use mio::unix::SourceFd; +use std::{ + io, + os::fd::AsRawFd, + sync::{atomic::AtomicBool, Arc}, + thread::{self, spawn}, + time::Duration, +}; + +use aya::{ + include_bytes_aligned, + maps::{ring_buf::RingBufItem, Array, MapData, RingBuf}, + programs::{tc, SchedClassifier, TcAttachType}, + Bpf, +}; +use oryx_common::{protocols::Protocol, RawPacket}; + +use crate::{ + event::Event, + notification::{Notification, NotificationLevel}, +}; +use mio::{event::Source, unix::SourceFd, Events, Interest, Poll, Registry, Token}; pub struct Ebpf; diff --git a/oryx-tui/src/event.rs b/oryx-tui/src/event.rs index 5c71572..42dcee7 100644 --- a/oryx-tui/src/event.rs +++ b/oryx-tui/src/event.rs @@ -1,8 +1,9 @@ -use crate::app::AppResult; -use crate::notification::Notification; +use crate::{app::AppResult, notification::Notification}; use crossterm::event::{self, Event as CrosstermEvent, KeyEvent, KeyEventKind, MouseEvent}; -use std::thread; -use std::time::{Duration, Instant}; +use std::{ + thread, + time::{Duration, Instant}, +}; #[derive(Clone)] pub enum Event { diff --git a/oryx-tui/src/export.rs b/oryx-tui/src/export.rs index a35aa02..e93ac93 100644 --- a/oryx-tui/src/export.rs +++ b/oryx-tui/src/export.rs @@ -1,10 +1,16 @@ -use std::fs::{create_dir, OpenOptions}; -use std::io::prelude::*; -use std::os::unix::fs::chown; +use std::{ + fs::{create_dir, OpenOptions}, + io::prelude::*, + os::unix::fs::chown, +}; -use crate::app::AppResult; -use crate::packets::network::{IpPacket, IpProto}; -use crate::packets::packet::AppPacket; +use crate::{ + app::AppResult, + packets::{ + network::{IpPacket, IpProto}, + packet::AppPacket, + }, +}; pub fn export(packets: &[AppPacket]) -> AppResult<()> { let uid = unsafe { libc::geteuid() }; diff --git a/oryx-tui/src/filters/direction.rs b/oryx-tui/src/filters/direction.rs index ebcccd5..9107a40 100644 --- a/oryx-tui/src/filters/direction.rs +++ b/oryx-tui/src/filters/direction.rs @@ -1,6 +1,9 @@ use std::{ fmt::Display, - sync::{atomic::AtomicBool, Arc}, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, }; use ratatui::{ @@ -38,6 +41,12 @@ impl Display for TrafficDirection { impl Default for TrafficDirectionFilter { fn default() -> Self { + Self::new() + } +} + +impl TrafficDirectionFilter { + pub fn new() -> Self { TrafficDirectionFilter { state: TableState::default(), selected_direction: vec![TrafficDirection::Ingress, TrafficDirection::Egress], @@ -46,9 +55,30 @@ impl Default for TrafficDirectionFilter { terminate_egress: Arc::new(AtomicBool::new(false)), } } -} -impl TrafficDirectionFilter { + pub fn terminate(&mut self, direction: TrafficDirection) { + match direction { + TrafficDirection::Ingress => self.terminate_ingress.store(true, Ordering::Relaxed), + TrafficDirection::Egress => self.terminate_egress.store(true, Ordering::Relaxed), + } + } + + pub fn select(&mut self) { + if let Some(i) = self.state.selected() { + let traffic_direction = match i { + 0 => TrafficDirection::Ingress, + _ => TrafficDirection::Egress, + }; + + if self.selected_direction.contains(&traffic_direction) { + self.selected_direction + .retain(|&direction| direction != traffic_direction); + } else { + self.selected_direction.push(traffic_direction); + } + } + } + pub fn apply(&mut self) { self.applied_direction = self.selected_direction.clone(); self.selected_direction.clear(); diff --git a/oryx-tui/src/filters/filter.rs b/oryx-tui/src/filters/filter.rs new file mode 100644 index 0000000..0776156 --- /dev/null +++ b/oryx-tui/src/filters/filter.rs @@ -0,0 +1,272 @@ +use oryx_common::protocols::{ + Protocol, NB_LINK_PROTOCOL, NB_NETWORK_PROTOCOL, NB_TRANSPORT_PROTOCOL, +}; +use ratatui::{ + layout::{Alignment, Constraint, Direction, Flex, Layout, Rect}, + style::{Style, Stylize}, + text::{Line, Span}, + widgets::{Block, BorderType, Borders, Clear, Padding, Row, Table}, + Frame, +}; +use tui_big_text::{BigText, PixelSize}; + +use crate::app::FocusedBlock; + +use super::{ + direction::TrafficDirectionFilter, link::LinkFilter, network::NetworkFilter, + transport::TransportFilter, +}; + +#[derive(Debug, Clone)] +pub struct FilterChannel { + pub sender: kanal::Sender<(Protocol, bool)>, + pub receiver: kanal::Receiver<(Protocol, bool)>, +} + +impl FilterChannel { + pub fn new() -> Self { + let (sender, receiver) = kanal::unbounded(); + Self { sender, receiver } + } +} + +impl Default for FilterChannel { + fn default() -> Self { + Self::new() + } +} + +#[derive(Debug)] +pub struct Filter { + pub network: NetworkFilter, + pub transport: TransportFilter, + pub link: LinkFilter, + pub traffic_direction: TrafficDirectionFilter, + pub ingress_channel: FilterChannel, + pub egress_channel: FilterChannel, +} + +impl Default for Filter { + fn default() -> Self { + Self::new() + } +} + +impl Filter { + pub fn new() -> Self { + Self { + network: NetworkFilter::new(), + transport: TransportFilter::new(), + link: LinkFilter::new(), + traffic_direction: TrafficDirectionFilter::new(), + ingress_channel: FilterChannel::new(), + egress_channel: FilterChannel::new(), + } + } + + pub fn render_on_setup( + &mut self, + frame: &mut Frame, + block: Rect, + focused_block: &FocusedBlock, + ) { + let ( + transport_filter_block, + network_filter_block, + link_filter_block, + traffic_direction_block, + ) = { + let chunks = Layout::default() + .direction(Direction::Vertical) + .constraints([ + Constraint::Length(NB_TRANSPORT_PROTOCOL + 4), + Constraint::Length(NB_NETWORK_PROTOCOL + 4), + Constraint::Length(NB_LINK_PROTOCOL + 4), + Constraint::Length(6), + ]) + .margin(1) + .flex(Flex::SpaceAround) + .split(block); + (chunks[0], chunks[1], chunks[2], chunks[3]) + }; + + self.network + .render(frame, network_filter_block, focused_block); + + self.transport + .render(frame, transport_filter_block, focused_block); + + self.link.render(frame, link_filter_block, focused_block); + + self.traffic_direction + .render(frame, traffic_direction_block, focused_block); + } + + pub fn render_on_sniffing(&mut self, frame: &mut Frame, block: Rect) { + let widths = [Constraint::Length(10), Constraint::Fill(1)]; + let filters = { + [ + Row::new(vec![ + Line::styled("Transport", Style::new().bold()), + Line::from_iter(TransportFilter::new().selected_protocols.iter().map( + |filter| { + if self.transport.applied_protocols.contains(filter) { + Span::styled( + format!(" {} ", filter), + Style::default().light_green(), + ) + } else { + Span::styled( + format!(" {} ", filter), + Style::default().light_red(), + ) + } + }, + )), + ]), + Row::new(vec![ + Line::styled("Network", Style::new().bold()), + Line::from_iter( + NetworkFilter::new() + .selected_protocols + .iter() + .map(|filter| { + if self.network.applied_protocols.contains(filter) { + Span::styled( + format!(" {} ", filter), + Style::default().light_green(), + ) + } else { + Span::styled( + format!(" {} ", filter), + Style::default().light_red(), + ) + } + }), + ), + ]), + Row::new(vec![ + Line::styled("Link", Style::new().bold()), + Line::from_iter(LinkFilter::new().selected_protocols.iter().map(|filter| { + if self.link.applied_protocols.contains(filter) { + Span::styled(format!(" {} ", filter), Style::default().light_green()) + } else { + Span::styled(format!(" {} ", filter), Style::default().light_red()) + } + })), + ]), + Row::new(vec![ + Line::styled("Direction", Style::new().bold()), + Line::from_iter( + TrafficDirectionFilter::default() + .selected_direction + .iter() + .map(|filter| { + if self.traffic_direction.applied_direction.contains(filter) { + Span::styled( + format!("󰞁 {} ", filter), + Style::default().light_green(), + ) + } else { + Span::styled( + format!("󰿝 {} ", filter), + Style::default().light_red(), + ) + } + }), + ), + ]), + ] + }; + + let table = Table::new(filters, widths).column_spacing(3).block( + Block::default() + .title(" Filters 󱪤 ") + .title_style(Style::default().bold().green()) + .title_alignment(Alignment::Center) + .padding(Padding::horizontal(2)) + .borders(Borders::ALL) + .style(Style::default()) + .border_type(BorderType::default()) + .border_style(Style::default().green()), + ); + + frame.render_widget(table, block); + } + + pub fn update(&mut self, frame: &mut Frame, block: Rect, focused_block: &FocusedBlock) { + let layout = Layout::default() + .direction(Direction::Vertical) + .constraints([ + Constraint::Fill(1), + Constraint::Length(40), + Constraint::Fill(1), + ]) + .flex(ratatui::layout::Flex::SpaceBetween) + .split(block); + + let block = Layout::default() + .direction(Direction::Horizontal) + .constraints([ + Constraint::Fill(1), + Constraint::Length(60), + Constraint::Fill(1), + ]) + .flex(ratatui::layout::Flex::SpaceBetween) + .split(layout[1])[1]; + + let ( + transport_filter_block, + network_filter_block, + link_filter_block, + traffic_direction_block, + apply_block, + ) = { + let chunks = Layout::default() + .direction(Direction::Vertical) + .constraints([ + Constraint::Length(NB_TRANSPORT_PROTOCOL + 4), + Constraint::Length(NB_NETWORK_PROTOCOL + 4), + Constraint::Length(NB_LINK_PROTOCOL + 4), + Constraint::Length(6), + Constraint::Length(4), + ]) + .margin(1) + .flex(Flex::SpaceBetween) + .split(block); + (chunks[0], chunks[1], chunks[2], chunks[3], chunks[4]) + }; + + frame.render_widget(Clear, block); + frame.render_widget( + Block::new() + .borders(Borders::all()) + .border_type(BorderType::Thick) + .border_style(Style::default().green()), + block, + ); + + self.transport + .render(frame, transport_filter_block, focused_block); + + self.network + .render(frame, network_filter_block, focused_block); + + self.link.render(frame, link_filter_block, focused_block); + + self.traffic_direction + .render(frame, traffic_direction_block, focused_block); + + let apply = BigText::builder() + .pixel_size(PixelSize::Sextant) + .style(if *focused_block == FocusedBlock::Start { + Style::default().white().bold() + } else { + Style::default().dark_gray() + }) + .lines(vec!["APPLY".into()]) + .centered() + .build(); + frame.render_widget(apply, apply_block); + } +} diff --git a/oryx-tui/src/filters/fuzzy.rs b/oryx-tui/src/filters/fuzzy.rs index 2dfabb7..e26af39 100644 --- a/oryx-tui/src/filters/fuzzy.rs +++ b/oryx-tui/src/filters/fuzzy.rs @@ -1,3 +1,9 @@ +use std::{ + sync::{Arc, Mutex}, + thread, + time::Duration, +}; + use ratatui::{ style::{Style, Stylize}, text::{Line, Span}, @@ -5,7 +11,7 @@ use ratatui::{ }; use tui_input::Input; -use crate::packets::packet::AppPacket; +use crate::{app::TICK_RATE, packets::packet::AppPacket}; #[derive(Debug, Clone, Default)] pub struct Fuzzy { @@ -18,6 +24,38 @@ pub struct Fuzzy { } impl Fuzzy { + pub fn new(packets: Arc>>) -> Arc> { + let fuzzy = Arc::new(Mutex::new(Self::default())); + + thread::spawn({ + let fuzzy = fuzzy.clone(); + let packets = packets.clone(); + move || { + let mut last_index = 0; + let mut pattern = String::new(); + loop { + thread::sleep(Duration::from_millis(TICK_RATE)); + let packets = packets.lock().unwrap(); + let mut fuzzy = fuzzy.lock().unwrap(); + + if fuzzy.is_enabled() && !fuzzy.filter.value().is_empty() { + let current_pattern = fuzzy.filter.value().to_owned(); + if current_pattern != pattern { + fuzzy.find(packets.as_slice()); + pattern = current_pattern; + last_index = packets.len(); + } else { + fuzzy.append(&packets.as_slice()[last_index..]); + last_index = packets.len(); + } + } + } + } + }); + + fuzzy + } + pub fn find(&mut self, packets: &[AppPacket]) { self.packets = packets .iter() diff --git a/oryx-tui/src/filters/link.rs b/oryx-tui/src/filters/link.rs index d3dd157..2623ac3 100644 --- a/oryx-tui/src/filters/link.rs +++ b/oryx-tui/src/filters/link.rs @@ -1,4 +1,4 @@ -use oryx_common::protocols::LinkProtocol; +use oryx_common::protocols::{LinkProtocol, NB_LINK_PROTOCOL}; use ratatui::{ layout::{Alignment, Constraint, Direction, Flex, Layout, Rect}, style::{Color, Style, Stylize}, @@ -29,6 +29,47 @@ impl LinkFilter { applied_protocols: Vec::new(), } } + + pub fn select(&mut self) { + if self.state.selected().is_some() { + let protocol = LinkProtocol::Arp; + if self.selected_protocols.contains(&protocol) { + self.selected_protocols.retain(|&p| p != protocol); + } else { + self.selected_protocols.push(protocol); + } + } + } + + pub fn scroll_down(&mut self) { + let i = match self.state.selected() { + Some(i) => { + if i < (NB_LINK_PROTOCOL - 1).into() { + i + 1 + } else { + i + } + } + None => 0, + }; + + self.state.select(Some(i)); + } + + pub fn scroll_up(&mut self) { + let i = match self.state.selected() { + Some(i) => { + if i > 1 { + i - 1 + } else { + 0 + } + } + None => 0, + }; + + self.state.select(Some(i)); + } pub fn apply(&mut self) { self.applied_protocols = self.selected_protocols.clone(); self.selected_protocols.clear(); diff --git a/oryx-tui/src/filters/network.rs b/oryx-tui/src/filters/network.rs index 2e28b2e..1399953 100644 --- a/oryx-tui/src/filters/network.rs +++ b/oryx-tui/src/filters/network.rs @@ -1,4 +1,4 @@ -use oryx_common::protocols::NetworkProtocol; +use oryx_common::protocols::{NetworkProtocol, NB_NETWORK_PROTOCOL}; use ratatui::{ layout::{Alignment, Constraint, Direction, Flex, Layout, Rect}, style::{Color, Style, Stylize}, @@ -34,6 +34,52 @@ impl NetworkFilter { } } + pub fn select(&mut self) { + if let Some(i) = self.state.selected() { + let protocol = match i { + 0 => NetworkProtocol::Ipv4, + 1 => NetworkProtocol::Ipv6, + _ => NetworkProtocol::Icmp, + }; + + if self.selected_protocols.contains(&protocol) { + self.selected_protocols.retain(|&p| p != protocol); + } else { + self.selected_protocols.push(protocol); + } + } + } + + pub fn scroll_down(&mut self) { + let i = match self.state.selected() { + Some(i) => { + if i < (NB_NETWORK_PROTOCOL - 1).into() { + i + 1 + } else { + i + } + } + None => 0, + }; + + self.state.select(Some(i)); + } + + pub fn scroll_up(&mut self) { + let i = match self.state.selected() { + Some(i) => { + if i > 1 { + i - 1 + } else { + 0 + } + } + None => 0, + }; + + self.state.select(Some(i)); + } + pub fn apply(&mut self) { self.applied_protocols = self.selected_protocols.clone(); self.selected_protocols.clear(); diff --git a/oryx-tui/src/filters/transport.rs b/oryx-tui/src/filters/transport.rs index eb0ded4..1a0da9c 100644 --- a/oryx-tui/src/filters/transport.rs +++ b/oryx-tui/src/filters/transport.rs @@ -1,4 +1,4 @@ -use oryx_common::protocols::TransportProtocol; +use oryx_common::protocols::{TransportProtocol, NB_TRANSPORT_PROTOCOL}; use ratatui::{ layout::{Alignment, Constraint, Direction, Flex, Layout, Rect}, style::{Color, Style, Stylize}, @@ -29,6 +29,52 @@ impl TransportFilter { applied_protocols: Vec::new(), } } + + pub fn select(&mut self) { + if let Some(i) = self.state.selected() { + let protocol = match i { + 0 => TransportProtocol::TCP, + _ => TransportProtocol::UDP, + }; + + if self.selected_protocols.contains(&protocol) { + self.selected_protocols.retain(|&p| p != protocol); + } else { + self.selected_protocols.push(protocol); + } + } + } + + pub fn scroll_down(&mut self) { + let i = match self.state.selected() { + Some(i) => { + if i < (NB_TRANSPORT_PROTOCOL - 1).into() { + i + 1 + } else { + i + } + } + None => 0, + }; + + self.state.select(Some(i)); + } + + pub fn scroll_up(&mut self) { + let i = match self.state.selected() { + Some(i) => { + if i > 1 { + i - 1 + } else { + 0 + } + } + None => 0, + }; + + self.state.select(Some(i)); + } + pub fn apply(&mut self) { self.applied_protocols = self.selected_protocols.clone(); self.selected_protocols.clear(); diff --git a/oryx-tui/src/handler.rs b/oryx-tui/src/handler.rs index a0904d7..8efee88 100644 --- a/oryx-tui/src/handler.rs +++ b/oryx-tui/src/handler.rs @@ -1,7 +1,4 @@ -use oryx_common::protocols::{ - LinkProtocol, NetworkProtocol, Protocol, TransportProtocol, NB_LINK_PROTOCOL, - NB_NETWORK_PROTOCOL, NB_TRANSPORT_PROTOCOL, -}; +use oryx_common::protocols::{LinkProtocol, NetworkProtocol, Protocol, TransportProtocol}; use std::{thread, time::Duration}; use tui_input::backend::crossterm::EventHandler; @@ -70,25 +67,25 @@ pub fn handle_key_events( match &app.focused_block { FocusedBlock::TransportFilter => { app.focused_block = FocusedBlock::NetworkFilter; - app.network_filter.state.select(Some(0)); - app.transport_filter.state.select(Some(0)); + app.filter.network.state.select(Some(0)); + app.filter.transport.state.select(Some(0)); } FocusedBlock::NetworkFilter => { app.focused_block = FocusedBlock::LinkFilter; - app.link_filter.state.select(Some(0)); - app.network_filter.state.select(None); + app.filter.link.state.select(Some(0)); + app.filter.network.state.select(None); } FocusedBlock::LinkFilter => { app.focused_block = FocusedBlock::TrafficDirection; - app.traffic_direction_filter.state.select(Some(0)); - app.link_filter.state.select(None); + app.filter.traffic_direction.state.select(Some(0)); + app.filter.link.state.select(None); } FocusedBlock::TrafficDirection => { app.focused_block = FocusedBlock::Start; - app.traffic_direction_filter.state.select(None); + app.filter.traffic_direction.state.select(None); } FocusedBlock::Start => { @@ -117,30 +114,30 @@ pub fn handle_key_events( match &app.focused_block { FocusedBlock::TransportFilter => { app.focused_block = FocusedBlock::Start; - app.transport_filter.state.select(None); + app.filter.transport.state.select(None); } FocusedBlock::NetworkFilter => { app.focused_block = FocusedBlock::TransportFilter; - app.transport_filter.state.select(Some(0)); - app.network_filter.state.select(None); + app.filter.transport.state.select(Some(0)); + app.filter.network.state.select(None); } FocusedBlock::LinkFilter => { app.focused_block = FocusedBlock::NetworkFilter; - app.network_filter.state.select(Some(0)); - app.link_filter.state.select(None); + app.filter.network.state.select(Some(0)); + app.filter.link.state.select(None); } FocusedBlock::TrafficDirection => { app.focused_block = FocusedBlock::LinkFilter; - app.link_filter.state.select(Some(0)); - app.traffic_direction_filter.state.select(None); + app.filter.link.state.select(Some(0)); + app.filter.traffic_direction.state.select(None); } FocusedBlock::Start => { app.focused_block = FocusedBlock::TrafficDirection; - app.traffic_direction_filter.state.select(Some(0)); + app.filter.traffic_direction.state.select(Some(0)); } _ => {} } @@ -180,23 +177,26 @@ pub fn handle_key_events( } KeyCode::Char('f') => { - if app.focused_block != FocusedBlock::Help && app.start_sniffing { + if app.focused_block != FocusedBlock::Help + && app.start_sniffing + && !app.update_filters + { app.update_filters = true; app.focused_block = FocusedBlock::TransportFilter; - app.network_filter.selected_protocols = - app.network_filter.applied_protocols.clone(); + app.filter.network.selected_protocols = + app.filter.network.applied_protocols.clone(); - app.transport_filter.selected_protocols = - app.transport_filter.applied_protocols.clone(); + app.filter.transport.selected_protocols = + app.filter.transport.applied_protocols.clone(); - app.link_filter.selected_protocols = - app.link_filter.applied_protocols.clone(); + app.filter.link.selected_protocols = + app.filter.link.applied_protocols.clone(); - app.traffic_direction_filter.selected_direction = - app.traffic_direction_filter.applied_direction.clone(); + app.filter.traffic_direction.selected_direction = + app.filter.traffic_direction.applied_direction.clone(); - app.transport_filter.state = TableState::default().with_selected(0); + app.filter.transport.state = TableState::default().with_selected(0); } } @@ -228,52 +228,19 @@ pub fn handle_key_events( } else { match &app.focused_block { FocusedBlock::NetworkFilter => { - let i = match app.network_filter.state.selected() { - Some(i) => { - if i < (NB_NETWORK_PROTOCOL - 1).into() { - i + 1 - } else { - i - } - } - None => 0, - }; - - app.network_filter.state.select(Some(i)); + app.filter.network.scroll_down(); } FocusedBlock::TransportFilter => { - let i = match app.transport_filter.state.selected() { - Some(i) => { - if i < (NB_TRANSPORT_PROTOCOL - 1).into() { - i + 1 - } else { - i - } - } - None => 0, - }; - - app.transport_filter.state.select(Some(i)); + app.filter.transport.scroll_down(); } FocusedBlock::LinkFilter => { - let i = match app.link_filter.state.selected() { - Some(i) => { - if i < (NB_LINK_PROTOCOL - 1).into() { - i + 1 - } else { - i - } - } - None => 0, - }; - - app.link_filter.state.select(Some(i)); + app.filter.link.scroll_down(); } FocusedBlock::TrafficDirection => { - app.traffic_direction_filter.state.select(Some(1)); + app.filter.traffic_direction.state.select(Some(1)); } _ => {} @@ -308,52 +275,19 @@ pub fn handle_key_events( } else { match &app.focused_block { FocusedBlock::NetworkFilter => { - let i = match app.network_filter.state.selected() { - Some(i) => { - if i > 1 { - i - 1 - } else { - 0 - } - } - None => 0, - }; - - app.network_filter.state.select(Some(i)); + app.filter.network.scroll_up(); } FocusedBlock::TransportFilter => { - let i = match app.transport_filter.state.selected() { - Some(i) => { - if i > 1 { - i - 1 - } else { - 0 - } - } - None => 0, - }; - - app.transport_filter.state.select(Some(i)); + app.filter.transport.scroll_up(); } FocusedBlock::LinkFilter => { - let i = match app.link_filter.state.selected() { - Some(i) => { - if i > 1 { - i - 1 - } else { - 0 - } - } - None => 0, - }; - - app.link_filter.state.select(Some(i)); + app.filter.link.scroll_up(); } FocusedBlock::TrafficDirection => { - app.traffic_direction_filter.state.select(Some(0)); + app.filter.traffic_direction.state.select(Some(0)); } FocusedBlock::Help => { @@ -371,24 +305,24 @@ pub fn handle_key_events( } else { match key_event.code { KeyCode::Char('q') => { - app.traffic_direction_filter - .terminate_egress - .store(true, std::sync::atomic::Ordering::Relaxed); - app.traffic_direction_filter - .terminate_ingress - .store(true, std::sync::atomic::Ordering::Relaxed); + app.filter + .traffic_direction + .terminate(TrafficDirection::Egress); + app.filter + .traffic_direction + .terminate(TrafficDirection::Ingress); thread::sleep(Duration::from_millis(110)); app.quit(); } KeyCode::Char('c') | KeyCode::Char('C') => { if key_event.modifiers == KeyModifiers::CONTROL { - app.traffic_direction_filter - .terminate_egress - .store(true, std::sync::atomic::Ordering::Relaxed); - app.traffic_direction_filter - .terminate_ingress - .store(true, std::sync::atomic::Ordering::Relaxed); + app.filter + .traffic_direction + .terminate(TrafficDirection::Egress); + app.filter + .traffic_direction + .terminate(TrafficDirection::Ingress); thread::sleep(Duration::from_millis(110)); app.quit(); } @@ -419,23 +353,26 @@ pub fn handle_key_events( } KeyCode::Char('f') => { - if app.focused_block != FocusedBlock::Help && app.start_sniffing { + if app.focused_block != FocusedBlock::Help + && app.start_sniffing + && !app.update_filters + { app.update_filters = true; app.focused_block = FocusedBlock::TransportFilter; - app.network_filter.selected_protocols = - app.network_filter.applied_protocols.clone(); + app.filter.network.selected_protocols = + app.filter.network.applied_protocols.clone(); - app.transport_filter.selected_protocols = - app.transport_filter.applied_protocols.clone(); + app.filter.transport.selected_protocols = + app.filter.transport.applied_protocols.clone(); - app.link_filter.selected_protocols = app.link_filter.applied_protocols.clone(); + app.filter.link.selected_protocols = app.filter.link.applied_protocols.clone(); - app.traffic_direction_filter.selected_direction = - app.traffic_direction_filter.applied_direction.clone(); + app.filter.traffic_direction.selected_direction = + app.filter.traffic_direction.applied_direction.clone(); - app.transport_filter.state = TableState::default().with_selected(0); + app.filter.transport.state = TableState::default().with_selected(0); } } @@ -501,12 +438,12 @@ pub fn handle_key_events( return Ok(()); } if key_event.modifiers == KeyModifiers::CONTROL { - app.traffic_direction_filter - .terminate_egress - .store(true, std::sync::atomic::Ordering::Relaxed); - app.traffic_direction_filter - .terminate_ingress - .store(true, std::sync::atomic::Ordering::Relaxed); + app.filter + .traffic_direction + .terminate(TrafficDirection::Ingress); + app.filter + .traffic_direction + .terminate(TrafficDirection::Egress); thread::sleep(Duration::from_millis(150)); sender.send(Event::Reset)?; } @@ -516,13 +453,14 @@ pub fn handle_key_events( if app.focused_block == FocusedBlock::Start && !app.start_sniffing { let iface = app.interface.selected_interface.name.clone(); - app.network_filter.apply(); - app.transport_filter.apply(); - app.link_filter.apply(); - app.traffic_direction_filter.apply(); + app.filter.network.apply(); + app.filter.transport.apply(); + app.filter.link.apply(); + app.filter.traffic_direction.apply(); if app - .traffic_direction_filter + .filter + .traffic_direction .applied_direction .contains(&TrafficDirection::Ingress) { @@ -530,13 +468,14 @@ pub fn handle_key_events( iface.clone(), sender.clone(), app.data_channel_sender.clone(), - app.ingress_filter_channel.receiver.clone(), - app.traffic_direction_filter.terminate_ingress.clone(), + app.filter.ingress_channel.receiver.clone(), + app.filter.traffic_direction.terminate_ingress.clone(), ); } if app - .traffic_direction_filter + .filter + .traffic_direction .applied_direction .contains(&TrafficDirection::Egress) { @@ -544,8 +483,8 @@ pub fn handle_key_events( iface, sender.clone(), app.data_channel_sender.clone(), - app.egress_filter_channel.receiver.clone(), - app.traffic_direction_filter.terminate_egress.clone(), + app.filter.egress_channel.receiver.clone(), + app.filter.traffic_direction.terminate_egress.clone(), ); } @@ -554,30 +493,35 @@ pub fn handle_key_events( } else if app.start_sniffing && app.update_filters { // Remove egress if app - .traffic_direction_filter + .filter + .traffic_direction .applied_direction .contains(&TrafficDirection::Egress) && !app - .traffic_direction_filter + .filter + .traffic_direction .selected_direction .contains(&TrafficDirection::Egress) { - app.traffic_direction_filter - .terminate_egress - .store(true, std::sync::atomic::Ordering::Relaxed); + app.filter + .traffic_direction + .terminate(TrafficDirection::Egress); } // Add egress if !app - .traffic_direction_filter + .filter + .traffic_direction .applied_direction .contains(&TrafficDirection::Egress) && app - .traffic_direction_filter + .filter + .traffic_direction .selected_direction .contains(&TrafficDirection::Egress) { - app.traffic_direction_filter + app.filter + .traffic_direction .terminate_egress .store(false, std::sync::atomic::Ordering::Relaxed); let iface = app.interface.selected_interface.name.clone(); @@ -585,58 +529,65 @@ pub fn handle_key_events( iface, sender.clone(), app.data_channel_sender.clone(), - app.egress_filter_channel.receiver.clone(), - app.traffic_direction_filter.terminate_egress.clone(), + app.filter.egress_channel.receiver.clone(), + app.filter.traffic_direction.terminate_egress.clone(), ); } // Remove ingress if app - .traffic_direction_filter + .filter + .traffic_direction .applied_direction .contains(&TrafficDirection::Ingress) && !app - .traffic_direction_filter + .filter + .traffic_direction .selected_direction .contains(&TrafficDirection::Ingress) { - app.traffic_direction_filter - .terminate_ingress - .store(true, std::sync::atomic::Ordering::Relaxed); + app.filter + .traffic_direction + .terminate(TrafficDirection::Ingress); } // Add ingress if !app - .traffic_direction_filter + .filter + .traffic_direction .applied_direction .contains(&TrafficDirection::Ingress) && app - .traffic_direction_filter + .filter + .traffic_direction .selected_direction .contains(&TrafficDirection::Ingress) { let iface = app.interface.selected_interface.name.clone(); - app.traffic_direction_filter + app.filter + .traffic_direction .terminate_ingress .store(false, std::sync::atomic::Ordering::Relaxed); Ebpf::load_ingress( iface, sender.clone(), app.data_channel_sender.clone(), - app.ingress_filter_channel.receiver.clone(), - app.traffic_direction_filter.terminate_ingress.clone(), + app.filter.ingress_channel.receiver.clone(), + app.filter.traffic_direction.terminate_ingress.clone(), ); } - app.network_filter.apply(); - app.transport_filter.apply(); - app.link_filter.apply(); - app.traffic_direction_filter.apply(); + app.filter.network.apply(); + app.filter.transport.apply(); + app.filter.link.apply(); + app.filter.traffic_direction.apply(); thread::sleep(Duration::from_millis(150)); - app.traffic_direction_filter + app.filter + .traffic_direction .terminate_ingress .store(false, std::sync::atomic::Ordering::Relaxed); - app.traffic_direction_filter + app.filter + .traffic_direction .terminate_ingress .store(false, std::sync::atomic::Ordering::Relaxed); @@ -644,54 +595,66 @@ pub fn handle_key_events( } for protocol in TransportProtocol::all().iter() { - if app.transport_filter.applied_protocols.contains(protocol) { - app.ingress_filter_channel + if app.filter.transport.applied_protocols.contains(protocol) { + app.filter + .ingress_channel .sender .send((Protocol::Transport(*protocol), false))?; - app.egress_filter_channel + app.filter + .egress_channel .sender .send((Protocol::Transport(*protocol), false))?; } else { - app.ingress_filter_channel + app.filter + .ingress_channel .sender .send((Protocol::Transport(*protocol), true))?; - app.egress_filter_channel + app.filter + .egress_channel .sender .send((Protocol::Transport(*protocol), true))?; } } for protocol in NetworkProtocol::all().iter() { - if app.network_filter.applied_protocols.contains(protocol) { - app.ingress_filter_channel + if app.filter.network.applied_protocols.contains(protocol) { + app.filter + .ingress_channel .sender .send((Protocol::Network(*protocol), false))?; - app.egress_filter_channel + app.filter + .egress_channel .sender .send((Protocol::Network(*protocol), false))?; } else { - app.ingress_filter_channel + app.filter + .ingress_channel .sender .send((Protocol::Network(*protocol), true))?; - app.egress_filter_channel + app.filter + .egress_channel .sender .send((Protocol::Network(*protocol), true))?; } } for protocol in LinkProtocol::all().iter() { - if app.link_filter.applied_protocols.contains(protocol) { - app.ingress_filter_channel + if app.filter.link.applied_protocols.contains(protocol) { + app.filter + .ingress_channel .sender .send((Protocol::Link(*protocol), false))?; - app.egress_filter_channel + app.filter + .egress_channel .sender .send((Protocol::Link(*protocol), false))?; } else { - app.ingress_filter_channel + app.filter + .ingress_channel .sender .send((Protocol::Link(*protocol), true))?; - app.egress_filter_channel + app.filter + .egress_channel .sender .send((Protocol::Link(*protocol), true))?; } @@ -708,30 +671,30 @@ pub fn handle_key_events( match &app.focused_block { FocusedBlock::TransportFilter => { app.focused_block = FocusedBlock::NetworkFilter; - app.network_filter.state.select(Some(0)); - app.transport_filter.state.select(None); + app.filter.network.state.select(Some(0)); + app.filter.transport.state.select(None); } FocusedBlock::NetworkFilter => { app.focused_block = FocusedBlock::LinkFilter; - app.link_filter.state.select(Some(0)); - app.network_filter.state.select(None); + app.filter.link.state.select(Some(0)); + app.filter.network.state.select(None); } FocusedBlock::LinkFilter => { app.focused_block = FocusedBlock::TrafficDirection; - app.traffic_direction_filter.state.select(Some(0)); - app.link_filter.state.select(None); + app.filter.traffic_direction.state.select(Some(0)); + app.filter.link.state.select(None); } FocusedBlock::TrafficDirection => { app.focused_block = FocusedBlock::Start; - app.traffic_direction_filter.state.select(None); + app.filter.traffic_direction.state.select(None); } FocusedBlock::Start => { app.focused_block = FocusedBlock::TransportFilter; - app.transport_filter.state.select(Some(0)); + app.filter.transport.state.select(Some(0)); } _ => {} }; @@ -750,34 +713,34 @@ pub fn handle_key_events( app.focused_block = FocusedBlock::TransportFilter; app.previous_focused_block = app.focused_block; app.interface.state.select(None); - app.transport_filter.state.select(Some(0)); + app.filter.transport.state.select(Some(0)); } FocusedBlock::TransportFilter => { app.focused_block = FocusedBlock::NetworkFilter; app.previous_focused_block = app.focused_block; - app.network_filter.state.select(Some(0)); - app.transport_filter.state.select(None); + app.filter.network.state.select(Some(0)); + app.filter.transport.state.select(None); } FocusedBlock::NetworkFilter => { app.focused_block = FocusedBlock::LinkFilter; app.previous_focused_block = app.focused_block; - app.link_filter.state.select(Some(0)); - app.network_filter.state.select(None); + app.filter.link.state.select(Some(0)); + app.filter.network.state.select(None); } FocusedBlock::LinkFilter => { app.focused_block = FocusedBlock::TrafficDirection; app.previous_focused_block = app.focused_block; - app.traffic_direction_filter.state.select(Some(0)); - app.link_filter.state.select(None); + app.filter.traffic_direction.state.select(Some(0)); + app.filter.link.state.select(None); } FocusedBlock::TrafficDirection => { app.focused_block = FocusedBlock::Start; app.previous_focused_block = app.focused_block; - app.traffic_direction_filter.state.select(None); + app.filter.traffic_direction.state.select(None); } FocusedBlock::Start => { @@ -800,30 +763,30 @@ pub fn handle_key_events( match &app.focused_block { FocusedBlock::TransportFilter => { app.focused_block = FocusedBlock::Start; - app.transport_filter.state.select(None); + app.filter.transport.state.select(None); } FocusedBlock::NetworkFilter => { app.focused_block = FocusedBlock::TransportFilter; - app.transport_filter.state.select(Some(0)); - app.network_filter.state.select(None); + app.filter.transport.state.select(Some(0)); + app.filter.network.state.select(None); } FocusedBlock::LinkFilter => { app.focused_block = FocusedBlock::NetworkFilter; - app.network_filter.state.select(Some(0)); - app.link_filter.state.select(None); + app.filter.network.state.select(Some(0)); + app.filter.link.state.select(None); } FocusedBlock::TrafficDirection => { app.focused_block = FocusedBlock::LinkFilter; - app.link_filter.state.select(Some(0)); - app.traffic_direction_filter.state.select(None); + app.filter.link.state.select(Some(0)); + app.filter.traffic_direction.state.select(None); } FocusedBlock::Start => { app.focused_block = FocusedBlock::TrafficDirection; - app.traffic_direction_filter.state.select(Some(0)); + app.filter.traffic_direction.state.select(Some(0)); } _ => {} } @@ -845,30 +808,30 @@ pub fn handle_key_events( FocusedBlock::TransportFilter => { app.focused_block = FocusedBlock::Interface; app.interface.state.select(Some(0)); - app.transport_filter.state.select(None); + app.filter.transport.state.select(None); } FocusedBlock::NetworkFilter => { app.focused_block = FocusedBlock::TransportFilter; - app.transport_filter.state.select(Some(0)); - app.network_filter.state.select(None); + app.filter.transport.state.select(Some(0)); + app.filter.network.state.select(None); } FocusedBlock::LinkFilter => { app.focused_block = FocusedBlock::NetworkFilter; - app.network_filter.state.select(Some(0)); - app.link_filter.state.select(None); + app.filter.network.state.select(Some(0)); + app.filter.link.state.select(None); } FocusedBlock::TrafficDirection => { app.focused_block = FocusedBlock::LinkFilter; - app.link_filter.state.select(Some(0)); - app.traffic_direction_filter.state.select(None); + app.filter.link.state.select(Some(0)); + app.filter.traffic_direction.state.select(None); } FocusedBlock::Start => { app.focused_block = FocusedBlock::TrafficDirection; - app.traffic_direction_filter.state.select(Some(0)); + app.filter.traffic_direction.state.select(Some(0)); } _ => {} } @@ -888,73 +851,19 @@ pub fn handle_key_events( } } FocusedBlock::NetworkFilter => { - if let Some(i) = app.network_filter.state.selected() { - let protocol = match i { - 0 => NetworkProtocol::Ipv4, - 1 => NetworkProtocol::Ipv6, - _ => NetworkProtocol::Icmp, - }; - - if app.network_filter.selected_protocols.contains(&protocol) { - app.network_filter - .selected_protocols - .retain(|&p| p != protocol); - } else { - app.network_filter.selected_protocols.push(protocol); - } - } + app.filter.network.select(); } - FocusedBlock::TransportFilter => { - if let Some(i) = app.transport_filter.state.selected() { - let protocol = match i { - 0 => TransportProtocol::TCP, - _ => TransportProtocol::UDP, - }; - if app.transport_filter.selected_protocols.contains(&protocol) { - app.transport_filter - .selected_protocols - .retain(|&p| p != protocol); - } else { - app.transport_filter.selected_protocols.push(protocol); - } - } + FocusedBlock::TransportFilter => { + app.filter.transport.select(); } FocusedBlock::LinkFilter => { - if app.link_filter.state.selected().is_some() { - let protocol = LinkProtocol::Arp; - if app.link_filter.selected_protocols.contains(&protocol) { - app.link_filter - .selected_protocols - .retain(|&p| p != protocol); - } else { - app.link_filter.selected_protocols.push(protocol); - } - } + app.filter.link.select(); } FocusedBlock::TrafficDirection => { - if let Some(i) = app.traffic_direction_filter.state.selected() { - let traffic_direction = match i { - 0 => TrafficDirection::Ingress, - _ => TrafficDirection::Egress, - }; - - if app - .traffic_direction_filter - .selected_direction - .contains(&traffic_direction) - { - app.traffic_direction_filter - .selected_direction - .retain(|&direction| direction != traffic_direction); - } else { - app.traffic_direction_filter - .selected_direction - .push(traffic_direction); - } - } + app.filter.traffic_direction.select(); } _ => {} @@ -995,67 +904,23 @@ pub fn handle_key_events( } else { match &app.focused_block { FocusedBlock::Interface => { - let i = match app.interface.state.selected() { - Some(i) => { - if i < app.interface.interfaces.len() - 1 { - i + 1 - } else { - i - } - } - None => 0, - }; - - app.interface.state.select(Some(i)); + app.interface.scroll_down(); } FocusedBlock::NetworkFilter => { - let i = match app.network_filter.state.selected() { - Some(i) => { - if i < (NB_NETWORK_PROTOCOL - 1).into() { - i + 1 - } else { - i - } - } - None => 0, - }; - - app.network_filter.state.select(Some(i)); + app.filter.network.scroll_down(); } FocusedBlock::TransportFilter => { - let i = match app.transport_filter.state.selected() { - Some(i) => { - if i < (NB_TRANSPORT_PROTOCOL - 1).into() { - i + 1 - } else { - i - } - } - None => 0, - }; - - app.transport_filter.state.select(Some(i)); + app.filter.transport.scroll_down(); } FocusedBlock::LinkFilter => { - let i = match app.link_filter.state.selected() { - Some(i) => { - if i < (NB_LINK_PROTOCOL - 1).into() { - i + 1 - } else { - i - } - } - None => 0, - }; - - app.link_filter.state.select(Some(i)); + app.filter.link.scroll_down(); } FocusedBlock::TrafficDirection => { - app.traffic_direction_filter.state.select(Some(1)); + app.filter.traffic_direction.state.select(Some(1)); } FocusedBlock::Help => { @@ -1096,66 +961,22 @@ pub fn handle_key_events( } else { match &app.focused_block { FocusedBlock::Interface => { - let i = match app.interface.state.selected() { - Some(i) => { - if i > 1 { - i - 1 - } else { - 0 - } - } - None => 0, - }; - - app.interface.state.select(Some(i)); + app.interface.scroll_up(); } FocusedBlock::NetworkFilter => { - let i = match app.network_filter.state.selected() { - Some(i) => { - if i > 1 { - i - 1 - } else { - 0 - } - } - None => 0, - }; - - app.network_filter.state.select(Some(i)); + app.filter.network.scroll_up(); } FocusedBlock::TransportFilter => { - let i = match app.transport_filter.state.selected() { - Some(i) => { - if i > 1 { - i - 1 - } else { - 0 - } - } - None => 0, - }; - - app.transport_filter.state.select(Some(i)); + app.filter.transport.scroll_up(); } FocusedBlock::LinkFilter => { - let i = match app.link_filter.state.selected() { - Some(i) => { - if i > 1 { - i - 1 - } else { - 0 - } - } - None => 0, - }; - - app.link_filter.state.select(Some(i)); + app.filter.link.scroll_up(); } FocusedBlock::TrafficDirection => { - app.traffic_direction_filter.state.select(Some(0)); + app.filter.traffic_direction.state.select(Some(0)); } FocusedBlock::Help => { diff --git a/oryx-tui/src/infos.rs b/oryx-tui/src/infos.rs deleted file mode 100644 index e69de29..0000000 diff --git a/oryx-tui/src/interface.rs b/oryx-tui/src/interface.rs index b640f9f..81d6e56 100644 --- a/oryx-tui/src/interface.rs +++ b/oryx-tui/src/interface.rs @@ -2,19 +2,18 @@ use libc::{AF_INET, AF_INET6, IFF_UP}; use ratatui::{ layout::{Alignment, Constraint, Direction, Flex, Layout, Rect}, style::{Color, Style, Stylize}, - text::Line, - widgets::{Block, BorderType, Borders, Row, Table, TableState}, + text::{Line, Span}, + widgets::{Block, BorderType, Borders, Padding, Row, Table, TableState}, Frame, }; use std::{ + ffi::CStr, fs::{self}, net::{IpAddr, Ipv4Addr, Ipv6Addr}, path::PathBuf, }; -use std::ffi::CStr; - use crate::app::FocusedBlock; #[derive(Debug, Clone)] @@ -122,7 +121,41 @@ impl Interface { } } - pub fn render(&mut self, frame: &mut Frame, block: Rect, focused_block: &FocusedBlock) { + pub fn scroll_down(&mut self) { + let i = match self.state.selected() { + Some(i) => { + if i < self.interfaces.len() - 1 { + i + 1 + } else { + i + } + } + None => 0, + }; + + self.state.select(Some(i)); + } + pub fn scroll_up(&mut self) { + let i = match self.state.selected() { + Some(i) => { + if i > 1 { + i - 1 + } else { + 0 + } + } + None => 0, + }; + + self.state.select(Some(i)); + } + + pub fn render_on_setup( + &mut self, + frame: &mut Frame, + block: Rect, + focused_block: &FocusedBlock, + ) { let layout = Layout::default() .direction(Direction::Horizontal) .constraints([ @@ -210,4 +243,62 @@ impl Interface { &mut self.state, ); } + + pub fn render_on_sniffing(&mut self, frame: &mut Frame, block: Rect) { + let widths = [Constraint::Length(4), Constraint::Fill(1)]; + + let interface_infos = [ + Row::new(vec![ + Span::styled("Name", Style::new().bold()), + Span::from(self.selected_interface.name.clone()), + ]), + Row::new(vec![ + Span::styled("Mac", Style::new().bold()), + Span::from( + self.selected_interface + .mac_address + .clone() + .unwrap_or("-".to_string()), + ), + ]), + Row::new(vec![ + Span::styled("IPv4", Style::new().bold()), + Span::from( + self.selected_interface + .addresses + .iter() + .find(|a| matches!(a, IpAddr::V4(_) | IpAddr::V6(_))) + .unwrap() + .to_string(), + ), + ]), + Row::new(vec![ + Span::styled("IPv6", Style::new().bold()), + Span::from({ + match self + .selected_interface + .addresses + .iter() + .find(|a| matches!(a, IpAddr::V6(_))) + { + Some(ip) => ip.to_string(), + None => "-".to_string(), + } + }), + ]), + ]; + + let table = Table::new(interface_infos, widths).column_spacing(3).block( + Block::default() + .title(" Interface 󰲝 ") + .title_style(Style::default().bold().green()) + .title_alignment(Alignment::Center) + .padding(Padding::horizontal(2)) + .borders(Borders::ALL) + .style(Style::default()) + .border_type(BorderType::default()) + .border_style(Style::default().green()), + ); + frame.render_widget(table, block); + } } diff --git a/oryx-tui/src/lib.rs b/oryx-tui/src/lib.rs index 0216fea..2534f24 100644 --- a/oryx-tui/src/lib.rs +++ b/oryx-tui/src/lib.rs @@ -16,6 +16,7 @@ pub mod ebpf; pub mod filters { pub mod direction; + pub mod filter; pub mod fuzzy; pub mod link; pub mod network; @@ -36,3 +37,8 @@ pub mod packets { pub mod packet; pub mod transport; } + +pub mod alerts { + pub mod alert; + pub mod syn_flood; +} diff --git a/oryx-tui/src/main.rs b/oryx-tui/src/main.rs index 623240a..e0e5816 100644 --- a/oryx-tui/src/main.rs +++ b/oryx-tui/src/main.rs @@ -4,12 +4,13 @@ static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; use std::io; use clap::{crate_description, crate_version, Command}; -use oryx_tui::app::{App, AppResult, TICK_RATE}; -use oryx_tui::event::{Event, EventHandler}; -use oryx_tui::handler::handle_key_events; -use oryx_tui::tui::Tui; -use ratatui::backend::CrosstermBackend; -use ratatui::Terminal; +use oryx_tui::{ + app::{App, AppResult, TICK_RATE}, + event::{Event, EventHandler}, + handler::handle_key_events, + tui::Tui, +}; +use ratatui::{backend::CrosstermBackend, Terminal}; fn main() -> AppResult<()> { Command::new("oryx") diff --git a/oryx-tui/src/packets/network.rs b/oryx-tui/src/packets/network.rs index 33ec9c1..e25dff2 100644 --- a/oryx-tui/src/packets/network.rs +++ b/oryx-tui/src/packets/network.rs @@ -1,4 +1,7 @@ -use core::fmt::Display; +use core::{ + fmt::Display, + net::{Ipv4Addr, Ipv6Addr}, +}; use ratatui::{ layout::{Constraint, Direction, Layout, Rect}, style::{Style, Stylize}, @@ -7,8 +10,6 @@ use ratatui::{ Frame, }; -use core::net::{Ipv4Addr, Ipv6Addr}; - use super::transport::{TcpPacket, UdpPacket}; #[derive(Debug, Copy, Clone)] diff --git a/oryx-tui/src/packets/packet.rs b/oryx-tui/src/packets/packet.rs index 4f4c79e..79e3cb0 100644 --- a/oryx-tui/src/packets/packet.rs +++ b/oryx-tui/src/packets/packet.rs @@ -1,14 +1,13 @@ -use core::fmt::Display; - -use core::net::Ipv4Addr; -use std::mem; +use std::{fmt::Display, mem, net::Ipv4Addr}; use network_types::ip::IpHdr; use oryx_common::{ProtoHdr, RawPacket}; -use super::link::{ArpPacket, ArpType, MacAddr}; -use super::network::{IcmpPacket, IcmpType, IpPacket, IpProto, Ipv4Packet, Ipv6Packet}; -use super::transport::{TcpPacket, UdpPacket}; +use super::{ + link::{ArpPacket, ArpType, MacAddr}, + network::{IcmpPacket, IcmpType, IpPacket, IpProto, Ipv4Packet, Ipv6Packet}, + transport::{TcpPacket, UdpPacket}, +}; #[derive(Debug, Copy, Clone)] pub enum AppPacket { diff --git a/oryx-tui/src/stats.rs b/oryx-tui/src/stats.rs index 7ce59c7..0555cc6 100644 --- a/oryx-tui/src/stats.rs +++ b/oryx-tui/src/stats.rs @@ -1,17 +1,21 @@ use dns_lookup::lookup_addr; -use std::collections::HashMap; -use std::net::{IpAddr, Ipv4Addr}; +use std::{ + collections::HashMap, + net::{IpAddr, Ipv4Addr}, +}; -use ratatui::layout::{Alignment, Constraint, Direction, Flex, Layout, Rect}; -use ratatui::style::{Color, Style}; -use ratatui::text::Line; use ratatui::{ + layout::{Alignment, Constraint, Direction, Flex, Layout, Rect}, + style::{Color, Style}, + text::Line, widgets::{Bar, BarChart, BarGroup, Block, Padding}, Frame, }; -use crate::packets::network::{IpPacket, IpProto}; -use crate::packets::packet::AppPacket; +use crate::packets::{ + network::{IpPacket, IpProto}, + packet::AppPacket, +}; #[derive(Debug)] pub struct Stats { diff --git a/oryx-tui/src/tui.rs b/oryx-tui/src/tui.rs index 732dc64..8bcf7d0 100644 --- a/oryx-tui/src/tui.rs +++ b/oryx-tui/src/tui.rs @@ -1,12 +1,14 @@ -use crate::app::{App, AppResult}; -use crate::event::EventHandler; -use crate::ui; -use crossterm::event::{DisableMouseCapture, EnableMouseCapture}; -use crossterm::terminal::{self, EnterAlternateScreen, LeaveAlternateScreen}; -use ratatui::backend::Backend; -use ratatui::Terminal; -use std::io; -use std::panic; +use crate::{ + app::{App, AppResult}, + event::EventHandler, + ui, +}; +use crossterm::{ + event::{DisableMouseCapture, EnableMouseCapture}, + terminal::{self, EnterAlternateScreen, LeaveAlternateScreen}, +}; +use ratatui::{backend::Backend, Terminal}; +use std::{io, panic}; #[derive(Debug)] pub struct Tui {