use chrono::{DateTime, Local};
use color_eyre::eyre::Result;
use color_eyre::owo_colors::OwoColorize;
use ratatui::crossterm::event::{KeyCode, KeyEvent};
use ipnetwork::Ipv4Network;
use pnet::datalink::{Channel, ChannelType, NetworkInterface};
use pnet::packet::icmpv6::Icmpv6Types;
use pnet::packet::{
arp::{ArpHardwareTypes, ArpOperations, ArpPacket, MutableArpPacket},
ethernet::{EtherTypes, EthernetPacket, MutableEthernetPacket},
icmp::{echo_reply, echo_request, IcmpPacket, IcmpTypes},
icmpv6::Icmpv6Packet,
ip::{IpNextHeaderProtocol, IpNextHeaderProtocols},
ipv4::Ipv4Packet,
ipv6::Ipv6Packet,
tcp::TcpPacket,
udp::UdpPacket,
MutablePacket, Packet,
};
use pnet::util::MacAddr;
use ratatui::layout::Position;
use ratatui::style::Stylize;
use ratatui::{prelude::*, widgets::*};
use std::{
collections::HashMap,
net::{IpAddr, Ipv4Addr},
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
thread::{self, JoinHandle},
time::Duration,
};
use tokio::{
sync::mpsc::{self, UnboundedReceiver, UnboundedSender},
task,
};
use tui_input::backend::crossterm::EventHandler;
use tui_input::Input;
use super::{Component, Frame};
use crate::{
action::Action,
config::DEFAULT_BORDER_STYLE,
config::{Config, KeyBindings},
enums::{
ARPPacketInfo, ICMP6PacketInfo, ICMPPacketInfo, PacketTypeEnum, PacketsInfoTypesEnum,
TCPPacketInfo, TabsEnum, UDPPacketInfo,
},
layout::get_vertical_layout,
mode::Mode,
utils::MaxSizeVec,
};
use strum::{EnumCount, IntoEnumIterator};
static INPUT_SIZE: usize = 30;
#[derive(Debug, Clone, PartialEq)]
pub struct ArpPacketData {
pub sender_mac: MacAddr,
pub sender_ip: Ipv4Addr,
pub target_mac: MacAddr,
pub target_ip: Ipv4Addr,
pub operation: pnet::packet::arp::ArpOperation,
}
pub struct PacketDump {
active_tab: TabsEnum,
action_tx: Option<UnboundedSender<Action>>,
loop_thread: Option<JoinHandle<()>>,
should_quit: bool,
dump_paused: Arc<AtomicBool>,
dump_stop: Arc<AtomicBool>,
active_interface: Option<NetworkInterface>,
table_state: TableState,
scrollbar_state: ScrollbarState,
packet_type: PacketTypeEnum,
input: Input,
mode: Mode,
filter_str: String,
changed_interface: bool,
arp_packets: MaxSizeVec<(DateTime<Local>, PacketsInfoTypesEnum)>,
udp_packets: MaxSizeVec<(DateTime<Local>, PacketsInfoTypesEnum)>,
tcp_packets: MaxSizeVec<(DateTime<Local>, PacketsInfoTypesEnum)>,
icmp_packets: MaxSizeVec<(DateTime<Local>, PacketsInfoTypesEnum)>,
icmp6_packets: MaxSizeVec<(DateTime<Local>, PacketsInfoTypesEnum)>,
all_packets: MaxSizeVec<(DateTime<Local>, PacketsInfoTypesEnum)>,
}
impl Default for PacketDump {
fn default() -> Self {
Self::new()
}
}
impl PacketDump {
pub fn new() -> Self {
Self {
active_tab: TabsEnum::Discovery,
action_tx: None,
loop_thread: None,
should_quit: false,
dump_paused: Arc::new(AtomicBool::new(false)),
dump_stop: Arc::new(AtomicBool::new(false)),
active_interface: None,
table_state: {
let mut state = TableState::default();
state.select(Some(0));
state
},
scrollbar_state: ScrollbarState::new(0),
packet_type: PacketTypeEnum::All,
input: Input::default().with_value(String::from("")),
mode: Mode::Normal,
filter_str: String::from(""),
changed_interface: false,
arp_packets: MaxSizeVec::new(1000),
udp_packets: MaxSizeVec::new(1000),
tcp_packets: MaxSizeVec::new(1000),
icmp_packets: MaxSizeVec::new(1000),
icmp6_packets: MaxSizeVec::new(1000),
all_packets: MaxSizeVec::new(1000),
}
}
fn handle_udp_packet(
interface_name: &str,
source: IpAddr,
destination: IpAddr,
packet: &[u8],
tx: UnboundedSender<Action>,
) {
let udp = UdpPacket::new(packet);
if let Some(udp) = udp {
let raw_str = format!(
"[{}]: UDP Packet: {}:{} > {}:{}; length: {}",
interface_name,
source,
udp.get_source(),
destination,
udp.get_destination(),
udp.get_length()
);
tx.send(Action::PacketDump(
Local::now(),
PacketsInfoTypesEnum::Udp(UDPPacketInfo {
interface_name: interface_name.to_string(),
source,
source_port: udp.get_source(),
destination,
destination_port: udp.get_destination(),
length: udp.get_length() as usize,
raw_str,
}),
PacketTypeEnum::Udp,
))
.unwrap();
}
}
fn handle_icmp_packet(
interface_name: &str,
source: IpAddr,
destination: IpAddr,
packet: &[u8],
tx: UnboundedSender<Action>,
) {
let icmp_packet = IcmpPacket::new(packet);
if let Some(icmp_packet) = icmp_packet {
match icmp_packet.get_icmp_type() {
IcmpTypes::EchoReply => {
let echo_reply_packet = echo_reply::EchoReplyPacket::new(packet).unwrap();
let raw_str = format!(
"[{}]: ICMP echo reply {} -> {} (seq={:?}, id={:?})",
interface_name,
source,
destination,
echo_reply_packet.get_sequence_number(),
echo_reply_packet.get_identifier()
);
tx.send(Action::PacketDump(
Local::now(),
PacketsInfoTypesEnum::Icmp(ICMPPacketInfo {
interface_name: interface_name.to_string(),
source,
destination,
seq: echo_reply_packet.get_sequence_number(),
id: echo_reply_packet.get_identifier(),
icmp_type: IcmpTypes::EchoReply,
raw_str,
}),
PacketTypeEnum::Icmp,
))
.unwrap();
}
IcmpTypes::EchoRequest => {
let echo_request_packet = echo_request::EchoRequestPacket::new(packet).unwrap();
let raw_str = format!(
"[{}]: ICMP echo request {} -> {} (seq={:?}, id={:?})",
interface_name,
source,
destination,
echo_request_packet.get_sequence_number(),
echo_request_packet.get_identifier()
);
tx.send(Action::PacketDump(
Local::now(),
PacketsInfoTypesEnum::Icmp(ICMPPacketInfo {
interface_name: interface_name.to_string(),
source,
destination,
seq: echo_request_packet.get_sequence_number(),
id: echo_request_packet.get_identifier(),
icmp_type: IcmpTypes::EchoRequest,
raw_str,
}),
PacketTypeEnum::Icmp,
))
.unwrap();
}
_ => {}
}
}
}
fn handle_icmpv6_packet(
interface_name: &str,
source: IpAddr,
destination: IpAddr,
packet: &[u8],
tx: UnboundedSender<Action>,
) {
let icmpv6_packet = Icmpv6Packet::new(packet);
if let Some(icmpv6_packet) = icmpv6_packet {
let raw_str = format!(
"[{}]: ICMPv6 packet {} -> {} (type={:?})",
interface_name,
source,
destination,
icmpv6_packet.get_icmpv6_type()
);
tx.send(Action::PacketDump(
Local::now(),
PacketsInfoTypesEnum::Icmp6(ICMP6PacketInfo {
interface_name: interface_name.to_string(),
source,
destination,
icmp_type: icmpv6_packet.get_icmpv6_type(),
raw_str,
}),
PacketTypeEnum::Icmp6,
))
.unwrap();
}
}
fn handle_tcp_packet(
interface_name: &str,
source: IpAddr,
destination: IpAddr,
packet: &[u8],
tx: UnboundedSender<Action>,
) {
let tcp = TcpPacket::new(packet);
if let Some(tcp) = tcp {
let raw_str = format!(
"[{}]: TCP Packet: {}:{} > {}:{}; length: {}",
interface_name,
source,
tcp.get_source(),
destination,
tcp.get_destination(),
packet.len()
);
tx.send(Action::PacketDump(
Local::now(),
PacketsInfoTypesEnum::Tcp(TCPPacketInfo {
interface_name: interface_name.to_string(),
source,
source_port: tcp.get_source(),
destination,
destination_port: tcp.get_destination(),
length: packet.len(),
raw_str,
}),
PacketTypeEnum::Tcp,
))
.unwrap();
}
}
fn handle_transport_protocol(
interface_name: &str,
source: IpAddr,
destination: IpAddr,
protocol: IpNextHeaderProtocol,
packet: &[u8],
tx: UnboundedSender<Action>,
) {
match protocol {
IpNextHeaderProtocols::Udp => {
Self::handle_udp_packet(interface_name, source, destination, packet, tx)
}
IpNextHeaderProtocols::Tcp => {
Self::handle_tcp_packet(interface_name, source, destination, packet, tx)
}
IpNextHeaderProtocols::Icmp => {
Self::handle_icmp_packet(interface_name, source, destination, packet, tx)
}
IpNextHeaderProtocols::Icmpv6 => {
Self::handle_icmpv6_packet(interface_name, source, destination, packet, tx)
}
_ => {}
}
}
fn handle_ipv4_packet(
interface_name: &str,
ethernet: &EthernetPacket,
tx: UnboundedSender<Action>,
) {
let header = Ipv4Packet::new(ethernet.payload());
if let Some(header) = header {
Self::handle_transport_protocol(
interface_name,
IpAddr::V4(header.get_source()),
IpAddr::V4(header.get_destination()),
header.get_next_level_protocol(),
header.payload(),
tx,
);
}
}
fn handle_ipv6_packet(
interface_name: &str,
ethernet: &EthernetPacket,
tx: UnboundedSender<Action>,
) {
let header = Ipv6Packet::new(ethernet.payload());
if let Some(header) = header {
Self::handle_transport_protocol(
interface_name,
IpAddr::V6(header.get_source()),
IpAddr::V6(header.get_destination()),
header.get_next_header(),
header.payload(),
tx,
);
} else {
println!("[{}]: Malformed IPv6 Packet", interface_name);
}
}
fn handle_arp_packet(
interface_name: &str,
ethernet: &EthernetPacket,
tx: UnboundedSender<Action>,
) {
let header = ArpPacket::new(ethernet.payload());
if let Some(header) = header {
tx.send(Action::ArpRecieve(ArpPacketData {
sender_mac: header.get_sender_hw_addr(),
sender_ip: header.get_sender_proto_addr(),
target_mac: header.get_target_hw_addr(),
target_ip: header.get_target_proto_addr(),
operation: header.get_operation(),
}))
.unwrap();
let raw_str = format!(
"[{}]: ARP packet: {}({}) > {}({}); operation: {:?}",
interface_name,
ethernet.get_source(),
header.get_sender_proto_addr(),
ethernet.get_destination(),
header.get_target_proto_addr(),
header.get_operation()
);
tx.send(Action::PacketDump(
Local::now(),
PacketsInfoTypesEnum::Arp(ARPPacketInfo {
interface_name: interface_name.to_string(),
source_mac: ethernet.get_source(),
source_ip: header.get_sender_proto_addr(),
destination_mac: ethernet.get_destination(),
destination_ip: header.get_target_proto_addr(),
operation: header.get_operation(),
raw_str,
}),
PacketTypeEnum::Arp,
))
.unwrap();
}
}
fn handle_ethernet_frame(
interface: &NetworkInterface,
ethernet: &EthernetPacket,
tx: UnboundedSender<Action>,
) {
let interface_name = &interface.name[..];
match ethernet.get_ethertype() {
EtherTypes::Ipv4 => Self::handle_ipv4_packet(interface_name, ethernet, tx),
EtherTypes::Ipv6 => Self::handle_ipv6_packet(interface_name, ethernet, tx),
EtherTypes::Arp => Self::handle_arp_packet(interface_name, ethernet, tx),
_ => {}
}
}
fn t_logic(tx: UnboundedSender<Action>, interface: NetworkInterface, stop: Arc<AtomicBool>) {
let (_, mut receiver) = match pnet::datalink::channel(
&interface,
pnet::datalink::Config {
write_buffer_size: 4096,
read_buffer_size: 4096,
read_timeout: Some(Duration::new(1, 0)),
write_timeout: None,
channel_type: ChannelType::Layer2,
bpf_fd_attempts: 1000,
linux_fanout: None,
promiscuous: true,
socket_fd: None,
},
) {
Ok(Channel::Ethernet(tx, rx)) => (tx, rx),
Ok(_) => {
tx.send(Action::Error("Unknown or unsupported channel type".into()))
.unwrap();
return;
}
Err(e) => {
tx.send(Action::Error(format!(
"Unable to create datalink channel: {e}"
)))
.unwrap();
return;
}
};
loop {
if stop.load(Ordering::Relaxed) {
break;
}
let mut buf: [u8; 1600] = [0u8; 1600];
let mut fake_ethernet_frame = MutableEthernetPacket::new(&mut buf[..]).unwrap();
match receiver.next() {
Ok(packet) => {
let payload_offset;
if cfg!(any(target_os = "macos", target_os = "ios"))
&& interface.is_up()
&& !interface.is_broadcast()
&& ((!interface.is_loopback() && interface.is_point_to_point())
|| interface.is_loopback())
{
if interface.is_loopback() {
payload_offset = 14;
} else {
payload_offset = 0;
}
if packet.len() > payload_offset {
let version = Ipv4Packet::new(&packet[payload_offset..])
.unwrap()
.get_version();
if version == 4 {
fake_ethernet_frame.set_destination(MacAddr(0, 0, 0, 0, 0, 0));
fake_ethernet_frame.set_source(MacAddr(0, 0, 0, 0, 0, 0));
fake_ethernet_frame.set_ethertype(EtherTypes::Ipv4);
fake_ethernet_frame.set_payload(&packet[payload_offset..]);
Self::handle_ethernet_frame(
&interface,
&fake_ethernet_frame.to_immutable(),
tx.clone(),
);
continue;
} else if version == 6 {
fake_ethernet_frame.set_destination(MacAddr(0, 0, 0, 0, 0, 0));
fake_ethernet_frame.set_source(MacAddr(0, 0, 0, 0, 0, 0));
fake_ethernet_frame.set_ethertype(EtherTypes::Ipv6);
fake_ethernet_frame.set_payload(&packet[payload_offset..]);
Self::handle_ethernet_frame(
&interface,
&fake_ethernet_frame.to_immutable(),
tx.clone(),
);
continue;
}
}
}
Self::handle_ethernet_frame(
&interface,
&EthernetPacket::new(packet).unwrap(),
tx.clone(),
);
}
Err(e) => {}
}
}
}
fn start_loop(&mut self) {
if self.loop_thread.is_none() {
let tx = self.action_tx.clone().unwrap();
let interface = self.active_interface.clone().unwrap();
let dump_stop = self.dump_stop.clone();
let t_handle = thread::spawn(move || {
Self::t_logic(tx, interface, dump_stop);
});
self.loop_thread = Some(t_handle);
}
}
fn restart_loop(&mut self) {
self.dump_stop.store(true, Ordering::Relaxed);
}
pub fn get_array_by_packet_type(
&mut self,
packet_type: PacketTypeEnum,
) -> &Vec<(DateTime<Local>, PacketsInfoTypesEnum)> {
match packet_type {
PacketTypeEnum::Arp => self.arp_packets.get_vec(),
PacketTypeEnum::Tcp => self.tcp_packets.get_vec(),
PacketTypeEnum::Udp => self.udp_packets.get_vec(),
PacketTypeEnum::Icmp => self.icmp_packets.get_vec(),
PacketTypeEnum::Icmp6 => self.icmp6_packets.get_vec(),
PacketTypeEnum::All => self.all_packets.get_vec(),
}
}
pub fn get_arp_packages(&self) -> Vec<(DateTime<Local>, PacketsInfoTypesEnum)> {
let a = &self.arp_packets.get_vec().to_vec();
a.clone()
}
pub fn clone_array_by_packet_type(
&self,
packet_type: PacketTypeEnum,
) -> Vec<(DateTime<Local>, PacketsInfoTypesEnum)> {
match packet_type {
PacketTypeEnum::Arp => self.arp_packets.get_vec().to_vec(),
PacketTypeEnum::Tcp => self.tcp_packets.get_vec().to_vec(),
PacketTypeEnum::Udp => self.udp_packets.get_vec().to_vec(),
PacketTypeEnum::Icmp => self.icmp_packets.get_vec().to_vec(),
PacketTypeEnum::Icmp6 => self.icmp6_packets.get_vec().to_vec(),
PacketTypeEnum::All => self.all_packets.get_vec().to_vec(),
}
}
fn set_scrollbar_height(&mut self) {
let logs_len = self.get_array_by_packet_type(self.packet_type).len();
if logs_len > 0 {
self.scrollbar_state = self.scrollbar_state.content_length(logs_len - 1);
}
}
fn previous_in_table(&mut self) {
let index = match self.table_state.selected() {
Some(index) => {
let logs = self.get_array_by_packet_type(self.packet_type);
let logs_len = logs.len();
if index == 0 {
if logs_len > 0 {
logs.len() - 1
} else {
0
}
} else {
index - 1
}
}
None => 0,
};
self.table_state.select(Some(index));
self.scrollbar_state = self.scrollbar_state.position(index);
}
fn next_in_table(&mut self) {
let index = match self.table_state.selected() {
Some(index) => {
let logs = self.get_array_by_packet_type(self.packet_type);
if logs.is_empty() || index >= logs.len() - 1 {
0
} else {
index + 1
}
}
None => 0,
};
self.table_state.select(Some(index));
self.scrollbar_state = self.scrollbar_state.position(index);
}
fn get_table_rows_by_packet_type<'a>(&mut self, packet_type: PacketTypeEnum) -> Vec<Row<'a>> {
let f_str = self.filter_str.clone();
let logs_data = self.get_array_by_packet_type(packet_type);
let mut logs: Vec<(DateTime<Local>, PacketsInfoTypesEnum)> = vec![];
for (d, p) in logs_data {
match p {
PacketsInfoTypesEnum::Icmp(log) => {
if log.raw_str.contains(f_str.as_str()) {
logs.push((d.to_owned(), p.to_owned()));
}
}
PacketsInfoTypesEnum::Arp(log) => {
if log.raw_str.contains(f_str.as_str()) {
logs.push((d.to_owned(), p.to_owned()));
}
}
PacketsInfoTypesEnum::Icmp6(log) => {
if log.raw_str.contains(f_str.as_str()) {
logs.push((d.to_owned(), p.to_owned()));
}
}
PacketsInfoTypesEnum::Udp(log) => {
if log.raw_str.contains(f_str.as_str()) {
logs.push((d.to_owned(), p.to_owned()));
}
}
PacketsInfoTypesEnum::Tcp(log) => {
if log.raw_str.contains(f_str.as_str()) {
logs.push((d.to_owned(), p.to_owned()));
}
}
}
}
let rows: Vec<Row> = logs
.iter()
.map(|(time, log)| {
let t = time.format("%H:%M:%S").to_string();
let mut spans = vec![];
match log {
PacketsInfoTypesEnum::Icmp(icmp) => {
spans.push(Span::styled(
format!("[{}] ", icmp.interface_name.clone()),
Style::default().fg(Color::Green),
));
spans.push(Span::styled(
"ICMP",
Style::default().fg(Color::Black).bg(Color::White),
));
match icmp.icmp_type {
IcmpTypes::EchoRequest => {
spans.push(Span::styled(
" echo request ",
Style::default().fg(Color::Yellow),
));
}
IcmpTypes::EchoReply => {
spans.push(Span::styled(
" echo reply ",
Style::default().fg(Color::Yellow),
));
}
_ => {}
}
spans.push(Span::styled(
icmp.source.to_string(),
Style::default().fg(Color::Blue),
));
spans.push(Span::styled(" -> ", Style::default().fg(Color::Yellow)));
spans.push(Span::styled(
icmp.destination.to_string(),
Style::default().fg(Color::Blue),
));
spans.push(Span::styled("(seq=", Style::default().fg(Color::Yellow)));
spans.push(Span::styled(
format!("{:?}", icmp.seq.to_string()),
Style::default().fg(Color::Green),
));
spans.push(Span::styled(", ", Style::default().fg(Color::Yellow)));
spans.push(Span::styled("id=", Style::default().fg(Color::Yellow)));
spans.push(Span::styled(
format!("{:?}", icmp.id.to_string()),
Style::default().fg(Color::Green),
));
spans.push(Span::styled(")", Style::default().fg(Color::Yellow)));
}
PacketsInfoTypesEnum::Icmp6(icmp) => {
spans.push(Span::styled(
format!("[{}] ", icmp.interface_name.clone()),
Style::default().fg(Color::Green),
));
spans.push(Span::styled(
"ICMP6",
Style::default().fg(Color::Red).bg(Color::Black),
));
let mut icmp_type_str = " unknown ";
match icmp.icmp_type {
Icmpv6Types::EchoRequest => {
icmp_type_str = " echo request ";
}
Icmpv6Types::EchoReply => {
icmp_type_str = " echo reply ";
}
Icmpv6Types::NeighborAdvert => {
icmp_type_str = " neighbor advert ";
}
Icmpv6Types::NeighborSolicit => {
icmp_type_str = " neighbor solicit ";
}
Icmpv6Types::Redirect => {
icmp_type_str = " redirect ";
}
_ => {}
}
spans.push(Span::styled(
icmp_type_str,
Style::default().fg(Color::Yellow),
));
spans.push(Span::styled(
icmp.source.to_string(),
Style::default().fg(Color::Blue),
));
spans.push(Span::styled(" -> ", Style::default().fg(Color::Yellow)));
spans.push(Span::styled(
icmp.destination.to_string(),
Style::default().fg(Color::Blue),
));
spans.push(Span::styled(", ", Style::default().fg(Color::Yellow)));
spans.push(Span::styled(")", Style::default().fg(Color::Yellow)));
}
PacketsInfoTypesEnum::Udp(udp) => {
spans.push(Span::styled(
format!("[{}] ", udp.interface_name.clone()),
Style::default().fg(Color::Green),
));
spans.push(Span::styled(
"UDP",
Style::default().fg(Color::Yellow).bg(Color::Blue),
));
spans.push(Span::styled(
" Packet: ",
Style::default().fg(Color::Yellow),
));
spans.push(Span::styled(
udp.source.to_string(),
Style::default().fg(Color::Blue),
));
spans.push(Span::styled(":", Style::default().fg(Color::Yellow)));
spans.push(Span::styled(
udp.source_port.to_string(),
Style::default().fg(Color::Green),
));
spans.push(Span::styled(" > ", Style::default().fg(Color::Yellow)));
spans.push(Span::styled(
udp.destination.to_string(),
Style::default().fg(Color::Blue),
));
spans.push(Span::styled(":", Style::default().fg(Color::Yellow)));
spans.push(Span::styled(
udp.destination_port.to_string(),
Style::default().fg(Color::Green),
));
spans.push(Span::styled(";", Style::default().fg(Color::Yellow)));
spans.push(Span::styled(
" length: ",
Style::default().fg(Color::Yellow),
));
spans.push(Span::styled(
format!("{}", udp.length),
Style::default().fg(Color::Red),
));
}
PacketsInfoTypesEnum::Tcp(tcp) => {
spans.push(Span::styled(
format!("[{}] ", tcp.interface_name.clone()),
Style::default().fg(Color::Green),
));
spans.push(Span::styled(
"TCP",
Style::default().fg(Color::Black).bg(Color::Green),
));
spans.push(Span::styled(
" Packet: ",
Style::default().fg(Color::Yellow),
));
spans.push(Span::styled(
tcp.source.to_string(),
Style::default().fg(Color::Blue),
));
spans.push(Span::styled(":", Style::default().fg(Color::Yellow)));
spans.push(Span::styled(
tcp.source_port.to_string(),
Style::default().fg(Color::Green),
));
spans.push(Span::styled(" > ", Style::default().fg(Color::Yellow)));
spans.push(Span::styled(
tcp.destination.to_string(),
Style::default().fg(Color::Blue),
));
spans.push(Span::styled(":", Style::default().fg(Color::Yellow)));
spans.push(Span::styled(
tcp.destination_port.to_string(),
Style::default().fg(Color::Green),
));
spans.push(Span::styled(";", Style::default().fg(Color::Yellow)));
spans.push(Span::styled(
" length: ",
Style::default().fg(Color::Yellow),
));
spans.push(Span::styled(
format!("{}", tcp.length),
Style::default().fg(Color::Red),
));
}
PacketsInfoTypesEnum::Arp(arp) => {
spans.push(Span::styled(
format!("[{}] ", arp.interface_name.clone()),
Style::default().fg(Color::Green),
));
spans.push(Span::styled(
"ARP",
Style::default().fg(Color::Yellow).bg(Color::Red),
));
spans.push(Span::styled(
" Packet: ",
Style::default().fg(Color::Yellow),
));
spans.push(Span::styled(
arp.source_mac.to_string(),
Style::default().fg(Color::Green),
));
spans.push(Span::styled(
arp.source_ip.to_string(),
Style::default().fg(Color::Blue),
));
spans.push(Span::styled(" > ", Style::default().fg(Color::Yellow)));
spans.push(Span::styled(
arp.destination_mac.to_string(),
Style::default().fg(Color::Green),
));
spans.push(Span::styled(
arp.destination_ip.to_string(),
Style::default().fg(Color::Blue),
));
spans.push(Span::styled(";", Style::default().fg(Color::Yellow)));
spans.push(Span::styled(
format!(" {:?}", arp.operation),
Style::default().fg(Color::Red),
));
}
}
let line = Line::from(spans);
Row::new(vec![
Cell::from(Span::styled(t, Style::default().fg(Color::Cyan))),
Cell::from(line),
])
})
.collect();
rows
}
fn make_table(rows: Vec<Row>, packet_type: PacketTypeEnum, dump_paused: bool) -> Table {
let header = Row::new(vec!["time", "packet log"])
.style(Style::default().fg(Color::Yellow))
.top_margin(1)
.bottom_margin(1);
let mut type_titles = vec![
Span::styled("|", Style::default().fg(Color::Yellow)),
Span::styled(
String::from(char::from_u32(0x25c0).unwrap_or('<')),
Style::default().fg(Color::Red),
),
];
let mut enum_titles = PacketTypeEnum::iter()
.enumerate()
.map(|(idx, p)| {
let mut span_str = format!("{} ", p);
if idx == PacketTypeEnum::COUNT - 1 {
span_str = format!("{}", p);
}
if p == packet_type {
Span::styled(span_str, Style::new().green().bold())
} else {
Span::styled(span_str, Style::new().dark_gray())
}
})
.collect::<Vec<Span>>();
type_titles.append(&mut enum_titles);
type_titles.push(Span::styled(
String::from(char::from_u32(0x25b6).unwrap_or('>')),
Style::default().fg(Color::Red),
));
type_titles.push(Span::styled("|", Style::default().fg(Color::Yellow)));
let mut dump_spans = vec![
Span::styled("|", Style::default().fg(Color::Yellow)),
Span::styled(
"d",
Style::default().add_modifier(Modifier::BOLD).fg(Color::Red),
),
Span::styled("ump:", Style::default().fg(Color::Yellow)),
];
if dump_paused {
dump_spans.push(Span::styled("paused", Style::default().fg(Color::DarkGray)))
} else {
dump_spans.push(Span::styled("running", Style::default().fg(Color::Green)))
}
dump_spans.push(Span::styled("|", Style::default().fg(Color::Yellow)));
let table = Table::new(rows, [Constraint::Min(10), Constraint::Percentage(100)])
.header(header)
.block(
Block::default()
.title_top(Line::from(dump_spans).right_aligned())
.title_bottom(Line::from(vec![
Span::raw("|"),
Span::styled(
"e",
Style::default().add_modifier(Modifier::BOLD).fg(Color::Red),
),
Span::styled("xport data", Style::default().fg(Color::Yellow)),
Span::raw("|"),
]).left_aligned())
.title_top(Line::from(vec![Span::styled(
"|Packets|",
Style::default().fg(Color::Yellow),
)]).right_aligned())
.title_top(Line::from(type_titles).left_aligned())
.title_bottom(Line::from(vec![
Span::styled("|", Style::default().fg(Color::Yellow)),
Span::styled(
String::from(char::from_u32(0x25b2).unwrap_or('>')),
Style::default().fg(Color::Red),
),
Span::styled(
String::from(char::from_u32(0x25bc).unwrap_or('>')),
Style::default().fg(Color::Red),
),
Span::styled("select|", Style::default().fg(Color::Yellow)),
]).right_aligned())
.border_style(Style::default().fg(Color::Rgb(100, 100, 100)))
.borders(Borders::ALL) .border_type(DEFAULT_BORDER_STYLE),
)
.highlight_symbol(Span::styled(
String::from(char::from_u32(0x25b6).unwrap_or('>')),
Style::default().fg(Color::Red),
))
.column_spacing(1);
table
}
pub fn make_scrollbar<'a>() -> Scrollbar<'a> {
let scrollbar = Scrollbar::default()
.orientation(ScrollbarOrientation::VerticalRight)
.style(Style::default().fg(Color::Rgb(100, 100, 100)))
.begin_symbol(None)
.end_symbol(None);
scrollbar
}
fn make_input(&self, scroll: usize) -> Paragraph<'_> {
let input = Paragraph::new(self.input.value())
.style(Style::default().fg(Color::Green))
.scroll((0, scroll as u16))
.block(
Block::default()
.borders(Borders::ALL)
.border_style(match self.mode {
Mode::Input => Style::default().fg(Color::Green),
Mode::Normal => Style::default().fg(Color::Rgb(100, 100, 100)),
})
.border_type(DEFAULT_BORDER_STYLE)
.title_bottom(Line::from(vec![
Span::raw("|"),
Span::styled(
"i",
Style::default().add_modifier(Modifier::BOLD).fg(Color::Red),
),
Span::styled("nput", Style::default().fg(Color::Yellow)),
Span::raw("/"),
Span::styled(
"ESC",
Style::default().add_modifier(Modifier::BOLD).fg(Color::Red),
),
Span::raw("|"),
]).right_aligned())
.title_bottom(Line::from(vec![
Span::raw("|"),
Span::styled(
"c",
Style::default().add_modifier(Modifier::BOLD).fg(Color::Red),
),
Span::styled("lear", Style::default().fg(Color::Yellow)),
Span::raw("|"),
]).left_aligned()),
);
input
}
fn set_filter_str(&mut self, value: String) {
self.filter_str = value;
}
}
impl Component for PacketDump {
fn register_action_handler(&mut self, tx: UnboundedSender<Action>) -> Result<()> {
self.action_tx = Some(tx);
Ok(())
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn handle_key_events(&mut self, key: KeyEvent) -> Result<Option<Action>> {
if self.active_tab == TabsEnum::Packets {
let action = match self.mode {
Mode::Normal => return Ok(None),
Mode::Input => match key.code {
KeyCode::Enter => {
if let Some(sender) = &self.action_tx {
self.set_filter_str(self.input.value().to_string());
}
Action::ModeChange(Mode::Normal)
}
_ => {
self.input.handle_event(&ratatui::crossterm::event::Event::Key(key));
return Ok(None);
}
},
};
Ok(Some(action))
} else {
Ok(None)
}
}
fn update(&mut self, action: Action) -> Result<Option<Action>> {
if self.changed_interface {
if let Some(ref lt) = self.loop_thread {
if lt.is_finished() {
self.dump_stop.store(true, Ordering::SeqCst);
self.loop_thread = None;
self.dump_stop.store(false, Ordering::SeqCst);
self.start_loop();
self.changed_interface = false;
}
} else {
self.dump_stop.store(true, Ordering::SeqCst);
self.loop_thread = None;
self.dump_stop.store(false, Ordering::SeqCst);
self.start_loop();
self.changed_interface = false;
}
}
if let Action::TabChange(tab) = action {
self.tab_changed(tab).unwrap();
}
if let Action::ActiveInterface(ref interface) = action {
let mut was_none = false;
if self.active_interface.is_none() {
was_none = true;
}
self.active_interface = Some(interface.clone());
if was_none {
self.start_loop();
} else {
self.changed_interface = true;
self.restart_loop();
}
}
if self.active_tab == TabsEnum::Packets {
if let Action::Down = action {
self.next_in_table();
}
if let Action::Up = action {
self.previous_in_table();
}
if let Action::Left = action {
self.packet_type = self.packet_type.previous();
self.set_scrollbar_height();
self.table_state.select(Some(0));
self.set_scrollbar_height();
}
if let Action::Right = action {
self.packet_type = self.packet_type.next();
self.set_scrollbar_height();
self.table_state.select(Some(0));
self.set_scrollbar_height();
}
if let Action::DumpToggle = action {
if self.dump_paused.load(Ordering::Relaxed) {
self.dump_paused.store(false, Ordering::Relaxed);
self.start_loop();
} else {
self.dump_paused.store(true, Ordering::Relaxed);
self.loop_thread = None;
}
}
if let Action::ModeChange(mode) = action {
self.action_tx
.clone()
.unwrap()
.send(Action::AppModeChange(mode))
.unwrap();
self.mode = mode;
}
if let Action::Clear = action {
self.input.reset();
self.filter_str = String::from("");
}
}
if !self.dump_paused.load(Ordering::Relaxed) {
if let Action::PacketDump(time, packet, packet_type) = action {
match packet_type {
PacketTypeEnum::Tcp => self.tcp_packets.push((time, packet.clone())),
PacketTypeEnum::Arp => self.arp_packets.push((time, packet.clone())),
PacketTypeEnum::Udp => self.udp_packets.push((time, packet.clone())),
PacketTypeEnum::Icmp => self.icmp_packets.push((time, packet.clone())),
PacketTypeEnum::Icmp6 => self.icmp6_packets.push((time, packet.clone())),
_ => {}
}
self.all_packets.push((time, packet.clone()));
}
}
Ok(None)
}
fn tab_changed(&mut self, tab: TabsEnum) -> Result<()> {
self.active_tab = tab;
Ok(())
}
fn draw(&mut self, f: &mut Frame<'_>, area: Rect) -> Result<()> {
if self.active_tab == TabsEnum::Packets {
let layout = get_vertical_layout(area);
let mut table_rect = layout.bottom;
table_rect.y += 1;
table_rect.height -= 1;
let mut dump_paused = false;
if self.dump_paused.load(Ordering::Relaxed) {
dump_paused = true;
}
let rows = self.get_table_rows_by_packet_type(self.packet_type);
let table = Self::make_table(rows, self.packet_type, dump_paused);
f.render_stateful_widget(table, table_rect, &mut self.table_state.clone());
let input_size: u16 = INPUT_SIZE as u16;
let input_rect = Rect::new(
table_rect.width - (input_size + 1),
table_rect.y + 1,
input_size,
3,
);
let scroll = self.input.visual_scroll(INPUT_SIZE - 3);
let block = self.make_input(scroll);
f.render_widget(block, input_rect);
match self.mode {
Mode::Input => {
f.set_cursor_position(Position {
x: input_rect.x
+ ((self.input.visual_cursor()).max(scroll) - scroll) as u16
+ 1,
y: input_rect.y + 1,
});
}
Mode::Normal => {}
}
let scrollbar = Self::make_scrollbar();
let mut scroll_rect = table_rect;
scroll_rect.y += 1;
scroll_rect.height -= 1;
f.render_stateful_widget(
scrollbar,
scroll_rect.inner(Margin {
vertical: 1,
horizontal: 1,
}),
&mut self.scrollbar_state,
);
}
Ok(())
}
}