From 7feca2391e461e5f2e22867170eda3f160bb58c4 Mon Sep 17 00:00:00 2001 From: Badr Date: Mon, 23 Sep 2024 13:42:09 +0200 Subject: [PATCH] Show packet details (#18) --- Cargo.lock | 1 + Readme.md | 2 + Release.md | 4 + oryx-common/src/arp.rs | 52 ---- oryx-common/src/ip.rs | 227 +++++++++------ oryx-common/src/lib.rs | 130 +-------- oryx-ebpf/src/main.rs | 3 +- oryx-tui/Cargo.toml | 1 + oryx-tui/src/app.rs | 274 +++++++++++++---- oryx-tui/src/export.rs | 72 +++-- oryx-tui/src/filters/fuzzy.rs | 2 +- oryx-tui/src/handler.rs | 22 ++ oryx-tui/src/help.rs | 6 +- oryx-tui/src/infos.rs | 0 oryx-tui/src/lib.rs | 7 + oryx-tui/src/packets/link.rs | 151 ++++++++++ oryx-tui/src/packets/network.rs | 469 ++++++++++++++++++++++++++++++ oryx-tui/src/packets/packet.rs | 188 ++++++++++++ oryx-tui/src/packets/transport.rs | 190 ++++++++++++ oryx-tui/src/stats.rs | 127 ++------ 20 files changed, 1478 insertions(+), 450 deletions(-) delete mode 100644 oryx-common/src/arp.rs create mode 100644 oryx-tui/src/infos.rs create mode 100644 oryx-tui/src/packets/link.rs create mode 100644 oryx-tui/src/packets/network.rs create mode 100644 oryx-tui/src/packets/packet.rs create mode 100644 oryx-tui/src/packets/transport.rs diff --git a/Cargo.lock b/Cargo.lock index a4f7dc1..6d09b0d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -606,6 +606,7 @@ dependencies = [ "libc", "mimalloc", "mio", + "network-types", "oryx-common", "ratatui", "tui-big-text", diff --git a/Readme.md b/Readme.md index 49f5215..1f7a9d1 100644 --- a/Readme.md +++ b/Readme.md @@ -95,6 +95,8 @@ sudo oryx `f`: Update the applied filters. +`i`: Show more infos about the selected packet. + `ctrl + r`: Reset the app. `ctrl + s`: Export the capture to `~/oryx/capture` file. diff --git a/Release.md b/Release.md index 961f8f8..7284502 100644 --- a/Release.md +++ b/Release.md @@ -1,5 +1,9 @@ ## v0.3 - TBA +### Added + +- Show packet details + ### Changed - move filters to the epbf program diff --git a/oryx-common/src/arp.rs b/oryx-common/src/arp.rs deleted file mode 100644 index 4c7beb2..0000000 --- a/oryx-common/src/arp.rs +++ /dev/null @@ -1,52 +0,0 @@ -use core::fmt::Display; - -#[derive(Debug, Copy, Clone)] -pub struct ArpPacket { - pub arp_type: ArpType, - pub src_mac: MacAddr, - pub dst_mac: MacAddr, -} - -impl Display for ArpPacket { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "{} {} ARP", self.src_mac, self.dst_mac) - } -} - -#[derive(Debug, Copy, Clone)] -pub enum ArpType { - Request, - Reply, -} - -impl Display for ArpType { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - Self::Request => write!(f, "Arp Request"), - Self::Reply => write!(f, "Arp Reply"), - } - } -} - -#[derive(Debug, Copy, Clone)] -pub struct MacAddr(pub [u8; 6]); - -impl Display for MacAddr { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - //FIX: workaround for the moment - if self.0.iter().all(|x| *x == 0) { - write!(f, "ff:ff:ff:ff:ff:ff",) - } else { - write!( - f, - "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}", - self.0[0].to_be(), - self.0[1].to_be(), - self.0[2].to_be(), - self.0[3].to_be(), - self.0[4].to_be(), - self.0[5].to_be() - ) - } - } -} diff --git a/oryx-common/src/ip.rs b/oryx-common/src/ip.rs index 2af062c..7dbc32d 100644 --- a/oryx-common/src/ip.rs +++ b/oryx-common/src/ip.rs @@ -1,93 +1,134 @@ -use core::{fmt::Display, net::IpAddr}; - -use network_types::{icmp::IcmpHdr, tcp::TcpHdr, udp::UdpHdr}; - -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct TcpPacket { - pub dst_port: u16, - pub src_port: u16, - pub dst_ip: IpAddr, - pub src_ip: IpAddr, -} - -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct UdpPacket { - pub dst_port: u16, - pub src_port: u16, - pub dst_ip: IpAddr, - pub src_ip: IpAddr, -} - -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct IcmpPacket { - pub icmp_type: IcmpType, - pub dst_ip: IpAddr, - pub src_ip: IpAddr, -} - -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub enum IcmpType { - EchoRequest, - EchoReply, - DestinationUnreachable, -} - -impl Display for IcmpType { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - IcmpType::EchoReply => { - write!(f, "Echo Reply") - } - IcmpType::EchoRequest => { - write!(f, "Echo Request") - } - IcmpType::DestinationUnreachable => { - write!(f, "Destination Unreachable") - } - } - } -} - -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub enum IpPacket { - Tcp(TcpPacket), - Udp(UdpPacket), - Icmp(IcmpPacket), -} - -#[repr(C)] -#[derive(Copy, Clone)] -pub enum ProtoHdr { - Tcp(TcpHdr), - Udp(UdpHdr), - Icmp(IcmpHdr), -} - -impl Display for IpPacket { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - IpPacket::Tcp(p) => { - write!( - f, - "{} {} {} {} TCP", - p.src_ip, p.src_port, p.dst_ip, p.dst_port - ) - } - IpPacket::Udp(p) => { - write!( - f, - "{} {} {} {} UDP", - p.src_ip, p.src_port, p.dst_ip, p.dst_port - ) - } - IpPacket::Icmp(p) => { - write!(f, "{} {} ICMP", p.src_ip, p.dst_ip) - } - } - } -} +// use core::fmt::Display; +// +// use core::net::{Ipv4Addr, Ipv6Addr}; +// +// #[derive(Debug, Copy, Clone)] +// pub enum IpPacket { +// V4(Ipv4Packet), +// V6(Ipv6Packet), +// } +// +// #[derive(Debug, Copy, Clone)] +// pub struct Ipv4Packet { +// pub src_ip: Ipv4Addr, +// pub dst_ip: Ipv4Addr, +// pub ihl: u8, +// pub tos: u8, +// pub total_length: u16, +// pub id: u16, +// pub fragment_offset: u16, +// pub ttl: u8, +// pub proto: IpProto, +// pub checksum: u16, +// } +// +// #[derive(Debug, Copy, Clone)] +// pub struct Ipv6Packet { +// pub src_ip: Ipv6Addr, +// pub dst_ip: Ipv6Addr, +// pub proto: IpProto, +// } +// +// #[derive(Debug, Copy, Clone)] +// pub enum IpProto { +// Tcp(TcpPacket), +// Udp(UdpPacket), +// Icmp(IcmpPacket), +// } +// +// #[derive(Debug, Copy, Clone)] +// pub struct TcpPacket { +// pub dst_port: u16, +// pub src_port: u16, +// } +// +// #[derive(Debug, Copy, Clone)] +// pub struct UdpPacket { +// pub dst_port: u16, +// pub src_port: u16, +// } +// +// #[derive(Debug, Copy, Clone)] +// pub struct IcmpPacket { +// pub icmp_type: IcmpType, +// } +// +// #[derive(Debug, Copy, Clone)] +// pub enum IcmpType { +// EchoRequest, +// EchoReply, +// DestinationUnreachable, +// } +// +// impl Display for IcmpType { +// fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { +// match self { +// IcmpType::EchoReply => { +// write!(f, "Echo Reply") +// } +// IcmpType::EchoRequest => { +// write!(f, "Echo Request") +// } +// IcmpType::DestinationUnreachable => { +// write!(f, "Destination Unreachable") +// } +// } +// } +// } +// +// impl Display for IpPacket { +// fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { +// match self { +// IpPacket::V4(ipv4_packet) => match ipv4_packet.proto { +// IpProto::Tcp(tcp_packet) => { +// write!( +// f, +// "{} {} {} {} TCP", +// ipv4_packet.src_ip, +// tcp_packet.src_port, +// ipv4_packet.dst_ip, +// tcp_packet.dst_port +// ) +// } +// IpProto::Udp(udp_packet) => { +// write!( +// f, +// "{} {} {} {} UDP", +// ipv4_packet.src_ip, +// udp_packet.src_port, +// ipv4_packet.dst_ip, +// udp_packet.dst_port +// ) +// } +// IpProto::Icmp(_) => { +// write!(f, "{} {} ICMP", ipv4_packet.src_ip, ipv4_packet.dst_ip) +// } +// }, +// IpPacket::V6(ipv6_packet) => match ipv6_packet.proto { +// IpProto::Tcp(tcp_packet) => { +// write!( +// f, +// "{} {} {} {} TCP", +// ipv6_packet.src_ip, +// tcp_packet.src_port, +// ipv6_packet.dst_ip, +// tcp_packet.dst_port +// ) +// } +// IpProto::Udp(udp_packet) => { +// write!( +// f, +// "{} {} {} {} UDP", +// ipv6_packet.src_ip, +// udp_packet.src_port, +// ipv6_packet.dst_ip, +// udp_packet.dst_port +// ) +// } +// IpProto::Icmp(_) => { +// write!(f, "{} {} ICMP", ipv6_packet.src_ip, ipv6_packet.dst_ip) +// } +// }, +// } +// } +// } diff --git a/oryx-common/src/lib.rs b/oryx-common/src/lib.rs index fda3a56..32483fb 100644 --- a/oryx-common/src/lib.rs +++ b/oryx-common/src/lib.rs @@ -1,136 +1,26 @@ #![no_std] -use core::{ - fmt::Display, - mem, - net::{IpAddr, Ipv4Addr}, -}; +use core::mem; -use arp::{ArpPacket, ArpType, MacAddr}; -use ip::{IcmpPacket, IcmpType, IpPacket, ProtoHdr, TcpPacket, UdpPacket}; -use network_types::{arp::ArpHdr, ip::IpHdr}; +use network_types::{arp::ArpHdr, icmp::IcmpHdr, ip::IpHdr, tcp::TcpHdr, udp::UdpHdr}; -pub mod arp; pub mod ip; pub mod protocols; #[repr(C)] pub enum RawPacket { - Ip(IpHdr, ip::ProtoHdr), + Ip(IpHdr, ProtoHdr), Arp(ArpHdr), } -impl RawPacket { - pub const LEN: usize = mem::size_of::(); -} - -#[derive(Debug, Copy, Clone)] #[repr(C)] -pub enum AppPacket { - Ip(IpPacket), - Arp(ArpPacket), +#[derive(Copy, Clone)] +pub enum ProtoHdr { + Tcp(TcpHdr), + Udp(UdpHdr), + Icmp(IcmpHdr), } -impl AppPacket { - pub const LEN: usize = mem::size_of::(); -} - -impl Display for AppPacket { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - Self::Arp(packet) => write!(f, "{}", packet), - Self::Ip(packet) => write!(f, "{}", packet), - } - } -} - -impl From<[u8; RawPacket::LEN]> for AppPacket { - fn from(value: [u8; RawPacket::LEN]) -> Self { - let raw_packet = value.as_ptr() as *const RawPacket; - match unsafe { &*raw_packet } { - RawPacket::Ip(packet, proto) => match packet { - IpHdr::V4(ipv4_packet) => match proto { - ProtoHdr::Tcp(header) => { - let tcp_packet = TcpPacket { - src_ip: IpAddr::V4(Ipv4Addr::from(u32::from_be(ipv4_packet.src_addr))), - src_port: u16::from_be(header.source), - dst_ip: IpAddr::V4(Ipv4Addr::from(u32::from_be(ipv4_packet.dst_addr))), - dst_port: u16::from_be(header.dest), - }; - AppPacket::Ip(IpPacket::Tcp(tcp_packet)) - } - ProtoHdr::Udp(header) => { - let udp_packet = UdpPacket { - src_ip: IpAddr::V4(Ipv4Addr::from(u32::from_be(ipv4_packet.src_addr))), - src_port: u16::from_be(header.source), - dst_ip: IpAddr::V4(Ipv4Addr::from(u32::from_be(ipv4_packet.dst_addr))), - dst_port: u16::from_be(header.dest), - }; - Self::Ip(IpPacket::Udp(udp_packet)) - } - ProtoHdr::Icmp(header) => { - let icmp_type = match header.type_ { - 0 => IcmpType::EchoRequest, - 1 => IcmpType::EchoReply, - _ => IcmpType::DestinationUnreachable, - }; - - let icmp_packet = IcmpPacket { - src_ip: IpAddr::V4(Ipv4Addr::from(u32::from_be(ipv4_packet.src_addr))), - dst_ip: IpAddr::V4(Ipv4Addr::from(u32::from_be(ipv4_packet.dst_addr))), - icmp_type, - }; - Self::Ip(IpPacket::Icmp(icmp_packet)) - } - }, - IpHdr::V6(ipv6_packet) => match proto { - ProtoHdr::Tcp(header) => { - let tcp_packet = TcpPacket { - src_ip: IpAddr::V6(ipv6_packet.src_addr()), - src_port: u16::from_be(header.source), - dst_ip: IpAddr::V6(ipv6_packet.dst_addr()), - dst_port: u16::from_be(header.dest), - }; - Self::Ip(IpPacket::Tcp(tcp_packet)) - } - ProtoHdr::Udp(header) => { - let udp_packet = UdpPacket { - src_ip: IpAddr::V6(ipv6_packet.src_addr()), - src_port: u16::from_be(header.source), - dst_ip: IpAddr::V6(ipv6_packet.dst_addr()), - dst_port: u16::from_be(header.dest), - }; - Self::Ip(IpPacket::Udp(udp_packet)) - } - ProtoHdr::Icmp(header) => { - let icmp_type = match header.type_ { - 0 => IcmpType::EchoRequest, - 1 => IcmpType::EchoReply, - _ => IcmpType::DestinationUnreachable, - }; - - let icmp_packet = IcmpPacket { - src_ip: IpAddr::V6(ipv6_packet.src_addr()), - dst_ip: IpAddr::V6(ipv6_packet.dst_addr()), - icmp_type, - }; - Self::Ip(IpPacket::Icmp(icmp_packet)) - } - }, - }, - RawPacket::Arp(packet) => { - let arp_type = match u16::from_be(packet.oper) { - 1 => ArpType::Request, - 2 => ArpType::Reply, - _ => unreachable!(), - }; - - Self::Arp(ArpPacket { - arp_type, - src_mac: MacAddr(packet.sha), - dst_mac: MacAddr(packet.tha), - }) - } - } - } +impl RawPacket { + pub const LEN: usize = mem::size_of::(); } diff --git a/oryx-ebpf/src/main.rs b/oryx-ebpf/src/main.rs index 7cea341..f124f3d 100644 --- a/oryx-ebpf/src/main.rs +++ b/oryx-ebpf/src/main.rs @@ -17,9 +17,8 @@ use network_types::{ udp::UdpHdr, }; use oryx_common::{ - ip::ProtoHdr, protocols::{LinkProtocol, NetworkProtocol, Protocol, TransportProtocol}, - RawPacket, + ProtoHdr, RawPacket, }; #[map] diff --git a/oryx-tui/Cargo.toml b/oryx-tui/Cargo.toml index 9b716a5..0c97533 100644 --- a/oryx-tui/Cargo.toml +++ b/oryx-tui/Cargo.toml @@ -23,6 +23,7 @@ dns-lookup = "2" kanal = "0.1.0-pre8" mimalloc = "0.1" clap = { version = "4", features = ["derive", "cargo"] } +network-types = "0.0.7" [[bin]] name = "oryx" diff --git a/oryx-tui/src/app.rs b/oryx-tui/src/app.rs index d4a28a8..0a22538 100644 --- a/oryx-tui/src/app.rs +++ b/oryx-tui/src/app.rs @@ -1,8 +1,7 @@ -use oryx_common::ip::IpPacket; use oryx_common::protocols::{ Protocol, NB_LINK_PROTOCOL, NB_NETWORK_PROTOCOL, NB_TRANSPORT_PROTOCOL, }; -use oryx_common::{AppPacket, RawPacket}; +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}; @@ -14,7 +13,6 @@ use ratatui::{ }, Frame, }; -use std::borrow::Borrow; use std::net::IpAddr; use std::sync::{Arc, Mutex}; use std::time::Duration; @@ -30,6 +28,8 @@ use crate::filters::transport::TransportFilter; 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::stats::Stats; pub type AppResult = std::result::Result>; @@ -106,6 +106,8 @@ pub struct App { pub update_filters: bool, pub data_channel_sender: kanal::Sender<[u8; RawPacket::LEN]>, pub bandwidth: Arc>>, + pub show_packet_infos_popup: bool, + pub packet_index: Option, } impl Default for App { @@ -203,6 +205,8 @@ impl App { update_filters: false, data_channel_sender: sender, bandwidth, + show_packet_infos_popup: false, + packet_index: None, } } @@ -448,7 +452,12 @@ impl App { // Packets/Stats match self.mode { - Mode::Packet => self.render_packets_mode(frame, mode_block), + Mode::Packet => { + self.render_packets_mode(frame, mode_block); + if self.show_packet_infos_popup { + self.render_packet_infos_popup(frame); + } + } Mode::Stats => self.render_stats_mode(frame, mode_block), } @@ -588,36 +597,52 @@ impl App { let packets_to_display = match self.manuall_scroll { true => { if fuzzy.is_enabled() & !fuzzy.filter.value().is_empty() { + let selected_packet_index = fuzzy.scroll_state.selected().unwrap(); if fuzzy_packets.len() > window_size { + self.packet_index = Some( + fuzzy.packet_end_index.saturating_sub(window_size) + + selected_packet_index, + ); &fuzzy_packets[fuzzy.packet_end_index.saturating_sub(window_size) ..fuzzy.packet_end_index] } else { + self.packet_index = Some(selected_packet_index); &fuzzy_packets } } else if app_packets.len() > window_size { + let selected_packet_index = self.packets_table_state.selected().unwrap(); + self.packet_index = Some( + self.packet_end_index.saturating_sub(window_size) + selected_packet_index, + ); &app_packets [self.packet_end_index.saturating_sub(window_size)..self.packet_end_index] } else { + let selected_packet_index = self.packets_table_state.selected().unwrap(); + self.packet_index = Some(selected_packet_index); &app_packets } } false => { if fuzzy.is_enabled() & !fuzzy.filter.value().is_empty() { if fuzzy_packets.len() > window_size { + self.packet_index = Some(fuzzy_packets.len().saturating_sub(1)); &fuzzy_packets[fuzzy_packets.len().saturating_sub(window_size)..] } else { + self.packet_index = Some(fuzzy_packets.len().saturating_sub(1)); &fuzzy_packets } } else if app_packets.len() > window_size { + self.packet_index = Some(app_packets.len().saturating_sub(1)); &app_packets[app_packets.len().saturating_sub(window_size)..] } else { + self.packet_index = Some(app_packets.len().saturating_sub(1)); &app_packets } } }; // Style the packets - let packets: Vec = if fuzzy.is_enabled() & !fuzzy.borrow().filter.value().is_empty() { + let packets: Vec = if fuzzy.is_enabled() & !fuzzy.filter.value().is_empty() { packets_to_display .iter() .map(|app_packet| match app_packet { @@ -629,27 +654,52 @@ impl App { fuzzy::highlight(pattern, "ARP".to_string()).cyan(), ]), AppPacket::Ip(packet) => match packet { - IpPacket::Tcp(p) => Row::new(vec![ - fuzzy::highlight(pattern, p.src_ip.to_string()).blue(), - fuzzy::highlight(pattern, p.src_port.to_string()).yellow(), - fuzzy::highlight(pattern, p.dst_ip.to_string()).blue(), - fuzzy::highlight(pattern, p.dst_port.to_string()).yellow(), - fuzzy::highlight(pattern, "TCP".to_string()).cyan(), - ]), - IpPacket::Udp(p) => Row::new(vec![ - fuzzy::highlight(pattern, p.src_ip.to_string()).blue(), - fuzzy::highlight(pattern, p.src_port.to_string()).yellow(), - fuzzy::highlight(pattern, p.dst_ip.to_string()).blue(), - fuzzy::highlight(pattern, p.dst_port.to_string()).yellow(), - fuzzy::highlight(pattern, "UDP".to_string()).cyan(), - ]), - IpPacket::Icmp(p) => Row::new(vec![ - fuzzy::highlight(pattern, p.src_ip.to_string()).blue(), - Cell::from(Line::from("-").centered()).yellow(), - fuzzy::highlight(pattern, p.dst_ip.to_string()).blue(), - Cell::from(Line::from("-").centered()).yellow(), - fuzzy::highlight(pattern, "ICMP".to_string()).cyan(), - ]), + IpPacket::V4(ipv4_packet) => match ipv4_packet.proto { + IpProto::Tcp(p) => Row::new(vec![ + fuzzy::highlight(pattern, ipv4_packet.src_ip.to_string()).blue(), + fuzzy::highlight(pattern, p.src_port.to_string()).yellow(), + fuzzy::highlight(pattern, ipv4_packet.dst_ip.to_string()).blue(), + fuzzy::highlight(pattern, p.dst_port.to_string()).yellow(), + fuzzy::highlight(pattern, "TCP".to_string()).cyan(), + ]), + IpProto::Udp(p) => Row::new(vec![ + fuzzy::highlight(pattern, ipv4_packet.src_ip.to_string()).blue(), + fuzzy::highlight(pattern, p.src_port.to_string()).yellow(), + fuzzy::highlight(pattern, ipv4_packet.dst_ip.to_string()).blue(), + fuzzy::highlight(pattern, p.dst_port.to_string()).yellow(), + fuzzy::highlight(pattern, "UDP".to_string()).cyan(), + ]), + IpProto::Icmp(_) => Row::new(vec![ + fuzzy::highlight(pattern, ipv4_packet.src_ip.to_string()).blue(), + Cell::from(Line::from("-").centered()).yellow(), + fuzzy::highlight(pattern, ipv4_packet.dst_ip.to_string()).blue(), + Cell::from(Line::from("-").centered()).yellow(), + fuzzy::highlight(pattern, "ICMP".to_string()).cyan(), + ]), + }, + IpPacket::V6(ipv6_packet) => match ipv6_packet.proto { + IpProto::Tcp(p) => Row::new(vec![ + fuzzy::highlight(pattern, ipv6_packet.src_ip.to_string()).blue(), + fuzzy::highlight(pattern, p.src_port.to_string()).yellow(), + fuzzy::highlight(pattern, ipv6_packet.dst_ip.to_string()).blue(), + fuzzy::highlight(pattern, p.dst_port.to_string()).yellow(), + fuzzy::highlight(pattern, "TCP".to_string()).cyan(), + ]), + IpProto::Udp(p) => Row::new(vec![ + fuzzy::highlight(pattern, ipv6_packet.src_ip.to_string()).blue(), + fuzzy::highlight(pattern, p.src_port.to_string()).yellow(), + fuzzy::highlight(pattern, ipv6_packet.dst_ip.to_string()).blue(), + fuzzy::highlight(pattern, p.dst_port.to_string()).yellow(), + fuzzy::highlight(pattern, "UDP".to_string()).cyan(), + ]), + IpProto::Icmp(_) => Row::new(vec![ + fuzzy::highlight(pattern, ipv6_packet.src_ip.to_string()).blue(), + Cell::from(Line::from("-").centered()).yellow(), + fuzzy::highlight(pattern, ipv6_packet.dst_ip.to_string()).blue(), + Cell::from(Line::from("-").centered()).yellow(), + fuzzy::highlight(pattern, "ICMP".to_string()).cyan(), + ]), + }, }, }) .collect() @@ -669,35 +719,92 @@ impl App { Span::from("ARP".to_string()).into_centered_line().cyan(), ]), AppPacket::Ip(packet) => match packet { - IpPacket::Tcp(p) => Row::new(vec![ - Span::from(p.src_ip.to_string()).into_centered_line().blue(), - Span::from(p.src_port.to_string()) - .into_centered_line() - .yellow(), - Span::from(p.dst_ip.to_string()).into_centered_line().blue(), - Span::from(p.dst_port.to_string()) - .into_centered_line() - .yellow(), - Span::from("TCP".to_string()).into_centered_line().cyan(), - ]), - IpPacket::Udp(p) => Row::new(vec![ - Span::from(p.src_ip.to_string()).into_centered_line().blue(), - Span::from(p.src_port.to_string()) - .into_centered_line() - .yellow(), - Span::from(p.dst_ip.to_string()).into_centered_line().blue(), - Span::from(p.dst_port.to_string()) - .into_centered_line() - .yellow(), - Span::from("UDP".to_string()).into_centered_line().cyan(), - ]), - IpPacket::Icmp(p) => Row::new(vec![ - Span::from(p.src_ip.to_string()).into_centered_line().blue(), - Span::from("-").into_centered_line().yellow(), - Span::from(p.dst_ip.to_string()).into_centered_line().blue(), - Span::from("-").into_centered_line().yellow(), - Span::from("ICMP".to_string()).into_centered_line().cyan(), - ]), + IpPacket::V4(ipv4_packet) => match ipv4_packet.proto { + IpProto::Tcp(p) => Row::new(vec![ + Span::from(ipv4_packet.src_ip.to_string()) + .into_centered_line() + .blue(), + Span::from(p.src_port.to_string()) + .into_centered_line() + .yellow(), + Span::from(ipv4_packet.dst_ip.to_string()) + .into_centered_line() + .blue(), + Span::from(p.dst_port.to_string()) + .into_centered_line() + .yellow(), + Span::from("TCP".to_string()).into_centered_line().cyan(), + ]), + IpProto::Udp(p) => Row::new(vec![ + Span::from(ipv4_packet.src_ip.to_string()) + .into_centered_line() + .blue(), + Span::from(p.src_port.to_string()) + .into_centered_line() + .yellow(), + Span::from(ipv4_packet.dst_ip.to_string()) + .into_centered_line() + .blue(), + Span::from(p.dst_port.to_string()) + .into_centered_line() + .yellow(), + Span::from("UDP".to_string()).into_centered_line().cyan(), + ]), + IpProto::Icmp(_) => Row::new(vec![ + Span::from(ipv4_packet.src_ip.to_string()) + .into_centered_line() + .blue(), + Span::from("-").into_centered_line().yellow(), + Span::from(ipv4_packet.dst_ip.to_string()) + .into_centered_line() + .blue(), + Span::from("-").into_centered_line().yellow(), + Span::from("ICMP".to_string()).into_centered_line().cyan(), + ]), + }, + IpPacket::V6(ipv6_packet) => match ipv6_packet.proto { + IpProto::Tcp(p) => Row::new(vec![ + Span::from(ipv6_packet.src_ip.to_string()) + .into_centered_line() + .blue(), + Span::from(p.src_port.to_string()) + .into_centered_line() + .yellow(), + Span::from(ipv6_packet.dst_ip.to_string()) + .into_centered_line() + .blue(), + Span::from(p.dst_port.to_string()) + .into_centered_line() + .yellow(), + Span::from("TCP".to_string()).into_centered_line().cyan(), + ]), + IpProto::Udp(p) => Row::new(vec![ + Span::from(ipv6_packet.src_ip.to_string()) + .into_centered_line() + .blue(), + Span::from(p.src_port.to_string()) + .into_centered_line() + .yellow(), + Span::from(ipv6_packet.dst_ip.to_string()) + .into_centered_line() + .blue(), + Span::from(p.dst_port.to_string()) + .into_centered_line() + .yellow(), + Span::from("UDP".to_string()).into_centered_line().cyan(), + ]), + IpProto::Icmp(_) => Row::new(vec![ + Span::from(ipv6_packet.src_ip.to_string()) + .into_centered_line() + .blue(), + Span::from("-").into_centered_line().yellow(), + Span::from(ipv6_packet.dst_ip.to_string()) + .into_centered_line() + .blue(), + Span::from("-").into_centered_line().yellow(), + Span::from("ICMP".to_string()).into_centered_line().cyan(), + ]), + }, }, }) .collect() @@ -755,7 +862,7 @@ impl App { .border_style(Style::default().green()), ); - if fuzzy.borrow().is_enabled() { + if fuzzy.is_enabled() { frame.render_stateful_widget(table, packet_block, &mut fuzzy.scroll_state); } else { frame.render_stateful_widget(table, packet_block, &mut self.packets_table_state); @@ -770,13 +877,13 @@ impl App { let mut scrollbar_state = if fuzzy.is_enabled() && fuzzy_packets.len() > window_size { ScrollbarState::new(fuzzy_packets.len()).position({ if self.manuall_scroll { - if fuzzy.borrow().packet_end_index == window_size { + if fuzzy.packet_end_index == window_size { 0 } else { - fuzzy.borrow().packet_end_index + fuzzy.packet_end_index } } else { - fuzzy.borrow().packets.len() + fuzzy.packets.len() } }) } else if !fuzzy.is_enabled() && app_packets.len() > window_size { @@ -804,7 +911,7 @@ impl App { &mut scrollbar_state, ); - if fuzzy.borrow().is_enabled() { + if fuzzy.is_enabled() { let fuzzy = Paragraph::new(format!("> {}", fuzzy.filter.value())) .alignment(Alignment::Left) .style(Style::default().white()) @@ -879,6 +986,53 @@ impl App { } } + fn render_packet_infos_popup(&self, frame: &mut Frame) { + let layout = Layout::default() + .direction(Direction::Vertical) + .constraints([ + Constraint::Fill(1), + Constraint::Length(36), + Constraint::Fill(1), + ]) + .flex(ratatui::layout::Flex::SpaceBetween) + .split(frame.area()); + + let block = Layout::default() + .direction(Direction::Horizontal) + .constraints([ + Constraint::Fill(1), + Constraint::Max(80), + Constraint::Fill(1), + ]) + .flex(ratatui::layout::Flex::SpaceBetween) + .split(layout[1])[1]; + + let fuzzy = self.fuzzy.lock().unwrap(); + let packets = self.packets.lock().unwrap(); + + let packet = if fuzzy.is_enabled() { + fuzzy.packets[self.packet_index.unwrap()] + } else { + packets[self.packet_index.unwrap()] + }; + + frame.render_widget(Clear, block); + frame.render_widget( + Block::new() + .title(" Packet Infos 󰋼 ") + .title_style(Style::new().bold().green()) + .title_alignment(Alignment::Center) + .borders(Borders::all()) + .border_style(Style::new().green()) + .border_type(BorderType::Thick), + block, + ); + match packet { + AppPacket::Ip(ip_packet) => ip_packet.render(block, frame), + AppPacket::Arp(arp_packet) => arp_packet.render(block, frame), + }; + } + pub fn process( packets: Arc>>, stats: Arc>, diff --git a/oryx-tui/src/export.rs b/oryx-tui/src/export.rs index 5cc6c85..a35aa02 100644 --- a/oryx-tui/src/export.rs +++ b/oryx-tui/src/export.rs @@ -2,10 +2,9 @@ use std::fs::{create_dir, OpenOptions}; use std::io::prelude::*; use std::os::unix::fs::chown; -use oryx_common::ip::IpPacket; -use oryx_common::AppPacket; - use crate::app::AppResult; +use crate::packets::network::{IpPacket, IpProto}; +use crate::packets::packet::AppPacket; pub fn export(packets: &[AppPacket]) -> AppResult<()> { let uid = unsafe { libc::geteuid() }; @@ -46,27 +45,52 @@ pub fn export(packets: &[AppPacket]) -> AppResult<()> { )?; } AppPacket::Ip(packet) => match packet { - IpPacket::Tcp(p) => { - writeln!( - file, - "{:39} {:<11} {:39} {:<11} TCP", - p.src_ip, p.src_port, p.dst_ip, p.dst_port - )?; - } - IpPacket::Udp(p) => { - writeln!( - file, - "{:39} {:<11} {:39} {:<11} UDP", - p.src_ip, p.src_port, p.dst_ip, p.dst_port - )?; - } - IpPacket::Icmp(p) => { - writeln!( - file, - "{:39} {:^11} {:39} {:^11} ICMP", - p.src_ip, "-", p.dst_ip, "-" - )?; - } + IpPacket::V4(ipv4_packet) => match ipv4_packet.proto { + IpProto::Tcp(p) => { + writeln!( + file, + "{:39} {:<11} {:39} {:<11} TCP", + ipv4_packet.src_ip, p.src_port, ipv4_packet.dst_ip, p.dst_port + )?; + } + IpProto::Udp(p) => { + writeln!( + file, + "{:39} {:<11} {:39} {:<11} UDP", + ipv4_packet.src_ip, p.src_port, ipv4_packet.dst_ip, p.dst_port + )?; + } + IpProto::Icmp(_) => { + writeln!( + file, + "{:39} {:^11} {:39} {:^11} ICMP", + ipv4_packet.src_ip, "-", ipv4_packet.dst_ip, "-" + )?; + } + }, + IpPacket::V6(ipv6_packet) => match ipv6_packet.proto { + IpProto::Tcp(p) => { + writeln!( + file, + "{:39} {:<11} {:39} {:<11} TCP", + ipv6_packet.src_ip, p.src_port, ipv6_packet.dst_ip, p.dst_port + )?; + } + IpProto::Udp(p) => { + writeln!( + file, + "{:39} {:<11} {:39} {:<11} UDP", + ipv6_packet.src_ip, p.src_port, ipv6_packet.dst_ip, p.dst_port + )?; + } + IpProto::Icmp(_) => { + writeln!( + file, + "{:39} {:^11} {:39} {:^11} ICMP", + ipv6_packet.src_ip, "-", ipv6_packet.dst_ip, "-" + )?; + } + }, }, } } diff --git a/oryx-tui/src/filters/fuzzy.rs b/oryx-tui/src/filters/fuzzy.rs index 6d315fa..2dfabb7 100644 --- a/oryx-tui/src/filters/fuzzy.rs +++ b/oryx-tui/src/filters/fuzzy.rs @@ -5,7 +5,7 @@ use ratatui::{ }; use tui_input::Input; -use oryx_common::AppPacket; +use crate::packets::packet::AppPacket; #[derive(Debug, Clone, Default)] pub struct Fuzzy { diff --git a/oryx-tui/src/handler.rs b/oryx-tui/src/handler.rs index 962ba5c..8fd7403 100644 --- a/oryx-tui/src/handler.rs +++ b/oryx-tui/src/handler.rs @@ -26,6 +26,14 @@ pub fn handle_key_events( app: &mut App, sender: kanal::Sender, ) -> AppResult<()> { + if app.show_packet_infos_popup { + if key_event.code == KeyCode::Esc { + app.show_packet_infos_popup = false; + } + + return Ok(()); + } + let fuzzy = app.fuzzy.clone(); let mut fuzzy = fuzzy.lock().unwrap(); @@ -159,6 +167,13 @@ pub fn handle_key_events( app.focused_block = FocusedBlock::Help; } + KeyCode::Char('i') => { + if app.focused_block == FocusedBlock::Help || app.update_filters { + return Ok(()); + } + app.show_packet_infos_popup = true; + } + KeyCode::Char('f') => { if app.focused_block != FocusedBlock::Help && app.start_sniffing { app.update_filters = true; @@ -463,6 +478,13 @@ pub fn handle_key_events( } } + KeyCode::Char('i') => { + if app.focused_block == FocusedBlock::Help || app.update_filters { + return Ok(()); + } + app.show_packet_infos_popup = true; + } + KeyCode::Char('r') => { if app.focused_block == FocusedBlock::Help || app.update_filters { return Ok(()); diff --git a/oryx-tui/src/help.rs b/oryx-tui/src/help.rs index 7129ca8..cd867ef 100644 --- a/oryx-tui/src/help.rs +++ b/oryx-tui/src/help.rs @@ -46,6 +46,10 @@ impl Help { Cell::from("f").bold().yellow(), "Update the applied filters", ), + ( + Cell::from("i").bold().yellow(), + "Show more infos about the selected packet", + ), ( Cell::from("ctrl + s").bold().yellow(), "Export the capture to ~/oryx/capture file", @@ -88,7 +92,7 @@ impl Help { .direction(Direction::Vertical) .constraints([ Constraint::Fill(1), - Constraint::Length(17), + Constraint::Length(18), Constraint::Fill(1), ]) .flex(ratatui::layout::Flex::SpaceBetween) diff --git a/oryx-tui/src/infos.rs b/oryx-tui/src/infos.rs new file mode 100644 index 0000000..e69de29 diff --git a/oryx-tui/src/lib.rs b/oryx-tui/src/lib.rs index 81930dc..0216fea 100644 --- a/oryx-tui/src/lib.rs +++ b/oryx-tui/src/lib.rs @@ -29,3 +29,10 @@ pub mod export; pub mod stats; pub mod bandwidth; + +pub mod packets { + pub mod link; + pub mod network; + pub mod packet; + pub mod transport; +} diff --git a/oryx-tui/src/packets/link.rs b/oryx-tui/src/packets/link.rs new file mode 100644 index 0000000..189cb44 --- /dev/null +++ b/oryx-tui/src/packets/link.rs @@ -0,0 +1,151 @@ +use core::fmt::Display; +use std::net::Ipv4Addr; + +use ratatui::{ + layout::{Constraint, Direction, Layout, Rect}, + style::{Style, Stylize}, + text::Span, + widgets::{Block, Borders, Padding, Paragraph, Row, Table}, + Frame, +}; + +#[derive(Debug, Copy, Clone)] +pub struct ArpPacket { + pub htype: u16, + pub ptype: u16, + pub hlen: u8, + pub plen: u8, + pub arp_type: ArpType, + pub src_mac: MacAddr, + pub src_ip: Ipv4Addr, + pub dst_mac: MacAddr, + pub dst_ip: Ipv4Addr, +} + +impl ArpPacket { + pub fn render(self, block: Rect, frame: &mut Frame) { + let block = Layout::default() + .direction(Direction::Vertical) + .constraints([ + Constraint::Fill(1), + Constraint::Length(13), + Constraint::Fill(1), + ]) + .flex(ratatui::layout::Flex::SpaceBetween) + .margin(1) + .split(block)[1]; + + let (title_block, data_block) = { + let chunks = Layout::default() + .direction(Direction::Horizontal) + .constraints([Constraint::Length(6), Constraint::Fill(1)]) + .margin(2) + .split(block); + + (chunks[0], chunks[1]) + }; + + let title = Paragraph::new("ARP") + .bold() + .block(Block::new().padding(Padding::top({ + if title_block.height % 2 == 0 { + title_block.height / 2 - 1 + } else { + title_block.height / 2 + } + }))); + + let widths = [Constraint::Length(23), Constraint::Fill(1)]; + let infos = [ + Row::new(vec![ + Span::styled("Hardware Type", Style::new().bold()), + Span::from(self.htype.to_string()), + ]), + Row::new(vec![ + Span::styled("Protocol Type", Style::new().bold()), + Span::from(self.ptype.to_string()), + ]), + Row::new(vec![ + Span::styled("Hardware Length", Style::new().bold()), + Span::from(self.hlen.to_string()), + ]), + Row::new(vec![ + Span::styled("Protocol Length", Style::new().bold()), + Span::from(self.plen.to_string()), + ]), + Row::new(vec![ + Span::styled("Operation", Style::new().bold()), + Span::from(self.arp_type.to_string()), + ]), + Row::new(vec![ + Span::styled("Sender hardware address", Style::new().bold()), + Span::from(self.src_mac.to_string()), + ]), + Row::new(vec![ + Span::styled("Sender protocol address", Style::new().bold()), + Span::from(self.src_ip.to_string()), + ]), + Row::new(vec![ + Span::styled("Target hardware address", Style::new().bold()), + Span::from(self.dst_mac.to_string()), + ]), + Row::new(vec![ + Span::styled("Target protocol address", Style::new().bold()), + Span::from(self.dst_ip.to_string()), + ]), + ]; + let table = Table::new(infos, widths).column_spacing(3).block( + Block::default() + .borders(Borders::LEFT) + .border_style(Style::new().bold().yellow()) + .border_type(ratatui::widgets::BorderType::Thick) + .style(Style::default()), + ); + frame.render_widget(table, data_block); + frame.render_widget(title, title_block); + } +} + +impl Display for ArpPacket { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{} {} ARP", self.src_mac, self.dst_mac) + } +} + +#[derive(Debug, Copy, Clone)] +pub enum ArpType { + Request, + Reply, +} + +impl Display for ArpType { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::Request => write!(f, "Arp Request"), + Self::Reply => write!(f, "Arp Reply"), + } + } +} + +#[derive(Debug, Copy, Clone)] +pub struct MacAddr(pub [u8; 6]); + +impl Display for MacAddr { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + //FIX: workaround for the moment + if self.0.iter().all(|x| *x == 0) { + write!(f, "ff:ff:ff:ff:ff:ff",) + } else { + write!( + f, + "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}", + self.0[0].to_be(), + self.0[1].to_be(), + self.0[2].to_be(), + self.0[3].to_be(), + self.0[4].to_be(), + self.0[5].to_be() + ) + } + } +} diff --git a/oryx-tui/src/packets/network.rs b/oryx-tui/src/packets/network.rs new file mode 100644 index 0000000..33ec9c1 --- /dev/null +++ b/oryx-tui/src/packets/network.rs @@ -0,0 +1,469 @@ +use core::fmt::Display; +use ratatui::{ + layout::{Constraint, Direction, Layout, Rect}, + style::{Style, Stylize}, + text::Span, + widgets::{Block, Borders, Padding, Paragraph, Row, Table}, + Frame, +}; + +use core::net::{Ipv4Addr, Ipv6Addr}; + +use super::transport::{TcpPacket, UdpPacket}; + +#[derive(Debug, Copy, Clone)] +pub enum IpPacket { + V4(Ipv4Packet), + V6(Ipv6Packet), +} + +impl IpPacket { + pub fn render(self, block: Rect, frame: &mut Frame) { + match self { + IpPacket::V4(ip_packet) => match ip_packet.proto { + IpProto::Tcp(tcp_packet) => { + let (transport_block, network_block) = { + let chunks = Layout::default() + .direction(Direction::Vertical) + .constraints([Constraint::Length(20), Constraint::Length(13)]) + .flex(ratatui::layout::Flex::SpaceAround) + .margin(2) + .split(block); + + (chunks[0], chunks[1]) + }; + + ip_packet.render(network_block, frame); + tcp_packet.render(transport_block, frame); + } + IpProto::Udp(udp_packet) => { + let (transport_block, network_block) = { + let chunks = Layout::default() + .direction(Direction::Vertical) + .constraints([Constraint::Length(8), Constraint::Length(13)]) + .flex(ratatui::layout::Flex::SpaceAround) + .margin(2) + .split(block); + + (chunks[0], chunks[1]) + }; + ip_packet.render(network_block, frame); + udp_packet.render(transport_block, frame); + } + IpProto::Icmp(icmp_packet) => { + let (transport_block, network_block) = { + let chunks = Layout::default() + .direction(Direction::Vertical) + .constraints([Constraint::Length(7), Constraint::Length(13)]) + .flex(ratatui::layout::Flex::SpaceAround) + .margin(2) + .split(block); + + (chunks[0], chunks[1]) + }; + ip_packet.render(network_block, frame); + icmp_packet.render(transport_block, frame); + } + }, + IpPacket::V6(ip_packet) => match ip_packet.proto { + IpProto::Tcp(tcp_packet) => { + let (transport_block, network_block) = { + let chunks = Layout::default() + .direction(Direction::Vertical) + .constraints([Constraint::Length(20), Constraint::Length(10)]) + .flex(ratatui::layout::Flex::SpaceAround) + .margin(2) + .split(block); + + (chunks[0], chunks[1]) + }; + + ip_packet.render(network_block, frame); + tcp_packet.render(transport_block, frame); + } + IpProto::Udp(udp_packet) => { + let (transport_block, network_block) = { + let chunks = Layout::default() + .direction(Direction::Vertical) + .constraints([Constraint::Length(8), Constraint::Length(10)]) + .flex(ratatui::layout::Flex::SpaceAround) + .margin(2) + .split(block); + + (chunks[0], chunks[1]) + }; + ip_packet.render(network_block, frame); + udp_packet.render(transport_block, frame); + } + IpProto::Icmp(icmp_packet) => { + let (transport_block, network_block) = { + let chunks = Layout::default() + .direction(Direction::Vertical) + .constraints([Constraint::Length(7), Constraint::Length(10)]) + .flex(ratatui::layout::Flex::SpaceAround) + .margin(2) + .split(block); + + (chunks[0], chunks[1]) + }; + ip_packet.render(network_block, frame); + icmp_packet.render(transport_block, frame); + } + }, + } + } +} + +#[derive(Debug, Copy, Clone)] +pub struct Ipv4Packet { + pub src_ip: Ipv4Addr, + pub dst_ip: Ipv4Addr, + pub ihl: u8, + pub tos: u8, + pub total_length: u16, + pub id: u16, + pub fragment_offset: u16, + pub ttl: u8, + pub proto: IpProto, + pub checksum: u16, +} + +impl Ipv4Packet { + pub fn render(self, block: Rect, frame: &mut Frame) { + let (title_block, data_block) = { + let chunks = Layout::default() + .direction(Direction::Horizontal) + .constraints([Constraint::Length(6), Constraint::Fill(1)]) + .margin(2) + .split(block); + + (chunks[0], chunks[1]) + }; + // Title + let title = Paragraph::new("IPv4") + .bold() + .block(Block::new().padding(Padding::top({ + if title_block.height % 2 == 0 { + title_block.height / 2 - 1 + } else { + title_block.height / 2 + } + }))); + + // IP + let widths = [Constraint::Length(23), Constraint::Fill(1)]; + let infos = [ + Row::new(vec![ + Span::styled("Source IP", Style::new().bold()), + Span::from(self.src_ip.to_string()), + ]), + Row::new(vec![ + Span::styled("Destination IP", Style::new().bold()), + Span::from(self.dst_ip.to_string()), + ]), + Row::new(vec![ + Span::styled("Internet Header Length", Style::new().bold()), + Span::from(format!("{} bytes", self.ihl * 4)), + ]), + Row::new(vec![ + Span::styled("Type Of Service", Style::new().bold()), + Span::from(self.tos.to_string()), + ]), + Row::new(vec![ + Span::styled("Total Length", Style::new().bold()), + Span::from(format!("{} bytes", self.total_length)), + ]), + Row::new(vec![ + Span::styled("ID", Style::new().bold()), + Span::from(self.id.to_string()), + ]), + Row::new(vec![ + Span::styled("Fragment Offset", Style::new().bold()), + Span::from(self.fragment_offset.to_string()), + ]), + Row::new(vec![ + Span::styled("TTL", Style::new().bold()), + Span::from(self.ttl.to_string()), + ]), + Row::new(vec![ + Span::styled("Checksum", Style::new().bold()), + Span::from(format!("{:#0x}", self.checksum)), + ]), + ]; + + let table = Table::new(infos, widths).column_spacing(2).block( + Block::default() + .borders(Borders::LEFT) + .border_style(Style::new().bold().magenta()) + .border_type(ratatui::widgets::BorderType::Thick) + .style(Style::default()), + ); + + frame.render_widget(table, data_block); + frame.render_widget(title, title_block); + } +} + +#[derive(Debug, Copy, Clone)] +pub struct Ipv6Packet { + pub traffic_class: u8, + pub flow_label: [u8; 3usize], + pub payload_length: u16, + pub hop_limit: u8, + pub src_ip: Ipv6Addr, + pub dst_ip: Ipv6Addr, + pub proto: IpProto, +} + +impl Ipv6Packet { + pub fn render(self, block: Rect, frame: &mut Frame) { + let (title_block, data_block) = { + let chunks = Layout::default() + .direction(Direction::Horizontal) + .constraints([Constraint::Length(6), Constraint::Fill(1)]) + .margin(2) + .split(block); + + (chunks[0], chunks[1]) + }; + // Title + let title = Paragraph::new("IPv6") + .bold() + .block(Block::new().padding(Padding::top({ + if title_block.height % 2 == 0 { + title_block.height / 2 - 1 + } else { + title_block.height / 2 + } + }))); + + // IP + let widths = [Constraint::Length(23), Constraint::Fill(1)]; + let infos = [ + Row::new(vec![ + Span::styled("Source IP", Style::new().bold()), + Span::from(self.src_ip.to_string()), + ]), + Row::new(vec![ + Span::styled("Destination IP", Style::new().bold()), + Span::from(self.dst_ip.to_string()), + ]), + Row::new(vec![ + Span::styled("Traffic Class", Style::new().bold()), + Span::from(self.traffic_class.to_string()), + ]), + Row::new(vec![ + Span::styled("Flow Label", Style::new().bold()), + Span::from(format!( + "{:#0x}", + (self.flow_label[0] as u32) << 16 + | (self.flow_label[1] as u32) << 8 + | (self.flow_label[2] as u32) + )), + ]), + Row::new(vec![ + Span::styled("Payload Length", Style::new().bold()), + Span::from(self.traffic_class.to_string()), + ]), + Row::new(vec![ + Span::styled("Hop Limit", Style::new().bold()), + Span::from(self.hop_limit.to_string()), + ]), + ]; + + let table = Table::new(infos, widths).column_spacing(2).block( + Block::default() + .borders(Borders::LEFT) + .border_style(Style::new().bold().magenta()) + .border_type(ratatui::widgets::BorderType::Thick) + .style(Style::default()), + ); + + frame.render_widget(table, data_block); + frame.render_widget(title, title_block); + } +} + +#[derive(Debug, Copy, Clone)] +pub enum IpProto { + Tcp(TcpPacket), + Udp(UdpPacket), + Icmp(IcmpPacket), +} + +#[derive(Debug, Copy, Clone)] +pub struct IcmpPacket { + pub icmp_type: IcmpType, + pub code: u8, + pub checksum: u16, +} + +impl IcmpPacket { + pub fn render(self, block: Rect, frame: &mut Frame) { + let (title_block, data_block) = { + let chunks = Layout::default() + .direction(Direction::Horizontal) + .constraints([Constraint::Length(6), Constraint::Fill(1)]) + .margin(2) + .split(block); + + (chunks[0], chunks[1]) + }; + let title = Paragraph::new("ICMP") + .bold() + .block(Block::new().padding(Padding::top({ + if title_block.height % 2 == 0 { + title_block.height / 2 - 1 + } else { + title_block.height / 2 + } + }))); + + let widths = [Constraint::Length(23), Constraint::Fill(1)]; + let infos = [ + Row::new(vec![ + Span::styled("Type", Style::new().bold()), + Span::from(self.icmp_type.to_string()), + ]), + Row::new(vec![ + Span::styled("Code", Style::new().bold()), + Span::from(self.code.to_string()), + ]), + Row::new(vec![ + Span::styled("Checksum", Style::new().bold()), + Span::from(format!("{:#0x}", self.checksum)), + ]), + ]; + + let table = Table::new(infos, widths).column_spacing(2).block( + Block::default() + .borders(Borders::LEFT) + .border_style(Style::new().bold().yellow()) + .border_type(ratatui::widgets::BorderType::Thick) + .style(Style::default()), + ); + + frame.render_widget(table, data_block); + frame.render_widget(title, title_block); + } +} + +#[derive(Debug, Copy, Clone)] +pub enum IcmpType { + EchoRequest, + EchoReply, + DestinationUnreachable, + RedirectMessage, + RouterAdvertisement, + RouterSolicitation, + TimeExceeded, + BadIPheader, + Timestamp, + TimestampReply, + ExtendedEchoRequest, + ExtendedEchoReply, + Deprecated, +} + +impl Display for IcmpType { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + IcmpType::EchoReply => { + write!(f, "Echo Reply") + } + IcmpType::EchoRequest => { + write!(f, "Echo Request") + } + IcmpType::DestinationUnreachable => { + write!(f, "Destination Unreachable") + } + IcmpType::RedirectMessage => { + write!(f, "Redirect Message") + } + IcmpType::RouterAdvertisement => { + write!(f, "Router Advertisement") + } + IcmpType::RouterSolicitation => { + write!(f, "Router Solicitation") + } + IcmpType::TimeExceeded => { + write!(f, "Time Exceeded") + } + IcmpType::BadIPheader => { + write!(f, "Bad IP header") + } + IcmpType::Timestamp => { + write!(f, "Timestamp") + } + IcmpType::TimestampReply => { + write!(f, "Timestamp Reply") + } + IcmpType::ExtendedEchoRequest => { + write!(f, "Extended Echo Request") + } + IcmpType::ExtendedEchoReply => { + write!(f, "Extended Echo Reply") + } + IcmpType::Deprecated => { + write!(f, "Deprecated") + } + } + } +} + +impl Display for IpPacket { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + IpPacket::V4(ipv4_packet) => match ipv4_packet.proto { + IpProto::Tcp(tcp_packet) => { + write!( + f, + "{} {} {} {} TCP", + ipv4_packet.src_ip, + tcp_packet.src_port, + ipv4_packet.dst_ip, + tcp_packet.dst_port + ) + } + IpProto::Udp(udp_packet) => { + write!( + f, + "{} {} {} {} UDP", + ipv4_packet.src_ip, + udp_packet.src_port, + ipv4_packet.dst_ip, + udp_packet.dst_port + ) + } + IpProto::Icmp(_) => { + write!(f, "{} {} ICMP", ipv4_packet.src_ip, ipv4_packet.dst_ip) + } + }, + IpPacket::V6(ipv6_packet) => match ipv6_packet.proto { + IpProto::Tcp(tcp_packet) => { + write!( + f, + "{} {} {} {} TCP", + ipv6_packet.src_ip, + tcp_packet.src_port, + ipv6_packet.dst_ip, + tcp_packet.dst_port + ) + } + IpProto::Udp(udp_packet) => { + write!( + f, + "{} {} {} {} UDP", + ipv6_packet.src_ip, + udp_packet.src_port, + ipv6_packet.dst_ip, + udp_packet.dst_port + ) + } + IpProto::Icmp(_) => { + write!(f, "{} {} ICMP", ipv6_packet.src_ip, ipv6_packet.dst_ip) + } + }, + } + } +} diff --git a/oryx-tui/src/packets/packet.rs b/oryx-tui/src/packets/packet.rs new file mode 100644 index 0000000..4f4c79e --- /dev/null +++ b/oryx-tui/src/packets/packet.rs @@ -0,0 +1,188 @@ +use core::fmt::Display; + +use core::net::Ipv4Addr; +use std::mem; + +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}; + +#[derive(Debug, Copy, Clone)] +pub enum AppPacket { + Ip(IpPacket), + Arp(ArpPacket), +} + +impl AppPacket { + pub const LEN: usize = mem::size_of::(); +} + +impl Display for AppPacket { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::Arp(packet) => write!(f, "{}", packet), + Self::Ip(packet) => write!(f, "{}", packet), + } + } +} + +impl From<[u8; RawPacket::LEN]> for AppPacket { + fn from(value: [u8; RawPacket::LEN]) -> Self { + let raw_packet = value.as_ptr() as *const RawPacket; + match unsafe { &*raw_packet } { + RawPacket::Ip(packet, proto) => match packet { + IpHdr::V4(ipv4_packet) => { + let src_ip = Ipv4Addr::from(u32::from_be(ipv4_packet.src_addr)); + let dst_ip = Ipv4Addr::from(u32::from_be(ipv4_packet.dst_addr)); + + let proto = match proto { + ProtoHdr::Tcp(tcp_header) => IpProto::Tcp(TcpPacket { + src_port: u16::from_be(tcp_header.source), + dst_port: u16::from_be(tcp_header.dest), + seq: u32::from_be(tcp_header.seq), + ack_seq: u32::from_be(tcp_header.ack_seq), + data_offset: tcp_header.doff(), + cwr: tcp_header.cwr(), + ece: tcp_header.ece(), + urg: tcp_header.urg(), + ack: tcp_header.ack(), + psh: tcp_header.psh(), + rst: tcp_header.rst(), + syn: tcp_header.syn(), + fin: tcp_header.fin(), + window: u16::from_be(tcp_header.window), + checksum: u16::from_be(tcp_header.check), + urg_ptr: u16::from_be(tcp_header.urg_ptr), + }), + ProtoHdr::Udp(udp_header) => IpProto::Udp(UdpPacket { + src_port: u16::from_be(udp_header.source), + dst_port: u16::from_be(udp_header.dest), + length: u16::from_be(udp_header.len), + checksum: u16::from_be(udp_header.check), + }), + ProtoHdr::Icmp(icmp_header) => { + let icmp_type = match u8::from_be(icmp_header.type_) { + 0 => IcmpType::EchoReply, + 3 => IcmpType::DestinationUnreachable, + 5 => IcmpType::RedirectMessage, + 8 => IcmpType::EchoRequest, + 9 => IcmpType::RouterAdvertisement, + 10 => IcmpType::RouterSolicitation, + 11 => IcmpType::TimeExceeded, + 12 => IcmpType::BadIPheader, + 13 => IcmpType::Timestamp, + 14 => IcmpType::TimestampReply, + 42 => IcmpType::ExtendedEchoRequest, + 43 => IcmpType::ExtendedEchoReply, + _ => IcmpType::Deprecated, + }; + IpProto::Icmp(IcmpPacket { + icmp_type, + code: u8::from_be(icmp_header.code), + checksum: u16::from_be(icmp_header.checksum), + }) + } + }; + + AppPacket::Ip(IpPacket::V4(Ipv4Packet { + src_ip, + dst_ip, + ihl: u8::from_be(ipv4_packet.ihl()), + tos: u8::from_be(ipv4_packet.tos), + total_length: u16::from_be(ipv4_packet.tot_len), + id: u16::from_be(ipv4_packet.id), + fragment_offset: u16::from_be(ipv4_packet.frag_off), + ttl: u8::from_be(ipv4_packet.ttl), + checksum: u16::from_be(ipv4_packet.check), + proto, + })) + } + IpHdr::V6(ipv6_packet) => { + let src_ip = ipv6_packet.src_addr(); + let dst_ip = ipv6_packet.dst_addr(); + + let proto = match proto { + ProtoHdr::Tcp(tcp_header) => IpProto::Tcp(TcpPacket { + src_port: u16::from_be(tcp_header.source), + dst_port: u16::from_be(tcp_header.dest), + seq: u32::from_be(tcp_header.seq), + ack_seq: u32::from_be(tcp_header.ack_seq), + data_offset: tcp_header.doff(), + cwr: tcp_header.cwr(), + ece: tcp_header.ece(), + urg: tcp_header.urg(), + ack: tcp_header.ack(), + psh: tcp_header.psh(), + rst: tcp_header.rst(), + syn: tcp_header.syn(), + fin: tcp_header.fin(), + window: u16::from_be(tcp_header.window), + checksum: u16::from_be(tcp_header.check), + urg_ptr: u16::from_be(tcp_header.urg_ptr), + }), + ProtoHdr::Udp(udp_header) => IpProto::Udp(UdpPacket { + src_port: u16::from_be(udp_header.source), + dst_port: u16::from_be(udp_header.dest), + length: u16::from_be(udp_header.len), + checksum: u16::from_be(udp_header.check), + }), + ProtoHdr::Icmp(icmp_header) => { + let icmp_type = match u8::from_be(icmp_header.type_) { + 0 => IcmpType::EchoReply, + 3 => IcmpType::DestinationUnreachable, + 5 => IcmpType::RedirectMessage, + 8 => IcmpType::EchoRequest, + 9 => IcmpType::RouterAdvertisement, + 10 => IcmpType::RouterSolicitation, + 11 => IcmpType::TimeExceeded, + 12 => IcmpType::BadIPheader, + 13 => IcmpType::Timestamp, + 14 => IcmpType::TimestampReply, + 42 => IcmpType::ExtendedEchoRequest, + 43 => IcmpType::ExtendedEchoReply, + _ => IcmpType::Deprecated, + }; + IpProto::Icmp(IcmpPacket { + icmp_type, + code: u8::from_be(icmp_header.code), + checksum: u16::from_be(icmp_header.checksum), + }) + } + }; + + AppPacket::Ip(IpPacket::V6(Ipv6Packet { + traffic_class: ipv6_packet.priority(), + flow_label: ipv6_packet.flow_label, + payload_length: u16::from_be(ipv6_packet.payload_len), + hop_limit: u8::from_be(ipv6_packet.hop_limit), + src_ip, + dst_ip, + proto, + })) + } + }, + RawPacket::Arp(packet) => { + let arp_type = match u16::from_be(packet.oper) { + 1 => ArpType::Request, + 2 => ArpType::Reply, + _ => unreachable!(), + }; + + Self::Arp(ArpPacket { + htype: packet.ptype, + ptype: packet.ptype, + hlen: u8::from_be(packet.hlen), + plen: u8::from_be(packet.plen), + arp_type, + src_mac: MacAddr(packet.sha), + src_ip: Ipv4Addr::from(packet.spa), + dst_mac: MacAddr(packet.tha), + dst_ip: Ipv4Addr::from(packet.tpa), + }) + } + } + } +} diff --git a/oryx-tui/src/packets/transport.rs b/oryx-tui/src/packets/transport.rs new file mode 100644 index 0000000..7f1759a --- /dev/null +++ b/oryx-tui/src/packets/transport.rs @@ -0,0 +1,190 @@ +use ratatui::{ + layout::{Constraint, Direction, Layout, Rect}, + style::{Style, Stylize}, + text::Span, + widgets::{Block, Borders, Padding, Paragraph, Row, Table}, + Frame, +}; + +#[derive(Debug, Copy, Clone)] +pub struct TcpPacket { + pub dst_port: u16, + pub src_port: u16, + pub seq: u32, + pub ack_seq: u32, + pub data_offset: u16, + pub cwr: u16, + pub ece: u16, + pub urg: u16, + pub ack: u16, + pub psh: u16, + pub rst: u16, + pub syn: u16, + pub fin: u16, + pub window: u16, + pub checksum: u16, + pub urg_ptr: u16, +} + +impl TcpPacket { + pub fn render(self, block: Rect, frame: &mut Frame) { + let (title_block, data_block) = { + let chunks = Layout::default() + .direction(Direction::Horizontal) + .constraints([Constraint::Length(6), Constraint::Fill(1)]) + .margin(2) + .split(block); + + (chunks[0], chunks[1]) + }; + + let title = Paragraph::new("TCP") + .bold() + .block(Block::new().padding(Padding::top({ + if title_block.height % 2 == 0 { + title_block.height / 2 - 1 + } else { + title_block.height / 2 + } + }))); + + let widths = [Constraint::Length(23), Constraint::Fill(1)]; + let infos = [ + Row::new(vec![ + Span::styled("Source Port", Style::new().bold()), + Span::from(self.src_port.to_string()), + ]), + Row::new(vec![ + Span::styled("Destination Port", Style::new().bold()), + Span::from(self.dst_port.to_string()), + ]), + Row::new(vec![ + Span::styled("Sequence Number", Style::new().bold()), + Span::from(self.seq.to_string()), + ]), + Row::new(vec![ + Span::styled("Acknowledgment Number", Style::new().bold()), + Span::from(self.ack_seq.to_string()), + ]), + Row::new(vec![ + Span::styled("Data Offset", Style::new().bold()), + Span::from(self.data_offset.to_string()), + ]), + Row::new(vec![ + Span::styled("Congestion Window Reduced", Style::new().bold()), + Span::from(self.cwr.to_string()), + ]), + Row::new(vec![ + Span::styled("ECE", Style::new().bold()), + Span::from(self.ece.to_string()), + ]), + Row::new(vec![ + Span::styled("URG", Style::new().bold()), + Span::from(self.urg.to_string()), + ]), + Row::new(vec![ + Span::styled("ACK", Style::new().bold()), + Span::from(self.ack.to_string()), + ]), + Row::new(vec![ + Span::styled("Push", Style::new().bold()), + Span::from(self.psh.to_string()), + ]), + Row::new(vec![ + Span::styled("Reset", Style::new().bold()), + Span::from(self.rst.to_string()), + ]), + Row::new(vec![ + Span::styled("SYN", Style::new().bold()), + Span::from(self.syn.to_string()), + ]), + Row::new(vec![ + Span::styled("FIN", Style::new().bold()), + Span::from(self.fin.to_string()), + ]), + Row::new(vec![ + Span::styled("Window", Style::new().bold()), + Span::from(self.window.to_string()), + ]), + Row::new(vec![ + Span::styled("Checksum", Style::new().bold()), + Span::from(format!("{:#0x}", self.checksum)), + ]), + Row::new(vec![ + Span::styled("Urgent Pointer", Style::new().bold()), + Span::from(self.urg_ptr.to_string()), + ]), + ]; + + let table = Table::new(infos, widths).column_spacing(2).block( + Block::default() + .borders(Borders::LEFT) + .border_style(Style::new().bold().yellow()) + .border_type(ratatui::widgets::BorderType::Thick) + .style(Style::default()), + ); + frame.render_widget(table, data_block); + frame.render_widget(title, title_block); + } +} + +#[derive(Debug, Copy, Clone)] +pub struct UdpPacket { + pub dst_port: u16, + pub src_port: u16, + pub length: u16, + pub checksum: u16, +} + +impl UdpPacket { + pub fn render(self, block: Rect, frame: &mut Frame) { + let (title_block, data_block) = { + let chunks = Layout::default() + .direction(Direction::Horizontal) + .constraints([Constraint::Length(6), Constraint::Fill(1)]) + .margin(2) + .split(block); + + (chunks[0], chunks[1]) + }; + let title = Paragraph::new("UDP") + .bold() + .block(Block::new().padding(Padding::top({ + if title_block.height % 2 == 0 { + title_block.height / 2 - 1 + } else { + title_block.height / 2 + } + }))); + + let widths = [Constraint::Length(23), Constraint::Fill(1)]; + let infos = [ + Row::new(vec![ + Span::styled("Source Port", Style::new().bold()), + Span::from(self.src_port.to_string()), + ]), + Row::new(vec![ + Span::styled("Destination Port", Style::new().bold()), + Span::from(self.dst_port.to_string()), + ]), + Row::new(vec![ + Span::styled("Length", Style::new().bold()), + Span::from(format!("{} bytes", self.length)), + ]), + Row::new(vec![ + Span::styled("Checksum", Style::new().bold()), + Span::from(format!("{:#0x}", self.checksum)), + ]), + ]; + + let table = Table::new(infos, widths).column_spacing(2).block( + Block::default() + .borders(Borders::LEFT) + .border_style(Style::new().bold().yellow()) + .border_type(ratatui::widgets::BorderType::Thick) + .style(Style::default()), + ); + frame.render_widget(table, data_block); + frame.render_widget(title, title_block); + } +} diff --git a/oryx-tui/src/stats.rs b/oryx-tui/src/stats.rs index 3764ecc..7ce59c7 100644 --- a/oryx-tui/src/stats.rs +++ b/oryx-tui/src/stats.rs @@ -1,10 +1,7 @@ use dns_lookup::lookup_addr; -use oryx_common::AppPacket; use std::collections::HashMap; use std::net::{IpAddr, Ipv4Addr}; -use oryx_common::ip::IpPacket; - use ratatui::layout::{Alignment, Constraint, Direction, Flex, Layout, Rect}; use ratatui::style::{Color, Style}; use ratatui::text::Line; @@ -13,6 +10,9 @@ use ratatui::{ Frame, }; +use crate::packets::network::{IpPacket, IpProto}; +use crate::packets::packet::AppPacket; + #[derive(Debug)] pub struct Stats { pub total: usize, @@ -52,115 +52,48 @@ impl Stats { self.link.arp += 1; } AppPacket::Ip(packet) => match packet { - IpPacket::Tcp(p) => { - match p.src_ip { - IpAddr::V4(ip) => { - self.network.ipv4 += 1; - self.transport.tcp += 1; + IpPacket::V4(ipv4_packet) => { + self.network.ipv4 += 1; - if !ip.is_private() && !ip.is_loopback() { - if let Some((_, counts)) = self.addresses.get_mut(&ip) { - *counts += 1; - } else if let Ok(host) = lookup_addr(&IpAddr::V4(ip)) { - self.addresses.insert(ip, (Some(host), 1)); - } else { - self.addresses.insert(ip, (None, 1)); - } - } + if !ipv4_packet.dst_ip.is_private() && !ipv4_packet.dst_ip.is_loopback() { + if let Some((_, counts)) = self.addresses.get_mut(&ipv4_packet.dst_ip) { + *counts += 1; + } else if let Ok(host) = lookup_addr(&IpAddr::V4(ipv4_packet.dst_ip)) { + self.addresses.insert(ipv4_packet.dst_ip, (Some(host), 1)); + } else { + self.addresses.insert(ipv4_packet.dst_ip, (None, 1)); } + } - IpAddr::V6(_) => { - self.network.ipv6 += 1; + match ipv4_packet.proto { + IpProto::Tcp(_) => { self.transport.tcp += 1; } - }; - - if let IpAddr::V4(ip) = p.dst_ip { - if !ip.is_private() && !ip.is_loopback() { - if let Some((_, counts)) = self.addresses.get_mut(&ip) { - *counts += 1; - } else if let Ok(host) = lookup_addr(&IpAddr::V4(ip)) { - self.addresses.insert(ip, (Some(host), 1)); - } else { - self.addresses.insert(ip, (None, 1)); - } - } - }; - } - IpPacket::Udp(p) => { - match p.src_ip { - IpAddr::V4(ip) => { - self.network.ipv4 += 1; - self.transport.udp += 1; - - if !ip.is_private() && !ip.is_loopback() { - if let Some((_, counts)) = self.addresses.get_mut(&ip) { - *counts += 1; - } else if let Ok(host) = lookup_addr(&IpAddr::V4(ip)) { - self.addresses.insert(ip, (Some(host), 1)); - } else { - self.addresses.insert(ip, (None, 1)); - } - } - } - - IpAddr::V6(_) => { - self.network.ipv6 += 1; + IpProto::Udp(_) => { self.transport.udp += 1; } - }; - - if let IpAddr::V4(ip) = p.dst_ip { - if !ip.is_private() && !ip.is_loopback() { - if let Some((_, counts)) = self.addresses.get_mut(&ip) { - *counts += 1; - } else if let Ok(host) = lookup_addr(&IpAddr::V4(ip)) { - self.addresses.insert(ip, (Some(host), 1)); - } else { - self.addresses.insert(ip, (None, 1)); - } + IpProto::Icmp(_) => { + self.network.icmp += 1; } - }; + } } - IpPacket::Icmp(p) => { - self.network.icmp += 1; - - match p.src_ip { - IpAddr::V4(_) => { - self.network.ipv4 += 1; - } - - IpAddr::V6(_) => { - self.network.ipv6 += 1; + IpPacket::V6(ipv6_packet) => { + self.network.ipv6 += 1; + match ipv6_packet.proto { + IpProto::Tcp(_) => { + self.transport.tcp += 1; } - }; - - if let IpAddr::V4(ip) = p.src_ip { - if !ip.is_private() && !ip.is_loopback() { - if let Some((_, counts)) = self.addresses.get_mut(&ip) { - *counts += 1; - } else if let Ok(host) = lookup_addr(&IpAddr::V4(ip)) { - self.addresses.insert(ip, (Some(host), 1)); - } else { - self.addresses.insert(ip, (None, 1)); - } + IpProto::Udp(_) => { + self.transport.udp += 1; } - }; - - if let IpAddr::V4(ip) = p.dst_ip { - if !ip.is_private() && !ip.is_loopback() { - if let Some((_, counts)) = self.addresses.get_mut(&ip) { - *counts += 1; - } else if let Ok(host) = lookup_addr(&IpAddr::V4(ip)) { - self.addresses.insert(ip, (Some(host), 1)); - } else { - self.addresses.insert(ip, (None, 1)); - } + IpProto::Icmp(_) => { + self.network.icmp += 1; } - }; + } } }, } + self.total += 1; }