photoncloud-monorepo/fiberlb/crates/fiberlb-server/src/config.rs
centra ce979d8f26
Some checks failed
Nix CI / filter (push) Successful in 6s
Nix CI / gate () (push) Failing after 1s
Nix CI / gate (shared crates) (push) Has been skipped
Nix CI / build () (push) Has been skipped
Nix CI / ci-status (push) Failing after 1s
fiberlb: add BGP interop, drain, and policy validation
2026-03-30 20:06:08 +09:00

398 lines
10 KiB
Rust

//! Server configuration
use serde::{Deserialize, Serialize};
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
/// TLS configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TlsConfig {
/// Path to certificate file (PEM)
pub cert_file: String,
/// Path to private key file (PEM)
pub key_file: String,
/// Path to CA certificate for client verification (optional, for mTLS)
pub ca_file: Option<String>,
/// Require client certificates (mTLS)
#[serde(default)]
pub require_client_cert: bool,
}
/// Metadata storage backend
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum MetadataBackend {
/// FlareDB distributed metadata database
FlareDb,
/// PostgreSQL metadata database
Postgres,
/// SQLite metadata database (single-node only)
Sqlite,
}
impl Default for MetadataBackend {
fn default() -> Self {
Self::FlareDb
}
}
/// Server configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerConfig {
/// gRPC management API address
pub grpc_addr: SocketAddr,
/// ChainFire endpoint used for cluster coordination only
pub chainfire_endpoint: Option<String>,
/// FlareDB endpoint used for metadata and tenant data storage
pub flaredb_endpoint: Option<String>,
/// Metadata backend selection (flaredb, postgres, sqlite)
#[serde(default)]
pub metadata_backend: MetadataBackend,
/// SQL database URL for metadata when backend is postgres or sqlite
pub metadata_database_url: Option<String>,
/// Allow single-node mode (required for SQLite)
#[serde(default)]
pub single_node: bool,
/// Log level
pub log_level: String,
/// TLS configuration (optional)
pub tls: Option<TlsConfig>,
/// Authentication configuration
#[serde(default)]
pub auth: AuthConfig,
/// Backend health checker configuration
#[serde(default)]
pub health: HealthRuntimeConfig,
/// VIP advertisement reconciliation configuration
#[serde(default)]
pub vip_advertisement: VipAdvertisementConfig,
/// Local VIP ownership configuration.
#[serde(default)]
pub vip_ownership: VipOwnershipConfig,
/// Native BGP speaker configuration
#[serde(default)]
pub bgp: BgpConfig,
}
/// Authentication configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuthConfig {
/// IAM server endpoint
#[serde(default = "default_iam_server_addr")]
pub iam_server_addr: String,
}
fn default_iam_server_addr() -> String {
"127.0.0.1:50051".to_string()
}
/// Backend health checker runtime configuration.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HealthRuntimeConfig {
/// Interval between backend health check sweeps.
#[serde(default = "default_health_check_interval_secs")]
pub interval_secs: u64,
/// Timeout for individual backend checks.
#[serde(default = "default_health_check_timeout_secs")]
pub timeout_secs: u64,
}
fn default_health_check_interval_secs() -> u64 {
5
}
fn default_health_check_timeout_secs() -> u64 {
5
}
impl Default for HealthRuntimeConfig {
fn default() -> Self {
Self {
interval_secs: default_health_check_interval_secs(),
timeout_secs: default_health_check_timeout_secs(),
}
}
}
/// VIP advertisement reconciliation runtime configuration.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VipAdvertisementConfig {
/// Interval between BGP advertisement reconciliation sweeps.
#[serde(default = "default_vip_check_interval_secs")]
pub interval_secs: u64,
/// Presence of this file puts the node into control-plane drain mode.
#[serde(default = "default_vip_drain_file")]
pub drain_file: String,
/// Time to keep a locally owned VIP after withdrawing it for drain.
#[serde(default = "default_vip_drain_hold_time_secs")]
pub drain_hold_time_secs: u64,
}
fn default_vip_check_interval_secs() -> u64 {
3
}
fn default_vip_drain_file() -> String {
"/var/lib/fiberlb/drain".to_string()
}
fn default_vip_drain_hold_time_secs() -> u64 {
5
}
impl Default for VipAdvertisementConfig {
fn default() -> Self {
Self {
interval_secs: default_vip_check_interval_secs(),
drain_file: default_vip_drain_file(),
drain_hold_time_secs: default_vip_drain_hold_time_secs(),
}
}
}
/// Local VIP ownership runtime configuration.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VipOwnershipConfig {
/// Whether FiberLB should claim VIP /32 addresses on the local node.
#[serde(default)]
pub enabled: bool,
/// Interface used for local VIP ownership.
#[serde(default = "default_vip_ownership_interface")]
pub interface: String,
}
fn default_vip_ownership_interface() -> String {
"lo".to_string()
}
impl Default for VipOwnershipConfig {
fn default() -> Self {
Self {
enabled: false,
interface: default_vip_ownership_interface(),
}
}
}
/// Static BGP peer configuration.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct BgpPeerConfig {
/// Peer IP address or hostname.
pub address: String,
/// Peer TCP port.
#[serde(default = "default_bgp_peer_port")]
pub port: u16,
/// Peer AS number.
pub asn: u32,
/// Optional operator-visible description.
#[serde(default)]
pub description: String,
/// Optional export policy applied to announcements sent to this peer.
#[serde(default)]
pub export_policy: BgpExportPolicyConfig,
/// Optional single-hop BFD session parameters for this peer.
#[serde(default)]
pub bfd: BfdConfig,
}
fn default_bgp_peer_port() -> u16 {
179
}
/// Peer-scoped BGP export policy.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
pub struct BgpExportPolicyConfig {
/// Optional MED attached to announced VIP routes.
#[serde(default)]
pub med: Option<u32>,
/// Optional standard communities attached to announced VIP routes.
#[serde(default)]
pub communities: Vec<String>,
}
/// Single-hop BFD configuration for a BGP peer.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct BfdConfig {
/// Whether BFD should gate route advertisement for this peer.
#[serde(default)]
pub enabled: bool,
/// Desired transmit interval in milliseconds.
#[serde(default = "default_bfd_desired_min_tx_millis")]
pub desired_min_tx_millis: u64,
/// Required receive interval in milliseconds.
#[serde(default = "default_bfd_required_min_rx_millis")]
pub required_min_rx_millis: u64,
/// Detection multiplier.
#[serde(default = "default_bfd_detect_multiplier")]
pub detect_multiplier: u8,
/// Maximum time to wait for the session to reach Up after BGP establishment.
#[serde(default = "default_bfd_bootstrap_timeout_secs")]
pub bootstrap_timeout_secs: u64,
}
fn default_bfd_desired_min_tx_millis() -> u64 {
300
}
fn default_bfd_required_min_rx_millis() -> u64 {
300
}
fn default_bfd_detect_multiplier() -> u8 {
3
}
fn default_bfd_bootstrap_timeout_secs() -> u64 {
10
}
impl Default for BfdConfig {
fn default() -> Self {
Self {
enabled: false,
desired_min_tx_millis: default_bfd_desired_min_tx_millis(),
required_min_rx_millis: default_bfd_required_min_rx_millis(),
detect_multiplier: default_bfd_detect_multiplier(),
bootstrap_timeout_secs: default_bfd_bootstrap_timeout_secs(),
}
}
}
/// Native BGP speaker configuration.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BgpConfig {
/// Whether FiberLB should originate VIP routes itself.
#[serde(default)]
pub enabled: bool,
/// Local AS number.
#[serde(default = "default_bgp_local_as")]
pub local_as: u32,
/// BGP router ID. Must be IPv4.
#[serde(default = "default_bgp_router_id")]
pub router_id: String,
/// Optional explicit next-hop address. Falls back to router_id.
#[serde(default)]
pub next_hop: Option<String>,
/// Requested hold time in seconds.
#[serde(default = "default_bgp_hold_time_secs")]
pub hold_time_secs: u16,
/// Keepalive interval in seconds.
#[serde(default = "default_bgp_keepalive_secs")]
pub keepalive_secs: u16,
/// Delay before reconnecting to a failed peer.
#[serde(default = "default_bgp_connect_retry_secs")]
pub connect_retry_secs: u64,
/// Static peers for outbound eBGP sessions.
#[serde(default)]
pub peers: Vec<BgpPeerConfig>,
}
fn default_bgp_local_as() -> u32 {
65001
}
fn default_bgp_router_id() -> String {
Ipv4Addr::new(127, 0, 0, 1).to_string()
}
fn default_bgp_hold_time_secs() -> u16 {
90
}
fn default_bgp_keepalive_secs() -> u16 {
30
}
fn default_bgp_connect_retry_secs() -> u64 {
5
}
impl BgpConfig {
/// Effective next hop advertised in UPDATE messages.
pub fn next_hop_addr(&self) -> std::result::Result<IpAddr, std::net::AddrParseError> {
self.next_hop.as_deref().unwrap_or(&self.router_id).parse()
}
/// Parsed router ID as IPv4.
pub fn router_id_addr(&self) -> std::result::Result<Ipv4Addr, std::net::AddrParseError> {
self.router_id.parse()
}
}
impl Default for BgpConfig {
fn default() -> Self {
Self {
enabled: false,
local_as: default_bgp_local_as(),
router_id: default_bgp_router_id(),
next_hop: None,
hold_time_secs: default_bgp_hold_time_secs(),
keepalive_secs: default_bgp_keepalive_secs(),
connect_retry_secs: default_bgp_connect_retry_secs(),
peers: Vec::new(),
}
}
}
impl Default for AuthConfig {
fn default() -> Self {
Self {
iam_server_addr: default_iam_server_addr(),
}
}
}
impl Default for ServerConfig {
fn default() -> Self {
Self {
grpc_addr: "0.0.0.0:9080".parse().unwrap(),
chainfire_endpoint: None,
flaredb_endpoint: None,
metadata_backend: MetadataBackend::FlareDb,
metadata_database_url: None,
single_node: false,
log_level: "info".to_string(),
tls: None,
auth: AuthConfig::default(),
health: HealthRuntimeConfig::default(),
vip_advertisement: VipAdvertisementConfig::default(),
vip_ownership: VipOwnershipConfig::default(),
bgp: BgpConfig::default(),
}
}
}