@@ -67,15 +67,47 @@ impl PortManager {
6767 }
6868
6969 async fn list_processes_unix ( & self , protocol : & str ) -> Result < Vec < ProcessInfo > > {
70- // Try lsof first, fallback to ss, then netstat
71- if let Ok ( result) = self . try_lsof ( protocol) . await {
72- return Ok ( result) ;
70+ use std:: collections:: HashSet ;
71+
72+ // Collect results from lsof
73+ let lsof_result = self . try_lsof ( protocol) . await ;
74+
75+ // Collect results from ss
76+ let ss_result = self . try_ss ( protocol) . await ;
77+
78+ // Merge results from both sources, preferring lsof data when available
79+ // but including ports only found by ss
80+ let mut merged_results: Vec < ProcessInfo > = Vec :: new ( ) ;
81+ let mut seen_ports: HashSet < ( u16 , String ) > = HashSet :: new ( ) ;
82+
83+ // Add lsof results first (they tend to have more complete process info)
84+ if let Ok ( lsof_processes) = lsof_result {
85+ for process in lsof_processes {
86+ let key = ( process. port , process. protocol . clone ( ) ) ;
87+ if !seen_ports. contains ( & key) {
88+ seen_ports. insert ( key) ;
89+ merged_results. push ( process) ;
90+ }
91+ }
92+ }
93+
94+ // Add ss results for ports not found by lsof
95+ if let Ok ( ss_processes) = ss_result {
96+ for process in ss_processes {
97+ let key = ( process. port , process. protocol . clone ( ) ) ;
98+ if !seen_ports. contains ( & key) {
99+ seen_ports. insert ( key) ;
100+ merged_results. push ( process) ;
101+ }
102+ }
73103 }
74104
75- if let Ok ( result) = self . try_ss ( protocol) . await {
76- return Ok ( result) ;
105+ // If we found any processes, return them
106+ if !merged_results. is_empty ( ) {
107+ return Ok ( merged_results) ;
77108 }
78109
110+ // Final fallback: netstat
79111 self . try_netstat_unix ( protocol) . await
80112 }
81113
@@ -86,13 +118,14 @@ impl PortManager {
86118 protocol : & str ,
87119 ) -> Result < Option < ProcessInfo > > {
88120 // Try lsof for specific port first - much faster than scanning all ports
89- if let Ok ( result) = self . try_lsof_specific_port ( port, protocol) . await {
90- return Ok ( result) ;
121+ // Only return if we found a process, otherwise fall through to next method
122+ if let Ok ( Some ( result) ) = self . try_lsof_specific_port ( port, protocol) . await {
123+ return Ok ( Some ( result) ) ;
91124 }
92125
93126 // Fallback to ss for specific port
94- if let Ok ( result) = self . try_ss_specific_port ( port, protocol) . await {
95- return Ok ( result) ;
127+ if let Ok ( Some ( result) ) = self . try_ss_specific_port ( port, protocol) . await {
128+ return Ok ( Some ( result) ) ;
96129 }
97130
98131 // Final fallback: netstat for specific port
@@ -107,23 +140,55 @@ impl PortManager {
107140 where
108141 F : Fn ( & str ) + Send + Sync ,
109142 {
143+ use std:: collections:: HashSet ;
144+
110145 if let Some ( ref cb) = callback {
111146 cb ( "Executing port scan with lsof..." ) ;
112147 }
113148
114- // Try lsof first, fallback to ss, then netstat
115- if let Ok ( result) = self . try_lsof_with_callback ( protocol, & callback) . await {
116- return Ok ( result) ;
117- }
149+ // Collect results from lsof
150+ let lsof_result = self . try_lsof_with_callback ( protocol, & callback) . await ;
118151
119152 if let Some ( ref cb) = callback {
120153 cb ( "Trying alternative method (ss)..." ) ;
121154 }
122155
123- if let Ok ( result) = self . try_ss ( protocol) . await {
124- return Ok ( result) ;
156+ // Collect results from ss
157+ let ss_result = self . try_ss ( protocol) . await ;
158+
159+ // Merge results from both sources, preferring lsof data when available
160+ // but including ports only found by ss
161+ let mut merged_results: Vec < ProcessInfo > = Vec :: new ( ) ;
162+ let mut seen_ports: HashSet < ( u16 , String ) > = HashSet :: new ( ) ;
163+
164+ // Add lsof results first (they tend to have more complete process info)
165+ if let Ok ( lsof_processes) = lsof_result {
166+ for process in lsof_processes {
167+ let key = ( process. port , process. protocol . clone ( ) ) ;
168+ if !seen_ports. contains ( & key) {
169+ seen_ports. insert ( key) ;
170+ merged_results. push ( process) ;
171+ }
172+ }
125173 }
126174
175+ // Add ss results for ports not found by lsof
176+ if let Ok ( ss_processes) = ss_result {
177+ for process in ss_processes {
178+ let key = ( process. port , process. protocol . clone ( ) ) ;
179+ if !seen_ports. contains ( & key) {
180+ seen_ports. insert ( key) ;
181+ merged_results. push ( process) ;
182+ }
183+ }
184+ }
185+
186+ // If we found any processes, return them
187+ if !merged_results. is_empty ( ) {
188+ return Ok ( merged_results) ;
189+ }
190+
191+ // Final fallback: netstat
127192 if let Some ( ref cb) = callback {
128193 cb ( "Trying fallback method (netstat)..." ) ;
129194 }
@@ -753,7 +818,7 @@ impl PortManager {
753818 Ok ( processes)
754819 }
755820
756- async fn parse_ss_output ( & self , output : & str , _protocol : & str ) -> Result < Vec < ProcessInfo > > {
821+ async fn parse_ss_output ( & self , output : & str , protocol : & str ) -> Result < Vec < ProcessInfo > > {
757822 let mut processes = Vec :: new ( ) ;
758823
759824 for line in output. lines ( ) . skip ( 1 ) {
@@ -763,7 +828,8 @@ impl PortManager {
763828 continue ;
764829 }
765830
766- let protocol = parts[ 0 ] . to_lowercase ( ) ;
831+ // parts[0] is the state (LISTEN, ESTAB, etc.), not protocol
832+ // Use the protocol parameter from the caller instead
767833 let local_address = parts[ 3 ] ;
768834 let process_info = parts[ 5 ..] . join ( " " ) ;
769835
@@ -831,7 +897,7 @@ impl PortManager {
831897 executable_path,
832898 working_directory,
833899 port,
834- protocol,
900+ protocol : protocol . to_string ( ) ,
835901 address,
836902 inode : None , // Legacy implementation doesn't track inodes
837903 } ) ;
@@ -1521,7 +1587,7 @@ LISTEN 0 128 *:3000 *:* users:(("node",pid=1234,fd
15211587 assert_eq ! ( processes[ 0 ] . port, 3000 ) ;
15221588 assert_eq ! ( processes[ 0 ] . pid, 1234 ) ;
15231589 assert_eq ! ( processes[ 0 ] . address, "*" ) ;
1524- assert_eq ! ( processes[ 0 ] . protocol, "listen " ) ;
1590+ assert_eq ! ( processes[ 0 ] . protocol, "tcp " ) ;
15251591 }
15261592
15271593 #[ tokio:: test]
0 commit comments