fix(probe): reject unsupported and undersized reflector sets
Build / build (pull_request) Failing after 4s
DevSkim / DevSkim (pull_request) Failing after 1m26s
rust-clippy analyze / Run rust-clippy analyzing (pull_request) Failing after 1m48s

This commit is contained in:
Nils Andreas Svee committed 2026-09-24 23:11:09 +07:00
1 parent eef16ed23d
commit 33a0ab1503
3 files changed
+38 -12

No files matched your search

+23
View File
@@ -191,6 +191,7 @@ fn restore_shaper<T: TrafficControlBackend>(
fn setup_reflectors(settings: &Settings) -> anyhow::Result<ReflectorSetup> { fn setup_reflectors(settings: &Settings) -> anyhow::Result<ReflectorSetup> {
let configured = settings.load_reflectors()?; let configured = settings.load_reflectors()?;
let configured_count = configured.len(); let configured_count = configured.len();
let requested_count = settings.advanced_settings.num_reflectors as usize;
let default_reflectors = [ let default_reflectors = [
IpAddr::from_str("9.9.9.9")?, IpAddr::from_str("9.9.9.9")?,
@@ -201,6 +202,17 @@ fn setup_reflectors(settings: &Settings) -> anyhow::Result<ReflectorSetup> {
IpAddr::from_str("94.140.14.14")?, IpAddr::from_str("94.140.14.14")?,
]; ];
let available_count = if configured.is_empty() {
default_reflectors.len()
} else {
configured_count
};
if available_count < requested_count {
anyhow::bail!(
"only {available_count} reflectors available, fewer than the configured {requested_count}"
);
}
let reselection_enabled = configured_count > settings.advanced_settings.num_reflectors as usize; let reselection_enabled = configured_count > settings.advanced_settings.num_reflectors as usize;
let (peers, pool) = if reselection_enabled { let (peers, pool) = if reselection_enabled {
(default_reflectors.to_vec(), configured) (default_reflectors.to_vec(), configured)
@@ -512,6 +524,17 @@ mod tests {
}; };
let setup = setup_reflectors(&settings).unwrap(); let setup = setup_reflectors(&settings).unwrap();
let configured = settings.load_reflectors().unwrap(); let configured = settings.load_reflectors().unwrap();
std::fs::write(
&path,
"reflector_ip,ip_version,description\n192.0.2.1,4,test\n192.0.2.2,4,test\n192.0.2.3,4,test\n192.0.2.4,4,test\n",
).unwrap();
assert!(setup_reflectors(&settings).is_err());
std::fs::write(
&path,
"reflector_ip,ip_version,description\n2001:db8::1,6,test\n",
)
.unwrap();
assert!(setup_reflectors(&settings).is_err());
std::fs::remove_file(path).unwrap(); std::fs::remove_file(path).unwrap();
assert!(!setup.reselection_enabled); assert!(!setup.reselection_enabled);
assert_eq!(*setup.peers.read().unwrap(), configured); assert_eq!(*setup.peers.read().unwrap(), configured);
+11 -11
View File
@@ -14,7 +14,7 @@ use icmp_socket2::socket::IcmpSocket;
use icmp_socket2::{IcmpSocket4, Icmpv4Packet}; use icmp_socket2::{IcmpSocket4, Icmpv4Packet};
use log::{debug, info, warn}; use log::{debug, info, warn};
use std::collections::HashMap; use std::collections::HashMap;
use std::net::{IpAddr, Ipv4Addr}; use std::net::IpAddr;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use std::sync::{Arc, Mutex, RwLock}; use std::sync::{Arc, Mutex, RwLock};
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
@@ -185,10 +185,15 @@ pub trait PingSender {
info!("Ping sender shutting down"); info!("Ping sender shutting down");
return Ok(()); return Ok(());
} }
// Clone the reflectors vec and drop the read lock immediately — // Release the read lock before sending so reselection can update peers.
// holding it for the entire tick would starve the reflector selector's write lock.
let reflectors_unlocked = reflectors_lock.read_anyhow()?; let reflectors_unlocked = reflectors_lock.read_anyhow()?;
let reflectors = reflectors_unlocked.clone(); let reflectors: Vec<_> = reflectors_unlocked
.iter()
.filter_map(|reflector| match reflector {
IpAddr::V4(addr) => Some(*addr),
IpAddr::V6(_) => None,
})
.collect();
drop(reflectors_unlocked); drop(reflectors_unlocked);
if reflectors.is_empty() { if reflectors.is_empty() {
@@ -202,15 +207,10 @@ pub trait PingSender {
let sleep_duration = Duration::from_secs_f64(tick_interval / reflectors.len() as f64); let sleep_duration = Duration::from_secs_f64(tick_interval / reflectors.len() as f64);
for reflector in reflectors.iter() { for &addr in &reflectors {
let addr: Ipv4Addr = match reflector {
IpAddr::V4(ipv4) => *ipv4,
IpAddr::V6(_) => continue,
};
let (packet, originate_timestamp) = self.craft_packet(id, seq); let (packet, originate_timestamp) = self.craft_packet(id, seq);
let sent_at = Instant::now(); let sent_at = Instant::now();
let key = (*reflector, type_, seq); let key = (IpAddr::V4(addr), type_, seq);
inflight.lock_anyhow()?.insert( inflight.lock_anyhow()?.insert(
key, key,
InFlightProbe { InFlightProbe {
+4 -1
View File
@@ -192,7 +192,10 @@ impl Settings {
})?; })?;
match (record.ip_version, record.reflector_ip) { match (record.ip_version, record.reflector_ip) {
(4, IpAddr::V4(_)) | (6, IpAddr::V6(_)) => {} (4, IpAddr::V4(_)) => {}
(6, address @ IpAddr::V6(_)) => {
bail!("reflector {address} is IPv6; only IPv4 reflectors are supported");
}
(version, address) => { (version, address) => {
bail!("reflector {address} has inconsistent IP version {version}"); bail!("reflector {address} has inconsistent IP version {version}");
} }