mirror of
https://github.com/rustfs/rustfs.git
synced 2026-07-26 16:28:15 +00:00
fix: reject overlapping console listener address
Fail startup when the configured console listener resolves to the same listener port covered by the S3 address. This keeps SO_REUSEPORT from hiding an intra-process S3/console endpoint collision. Co-Authored-By: heihutu <heihutu@gmail.com>
This commit is contained in:
@@ -93,6 +93,7 @@ pub(crate) async fn init_startup_listen_context(
|
||||
}
|
||||
|
||||
let server_addr = parse_and_resolve_address(config.address.as_str()).map_err(Error::other)?;
|
||||
validate_console_listener_address(config, server_addr)?;
|
||||
let server_port = server_addr.port();
|
||||
let server_address = server_addr.to_string();
|
||||
|
||||
@@ -343,13 +344,43 @@ fn console_http_server_config(config: &Config) -> Option<Config> {
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_console_listener_address(config: &Config, s3_addr: SocketAddr) -> Result<()> {
|
||||
if !config.console_enable || config.console_address.trim().is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let console_addr = parse_and_resolve_address(config.console_address.as_str())
|
||||
.map_err(|err| Error::other(format!("console address '{}': {err}", config.console_address)))?;
|
||||
|
||||
if listener_addresses_overlap(s3_addr, console_addr) {
|
||||
return Err(Error::new(
|
||||
ErrorKind::AddrInUse,
|
||||
format!(
|
||||
"console address '{}' resolves to {console_addr} and conflicts with S3 address '{}' ({s3_addr}); \
|
||||
set RUSTFS_CONSOLE_ADDRESS to a different port or disable the console with RUSTFS_CONSOLE_ENABLE=false",
|
||||
config.console_address, config.address
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn listener_addresses_overlap(left: SocketAddr, right: SocketAddr) -> bool {
|
||||
left.port() == right.port() && (left.ip() == right.ip() || left.ip().is_unspecified() || right.ip().is_unspecified())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{
|
||||
DEFAULT_CREDENTIALS_WARNING_MESSAGE, console_http_server_config, find_embedded_available_port,
|
||||
prepare_embedded_startup_config, s3_http_server_config,
|
||||
init_startup_listen_context, listener_addresses_overlap, prepare_embedded_startup_config, s3_http_server_config,
|
||||
validate_console_listener_address,
|
||||
};
|
||||
use crate::config::Config;
|
||||
use crate::storage_api::startup::runtime_sources::InstanceContext;
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[test]
|
||||
fn s3_http_server_config_disables_console_without_changing_address() {
|
||||
@@ -391,6 +422,85 @@ mod tests {
|
||||
assert!(console_http_server_config(&config).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_console_listener_address_rejects_same_endpoint() {
|
||||
let mut config = Config::new("127.0.0.1:9001", vec!["/tmp/rustfs-data".to_string()]);
|
||||
config.console_enable = true;
|
||||
config.console_address = "127.0.0.1:9001".to_string();
|
||||
|
||||
let err = validate_console_listener_address(&config, "127.0.0.1:9001".parse().expect("valid socket address"))
|
||||
.expect_err("console must not bind the S3 endpoint");
|
||||
|
||||
assert_eq!(err.kind(), std::io::ErrorKind::AddrInUse);
|
||||
let message = err.to_string();
|
||||
assert!(message.contains("RUSTFS_CONSOLE_ADDRESS"));
|
||||
assert!(message.contains("127.0.0.1:9001"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_console_listener_address_rejects_wildcard_same_port() {
|
||||
let mut config = Config::new("127.0.0.1:9001", vec!["/tmp/rustfs-data".to_string()]);
|
||||
config.console_enable = true;
|
||||
config.console_address = ":9001".to_string();
|
||||
|
||||
let err = validate_console_listener_address(&config, "127.0.0.1:9001".parse().expect("valid socket address"))
|
||||
.expect_err("wildcard console listener covers the S3 endpoint port");
|
||||
|
||||
assert_eq!(err.kind(), std::io::ErrorKind::AddrInUse);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_console_listener_address_allows_different_ports() {
|
||||
let mut config = Config::new("127.0.0.1:9000", vec!["/tmp/rustfs-data".to_string()]);
|
||||
config.console_enable = true;
|
||||
config.console_address = ":9001".to_string();
|
||||
|
||||
validate_console_listener_address(&config, "127.0.0.1:9000".parse().expect("valid socket address"))
|
||||
.expect("different listener ports should be allowed");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_console_listener_address_skips_disabled_or_empty_console() {
|
||||
let mut config = Config::new("127.0.0.1:9001", vec!["/tmp/rustfs-data".to_string()]);
|
||||
config.console_enable = false;
|
||||
config.console_address = "127.0.0.1:9001".to_string();
|
||||
validate_console_listener_address(&config, "127.0.0.1:9001".parse().expect("valid socket address"))
|
||||
.expect("disabled console should not reserve a listener");
|
||||
|
||||
config.console_enable = true;
|
||||
config.console_address.clear();
|
||||
validate_console_listener_address(&config, "127.0.0.1:9001".parse().expect("valid socket address"))
|
||||
.expect("empty console address should skip console listener");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn listener_addresses_overlap_treats_unspecified_as_covering_the_port() {
|
||||
assert!(listener_addresses_overlap(
|
||||
SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), 9001),
|
||||
SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 9001),
|
||||
));
|
||||
assert!(!listener_addresses_overlap(
|
||||
SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), 9001),
|
||||
SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 9002),
|
||||
));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn init_startup_listen_context_rejects_console_s3_port_conflict() {
|
||||
let mut config = Config::new("127.0.0.1:9001", vec!["/tmp/rustfs-data".to_string()]);
|
||||
config.console_enable = true;
|
||||
config.console_address = ":9001".to_string();
|
||||
let instance_ctx = Arc::new(InstanceContext::new());
|
||||
|
||||
let err = match init_startup_listen_context(&config, &instance_ctx).await {
|
||||
Ok(_) => panic!("startup should reject overlapping S3 and console listeners before binding sockets"),
|
||||
Err(err) => err,
|
||||
};
|
||||
|
||||
assert_eq!(err.kind(), std::io::ErrorKind::AddrInUse);
|
||||
assert!(err.to_string().contains("RUSTFS_CONSOLE_ADDRESS"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_using_default_credentials_returns_true_for_default_keys() {
|
||||
let mut config = Config::new("127.0.0.1:9000", Vec::new());
|
||||
|
||||
Reference in New Issue
Block a user