mirror of
https://github.com/domcyrus/rustnet.git
synced 2026-04-28 16:00:46 -05:00
adding broken dns lookup
This commit is contained in:
Generated
+33
-1
@@ -1,6 +1,6 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "adler2"
|
||||
@@ -270,6 +270,18 @@ dependencies = [
|
||||
"powerfmt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dns-lookup"
|
||||
version = "2.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5766087c2235fec47fafa4cfecc81e494ee679d0fd4a59887ea0919bfb0e4fc"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"socket2",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.15.0"
|
||||
@@ -774,6 +786,7 @@ dependencies = [
|
||||
"chrono",
|
||||
"clap",
|
||||
"crossterm",
|
||||
"dns-lookup",
|
||||
"log",
|
||||
"maxminddb",
|
||||
"pcap",
|
||||
@@ -889,6 +902,16 @@ version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.5.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stability"
|
||||
version = "0.2.1"
|
||||
@@ -1203,6 +1226,15 @@ dependencies = [
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
|
||||
@@ -8,6 +8,7 @@ anyhow = "1.0"
|
||||
chrono = "0.4"
|
||||
clap = { version = "4.0", features = ["derive"] }
|
||||
crossterm = "0.27"
|
||||
dns-lookup = "2.0.4"
|
||||
log = "0.4"
|
||||
maxminddb = "0.24"
|
||||
pcap = "2.0"
|
||||
|
||||
+51
-50
@@ -1,62 +1,63 @@
|
||||
# English translations for RustNet
|
||||
|
||||
# Basic UI elements
|
||||
rustnet: "RustNet"
|
||||
overview: "Overview"
|
||||
connections: "Connections"
|
||||
processes: "Processes"
|
||||
help: "Help"
|
||||
network: "Network"
|
||||
statistics: "Statistics"
|
||||
top_processes: "Top Processes"
|
||||
connection_details: "Connection Details"
|
||||
process_details: "Process Details"
|
||||
traffic: "Traffic"
|
||||
rustnet: 'RustNet'
|
||||
overview: 'Overview'
|
||||
connections: 'Connections'
|
||||
processes: 'Processes'
|
||||
help: 'Help'
|
||||
network: 'Network'
|
||||
statistics: 'Statistics'
|
||||
top_processes: 'Top Processes'
|
||||
connection_details: 'Connection Details'
|
||||
process_details: 'Process Details'
|
||||
traffic: 'Traffic'
|
||||
|
||||
# Properties
|
||||
interface: "Interface"
|
||||
protocol: "Protocol"
|
||||
local_address: "Local Address"
|
||||
remote_address: "Remote Address"
|
||||
state: "State"
|
||||
process: "Process"
|
||||
pid: "PID"
|
||||
age: "Age"
|
||||
country: "Country"
|
||||
city: "City"
|
||||
bytes_sent: "Bytes Sent"
|
||||
bytes_received: "Bytes Received"
|
||||
packets_sent: "Packets Sent"
|
||||
packets_received: "Packets Received"
|
||||
last_activity: "Last Activity"
|
||||
process_name: "Process Name"
|
||||
command_line: "Command Line"
|
||||
user: "User"
|
||||
cpu_usage: "CPU Usage"
|
||||
memory_usage: "Memory Usage"
|
||||
process_connections: "Process Connections"
|
||||
interface: 'Interface'
|
||||
protocol: 'Protocol'
|
||||
local_address: 'Local Address'
|
||||
remote_address: 'Remote Address'
|
||||
state: 'State'
|
||||
process: 'Process'
|
||||
pid: 'PID'
|
||||
age: 'Age'
|
||||
country: 'Country'
|
||||
city: 'City'
|
||||
bytes_sent: 'Bytes Sent'
|
||||
bytes_received: 'Bytes Received'
|
||||
packets_sent: 'Packets Sent'
|
||||
packets_received: 'Packets Received'
|
||||
last_activity: 'Last Activity'
|
||||
process_name: 'Process Name'
|
||||
command_line: 'Command Line'
|
||||
user: 'User'
|
||||
cpu_usage: 'CPU Usage'
|
||||
memory_usage: 'Memory Usage'
|
||||
process_connections: 'Process Connections'
|
||||
|
||||
# Statistics
|
||||
tcp_connections: "TCP Connections"
|
||||
udp_connections: "UDP Connections"
|
||||
total_connections: "Total Connections"
|
||||
tcp_connections: 'TCP Connections'
|
||||
udp_connections: 'UDP Connections'
|
||||
total_connections: 'Total Connections'
|
||||
|
||||
# Status messages
|
||||
no_connections: "No connections found"
|
||||
no_processes: "No processes found"
|
||||
process_not_found: "Process not found"
|
||||
no_pid_for_connection: "No process ID for this connection"
|
||||
press_for_process_details: "Press for process details"
|
||||
no_connections: 'No connections found'
|
||||
no_processes: 'No processes found'
|
||||
process_not_found: 'Process not found'
|
||||
no_pid_for_connection: 'No process ID for this connection'
|
||||
press_for_process_details: 'Press for process details'
|
||||
press_h_for_help: "Press 'h' for help"
|
||||
default: "default"
|
||||
language: "Language"
|
||||
default: 'default'
|
||||
language: 'Language'
|
||||
|
||||
# Help screen
|
||||
help_intro: "is a cross-platform network monitoring tool"
|
||||
help_quit: "Quit the application"
|
||||
help_refresh: "Refresh connections"
|
||||
help_navigate: "Navigate up/down"
|
||||
help_select: "Select connection/view details"
|
||||
help_back: "Go back to previous view"
|
||||
help_toggle_location: "Toggle IP location display"
|
||||
help_toggle_help: "Toggle help screen"
|
||||
help_intro: 'is a cross-platform network monitoring tool'
|
||||
help_quit: 'Quit the application'
|
||||
help_refresh: 'Refresh connections'
|
||||
help_navigate: 'Navigate up/down'
|
||||
help_select: 'Select connection/view details'
|
||||
help_back: 'Go back to previous view'
|
||||
help_toggle_location: 'Toggle IP location display'
|
||||
help_toggle_help: 'Toggle help screen'
|
||||
help_toggle_dns: 'Toggle DNS hostname display'
|
||||
|
||||
+46
-45
@@ -1,62 +1,63 @@
|
||||
# Traductions françaises pour RustNet
|
||||
|
||||
# Éléments de base de l'interface utilisateur
|
||||
rustnet: "RustNet"
|
||||
rustnet: 'RustNet'
|
||||
overview: "Vue d'ensemble"
|
||||
connections: "Connexions"
|
||||
processes: "Processus"
|
||||
help: "Aide"
|
||||
network: "Réseau"
|
||||
statistics: "Statistiques"
|
||||
top_processes: "Processus principaux"
|
||||
connection_details: "Détails de connexion"
|
||||
process_details: "Détails du processus"
|
||||
traffic: "Trafic"
|
||||
connections: 'Connexions'
|
||||
processes: 'Processus'
|
||||
help: 'Aide'
|
||||
network: 'Réseau'
|
||||
statistics: 'Statistiques'
|
||||
top_processes: 'Processus principaux'
|
||||
connection_details: 'Détails de connexion'
|
||||
process_details: 'Détails du processus'
|
||||
traffic: 'Trafic'
|
||||
|
||||
# Propriétés
|
||||
interface: "Interface"
|
||||
protocol: "Protocole"
|
||||
local_address: "Adresse locale"
|
||||
remote_address: "Adresse distante"
|
||||
state: "État"
|
||||
process: "Processus"
|
||||
pid: "PID"
|
||||
age: "Âge"
|
||||
country: "Pays"
|
||||
city: "Ville"
|
||||
bytes_sent: "Octets envoyés"
|
||||
bytes_received: "Octets reçus"
|
||||
packets_sent: "Paquets envoyés"
|
||||
packets_received: "Paquets reçus"
|
||||
last_activity: "Dernière activité"
|
||||
process_name: "Nom du processus"
|
||||
command_line: "Ligne de commande"
|
||||
user: "Utilisateur"
|
||||
cpu_usage: "Utilisation CPU"
|
||||
memory_usage: "Utilisation mémoire"
|
||||
process_connections: "Connexions du processus"
|
||||
interface: 'Interface'
|
||||
protocol: 'Protocole'
|
||||
local_address: 'Adresse locale'
|
||||
remote_address: 'Adresse distante'
|
||||
state: 'État'
|
||||
process: 'Processus'
|
||||
pid: 'PID'
|
||||
age: 'Âge'
|
||||
country: 'Pays'
|
||||
city: 'Ville'
|
||||
bytes_sent: 'Octets envoyés'
|
||||
bytes_received: 'Octets reçus'
|
||||
packets_sent: 'Paquets envoyés'
|
||||
packets_received: 'Paquets reçus'
|
||||
last_activity: 'Dernière activité'
|
||||
process_name: 'Nom du processus'
|
||||
command_line: 'Ligne de commande'
|
||||
user: 'Utilisateur'
|
||||
cpu_usage: 'Utilisation CPU'
|
||||
memory_usage: 'Utilisation mémoire'
|
||||
process_connections: 'Connexions du processus'
|
||||
|
||||
# Statistiques
|
||||
tcp_connections: "Connexions TCP"
|
||||
udp_connections: "Connexions UDP"
|
||||
total_connections: "Connexions totales"
|
||||
tcp_connections: 'Connexions TCP'
|
||||
udp_connections: 'Connexions UDP'
|
||||
total_connections: 'Connexions totales'
|
||||
|
||||
# Messages d'état
|
||||
no_connections: "Aucune connexion trouvée"
|
||||
no_processes: "Aucun processus trouvé"
|
||||
process_not_found: "Processus non trouvé"
|
||||
no_connections: 'Aucune connexion trouvée'
|
||||
no_processes: 'Aucun processus trouvé'
|
||||
process_not_found: 'Processus non trouvé'
|
||||
no_pid_for_connection: "Pas d'ID de processus pour cette connexion"
|
||||
press_for_process_details: "Appuyez pour les détails du processus"
|
||||
press_for_process_details: 'Appuyez pour les détails du processus'
|
||||
press_h_for_help: "Appuyez sur 'h' pour l'aide"
|
||||
default: "défaut"
|
||||
language: "Langue"
|
||||
default: 'défaut'
|
||||
language: 'Langue'
|
||||
|
||||
# Écran d'aide
|
||||
help_intro: "est un outil de surveillance réseau multiplateforme"
|
||||
help_intro: 'est un outil de surveillance réseau multiplateforme'
|
||||
help_quit: "Quitter l'application"
|
||||
help_refresh: "Rafraîchir les connexions"
|
||||
help_navigate: "Naviguer haut/bas"
|
||||
help_select: "Sélectionner connexion/voir détails"
|
||||
help_back: "Retourner à la vue précédente"
|
||||
help_refresh: 'Rafraîchir les connexions'
|
||||
help_navigate: 'Naviguer haut/bas'
|
||||
help_select: 'Sélectionner connexion/voir détails'
|
||||
help_back: 'Retourner à la vue précédente'
|
||||
help_toggle_location: "Activer/désactiver l'affichage de localisation IP"
|
||||
help_toggle_help: "Activer/désactiver l'écran d'aide"
|
||||
help_toggle_dns: "Activer/désactiver l'affichage des noms d'hôte DNS"
|
||||
|
||||
+259
-4
@@ -1,6 +1,8 @@
|
||||
use anyhow::Result;
|
||||
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
||||
use dns_lookup::lookup_addr;
|
||||
use std::collections::HashMap;
|
||||
use std::net::IpAddr;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::thread;
|
||||
|
||||
@@ -39,12 +41,24 @@ pub struct App {
|
||||
pub connections: Vec<Connection>,
|
||||
/// Process map (pid to process)
|
||||
pub processes: HashMap<u32, Process>,
|
||||
/// Currently selected connection
|
||||
pub selected_connection: Option<Connection>,
|
||||
/// Currently selected connection index
|
||||
pub selected_connection_idx: usize,
|
||||
/// Currently selected process index
|
||||
pub selected_process_idx: usize,
|
||||
/// Show IP locations (requires MaxMind DB)
|
||||
pub show_locations: bool,
|
||||
/// Show DNS hostnames instead of IP addresses
|
||||
pub show_hostnames: bool,
|
||||
/// Last connection sort time
|
||||
last_sort_time: std::time::Instant,
|
||||
/// Connection order map (for stable ordering)
|
||||
connection_order: HashMap<String, usize>,
|
||||
/// Next order index for new connections
|
||||
next_order_index: usize,
|
||||
/// DNS cache to avoid repeated lookups
|
||||
dns_cache: HashMap<IpAddr, String>,
|
||||
}
|
||||
|
||||
impl App {
|
||||
@@ -58,9 +72,15 @@ impl App {
|
||||
network_monitor: None,
|
||||
connections: Vec::new(),
|
||||
processes: HashMap::new(),
|
||||
selected_connection: None,
|
||||
selected_connection_idx: 0,
|
||||
selected_process_idx: 0,
|
||||
show_locations: true,
|
||||
show_hostnames: false,
|
||||
last_sort_time: std::time::Instant::now(),
|
||||
connection_order: HashMap::new(),
|
||||
next_order_index: 0,
|
||||
dns_cache: HashMap::new(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -121,15 +141,27 @@ impl App {
|
||||
Some(Action::Quit)
|
||||
}
|
||||
KeyCode::Char('r') => Some(Action::Refresh),
|
||||
KeyCode::Down | KeyCode::Char('j') => {
|
||||
KeyCode::Down => {
|
||||
if !self.connections.is_empty() {
|
||||
self.selected_connection = Some(
|
||||
self.connections
|
||||
[(self.selected_connection_idx + 1) % self.connections.len()]
|
||||
.clone(),
|
||||
);
|
||||
self.selected_connection_idx =
|
||||
(self.selected_connection_idx + 1) % self.connections.len();
|
||||
}
|
||||
None
|
||||
}
|
||||
KeyCode::Up | KeyCode::Char('k') => {
|
||||
KeyCode::Up => {
|
||||
if !self.connections.is_empty() {
|
||||
self.selected_connection = Some(
|
||||
self.connections[self
|
||||
.selected_connection_idx
|
||||
.checked_sub(1)
|
||||
.unwrap_or(self.connections.len() - 1)]
|
||||
.clone(),
|
||||
);
|
||||
self.selected_connection_idx = self
|
||||
.selected_connection_idx
|
||||
.checked_sub(1)
|
||||
@@ -151,6 +183,14 @@ impl App {
|
||||
self.show_locations = !self.show_locations;
|
||||
None
|
||||
}
|
||||
KeyCode::Char('d') => {
|
||||
self.show_hostnames = !self.show_hostnames;
|
||||
// Clear DNS cache when toggling off to ensure fresh lookups when toggled on again
|
||||
if !self.show_hostnames {
|
||||
self.dns_cache.clear();
|
||||
}
|
||||
None
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -194,10 +234,63 @@ impl App {
|
||||
|
||||
/// Update application state on tick
|
||||
pub fn on_tick(&mut self) -> Result<()> {
|
||||
// Store currently selected connection (if any)
|
||||
let selected = self.selected_connection.clone();
|
||||
|
||||
// Update connections from network monitor if available
|
||||
if let Some(monitor_arc) = &self.network_monitor {
|
||||
let mut monitor = monitor_arc.lock().unwrap(); // Lock the mutex
|
||||
self.connections = monitor.get_connections()?;
|
||||
let mut new_connections = monitor.get_connections()?;
|
||||
drop(monitor); // Release the mutex lock before self-mutation
|
||||
|
||||
// Extract keys for sorting
|
||||
let mut keys_to_process = Vec::new();
|
||||
for conn in &new_connections {
|
||||
let key = self.get_connection_key(conn);
|
||||
keys_to_process.push(key);
|
||||
}
|
||||
|
||||
// Update connection order
|
||||
for key in keys_to_process {
|
||||
if !self.connection_order.contains_key(&key) {
|
||||
self.connection_order.insert(key, self.next_order_index);
|
||||
self.next_order_index += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Sort connections by their assigned order
|
||||
new_connections.sort_by(|a, b| {
|
||||
let key_a = self.get_connection_key(a);
|
||||
let key_b = self.get_connection_key(b);
|
||||
|
||||
let order_a = self.connection_order.get(&key_a).unwrap_or(&usize::MAX);
|
||||
let order_b = self.connection_order.get(&key_b).unwrap_or(&usize::MAX);
|
||||
|
||||
order_a.cmp(order_b)
|
||||
});
|
||||
|
||||
// Update connections with the sorted list
|
||||
self.connections = new_connections;
|
||||
|
||||
// Restore selected connection position if possible
|
||||
if let Some(ref conn) = selected {
|
||||
if let Some(idx) = self.find_connection_index(conn) {
|
||||
self.selected_connection_idx = idx;
|
||||
self.selected_connection = Some(self.connections[idx].clone());
|
||||
} else if !self.connections.is_empty() {
|
||||
// If previously selected connection is gone, select first one
|
||||
self.selected_connection_idx = 0;
|
||||
self.selected_connection = Some(self.connections[0].clone());
|
||||
} else {
|
||||
// If no connections left, clear selection
|
||||
self.selected_connection_idx = 0;
|
||||
self.selected_connection = None;
|
||||
}
|
||||
} else if !self.connections.is_empty() && self.selected_connection.is_none() {
|
||||
// If no previous selection but we have connections, select the first one
|
||||
self.selected_connection_idx = 0;
|
||||
self.selected_connection = Some(self.connections[0].clone());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -205,9 +298,58 @@ impl App {
|
||||
|
||||
/// Refresh application data
|
||||
pub fn refresh(&mut self) -> Result<()> {
|
||||
// Store currently selected connection (if any)
|
||||
let selected = self.selected_connection.clone();
|
||||
|
||||
if let Some(monitor_arc) = &self.network_monitor {
|
||||
let mut monitor = monitor_arc.lock().unwrap(); // Lock the mutex
|
||||
self.connections = monitor.get_connections()?;
|
||||
let mut new_connections = monitor.get_connections()?;
|
||||
drop(monitor); // Release the mutex lock before self-mutation
|
||||
|
||||
// Extract keys for sorting
|
||||
let mut keys_to_process = Vec::new();
|
||||
for conn in &new_connections {
|
||||
let key = self.get_connection_key(conn);
|
||||
keys_to_process.push(key);
|
||||
}
|
||||
|
||||
// Update connection order
|
||||
for key in keys_to_process {
|
||||
if !self.connection_order.contains_key(&key) {
|
||||
self.connection_order.insert(key, self.next_order_index);
|
||||
self.next_order_index += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Sort connections by their assigned order
|
||||
new_connections.sort_by(|a, b| {
|
||||
let key_a = self.get_connection_key(a);
|
||||
let key_b = self.get_connection_key(b);
|
||||
|
||||
let order_a = self.connection_order.get(&key_a).unwrap_or(&usize::MAX);
|
||||
let order_b = self.connection_order.get(&key_b).unwrap_or(&usize::MAX);
|
||||
|
||||
order_a.cmp(order_b)
|
||||
});
|
||||
|
||||
// Update connections with the sorted list
|
||||
self.connections = new_connections;
|
||||
|
||||
// Restore selected connection position if possible
|
||||
if let Some(ref conn) = selected {
|
||||
if let Some(idx) = self.find_connection_index(conn) {
|
||||
self.selected_connection_idx = idx;
|
||||
self.selected_connection = Some(self.connections[idx].clone());
|
||||
} else if !self.connections.is_empty() {
|
||||
// If previously selected connection is gone, select first one
|
||||
self.selected_connection_idx = 0;
|
||||
self.selected_connection = Some(self.connections[0].clone());
|
||||
} else {
|
||||
// If no connections left, clear selection
|
||||
self.selected_connection_idx = 0;
|
||||
self.selected_connection = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -252,4 +394,117 @@ impl App {
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Generate a unique key for a connection
|
||||
fn get_connection_key(&self, conn: &Connection) -> String {
|
||||
format!(
|
||||
"{:?}-{}-{}-{:?}",
|
||||
conn.protocol, conn.local_addr, conn.remote_addr, conn.state
|
||||
)
|
||||
}
|
||||
|
||||
/// Assign stable order indices to connections
|
||||
fn update_connection_order(&mut self, connections: &mut Vec<Connection>) {
|
||||
// This ensures that new connections get added at the end and existing connections maintain their order
|
||||
for conn in connections.iter() {
|
||||
let key = self.get_connection_key(conn);
|
||||
if !self.connection_order.contains_key(&key) {
|
||||
self.connection_order.insert(key, self.next_order_index);
|
||||
self.next_order_index += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Sort connections in a stable way
|
||||
fn sort_connections_stable(&mut self, connections: &mut Vec<Connection>) {
|
||||
// Update order indices for any new connections
|
||||
self.update_connection_order(connections);
|
||||
|
||||
// Sort connections by their assigned order
|
||||
connections.sort_by(|a, b| {
|
||||
let key_a = self.get_connection_key(a);
|
||||
let key_b = self.get_connection_key(b);
|
||||
|
||||
let order_a = self.connection_order.get(&key_a).unwrap_or(&usize::MAX);
|
||||
let order_b = self.connection_order.get(&key_b).unwrap_or(&usize::MAX);
|
||||
|
||||
order_a.cmp(order_b)
|
||||
});
|
||||
}
|
||||
|
||||
/// Find the index of a connection that matches the selected connection
|
||||
fn find_connection_index(&self, selected: &Connection) -> Option<usize> {
|
||||
let selected_key = self.get_connection_key(selected);
|
||||
|
||||
for (i, conn) in self.connections.iter().enumerate() {
|
||||
let key = self.get_connection_key(conn);
|
||||
if key == selected_key {
|
||||
return Some(i);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Resolve an IP address to a hostname with caching
|
||||
pub fn resolve_hostname(&mut self, ip: IpAddr) -> String {
|
||||
// Check if the IP is in the cache
|
||||
if let Some(hostname) = self.dns_cache.get(&ip) {
|
||||
return hostname.clone();
|
||||
}
|
||||
|
||||
// Special handling for common IP addresses
|
||||
if ip.is_loopback() {
|
||||
let hostname = "localhost".to_string();
|
||||
self.dns_cache.insert(ip, hostname.clone());
|
||||
return hostname;
|
||||
}
|
||||
|
||||
if ip.is_unspecified() {
|
||||
let hostname = "*".to_string();
|
||||
self.dns_cache.insert(ip, hostname.clone());
|
||||
return hostname;
|
||||
}
|
||||
|
||||
// Perform DNS resolution using the dns-lookup crate
|
||||
match lookup_addr(&ip) {
|
||||
Ok(hostname) => {
|
||||
// Cache the result
|
||||
let hostname = hostname.trim_end_matches('.').to_string();
|
||||
self.dns_cache.insert(ip, hostname.clone());
|
||||
hostname
|
||||
}
|
||||
Err(_) => {
|
||||
// If resolution fails, return the IP as a string
|
||||
let ip_str = ip.to_string();
|
||||
self.dns_cache.insert(ip, ip_str.clone());
|
||||
ip_str
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Format a socket address with hostname if enabled (without mutating self)
|
||||
pub fn format_socket_addr(&self, addr: std::net::SocketAddr) -> String {
|
||||
if self.show_hostnames {
|
||||
let ip = addr.ip();
|
||||
// Check if it's in the cache
|
||||
if let Some(hostname) = self.dns_cache.get(&ip) {
|
||||
return format!("{}:{}", hostname, addr.port());
|
||||
}
|
||||
|
||||
// Special handling without cache insertion
|
||||
if ip.is_loopback() {
|
||||
return format!("localhost:{}", addr.port());
|
||||
}
|
||||
|
||||
if ip.is_unspecified() {
|
||||
return format!("*:{}", addr.port());
|
||||
}
|
||||
|
||||
// Just return the address as string if not in cache
|
||||
addr.to_string()
|
||||
} else {
|
||||
addr.to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-4
@@ -1,7 +1,7 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use log::{debug, error, info};
|
||||
use maxminddb::geoip2;
|
||||
use pcap::{Capture, Device, Packet};
|
||||
use pcap::{Capture, Device};
|
||||
use std::collections::HashMap;
|
||||
use std::net::{IpAddr, SocketAddr};
|
||||
use std::time::{Duration, Instant, SystemTime};
|
||||
@@ -16,8 +16,6 @@ use windows::*;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
mod macos;
|
||||
#[cfg(target_os = "macos")]
|
||||
use macos::get_interface_addresses;
|
||||
|
||||
/// Connection protocol
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
@@ -324,7 +322,7 @@ impl NetworkMonitor {
|
||||
let process_single_packet =
|
||||
|data: &[u8],
|
||||
connections: &mut HashMap<String, Connection>,
|
||||
interface: &Option<String>| {
|
||||
_interface: &Option<String>| {
|
||||
// Check if it's an ethernet frame
|
||||
if data.len() < 14 {
|
||||
return; // Too short for Ethernet
|
||||
|
||||
@@ -144,14 +144,17 @@ fn draw_connections_list(f: &mut Frame, app: &App, area: Rect) {
|
||||
.pid
|
||||
.map(|p| p.to_string())
|
||||
.unwrap_or_else(|| "-".to_string());
|
||||
|
||||
let process_str = conn.process_name.clone().unwrap_or_else(|| "-".to_string());
|
||||
let process_display = format!("{} ({})", process_str, pid_str);
|
||||
|
||||
let remote_display = conn.remote_addr.to_string();
|
||||
// Format addresses with hostnames if enabled - no mutable borrowing
|
||||
let local_display = app.format_socket_addr(conn.local_addr);
|
||||
let remote_display = app.format_socket_addr(conn.remote_addr);
|
||||
|
||||
let cells = [
|
||||
Cell::from(conn.protocol.to_string()),
|
||||
Cell::from(conn.local_addr.to_string()),
|
||||
Cell::from(local_display),
|
||||
Cell::from(remote_display),
|
||||
Cell::from(conn.state.to_string()),
|
||||
Cell::from(process_display),
|
||||
@@ -159,6 +162,12 @@ fn draw_connections_list(f: &mut Frame, app: &App, area: Rect) {
|
||||
rows.push(Row::new(cells));
|
||||
}
|
||||
|
||||
// Create table state with current selection
|
||||
let mut state = ratatui::widgets::TableState::default();
|
||||
if !app.connections.is_empty() {
|
||||
state.select(Some(app.selected_connection_idx));
|
||||
}
|
||||
|
||||
let connections = Table::new(rows, &widths)
|
||||
.header(header)
|
||||
.block(
|
||||
@@ -166,10 +175,10 @@ fn draw_connections_list(f: &mut Frame, app: &App, area: Rect) {
|
||||
.borders(Borders::ALL)
|
||||
.title(app.i18n.get("connections")),
|
||||
)
|
||||
.highlight_style(Style::default().add_modifier(Modifier::REVERSED)) // Changed from row_highlight_style to highlight_style
|
||||
.highlight_style(Style::default().add_modifier(Modifier::REVERSED))
|
||||
.highlight_symbol("> ");
|
||||
|
||||
f.render_widget(connections, area);
|
||||
f.render_stateful_widget(connections, area, &mut state);
|
||||
}
|
||||
|
||||
/// Draw side panel with stats
|
||||
@@ -309,12 +318,16 @@ fn draw_connection_details(f: &mut Frame, app: &App, area: Rect) -> Result<()> {
|
||||
Span::raw(conn.protocol.to_string()),
|
||||
]));
|
||||
|
||||
// Format addresses with hostnames if enabled
|
||||
let local_display = app.format_socket_addr(conn.local_addr);
|
||||
let remote_display = app.format_socket_addr(conn.remote_addr);
|
||||
|
||||
details_text.push(Line::from(vec![
|
||||
Span::styled(
|
||||
format!("{}: ", app.i18n.get("local_address")),
|
||||
Style::default().fg(Color::Yellow),
|
||||
),
|
||||
Span::raw(conn.local_addr.to_string()),
|
||||
Span::raw(local_display),
|
||||
]));
|
||||
|
||||
details_text.push(Line::from(vec![
|
||||
@@ -322,7 +335,7 @@ fn draw_connection_details(f: &mut Frame, app: &App, area: Rect) -> Result<()> {
|
||||
format!("{}: ", app.i18n.get("remote_address")),
|
||||
Style::default().fg(Color::Yellow),
|
||||
),
|
||||
Span::raw(conn.remote_addr.to_string()),
|
||||
Span::raw(remote_display),
|
||||
]));
|
||||
|
||||
if app.show_locations && !conn.remote_addr.ip().is_unspecified() {
|
||||
@@ -545,6 +558,10 @@ fn draw_process_details(f: &mut Frame, app: &mut App, area: Rect) -> Result<()>
|
||||
|
||||
let mut items = Vec::new();
|
||||
for conn in &connections {
|
||||
// Format addresses with hostnames if enabled
|
||||
let local_display = app.format_socket_addr(conn.local_addr);
|
||||
let remote_display = app.format_socket_addr(conn.remote_addr);
|
||||
|
||||
items.push(ListItem::new(Line::from(vec![
|
||||
Span::styled(
|
||||
format!("{}: ", conn.protocol),
|
||||
@@ -552,7 +569,7 @@ fn draw_process_details(f: &mut Frame, app: &mut App, area: Rect) -> Result<()>
|
||||
),
|
||||
Span::raw(format!(
|
||||
"{} -> {} ({})",
|
||||
conn.local_addr, conn.remote_addr, conn.state
|
||||
local_display, remote_display, conn.state
|
||||
)),
|
||||
])));
|
||||
}
|
||||
@@ -619,6 +636,10 @@ fn draw_help(f: &mut Frame, app: &App, area: Rect) -> Result<()> {
|
||||
Span::styled("l ", Style::default().fg(Color::Yellow)),
|
||||
Span::raw(app.i18n.get("help_toggle_location")),
|
||||
]),
|
||||
Line::from(vec![
|
||||
Span::styled("d ", Style::default().fg(Color::Yellow)),
|
||||
Span::raw(app.i18n.get("help_toggle_dns")),
|
||||
]),
|
||||
Line::from(vec![
|
||||
Span::styled("h ", Style::default().fg(Color::Yellow)),
|
||||
Span::raw(app.i18n.get("help_toggle_help")),
|
||||
|
||||
Reference in New Issue
Block a user