feat(settings): parse reflector lists with CSV

Add CSV-based reflector list parsing with Serde reflector records.
The load_reflectors method reads a CSV file with reflector_ip,
ip_version, and description columns, validating IP version
consistency for each entry.
This commit is contained in:
Nils Andreas Svee committed 2026-08-07 16:04:59 +07:00
1 parent a653bcf279
commit f51d90bff6
3 files changed
+79

No files matched your search

Generated
+34
View File
@@ -106,6 +106,27 @@ dependencies = [
"winnow", "winnow",
] ]
[[package]]
name = "csv"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52cd9d68cf7efc6ddfaaee42e7288d3a99d613d4b50f76ce9827ae0c6e14f938"
dependencies = [
"csv-core",
"itoa",
"ryu",
"serde_core",
]
[[package]]
name = "csv-core"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "704a3c26996a80471189265814dbc2c257598b96b8a7feae2d31ace646bb9782"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "either" name = "either"
version = "1.16.0" version = "1.16.0"
@@ -184,6 +205,12 @@ dependencies = [
"either", "either",
] ]
[[package]]
name = "itoa"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]] [[package]]
name = "js-sys" name = "js-sys"
version = "0.3.103" version = "0.3.103"
@@ -402,6 +429,12 @@ version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
[[package]]
name = "ryu"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
[[package]] [[package]]
name = "scopeguard" name = "scopeguard"
version = "1.2.0" version = "1.2.0"
@@ -484,6 +517,7 @@ version = "0.4.1"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"config", "config",
"csv",
"fastrand", "fastrand",
"flume", "flume",
"icmp-socket2", "icmp-socket2",
+1
View File
@@ -27,6 +27,7 @@ thiserror = "2.0"
libc = "0.2" libc = "0.2"
flume = { version = "0.12.0", default-features = false, features = ["eventual-fairness", "select"] } flume = { version = "0.12.0", default-features = false, features = ["eventual-fairness", "select"] }
config = { version = "0.15.25", default-features = false } config = { version = "0.15.25", default-features = false }
csv = "1.4.0"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
[dependencies.log] [dependencies.log]
+44
View File
@@ -7,6 +7,7 @@ use config::{Config, ConfigError, Environment};
use log::Level; use log::Level;
use serde::{Deserialize, Deserializer, de}; use serde::{Deserialize, Deserializer, de};
use std::fmt::{self, Display}; use std::fmt::{self, Display};
use std::net::IpAddr;
use std::str::FromStr; use std::str::FromStr;
#[cfg(all(feature = "uci", unix))] #[cfg(all(feature = "uci", unix))]
@@ -61,6 +62,14 @@ impl FromStr for ObservabilityProtocol {
} }
} }
#[derive(Debug, Deserialize)]
struct ReflectorRecord {
reflector_ip: IpAddr,
ip_version: u8,
#[allow(dead_code)]
description: String,
}
#[derive(Clone, Debug, Deserialize)] #[derive(Clone, Debug, Deserialize)]
pub(crate) struct Settings { pub(crate) struct Settings {
pub(crate) network: NetworkSettings, pub(crate) network: NetworkSettings,
@@ -92,6 +101,41 @@ impl Settings {
built_config.try_deserialize() built_config.try_deserialize()
} }
pub fn load_reflectors(&self) -> anyhow::Result<Vec<IpAddr>> {
let mut reader = csv::ReaderBuilder::new()
.has_headers(true)
.trim(csv::Trim::All)
.from_path(&self.advanced_settings.reflector_list_file)
.with_context(|| {
format!(
"failed to open reflector list: {}",
self.advanced_settings.reflector_list_file
)
})?;
let mut reflectors = Vec::with_capacity(50);
for record in reader.deserialize::<ReflectorRecord>() {
let record = record.with_context(|| {
format!(
"failed to parse reflector list: {}",
self.advanced_settings.reflector_list_file
)
})?;
match (record.ip_version, record.reflector_ip) {
(4, IpAddr::V4(_)) | (6, IpAddr::V6(_)) => {}
(version, address) => {
bail!("reflector {address} has inconsistent IP version {version}");
}
}
reflectors.push(record.reflector_ip);
}
Ok(reflectors)
}
} }
#[derive(Clone, Debug, Deserialize)] #[derive(Clone, Debug, Deserialize)]