aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorFlorian Klink <flokli@flokli.de>2024-04-23 11:57:43 +0300
committerFlorian Klink <flokli@flokli.de>2024-06-05 08:41:36 +0200
commita0f6bc5b7faa0d557179a7c4ed4c8d0facb9afa4 (patch)
treea16e5b4d951c793bbd7f6ef62a66d15e7c4605c6 /src
parenta2c1de646bce4a96cf8dc526f82bd88bcf3dde70 (diff)
downloadgarage-a0f6bc5b7faa0d557179a7c4ed4c8d0facb9afa4.tar.gz
garage-a0f6bc5b7faa0d557179a7c4ed4c8d0facb9afa4.zip
add rpc_public_addr_subnet config option
In case `rpc_public_addr` is not set, but autodiscovery is used, this allows filtering the list of automatically discovered IPs to a specific subnet. For example, if nodes should pick *their* IP inside a specific subnet, but you don't want to explicitly write the IP down (as it's dynamic, or you want to share configs across nodes), you can use this option.
Diffstat (limited to 'src')
-rw-r--r--src/rpc/Cargo.toml1
-rw-r--r--src/rpc/system.rs41
-rw-r--r--src/util/config.rs4
3 files changed, 40 insertions, 6 deletions
diff --git a/src/rpc/Cargo.toml b/src/rpc/Cargo.toml
index 43d5568e..4c8cafd9 100644
--- a/src/rpc/Cargo.toml
+++ b/src/rpc/Cargo.toml
@@ -24,6 +24,7 @@ bytes.workspace = true
bytesize.workspace = true
gethostname.workspace = true
hex.workspace = true
+ipnet.workspace = true
tracing.workspace = true
rand.workspace = true
itertools.workspace = true
diff --git a/src/rpc/system.rs b/src/rpc/system.rs
index 0e78060b..d94d4eec 100644
--- a/src/rpc/system.rs
+++ b/src/rpc/system.rs
@@ -844,12 +844,20 @@ impl NodeStatus {
}
}
-fn get_default_ip() -> Option<IpAddr> {
+/// Obtain the list of currently available IP addresses on all non-loopback
+/// interfaces, optionally filtering them to be inside a given IpNet.
+fn get_default_ip(filter_ipnet: Option<ipnet::IpNet>) -> Option<IpAddr> {
pnet_datalink::interfaces()
- .iter()
- .find(|e| e.is_up() && !e.is_loopback() && !e.ips.is_empty())
- .and_then(|e| e.ips.first())
- .map(|a| a.ip())
+ .into_iter()
+ // filter down and loopback interfaces
+ .filter(|i| i.is_up() && !i.is_loopback())
+ // get all IPs
+ .flat_map(|e| e.ips)
+ // optionally, filter to be inside filter_ipnet
+ .find(|ipn| {
+ filter_ipnet.is_some_and(|ipnet| ipnet.contains(&ipn.ip())) || filter_ipnet.is_none()
+ })
+ .map(|ipn| ipn.ip())
}
fn get_rpc_public_addr(config: &Config) -> Option<SocketAddr> {
@@ -877,7 +885,28 @@ fn get_rpc_public_addr(config: &Config) -> Option<SocketAddr> {
}
}
None => {
- let addr = get_default_ip().map(|ip| SocketAddr::new(ip, config.rpc_bind_addr.port()));
+ // `No rpc_public_addr` specified, try to discover one, optionally filtering by `rpc_public_addr_subnet`.
+ let filter_subnet: Option<ipnet::IpNet> = config
+ .rpc_public_addr_subnet
+ .as_ref()
+ .and_then(|filter_subnet_str| match filter_subnet_str.parse::<ipnet::IpNet>() {
+ Ok(filter_subnet) => {
+ let filter_subnet_trunc = filter_subnet.trunc();
+ if filter_subnet_trunc != filter_subnet {
+ warn!("`rpc_public_addr_subnet` changed after applying netmask, continuing with {}", filter_subnet.trunc());
+ }
+ Some(filter_subnet_trunc)
+ }
+ Err(e) => {
+ panic!(
+ "Cannot parse rpc_public_addr_subnet {} from config file: {}. Bailing out.",
+ filter_subnet_str, e
+ );
+ }
+ });
+
+ let addr = get_default_ip(filter_subnet)
+ .map(|ip| SocketAddr::new(ip, config.rpc_bind_addr.port()));
if let Some(a) = addr {
warn!("Using autodetected rpc_public_addr: {}. Consider specifying it explicitly in configuration file if possible.", a);
}
diff --git a/src/util/config.rs b/src/util/config.rs
index 028f8c68..59329c0b 100644
--- a/src/util/config.rs
+++ b/src/util/config.rs
@@ -85,6 +85,10 @@ pub struct Config {
/// Public IP address of this node
pub rpc_public_addr: Option<String>,
+ /// In case `rpc_public_addr` was not set, this can filter
+ /// the addresses announced to other peers to a specific subnet.
+ pub rpc_public_addr_subnet: Option<String>,
+
/// Timeout for Netapp's ping messagess
pub rpc_ping_timeout_msec: Option<u64>,
/// Timeout for Netapp RPC calls