@@ -21,9 +21,10 @@ use std::os::fd::AsRawFd;
2121#[ cfg( target_os = "linux" ) ]
2222use socket2:: { Domain , Protocol , Socket , Type } ;
2323
24+ use std:: net:: { Ipv4Addr , SocketAddr } ;
25+
2426#[ cfg( target_os = "linux" ) ]
25- use std:: net:: Ipv4Addr ;
26- use std:: net:: SocketAddr ;
27+ use std:: net:: Ipv6Addr ;
2728use std:: sync:: { Arc , OnceLock } ;
2829use std:: time:: Duration ;
2930use tokio:: net:: { TcpListener , TcpStream } ;
@@ -295,35 +296,44 @@ pub fn get_client() -> &'static Client<
295296}
296297
297298/// Try to bind to an available port in the given range (up to 16 attempts)
298- async fn bind_to_available_port ( start : u16 , end : u16 , bind_addr : [ u8 ; 4 ] ) -> Result < TcpListener > {
299+ async fn bind_to_available_port ( start : u16 , end : u16 , ip : std :: net :: IpAddr ) -> Result < TcpListener > {
299300 let mut rng = rand:: thread_rng ( ) ;
300301
301302 for _ in 0 ..16 {
302303 let port = rng. gen_range ( start..=end) ;
303- match bind_ipv4_listener ( bind_addr, port) . await {
304+ let addr = std:: net:: SocketAddr :: new ( ip, port) ;
305+ match bind_listener ( addr) . await {
304306 Ok ( listener) => {
305- debug ! ( "Successfully bound to port {}" , port) ;
307+ debug ! ( "Successfully bound to {}:{}" , ip , port) ;
306308 return Ok ( listener) ;
307309 }
308310 Err ( _) => continue ,
309311 }
310312 }
311313 anyhow:: bail!(
312- "No available port found after 16 attempts in range {}-{}" ,
314+ "No available port found after 16 attempts in range {}-{} on {} " ,
313315 start,
314- end
316+ end,
317+ ip
315318 )
316319}
317320
318- async fn bind_ipv4_listener ( bind_addr : [ u8 ; 4 ] , port : u16 ) -> Result < TcpListener > {
321+ async fn bind_listener ( addr : std :: net :: SocketAddr ) -> Result < TcpListener > {
319322 #[ cfg( target_os = "linux" ) ]
320323 {
321324 // Setup a raw socket to set IP_FREEBIND for specific non-loopback addresses
322- let ip = Ipv4Addr :: from ( bind_addr) ;
323- let is_specific_non_loopback =
324- ip != Ipv4Addr :: new ( 127 , 0 , 0 , 1 ) && ip != Ipv4Addr :: new ( 0 , 0 , 0 , 0 ) ;
325+ let is_specific_non_loopback = match addr. ip ( ) {
326+ std:: net:: IpAddr :: V4 ( ip) => {
327+ ip != Ipv4Addr :: new ( 127 , 0 , 0 , 1 ) && ip != Ipv4Addr :: new ( 0 , 0 , 0 , 0 )
328+ }
329+ std:: net:: IpAddr :: V6 ( ip) => ip != Ipv6Addr :: LOCALHOST && ip != Ipv6Addr :: UNSPECIFIED ,
330+ } ;
325331 if is_specific_non_loopback {
326- let sock = Socket :: new ( Domain :: IPV4 , Type :: STREAM , Some ( Protocol :: TCP ) ) ?;
332+ let domain = match addr {
333+ std:: net:: SocketAddr :: V4 ( _) => Domain :: IPV4 ,
334+ std:: net:: SocketAddr :: V6 ( _) => Domain :: IPV6 ,
335+ } ;
336+ let sock = Socket :: new ( domain, Type :: STREAM , Some ( Protocol :: TCP ) ) ?;
327337 // Enabling FREEBIND for non-local address binding before interface configuration
328338 unsafe {
329339 let yes: libc:: c_int = 1 ;
@@ -341,35 +351,31 @@ async fn bind_ipv4_listener(bind_addr: [u8; 4], port: u16) -> Result<TcpListener
341351 ) ;
342352 }
343353 }
344-
354+ sock . set_reuse_address ( true ) ? ;
345355 sock. set_nonblocking ( true ) ?;
346- let addr = SocketAddr :: from ( ( ip, port) ) ;
347356 sock. bind ( & addr. into ( ) ) ?;
348- sock. listen ( 1024 ) ?; // OS default backlog
357+ sock. listen ( 128 ) ?;
349358 let std_listener: std:: net:: TcpListener = sock. into ( ) ;
350359 std_listener. set_nonblocking ( true ) ?;
351360 return Ok ( TcpListener :: from_std ( std_listener) ?) ;
352361 }
353362 }
354- // Fallback: normal async bind if the conditions aren't met
355- let listener = TcpListener :: bind ( SocketAddr :: from ( ( bind_addr, port) ) ) . await ?;
356- Ok ( listener)
363+
364+ TcpListener :: bind ( addr) . await . map_err ( Into :: into)
357365}
358366
359367pub struct ProxyServer {
360- http_port : Option < u16 > ,
361- https_port : Option < u16 > ,
368+ http_bind : Option < std :: net :: SocketAddr > ,
369+ https_bind : Option < std :: net :: SocketAddr > ,
362370 rule_engine : Arc < RuleEngine > ,
363371 cert_manager : Arc < CertificateManager > ,
364- bind_address : [ u8 ; 4 ] ,
365372}
366373
367374impl ProxyServer {
368375 pub fn new (
369- http_port : Option < u16 > ,
370- https_port : Option < u16 > ,
376+ http_bind : Option < std :: net :: SocketAddr > ,
377+ https_bind : Option < std :: net :: SocketAddr > ,
371378 rule_engine : RuleEngine ,
372- bind_address : Option < [ u8 ; 4 ] > ,
373379 ) -> Self {
374380 let cert_manager = CertificateManager :: new ( ) . expect ( "Failed to create certificate manager" ) ;
375381
@@ -378,22 +384,20 @@ impl ProxyServer {
378384 init_client_with_ca ( ca_cert_der) ;
379385
380386 ProxyServer {
381- http_port ,
382- https_port ,
387+ http_bind ,
388+ https_bind ,
383389 rule_engine : Arc :: new ( rule_engine) ,
384390 cert_manager : Arc :: new ( cert_manager) ,
385- bind_address : bind_address. unwrap_or ( [ 127 , 0 , 0 , 1 ] ) ,
386391 }
387392 }
388393
389394 pub async fn start ( & mut self ) -> Result < ( u16 , u16 ) > {
390- let http_listener = if let Some ( port) = self . http_port {
391- bind_ipv4_listener ( self . bind_address , port) . await ?
395+ // Bind HTTP listener
396+ let http_listener = if let Some ( addr) = self . http_bind {
397+ bind_listener ( addr) . await ?
392398 } else {
393- // No port specified, find available port in 8000-8999 range
394- let listener = bind_to_available_port ( 8000 , 8999 , self . bind_address ) . await ?;
395- self . http_port = Some ( listener. local_addr ( ) ?. port ( ) ) ;
396- listener
399+ // No address specified, find available port in 8000-8999 range on localhost
400+ bind_to_available_port ( 8000 , 8999 , std:: net:: IpAddr :: V4 ( Ipv4Addr :: LOCALHOST ) ) . await ?
397401 } ;
398402
399403 let http_port = http_listener. local_addr ( ) ?. port ( ) ;
@@ -429,14 +433,12 @@ impl ProxyServer {
429433
430434 // IPv6-specific listener not required; IPv4 listener suffices for jail routing
431435
432- // Start HTTPS proxy
433- let https_listener = if let Some ( port ) = self . https_port {
434- bind_ipv4_listener ( self . bind_address , port ) . await ?
436+ // Bind HTTPS listener
437+ let https_listener = if let Some ( addr ) = self . https_bind {
438+ bind_listener ( addr ) . await ?
435439 } else {
436- // No port specified, find available port in 8000-8999 range
437- let listener = bind_to_available_port ( 8000 , 8999 , self . bind_address ) . await ?;
438- self . https_port = Some ( listener. local_addr ( ) ?. port ( ) ) ;
439- listener
440+ // No address specified, find available port in 8000-8999 range on localhost
441+ bind_to_available_port ( 8000 , 8999 , std:: net:: IpAddr :: V4 ( Ipv4Addr :: LOCALHOST ) ) . await ?
440442 } ;
441443
442444 let https_port = https_listener. local_addr ( ) ?. port ( ) ;
@@ -693,17 +695,19 @@ mod tests {
693695 let engine = V8JsRuleEngine :: new ( js. to_string ( ) ) . unwrap ( ) ;
694696 let rule_engine = RuleEngine :: from_trait ( Box :: new ( engine) , None ) ;
695697
696- let proxy = ProxyServer :: new ( Some ( 8080 ) , Some ( 8443 ) , rule_engine, None ) ;
698+ let http_bind = Some ( "127.0.0.1:8080" . parse ( ) . unwrap ( ) ) ;
699+ let https_bind = Some ( "127.0.0.1:8443" . parse ( ) . unwrap ( ) ) ;
700+ let proxy = ProxyServer :: new ( http_bind, https_bind, rule_engine) ;
697701
698- assert_eq ! ( proxy. http_port , Some ( 8080 ) ) ;
699- assert_eq ! ( proxy. https_port , Some ( 8443 ) ) ;
702+ assert_eq ! ( proxy. http_bind . map ( |s| s . port ( ) ) , Some ( 8080 ) ) ;
703+ assert_eq ! ( proxy. https_bind . map ( |s| s . port ( ) ) , Some ( 8443 ) ) ;
700704 }
701705
702706 #[ tokio:: test]
703707 async fn test_proxy_server_auto_port ( ) {
704708 let engine = V8JsRuleEngine :: new ( "true" . to_string ( ) ) . unwrap ( ) ;
705709 let rule_engine = RuleEngine :: from_trait ( Box :: new ( engine) , None ) ;
706- let mut proxy = ProxyServer :: new ( None , None , rule_engine, None ) ;
710+ let mut proxy = ProxyServer :: new ( None , None , rule_engine) ;
707711
708712 let ( http_port, https_port) = proxy. start ( ) . await . unwrap ( ) ;
709713
0 commit comments