From d1da41bc2cfc1035c08948594fabe59e0368acfd Mon Sep 17 00:00:00 2001 From: Badr Date: Sat, 16 Nov 2024 16:26:17 +0100 Subject: [PATCH] show pid for tcp and udp --- oryx-tui/src/app.rs | 16 ++++- oryx-tui/src/lib.rs | 2 + oryx-tui/src/pid.rs | 104 +++++++++++++++++++++++++++++ oryx-tui/src/pid/tcp.rs | 99 +++++++++++++++++++++++++++ oryx-tui/src/pid/udp.rs | 98 +++++++++++++++++++++++++++ oryx-tui/src/section/inspection.rs | 34 ++++++++++ 6 files changed, 352 insertions(+), 1 deletion(-) create mode 100644 oryx-tui/src/pid.rs create mode 100644 oryx-tui/src/pid/tcp.rs create mode 100644 oryx-tui/src/pid/udp.rs diff --git a/oryx-tui/src/app.rs b/oryx-tui/src/app.rs index 2446f2f..6fbaea5 100644 --- a/oryx-tui/src/app.rs +++ b/oryx-tui/src/app.rs @@ -14,7 +14,9 @@ use crate::{ filter::Filter, help::Help, packet::{direction::TrafficDirection, NetworkPacket}, + pid, }; + use crate::{filter::IoChannels, notification::Notification}; use crate::{packet::AppPacket, section::Section}; @@ -66,17 +68,29 @@ impl App { thread::spawn({ let packets = packets.clone(); move || loop { + let pid_map = pid::ConnectionMap::new(); if let Ok((raw_packet, direction)) = receiver.recv() { let network_packet = NetworkPacket::from(raw_packet); + + let pid = { + if direction == TrafficDirection::Egress { + pid::get_pid(network_packet, &pid_map) + } else { + None + } + }; + let app_packet = AppPacket { packet: network_packet, - pid: None, + pid, direction, }; + let mut packets = packets.lock().unwrap(); if packets.len() == packets.capacity() { packets.reserve(1024 * 1024); } + packets.push(app_packet); } } diff --git a/oryx-tui/src/lib.rs b/oryx-tui/src/lib.rs index 7a46ac4..f6f24f0 100644 --- a/oryx-tui/src/lib.rs +++ b/oryx-tui/src/lib.rs @@ -27,3 +27,5 @@ pub mod packet; pub mod section; pub mod dns; + +pub mod pid; diff --git a/oryx-tui/src/pid.rs b/oryx-tui/src/pid.rs new file mode 100644 index 0000000..15f9706 --- /dev/null +++ b/oryx-tui/src/pid.rs @@ -0,0 +1,104 @@ +use std::hash::{DefaultHasher, Hash, Hasher}; +use std::net::IpAddr; +use std::num::ParseIntError; + +pub mod tcp; +pub mod udp; + +use tcp::TcpConnectionMap; +use udp::UdpConnectionMap; + +use crate::app::AppResult; +use crate::packet::network::{IpPacket, IpProto}; +use crate::packet::NetworkPacket; + +pub fn get_pid(packet: NetworkPacket, map: &ConnectionMap) -> Option { + match packet { + NetworkPacket::Ip(ip_packet) => match ip_packet { + IpPacket::V4(ipv4_packet) => match ipv4_packet.proto { + IpProto::Tcp(tcp_packet) => { + let connection = Connection { + ip_local: IpAddr::V4(ipv4_packet.src_ip), + port_local: Some(tcp_packet.src_port), + ip_remote: IpAddr::V4(ipv4_packet.dst_ip), + port_remote: Some(tcp_packet.dst_port), + }; + return map.tcp.map.get(&connection.calculate_hash()).copied(); + } + + IpProto::Udp(udp_packet) => { + let connection = Connection { + ip_local: IpAddr::V4(ipv4_packet.src_ip), + port_local: Some(udp_packet.src_port), + ip_remote: IpAddr::V4(ipv4_packet.dst_ip), + port_remote: Some(udp_packet.dst_port), + }; + return map.udp.map.get(&connection.calculate_hash()).copied(); + } + _ => {} + }, + _ => {} + }, + _ => {} + }; + None +} + +fn decode_hex_ipv4(hex_str: &str) -> AppResult<[u8; 4]> { + let mut bytes = Vec::new(); + for i in (0..hex_str.len()).step_by(2) { + let byte_str = &hex_str[i..i + 2]; + let byte = u8::from_str_radix(byte_str, 16)?; + bytes.push(byte); + } + let mut res: [u8; 4] = bytes.as_slice().try_into()?; + res.reverse(); + Ok(res) +} + +fn decode_hex_port(hex_str: &str) -> Result { + Ok(u16::from_be_bytes([ + u8::from_str_radix(&hex_str[..2], 16)?, + u8::from_str_radix(&hex_str[2..], 16)?, + ])) +} + +#[derive(Clone, Debug)] +pub struct ConnectionMap { + tcp: TcpConnectionMap, + udp: UdpConnectionMap, +} + +impl ConnectionMap { + pub fn new() -> Self { + Self { + tcp: TcpConnectionMap::new(), + udp: UdpConnectionMap::new(), + } + } +} + +#[derive(Clone, Debug)] +pub struct Connection { + ip_local: IpAddr, + port_local: Option, + ip_remote: IpAddr, + port_remote: Option, +} + +impl Hash for Connection { + fn hash(&self, state: &mut H) { + self.ip_local.hash(state); + self.port_local.hash(state); + self.ip_remote.hash(state); + self.port_remote.hash(state); + } +} + +impl Connection { + pub fn calculate_hash(&self) -> u64 { + let mut s = DefaultHasher::new(); + self.hash(&mut s); + s.finish() + } +} diff --git a/oryx-tui/src/pid/tcp.rs b/oryx-tui/src/pid/tcp.rs new file mode 100644 index 0000000..b4ec080 --- /dev/null +++ b/oryx-tui/src/pid/tcp.rs @@ -0,0 +1,99 @@ +use std::{ + collections::HashMap, + fs::{self, File}, + io::Read, + net::{IpAddr, Ipv4Addr}, +}; + +use super::{decode_hex_ipv4, decode_hex_port, Connection}; + +#[derive(Clone, Debug)] +pub struct TcpConnectionMap { + pub map: HashMap, +} + +impl TcpConnectionMap { + fn inode_map() -> HashMap { + let mut map = HashMap::new(); + let mut file = File::open("/proc/net/tcp").unwrap(); + let mut buffer = String::new(); + file.read_to_string(&mut buffer).unwrap(); + + let mut lines_tcp = buffer.lines(); + lines_tcp.next(); + + for line in lines_tcp { + let splits: Vec<&str> = line.split_whitespace().collect(); + let ip_local: &str = splits[1]; + let mut ip_local_port = ip_local.split(":"); + let ip_local = ip_local_port.next().unwrap(); + let port_local = ip_local_port.next().unwrap(); + + let ip_local = decode_hex_ipv4(ip_local).unwrap(); + let ip_local = IpAddr::V4(Ipv4Addr::from(ip_local)); + + let port_local = decode_hex_port(port_local).unwrap(); + + let ip_remote = splits[2]; + let mut ip_remote_port = ip_remote.split(":"); + let ip_remote = ip_remote_port.next().unwrap(); + let port_remote = ip_remote_port.next().unwrap(); + + let ip_remote = decode_hex_ipv4(ip_remote).unwrap(); + let ip_remote = IpAddr::V4(Ipv4Addr::from(ip_remote)); + + let port_remote = decode_hex_port(port_remote).unwrap(); + + let inode = splits[9].parse::().unwrap(); + + let connection = Connection { + ip_local, + port_local: Some(port_local), + ip_remote, + port_remote: Some(port_remote), + }; + + map.insert(inode, connection.calculate_hash()); + } + map + } + + pub fn new() -> Self { + let mut map: HashMap = HashMap::new(); + + let inode_map = Self::inode_map(); + + if let Ok(entries) = fs::read_dir("/proc") { + for entry in entries.flatten() { + let pid_str = entry.file_name(); + let pid_str = pid_str.to_str().unwrap(); + if !pid_str.chars().all(char::is_numeric) { + continue; + } + let fd_dir = format!("/proc/{}/fd", pid_str); + if let Ok(fds) = fs::read_dir(&fd_dir) { + for fd in fds.flatten() { + let link_path = fd.path(); + + if let Ok(link_target) = fs::read_link(&link_path) { + // Socket inodes are typically shown as "socket:[inode]" + if let Some(inode_str) = link_target.to_str() { + if inode_str.starts_with("socket:[") && inode_str.ends_with(']') { + if let Ok(inode) = + inode_str[8..inode_str.len() - 1].parse::() + { + if let Some(connection_hash) = inode_map.get(&inode) { + let pid = pid_str.parse::().unwrap(); + map.insert(*connection_hash, pid); + } + } + } + } + } + } + } + } + } + Self { map } + } +} diff --git a/oryx-tui/src/pid/udp.rs b/oryx-tui/src/pid/udp.rs new file mode 100644 index 0000000..53929b6 --- /dev/null +++ b/oryx-tui/src/pid/udp.rs @@ -0,0 +1,98 @@ +use std::{ + collections::HashMap, + fs::{self, File}, + io::Read, + net::{IpAddr, Ipv4Addr}, +}; + +use super::{decode_hex_ipv4, decode_hex_port, Connection}; + +#[derive(Clone, Debug)] +pub struct UdpConnectionMap { + pub map: HashMap, +} + +impl UdpConnectionMap { + fn inode_map() -> HashMap { + let mut map = HashMap::new(); + let mut file = File::open("/proc/net/udp").unwrap(); + let mut buffer = String::new(); + file.read_to_string(&mut buffer).unwrap(); + + let mut lines_tcp = buffer.lines(); + lines_tcp.next(); + + for line in lines_tcp { + let splits: Vec<&str> = line.split_whitespace().collect(); + let ip_local: &str = splits[1]; + let mut ip_local_port = ip_local.split(":"); + let ip_local = ip_local_port.next().unwrap(); + let port_local = ip_local_port.next().unwrap(); + + let ip_local = decode_hex_ipv4(ip_local).unwrap(); + let ip_local = IpAddr::V4(Ipv4Addr::from(ip_local)); + + let port_local = decode_hex_port(port_local).unwrap(); + + let ip_remote = splits[2]; + let mut ip_remote_port = ip_remote.split(":"); + let ip_remote = ip_remote_port.next().unwrap(); + let port_remote = ip_remote_port.next().unwrap(); + + let ip_remote = decode_hex_ipv4(ip_remote).unwrap(); + let ip_remote = IpAddr::V4(Ipv4Addr::from(ip_remote)); + + let port_remote = decode_hex_port(port_remote).unwrap(); + + let inode = splits[9].parse::().unwrap(); + + let connection = Connection { + ip_local, + port_local: Some(port_local), + ip_remote, + port_remote: Some(port_remote), + }; + + map.insert(inode, connection.calculate_hash()); + } + map + } + + pub fn new() -> Self { + let mut map: HashMap = HashMap::new(); + + let inode_map = Self::inode_map(); + + if let Ok(entries) = fs::read_dir("/proc") { + for entry in entries.flatten() { + let pid_str = entry.file_name(); + let pid_str = pid_str.to_str().unwrap(); + if !pid_str.chars().all(char::is_numeric) { + continue; + } + let fd_dir = format!("/proc/{}/fd", pid_str); + if let Ok(fds) = fs::read_dir(&fd_dir) { + for fd in fds.flatten() { + let link_path = fd.path(); + + if let Ok(link_target) = fs::read_link(&link_path) { + if let Some(inode_str) = link_target.to_str() { + if inode_str.starts_with("socket:[") && inode_str.ends_with(']') { + if let Ok(inode) = + inode_str[8..inode_str.len() - 1].parse::() + { + if let Some(connection_hash) = inode_map.get(&inode) { + let pid = pid_str.parse::().unwrap(); + map.insert(*connection_hash, pid); + } + } + } + } + } + } + } + } + } + Self { map } + } +} diff --git a/oryx-tui/src/section/inspection.rs b/oryx-tui/src/section/inspection.rs index 5d0b482..35b8069 100644 --- a/oryx-tui/src/section/inspection.rs +++ b/oryx-tui/src/section/inspection.rs @@ -259,6 +259,7 @@ impl Inspection { Constraint::Min(19), // Destination Address Constraint::Length(16), // Destination Port Constraint::Length(8), // Protocol + Constraint::Length(8), // Pid Constraint::Length(3), // manual scroll sign ]; @@ -348,6 +349,13 @@ impl Inspection { 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(), + { + if let Some(pid) = app_packet.pid { + fuzzy::highlight(pattern, pid.to_string()).cyan() + } else { + Cell::from(Line::from("-").centered()).yellow() + } + }, ]), IpProto::Udp(p) => Row::new(vec![ fuzzy::highlight(pattern, ipv4_packet.src_ip.to_string()).blue(), @@ -355,6 +363,13 @@ impl Inspection { 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(), + { + if let Some(pid) = app_packet.pid { + fuzzy::highlight(pattern, pid.to_string()).cyan() + } else { + Cell::from(Line::from("-").centered()).yellow() + } + }, ]), IpProto::Icmp(_) => Row::new(vec![ fuzzy::highlight(pattern, ipv4_packet.src_ip.to_string()).blue(), @@ -362,6 +377,7 @@ impl Inspection { fuzzy::highlight(pattern, ipv4_packet.dst_ip.to_string()).blue(), Cell::from(Line::from("-").centered()).yellow(), fuzzy::highlight(pattern, "ICMP".to_string()).cyan(), + Cell::from(Line::from("-").centered()).yellow(), ]), }, IpPacket::V6(ipv6_packet) => match ipv6_packet.proto { @@ -371,6 +387,7 @@ impl Inspection { 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(), + Cell::from(Line::from("-").centered()).yellow(), ]), IpProto::Udp(p) => Row::new(vec![ fuzzy::highlight(pattern, ipv6_packet.src_ip.to_string()).blue(), @@ -378,6 +395,7 @@ impl Inspection { 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(), + Cell::from(Line::from("-").centered()).yellow(), ]), IpProto::Icmp(_) => Row::new(vec![ fuzzy::highlight(pattern, ipv6_packet.src_ip.to_string()).blue(), @@ -385,6 +403,7 @@ impl Inspection { fuzzy::highlight(pattern, ipv6_packet.dst_ip.to_string()).blue(), Cell::from(Line::from("-").centered()).yellow(), fuzzy::highlight(pattern, "ICMP".to_string()).cyan(), + Cell::from(Line::from("-").centered()).yellow(), ]), }, }, @@ -421,6 +440,13 @@ impl Inspection { .into_centered_line() .yellow(), Span::from("TCP".to_string()).into_centered_line().cyan(), + { + if let Some(pid) = app_packet.pid { + Span::from(pid.to_string()).into_centered_line().yellow() + } else { + Span::from("-".to_string()).into_centered_line().yellow() + } + }, ]), IpProto::Udp(p) => Row::new(vec![ Span::from(ipv4_packet.src_ip.to_string()) @@ -436,6 +462,13 @@ impl Inspection { .into_centered_line() .yellow(), Span::from("UDP".to_string()).into_centered_line().cyan(), + { + if let Some(pid) = app_packet.pid { + Span::from(pid.to_string()).into_centered_line().yellow() + } else { + Span::from("-".to_string()).into_centered_line().yellow() + } + }, ]), IpProto::Icmp(_) => Row::new(vec![ Span::from(ipv4_packet.src_ip.to_string()) @@ -514,6 +547,7 @@ impl Inspection { Line::from("Destination Address").centered(), Line::from("Destination Port").centered(), Line::from("Protocol").centered(), + Line::from("Pid").centered(), { if self.manuall_scroll { Line::from("󰹆").centered().yellow()