From 6b8aa7270ecb18c54836f53bc6612d88cedee9be Mon Sep 17 00:00:00 2001 From: Alexey <247128645+axkurcom@users.noreply.github.com> Date: Sat, 28 Feb 2026 01:54:29 +0300 Subject: [PATCH] Bind_addresses prio over interfaces --- src/transport/upstream.rs | 79 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 78 insertions(+), 1 deletion(-) diff --git a/src/transport/upstream.rs b/src/transport/upstream.rs index eff05b8..edcf476 100644 --- a/src/transport/upstream.rs +++ b/src/transport/upstream.rs @@ -185,21 +185,82 @@ impl UpstreamManager { } } + #[cfg(unix)] + fn resolve_interface_addrs(name: &str, want_ipv6: bool) -> Vec { + use nix::ifaddrs::getifaddrs; + + let mut out = Vec::new(); + if let Ok(addrs) = getifaddrs() { + for iface in addrs { + if iface.interface_name != name { + continue; + } + if let Some(address) = iface.address { + if let Some(v4) = address.as_sockaddr_in() { + if !want_ipv6 { + out.push(IpAddr::V4(v4.ip())); + } + } else if let Some(v6) = address.as_sockaddr_in6() + && want_ipv6 + { + out.push(IpAddr::V6(v6.ip())); + } + } + } + } + out.sort_unstable(); + out.dedup(); + out + } + fn resolve_bind_address( interface: &Option, bind_addresses: &Option>, target: SocketAddr, rr: Option<&AtomicUsize>, + validate_ip_on_interface: bool, ) -> Option { let want_ipv6 = target.is_ipv6(); if let Some(addrs) = bind_addresses { - let candidates: Vec = addrs + let mut candidates: Vec = addrs .iter() .filter_map(|s| s.parse::().ok()) .filter(|ip| ip.is_ipv6() == want_ipv6) .collect(); + // Explicit bind IP has strict priority over interface auto-selection. + if validate_ip_on_interface + && let Some(iface) = interface + && iface.parse::().is_err() + { + #[cfg(unix)] + { + let iface_addrs = Self::resolve_interface_addrs(iface, want_ipv6); + if !iface_addrs.is_empty() { + candidates.retain(|ip| { + let ok = iface_addrs.contains(ip); + if !ok { + warn!( + interface = %iface, + bind_ip = %ip, + target = %target, + "Configured bind address is not assigned to interface" + ); + } + ok + }); + } else if !candidates.is_empty() { + warn!( + interface = %iface, + target = %target, + "Configured interface has no addresses for target family; falling back to direct connect without bind" + ); + candidates.clear(); + } + } + } + if !candidates.is_empty() { if let Some(counter) = rr { let idx = counter.fetch_add(1, Ordering::Relaxed) % candidates.len(); @@ -207,6 +268,19 @@ impl UpstreamManager { } return candidates.first().copied(); } + + if validate_ip_on_interface + && interface + .as_ref() + .is_some_and(|iface| iface.parse::().is_err()) + { + warn!( + interface = interface.as_deref().unwrap_or(""), + target = %target, + "No valid bind_addresses left for interface; falling back to direct connect without bind" + ); + return None; + } } if let Some(iface) = interface { @@ -407,6 +481,7 @@ impl UpstreamManager { bind_addresses, target, bind_rr.as_deref(), + true, ); let socket = create_outgoing_socket_bound(target, bind_ip)?; @@ -461,6 +536,7 @@ impl UpstreamManager { &None, proxy_addr, bind_rr.as_deref(), + false, ); let socket = create_outgoing_socket_bound(proxy_addr, bind_ip)?; @@ -537,6 +613,7 @@ impl UpstreamManager { &None, proxy_addr, bind_rr.as_deref(), + false, ); let socket = create_outgoing_socket_bound(proxy_addr, bind_ip)?;