Load daemon configuration from file, environment and CLI
Bind addresses and peers are now optional on the command line and merged with the config file. Per-peer timers and detect multiplier are passed through to the session. Multiple bind addresses are accepted by the model but rejected at startup until supported.
This commit is contained in:
1 parent
ea56afd2f2
commit
5e5da0c78c
1 file changed
+69
-32
+69
-32
@@ -1,5 +1,6 @@
|
||||
use clap::Parser;
|
||||
use futures::future;
|
||||
use rust_bfd::config::{Config, ConfigError, ResolvedConfig, ResolvedPeer};
|
||||
use rust_bfd::events::{EventMessageSink, ScriptHookSink, StateChangeEvent};
|
||||
use rust_bfd::{bfd::*, events::EventMessage};
|
||||
use rust_bfd::{set_ttl_or_hop_limit, BfdSessionStats};
|
||||
@@ -10,6 +11,7 @@ use std::{
|
||||
io::ErrorKind,
|
||||
mem::swap,
|
||||
net::{IpAddr, SocketAddr},
|
||||
path::PathBuf,
|
||||
sync::{atomic::Ordering, Arc},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
@@ -37,12 +39,18 @@ const ECHO_PORT: u16 = 3785;
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(version, about = "BFD (RFC 5880) daemon")]
|
||||
struct Args {
|
||||
/// Local IP address to bind to and use as the source of BFD control packets
|
||||
#[arg(short, long, value_name = "IP")]
|
||||
bind: IpAddr,
|
||||
/// YAML configuration file [default: /etc/rust-bfd/config.yaml, ignored if absent]
|
||||
#[arg(short, long, value_name = "FILE", env = "BFD_CONFIG")]
|
||||
config: Option<PathBuf>,
|
||||
|
||||
/// Peer IP address to establish a session with (repeatable; must match the bind address family)
|
||||
#[arg(short, long = "peer", value_name = "IP", required = true)]
|
||||
/// Local IP address to bind to and use as the source of BFD control packets (repeatable). Replaces any bind
|
||||
/// addresses from the config file or environment.
|
||||
#[arg(short, long, value_name = "IP")]
|
||||
bind: Vec<IpAddr>,
|
||||
|
||||
/// Peer IP address to establish a session with, using the default timers (repeatable). Added to the peers from
|
||||
/// the config file.
|
||||
#[arg(short, long = "peer", value_name = "IP")]
|
||||
peers: Vec<IpAddr>,
|
||||
}
|
||||
|
||||
@@ -293,7 +301,7 @@ struct BfdSessionHandle {
|
||||
impl BfdSessionHandle {
|
||||
async fn new(
|
||||
local_addr: IpAddr,
|
||||
remote_addr: IpAddr,
|
||||
peer: ResolvedPeer,
|
||||
local_discr: BfdDiscriminator,
|
||||
event_dispatcher: EventDispatcherHandle,
|
||||
) -> io::Result<Self> {
|
||||
@@ -307,7 +315,7 @@ impl BfdSessionHandle {
|
||||
new_self.clone(),
|
||||
control_sock,
|
||||
local_addr,
|
||||
remote_addr,
|
||||
peer,
|
||||
local_discr,
|
||||
event_dispatcher,
|
||||
);
|
||||
@@ -418,14 +426,14 @@ impl BfdSession {
|
||||
handle: BfdSessionHandle,
|
||||
control_sock: UdpSocket,
|
||||
local_addr: IpAddr,
|
||||
remote_addr: IpAddr,
|
||||
peer: ResolvedPeer,
|
||||
local_discr: BfdDiscriminator,
|
||||
event_dispatcher: EventDispatcherHandle,
|
||||
) -> Self {
|
||||
let mut rng = rand::thread_rng();
|
||||
let periodic =
|
||||
BfdPeriodicSenderHandle::new(handle.clone(), time::Duration::from_secs(1).into());
|
||||
let configured_desired_min_tx = BfdInterval::from_millis(300);
|
||||
let configured_desired_min_tx = peer.desired_min_tx;
|
||||
let slow_desired_min_tx =
|
||||
std::cmp::max(configured_desired_min_tx, BfdInterval::from_secs(1));
|
||||
|
||||
@@ -446,18 +454,18 @@ impl BfdSession {
|
||||
state: BfdSessionState {
|
||||
control_sock: Arc::new(control_sock),
|
||||
local_ip: local_addr,
|
||||
peer_addr: remote_addr,
|
||||
peer_addr: peer.address,
|
||||
session_state: Atomic::new(BfdState::default()),
|
||||
remote_session_state: Atomic::new(BfdState::default()),
|
||||
local_discr,
|
||||
remote_discr: Atomic::new(BfdDiscriminator(0)),
|
||||
local_diag: Atomic::new(BfdDiagnostic::None),
|
||||
desired_min_tx_interval: Atomic::new(slow_desired_min_tx),
|
||||
required_min_rx_interval: Atomic::new(BfdInterval::from_millis(300)),
|
||||
required_min_rx_interval: Atomic::new(peer.required_min_rx),
|
||||
remote_min_rx_interval: Atomic::new(BfdInterval::from_millis(300)),
|
||||
demand_mode: Atomic::new(false),
|
||||
remote_demand_mode: Atomic::new(false),
|
||||
detect_mult: Atomic::new(3),
|
||||
detect_mult: Atomic::new(peer.detect_mult),
|
||||
auth_type: Atomic::new(BfdAuthType::None),
|
||||
rcv_auth_seq: Atomic::new(0),
|
||||
xmit_auth_seq: Atomic::new(rng.gen()),
|
||||
@@ -975,7 +983,7 @@ impl EventDispatcherHandle {
|
||||
|
||||
struct Bfdd {
|
||||
local: SocketAddr,
|
||||
peers: Vec<SocketAddr>,
|
||||
peers: Vec<ResolvedPeer>,
|
||||
sessions_by_ip: HashMap<(IpAddr, IpAddr), BfdDiscriminator>,
|
||||
sessions_by_discr: HashMap<BfdDiscriminator, BfdSessionHandle>,
|
||||
|
||||
@@ -985,24 +993,18 @@ struct Bfdd {
|
||||
}
|
||||
|
||||
impl Bfdd {
|
||||
async fn new(args: Args) -> Result<Self, Box<dyn Error>> {
|
||||
let local = SocketAddr::new(args.bind, CONTROL_PORT);
|
||||
if let Some(peer) = args
|
||||
.peers
|
||||
.iter()
|
||||
.find(|p| p.is_ipv4() != args.bind.is_ipv4())
|
||||
{
|
||||
async fn new(config: ResolvedConfig) -> Result<Self, Box<dyn Error>> {
|
||||
let local = match config.bind.as_slice() {
|
||||
[addr] => SocketAddr::new(*addr, CONTROL_PORT),
|
||||
addrs => {
|
||||
return Err(format!(
|
||||
"peer {peer} is not the same address family as bind address {}",
|
||||
args.bind
|
||||
"{} bind addresses configured, but only one is currently supported",
|
||||
addrs.len()
|
||||
)
|
||||
.into());
|
||||
.into())
|
||||
}
|
||||
let peers = args
|
||||
.peers
|
||||
.into_iter()
|
||||
.map(|ip| SocketAddr::new(ip, CONTROL_PORT))
|
||||
.collect();
|
||||
};
|
||||
let peers = config.peers;
|
||||
let sessions_by_ip = HashMap::new();
|
||||
let sessions_by_discr = HashMap::new();
|
||||
|
||||
@@ -1016,7 +1018,7 @@ impl Bfdd {
|
||||
|
||||
let event_dispatcher = EventDispatcherHandle::new().await;
|
||||
event_dispatcher
|
||||
.add_sink(Box::new(ScriptHookSink::new()))
|
||||
.add_sink(Box::new(ScriptHookSink::with_script_dir(config.hooks_dir)))
|
||||
.await;
|
||||
|
||||
Ok(Self {
|
||||
@@ -1037,14 +1039,14 @@ impl Bfdd {
|
||||
}
|
||||
let session = BfdSessionHandle::new(
|
||||
self.local.ip(),
|
||||
peer.ip(),
|
||||
*peer,
|
||||
local_discr,
|
||||
self.event_dispatcher.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
self.sessions_by_ip
|
||||
.insert((self.local.ip(), peer.ip()), local_discr);
|
||||
.insert((self.local.ip(), peer.address), local_discr);
|
||||
self.sessions_by_discr.insert(local_discr, session);
|
||||
}
|
||||
Ok(())
|
||||
@@ -1156,6 +1158,13 @@ impl Bfdd {
|
||||
}
|
||||
}
|
||||
|
||||
fn load_config(args: Args) -> Result<ResolvedConfig, ConfigError> {
|
||||
let mut config = Config::load(args.config.as_deref())?;
|
||||
config.apply_env(|name| std::env::var(name).ok())?;
|
||||
config.apply_cli(args.bind, args.peers);
|
||||
config.resolve()
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> io::Result<()> {
|
||||
let args = Args::parse();
|
||||
@@ -1164,8 +1173,36 @@ async fn main() -> io::Result<()> {
|
||||
.format_timestamp_micros()
|
||||
.init();
|
||||
|
||||
match Bfdd::new(args).await {
|
||||
let config = load_config(args).map_err(|e| std::io::Error::other(e.to_string()))?;
|
||||
if config.peers.is_empty() {
|
||||
warn!("No peers configured");
|
||||
}
|
||||
|
||||
match Bfdd::new(config).await {
|
||||
Ok(bfdd) => bfdd.run().await,
|
||||
Err(e) => Err(std::io::Error::other(e.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn repeated_bind_and_peer_flags() {
|
||||
let args = Args::try_parse_from([
|
||||
"rust-bfdd",
|
||||
"-b",
|
||||
"192.0.2.1",
|
||||
"--bind",
|
||||
"2001:db8::1",
|
||||
"-p",
|
||||
"192.0.2.2",
|
||||
"--peer",
|
||||
"2001:db8::2",
|
||||
])
|
||||
.unwrap();
|
||||
assert_eq!(args.bind.len(), 2);
|
||||
assert_eq!(args.peers.len(), 2);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user