Skip to content

Commit 64fd949

Browse files
authored
Refactor/code alignment (#149)
* feat: add NO_COLOR support and centralized theme palette Introduce a theme module with semantic color aliases derived from 7 base constants, replacing ~100 scattered hardcoded Color::* values. Add --no-color flag and NO_COLOR env var support (https://no-color.org). * refactor: simplify and align code patterns across codebase - Add Display impl for TcpState, deduplicating state name mapping - Extract NONE_PLACEHOLDER constant in ui.rs (15 scattered instances) - Replace magic port literals with named constants in DPI module - Standardize lock handling to .expect() with descriptive messages - Remove leftover Firefox-specific debug logging from ui.rs
1 parent bd6fb1c commit 64fd949

8 files changed

Lines changed: 579 additions & 425 deletions

File tree

src/cli.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,12 @@ pub fn build_cli() -> Command {
9595
.long("show-ptr-lookups")
9696
.help("Show PTR lookup connections in UI (hidden by default when --resolve-dns is enabled)")
9797
.action(clap::ArgAction::SetTrue),
98+
)
99+
.arg(
100+
Arg::new("no-color")
101+
.long("no-color")
102+
.help("Disable all colors in the UI (also respects NO_COLOR env var)")
103+
.action(clap::ArgAction::SetTrue),
98104
);
99105

100106
#[cfg(target_os = "linux")]

src/main.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,14 @@ fn main() -> Result<()> {
9191
info!("PTR lookup connections will be shown in UI");
9292
}
9393

94+
// Check NO_COLOR environment variable and --no-color flag (https://no-color.org)
95+
let no_color =
96+
matches.get_flag("no-color") || std::env::var("NO_COLOR").is_ok_and(|v| !v.is_empty());
97+
if no_color {
98+
info!("Colors disabled (NO_COLOR)");
99+
ui::set_no_color(true);
100+
}
101+
94102
// Set up terminal
95103
let backend = CrosstermBackend::new(io::stdout());
96104
let mut terminal = ui::setup_terminal(backend)?;

src/network/dpi/mod.rs

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,21 @@ mod ssh;
1818
pub use cipher_suites::{format_cipher_suite, is_secure_cipher_suite};
1919
pub use quic::{is_partial_sni, try_extract_tls_from_reassembler};
2020

21+
// Well-known port numbers used for DPI protocol detection.
22+
const PORT_SSH: u16 = 22;
23+
const PORT_DNS: u16 = 53;
24+
const PORT_DHCP_SERVER: u16 = 67;
25+
const PORT_DHCP_CLIENT: u16 = 68;
26+
const PORT_NTP: u16 = 123;
27+
const PORT_NETBIOS_NS: u16 = 137;
28+
const PORT_NETBIOS_DGM: u16 = 138;
29+
const PORT_SNMP: u16 = 161;
30+
const PORT_SNMP_TRAP: u16 = 162;
31+
const PORT_HTTPS: u16 = 443;
32+
const PORT_SSDP: u16 = 1900;
33+
const PORT_MDNS: u16 = 5353;
34+
const PORT_LLMNR: u16 = 5355;
35+
2136
/// Result of DPI analysis
2237
#[derive(Debug, Clone)]
2338
pub struct DpiResult {
@@ -45,7 +60,7 @@ pub fn analyze_tcp_packet(
4560
}
4661

4762
// 2. Check for TLS/HTTPS (port 443 or TLS handshake)
48-
if (local_port == 443 || remote_port == 443 || https::is_tls_handshake(payload))
63+
if (local_port == PORT_HTTPS || remote_port == PORT_HTTPS || https::is_tls_handshake(payload))
4964
&& let Some(tls_result) = https::analyze_https(payload)
5065
{
5166
return Some(DpiResult {
@@ -54,7 +69,7 @@ pub fn analyze_tcp_packet(
5469
}
5570

5671
// 3. Check for SSH (port 22 or SSH banner)
57-
if (local_port == 22 || remote_port == 22 || ssh::is_likely_ssh(payload))
72+
if (local_port == PORT_SSH || remote_port == PORT_SSH || ssh::is_likely_ssh(payload))
5873
&& let Some(ssh_result) = ssh::analyze_ssh(payload, _is_outgoing)
5974
{
6075
return Some(DpiResult {
@@ -79,7 +94,7 @@ pub fn analyze_udp_packet(
7994
}
8095

8196
// 1. DNS (port 53)
82-
if (local_port == 53 || remote_port == 53)
97+
if (local_port == PORT_DNS || remote_port == PORT_DNS)
8398
&& let Some(dns_result) = dns::analyze_dns(payload)
8499
{
85100
return Some(DpiResult {
@@ -88,7 +103,7 @@ pub fn analyze_udp_packet(
88103
}
89104

90105
// 2. QUIC/HTTP3 (port 443)
91-
if (local_port == 443 || remote_port == 443) && quic::is_quic_packet(payload) {
106+
if (local_port == PORT_HTTPS || remote_port == PORT_HTTPS) && quic::is_quic_packet(payload) {
92107
let quic_info = quic::parse_quic_packet(payload);
93108
if let Some(quic_info) = quic_info {
94109
debug!("QUIC packet detected: {:?}", quic_info);
@@ -106,7 +121,7 @@ pub fn analyze_udp_packet(
106121
}
107122

108123
// 3. mDNS (port 5353)
109-
if (local_port == 5353 || remote_port == 5353)
124+
if (local_port == PORT_MDNS || remote_port == PORT_MDNS)
110125
&& let Some(mdns_result) = mdns::analyze_mdns(payload)
111126
{
112127
return Some(DpiResult {
@@ -117,7 +132,10 @@ pub fn analyze_udp_packet(
117132
// 4. DHCP (ports 67-68)
118133
if matches!(
119134
(local_port, remote_port),
120-
(67, _) | (68, _) | (_, 67) | (_, 68)
135+
(PORT_DHCP_SERVER, _)
136+
| (PORT_DHCP_CLIENT, _)
137+
| (_, PORT_DHCP_SERVER)
138+
| (_, PORT_DHCP_CLIENT)
121139
) && let Some(dhcp_result) = dhcp::analyze_dhcp(payload)
122140
{
123141
return Some(DpiResult {
@@ -126,7 +144,7 @@ pub fn analyze_udp_packet(
126144
}
127145

128146
// 5. NTP (port 123)
129-
if (local_port == 123 || remote_port == 123)
147+
if (local_port == PORT_NTP || remote_port == PORT_NTP)
130148
&& let Some(ntp_result) = ntp::analyze_ntp(payload)
131149
{
132150
return Some(DpiResult {
@@ -135,7 +153,7 @@ pub fn analyze_udp_packet(
135153
}
136154

137155
// 6. LLMNR (port 5355)
138-
if (local_port == 5355 || remote_port == 5355)
156+
if (local_port == PORT_LLMNR || remote_port == PORT_LLMNR)
139157
&& let Some(llmnr_result) = llmnr::analyze_llmnr(payload)
140158
{
141159
return Some(DpiResult {
@@ -144,7 +162,7 @@ pub fn analyze_udp_packet(
144162
}
145163

146164
// 7. SSDP (port 1900)
147-
if (local_port == 1900 || remote_port == 1900)
165+
if (local_port == PORT_SSDP || remote_port == PORT_SSDP)
148166
&& let Some(ssdp_result) = ssdp::analyze_ssdp(payload)
149167
{
150168
return Some(DpiResult {
@@ -153,7 +171,7 @@ pub fn analyze_udp_packet(
153171
}
154172

155173
// 8. NetBIOS-NS (port 137)
156-
if (local_port == 137 || remote_port == 137)
174+
if (local_port == PORT_NETBIOS_NS || remote_port == PORT_NETBIOS_NS)
157175
&& let Some(netbios_result) = netbios::analyze_netbios_ns(payload)
158176
{
159177
return Some(DpiResult {
@@ -162,7 +180,7 @@ pub fn analyze_udp_packet(
162180
}
163181

164182
// 9. NetBIOS-DGM (port 138)
165-
if (local_port == 138 || remote_port == 138)
183+
if (local_port == PORT_NETBIOS_DGM || remote_port == PORT_NETBIOS_DGM)
166184
&& let Some(netbios_result) = netbios::analyze_netbios_dgm(payload)
167185
{
168186
return Some(DpiResult {
@@ -173,7 +191,7 @@ pub fn analyze_udp_packet(
173191
// 10. SNMP (ports 161-162)
174192
if matches!(
175193
(local_port, remote_port),
176-
(161, _) | (162, _) | (_, 161) | (_, 162)
194+
(PORT_SNMP, _) | (PORT_SNMP_TRAP, _) | (_, PORT_SNMP) | (_, PORT_SNMP_TRAP)
177195
) && let Some(snmp_result) = snmp::analyze_snmp(payload)
178196
{
179197
return Some(DpiResult {

src/network/platform/freebsd/process.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -179,14 +179,14 @@ impl ProcessLookup for FreeBSDProcessLookup {
179179

180180
// Simple cache lookup with no refresh on cache miss.
181181
// The enrichment thread handles periodic refresh.
182-
let cache = self.cache.read().unwrap();
182+
let cache = self.cache.read().expect("cache lock poisoned");
183183
cache.lookup.get(&key).cloned()
184184
}
185185

186186
fn refresh(&self) -> Result<()> {
187187
let process_map = Self::build_process_map()?;
188188

189-
let mut cache = self.cache.write().unwrap();
189+
let mut cache = self.cache.write().expect("cache lock poisoned");
190190
cache.lookup = process_map;
191191
cache.last_refresh = Instant::now();
192192

src/network/platform/linux/enhanced.rs

Lines changed: 54 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ mod ebpf_enhanced {
128128
);
129129

130130
if let Some(result) = self.try_ebpf_lookup(conn) {
131-
let mut stats = self.stats.write().unwrap();
131+
let mut stats = self.stats.write().expect("stats lock poisoned");
132132
stats.ebpf_hits += 1;
133133
debug!(
134134
"Enhanced lookup: eBPF hit for PID {} ({})",
@@ -153,7 +153,7 @@ mod ebpf_enhanced {
153153
);
154154

155155
if let Some(result) = self.try_ebpf_icmp_lookup(conn, *id) {
156-
let mut stats = self.stats.write().unwrap();
156+
let mut stats = self.stats.write().expect("stats lock poisoned");
157157
stats.ebpf_hits += 1;
158158
debug!(
159159
"Enhanced lookup: eBPF ICMP hit for PID {} ({})",
@@ -170,7 +170,7 @@ mod ebpf_enhanced {
170170

171171
// Fall back to procfs approach
172172
if let Some(result) = self.procfs_lookup.get_process_for_connection(conn) {
173-
let mut stats = self.stats.write().unwrap();
173+
let mut stats = self.stats.write().expect("stats lock poisoned");
174174
stats.procfs_hits += 1;
175175
return Some(result);
176176
}
@@ -179,7 +179,10 @@ mod ebpf_enhanced {
179179
}
180180

181181
fn try_ebpf_lookup(&self, conn: &Connection) -> Option<(u32, String)> {
182-
let mut tracker_guard = self.ebpf_tracker.write().unwrap();
182+
let mut tracker_guard = self
183+
.ebpf_tracker
184+
.write()
185+
.expect("ebpf_tracker lock poisoned");
183186
let tracker = match tracker_guard.as_mut() {
184187
Some(t) => {
185188
debug!("eBPF lookup: Tracker available, performing lookup");
@@ -240,7 +243,10 @@ mod ebpf_enhanced {
240243
}
241244

242245
fn try_ebpf_icmp_lookup(&self, conn: &Connection, icmp_id: u16) -> Option<(u32, String)> {
243-
let mut tracker_guard = self.ebpf_tracker.write().unwrap();
246+
let mut tracker_guard = self
247+
.ebpf_tracker
248+
.write()
249+
.expect("ebpf_tracker lock poisoned");
244250
let tracker = tracker_guard.as_mut()?;
245251

246252
match tracker.lookup_icmp(conn.local_addr.ip(), conn.remote_addr.ip(), icmp_id) {
@@ -276,7 +282,7 @@ mod ebpf_enhanced {
276282
pub fn is_ebpf_available(&self) -> bool {
277283
self.ebpf_tracker
278284
.read()
279-
.unwrap()
285+
.expect("ebpf_tracker lock poisoned")
280286
.as_ref()
281287
.map(|t| t.is_healthy())
282288
.unwrap_or(false)
@@ -285,7 +291,10 @@ mod ebpf_enhanced {
285291
/// Perform periodic cleanup of stale eBPF map entries
286292
fn maybe_cleanup_ebpf_map(&self) {
287293
let now = Instant::now();
288-
let mut last_cleanup = self.last_cleanup.write().unwrap();
294+
let mut last_cleanup = self
295+
.last_cleanup
296+
.write()
297+
.expect("last_cleanup lock poisoned");
289298

290299
if now.duration_since(*last_cleanup).as_secs()
291300
>= self.cleanup_config.cleanup_interval_secs
@@ -294,7 +303,12 @@ mod ebpf_enhanced {
294303
drop(last_cleanup);
295304

296305
// Perform cleanup
297-
if let Some(tracker) = self.ebpf_tracker.write().unwrap().as_mut() {
306+
if let Some(tracker) = self
307+
.ebpf_tracker
308+
.write()
309+
.expect("ebpf_tracker lock poisoned")
310+
.as_mut()
311+
{
298312
let cleaned =
299313
tracker.cleanup_stale_entries(self.cleanup_config.stale_threshold_secs);
300314
if cleaned > 0 {
@@ -314,7 +328,7 @@ mod ebpf_enhanced {
314328

315329
// Update protocol statistics
316330
{
317-
let mut stats = self.stats.write().unwrap();
331+
let mut stats = self.stats.write().expect("stats lock poisoned");
318332
stats.total_lookups += 1;
319333

320334
// Track IP version
@@ -336,11 +350,14 @@ mod ebpf_enhanced {
336350

337351
// Try cache first
338352
{
339-
let cache = self.unified_cache.read().unwrap();
353+
let cache = self
354+
.unified_cache
355+
.read()
356+
.expect("unified_cache lock poisoned");
340357
if cache.last_refresh.elapsed() < Duration::from_secs(2)
341358
&& let Some(process_info) = cache.lookup.get(&key)
342359
{
343-
let mut stats = self.stats.write().unwrap();
360+
let mut stats = self.stats.write().expect("stats lock poisoned");
344361
stats.cache_hits += 1;
345362
return Some(process_info.clone());
346363
}
@@ -350,16 +367,19 @@ mod ebpf_enhanced {
350367
if let Some(result) = self.lookup_process_enhanced(conn) {
351368
// Update cache with the result
352369
{
353-
let mut cache = self.unified_cache.write().unwrap();
370+
let mut cache = self
371+
.unified_cache
372+
.write()
373+
.expect("unified_cache lock poisoned");
354374
cache.lookup.insert(key, result.clone());
355375

356-
let mut stats = self.stats.write().unwrap();
376+
let mut stats = self.stats.write().expect("stats lock poisoned");
357377
stats.cache_entries = cache.lookup.len() as u64;
358378
}
359379
Some(result)
360380
} else {
361381
// Track failed lookups
362-
let mut stats = self.stats.write().unwrap();
382+
let mut stats = self.stats.write().expect("stats lock poisoned");
363383
stats.failed_lookups += 1;
364384
None
365385
}
@@ -371,7 +391,10 @@ mod ebpf_enhanced {
371391

372392
// Update our cache timestamp
373393
{
374-
let mut cache = self.unified_cache.write().unwrap();
394+
let mut cache = self
395+
.unified_cache
396+
.write()
397+
.expect("unified_cache lock poisoned");
375398
cache.last_refresh = Instant::now();
376399
// Optionally clear cache to force fresh lookups
377400
cache.lookup.clear();
@@ -538,7 +561,7 @@ mod procfs_only {
538561

539562
// Update protocol statistics
540563
{
541-
let mut stats = self.stats.write().unwrap();
564+
let mut stats = self.stats.write().expect("stats lock poisoned");
542565
stats.total_lookups += 1;
543566

544567
// Track IP version
@@ -560,11 +583,14 @@ mod procfs_only {
560583

561584
// Try cache first
562585
{
563-
let cache = self.unified_cache.read().unwrap();
586+
let cache = self
587+
.unified_cache
588+
.read()
589+
.expect("unified_cache lock poisoned");
564590
if cache.last_refresh.elapsed() < Duration::from_secs(2)
565591
&& let Some(process_info) = cache.lookup.get(&key)
566592
{
567-
let mut stats = self.stats.write().unwrap();
593+
let mut stats = self.stats.write().expect("stats lock poisoned");
568594
stats.cache_hits += 1;
569595
return Some(process_info.clone());
570596
}
@@ -574,17 +600,20 @@ mod procfs_only {
574600
if let Some(result) = self.procfs_lookup.get_process_for_connection(conn) {
575601
// Update cache with the result
576602
{
577-
let mut cache = self.unified_cache.write().unwrap();
603+
let mut cache = self
604+
.unified_cache
605+
.write()
606+
.expect("unified_cache lock poisoned");
578607
cache.lookup.insert(key, result.clone());
579608

580-
let mut stats = self.stats.write().unwrap();
609+
let mut stats = self.stats.write().expect("stats lock poisoned");
581610
stats.cache_entries = cache.lookup.len() as u64;
582611
stats.procfs_hits += 1;
583612
}
584613
Some(result)
585614
} else {
586615
// Track failed lookups
587-
let mut stats = self.stats.write().unwrap();
616+
let mut stats = self.stats.write().expect("stats lock poisoned");
588617
stats.failed_lookups += 1;
589618
None
590619
}
@@ -596,7 +625,10 @@ mod procfs_only {
596625

597626
// Update our cache timestamp
598627
{
599-
let mut cache = self.unified_cache.write().unwrap();
628+
let mut cache = self
629+
.unified_cache
630+
.write()
631+
.expect("unified_cache lock poisoned");
600632
cache.last_refresh = Instant::now();
601633
// Optionally clear cache to force fresh lookups
602634
cache.lookup.clear();

0 commit comments

Comments
 (0)