From 7baa9c357fc639b7caa8e73761e2d42892efa750 Mon Sep 17 00:00:00 2001 From: "user.email" Date: Sat, 24 Oct 2026 22:24:29 -0700 Subject: [PATCH] fix(rust-volume-server): advertise frontend address through the supervised backend Rewriting the backend's -ip and ports made its heartbeat register the private loopback backend instead of the listener clients should use. When the frontend ports can also be bound on a non-conflicting address (loopback alias for loopback frontends, 127.0.0.1 for remote ones), run the backend there with the frontend identity so master advertises the proxy; otherwise keep the old loopback-plus-random-ports fallback. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- rust/volume_server/src/main.rs | 83 +++++++++++++++++++++++++++++----- 1 file changed, 71 insertions(+), 12 deletions(-) diff --git a/rust/volume_server/src/main.rs b/rust/volume_server/src/main.rs index 0f0483b7a4..3540ea18f1 100644 --- a/rust/volume_server/src/main.rs +++ b/rust/volume_server/src/main.rs @@ -35,6 +35,7 @@ extern "C" fn handle_termination_signal(_sig: i32) { #[derive(Clone)] struct FrontendPorts { bind_ip: String, + advertised_ip: Option, http_port: u16, grpc_port: u16, public_port: u16, @@ -46,6 +47,7 @@ struct BackendPorts { http_port: u16, grpc_port: u16, public_port: u16, + advertised_ip: Option, } #[derive(Clone)] @@ -209,11 +211,14 @@ fn run_supervised_mode( let weed_binary = resolve_weed_binary()?; let frontend = parse_frontend_ports(forwarded)?; - let backend_bind_ip = "127.0.0.1"; - let backend = - allocate_backend_ports(backend_bind_ip, frontend.public_port == frontend.http_port)?; + let backend = pick_backend_layout(&frontend); + let advertised_ip = backend + .advertised_ip + .clone() + .unwrap_or_else(|| backend.bind_ip.clone()); - let mut backend_args = replace_flag_value(forwarded, "-ip", &backend.bind_ip); + let mut backend_args = replace_flag_value(forwarded, "-ip", &advertised_ip); + backend_args = replace_flag_value(&backend_args, "-ip.bind", &backend.bind_ip); backend_args = replace_flag_value(&backend_args, "-port", &backend.http_port.to_string()); backend_args = replace_flag_value(&backend_args, "-port.grpc", &backend.grpc_port.to_string()); backend_args = replace_flag_value( @@ -1092,7 +1097,11 @@ fn native_ui_index_html() -> &'static str { } fn parse_frontend_ports(args: &[String]) -> Result { - let bind_ip = extract_flag(args, "-ip").unwrap_or_else(|| "127.0.0.1".to_string()); + let advertised_ip = extract_flag(args, "-ip"); + let bind_ip = advertised_ip + .clone() + .map(|ip| extract_flag(args, "-ip.bind").unwrap_or(ip)) + .unwrap_or_else(|| "127.0.0.1".to_string()); let http_port = parse_port( &extract_flag(args, "-port").ok_or_else(|| "missing -port value".to_string())?, "-port", @@ -1108,26 +1117,76 @@ fn parse_frontend_ports(args: &[String]) -> Result { Ok(FrontendPorts { bind_ip, + advertised_ip, http_port, grpc_port, public_port, }) } -fn allocate_backend_ports(bind_ip: &str, share_public_http: bool) -> Result { - let http_port = allocate_free_port(bind_ip)?; - let grpc_port = allocate_free_port(bind_ip)?; +fn pick_backend_layout(frontend: &FrontendPorts) -> BackendPorts { + let advertised_ip = frontend + .advertised_ip + .clone() + .unwrap_or_else(|| frontend.bind_ip.clone()); + for bind_ip in candidate_backend_bind_ips(&frontend.bind_ip) { + if can_bind_all(&bind_ip, &backend_listen_ports(frontend)) { + return BackendPorts { + bind_ip, + http_port: frontend.http_port, + grpc_port: frontend.grpc_port, + public_port: frontend.public_port, + advertised_ip: Some(advertised_ip.clone()), + }; + } + } + + let bind_ip = "127.0.0.1".to_string(); + let share_public_http = frontend.public_port == frontend.http_port; + let http_port = allocate_free_port(&bind_ip).unwrap_or(0); + let grpc_port = allocate_free_port(&bind_ip).unwrap_or(0); let public_port = if share_public_http { http_port } else { - allocate_free_port(bind_ip)? + allocate_free_port(&bind_ip).unwrap_or(0) }; - Ok(BackendPorts { - bind_ip: bind_ip.to_string(), + BackendPorts { + bind_ip, http_port, grpc_port, public_port, - }) + advertised_ip: None, + } +} + +fn backend_listen_ports(frontend: &FrontendPorts) -> Vec { + let mut ports = vec![frontend.http_port, frontend.grpc_port]; + if frontend.public_port != frontend.http_port { + ports.push(frontend.public_port); + } + ports +} + +fn candidate_backend_bind_ips(frontend_bind_ip: &str) -> Vec { + match frontend_bind_ip.parse::() { + Ok(addr) if addr.is_loopback() => (2..=8) + .map(|n| format!("127.0.0.{}", n)) + .collect(), + Ok(addr) if addr.is_unspecified() => Vec::new(), + Ok(_) => vec!["127.0.0.1".to_string()], + Err(_) => vec!["127.0.0.1".to_string()], + } +} + +fn can_bind_all(ip: &str, ports: &[u16]) -> bool { + let mut listeners = Vec::with_capacity(ports.len()); + for port in ports { + match TcpListener::bind((ip, *port)) { + Ok(l) => listeners.push(l), + Err(_) => return false, + } + } + true } fn allocate_free_port(bind_ip: &str) -> Result {