- remove Java plugins (velocity/paper), dashboard, all MC-specific code
(handshake, death_code, varint, hostname-HMAC); available in history pre-v0.2
- merge crates/* into one package with src/bin/{rampart,rampart-manager,rampart-cli}
- ProtocolHandler trait + registry (no implementations yet), universal PoW kept
- XDP: universal L3/L4 filter (xdp/core/) + pluggable hook API (xdp/hooks/),
fix IPv6 saddr bug; clang build verified
- docs: bilingual knowledge base (docs/kb/: attacks x4, defense-levels,
practice x3), rewrite README/architecture for universal concept
- TODO.md v4.0: <=300-line module limit, competitor benchmark section (ref/)
- deploy/CI/docs cleanup: no MC references, new binary names
cargo build/clippy(-D warnings)/test green (55 tests)
143 lines
3.8 KiB
Rust
143 lines
3.8 KiB
Rust
//! Единый конфиг платформы Rampart.
|
|
mod sections;
|
|
|
|
pub use sections::{
|
|
BackendConfig, BanConfig, BindConfig, LimitsConfig, LoggingConfig, MetricsConfig, PowConfig, StoreConfig,
|
|
WorkerConfig, XdpConfig,
|
|
};
|
|
|
|
use serde::Deserialize;
|
|
use std::fs;
|
|
|
|
#[derive(Debug, Clone, Default, Deserialize)]
|
|
pub struct Config {
|
|
#[serde(default)]
|
|
pub bind: BindConfig,
|
|
#[serde(default)]
|
|
pub backend: BackendConfig,
|
|
#[serde(default)]
|
|
pub workers: WorkerConfig,
|
|
#[serde(default)]
|
|
pub limits: LimitsConfig,
|
|
#[serde(default)]
|
|
pub ban: BanConfig,
|
|
#[serde(default)]
|
|
pub store: StoreConfig,
|
|
#[serde(default)]
|
|
pub xdp: XdpConfig,
|
|
#[serde(default)]
|
|
pub logging: LoggingConfig,
|
|
#[serde(default)]
|
|
pub metrics: MetricsConfig,
|
|
#[serde(default)]
|
|
pub pow: PowConfig,
|
|
#[serde(default)]
|
|
pub whitelist: Vec<String>,
|
|
}
|
|
|
|
impl Config {
|
|
/// Парсит конфиг из TOML-строки и валидирует семантику.
|
|
///
|
|
/// # Errors
|
|
/// Возвращает ошибку при невалидном TOML или нарушении инвариантов
|
|
/// (пустой список upstream-бэкендов, некорректные адреса).
|
|
pub fn parse_str(contents: &str) -> anyhow::Result<Self> {
|
|
let config: Config = toml::from_str(contents)?;
|
|
config.validate()?;
|
|
Ok(config)
|
|
}
|
|
|
|
/// Читает и парсит конфиг из файла.
|
|
///
|
|
/// # Errors
|
|
/// Возвращает ошибку при невозможности прочитать файл или невалидном содержимом.
|
|
pub fn from_file(path: &str) -> anyhow::Result<Self> {
|
|
let contents = fs::read_to_string(path)?;
|
|
Self::parse_str(&contents)
|
|
}
|
|
|
|
fn validate(&self) -> anyhow::Result<()> {
|
|
if self.backend.upstreams.is_empty() {
|
|
anyhow::bail!("backend.upstreams must not be empty");
|
|
}
|
|
for upstream in &self.backend.upstreams {
|
|
if upstream.parse::<std::net::SocketAddr>().is_err() {
|
|
anyhow::bail!("invalid upstream address: {upstream}");
|
|
}
|
|
}
|
|
for entry in &self.whitelist {
|
|
if entry.parse::<std::net::IpAddr>().is_err() {
|
|
anyhow::bail!("invalid whitelist entry: {entry}");
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn empty_config_uses_defaults() {
|
|
let config = Config::parse_str("").expect("empty config should parse");
|
|
assert_eq!(config.bind.address, "0.0.0.0");
|
|
assert_eq!(config.bind.port, 25565);
|
|
assert_eq!(config.backend.upstreams, vec!["127.0.0.1:25566"]);
|
|
assert!(!config.pow.enabled);
|
|
assert!(!config.xdp.enabled);
|
|
}
|
|
|
|
#[test]
|
|
fn full_config_parses() {
|
|
let config = Config::parse_str(
|
|
r#"
|
|
[bind]
|
|
address = "10.0.0.1"
|
|
port = 443
|
|
|
|
[backend]
|
|
upstreams = ["192.168.1.10:8080", "192.168.1.11:8080"]
|
|
|
|
[pow]
|
|
enabled = true
|
|
difficulty = 6
|
|
|
|
whitelist = ["203.0.113.7"]
|
|
"#,
|
|
)
|
|
.expect("config should parse");
|
|
assert_eq!(config.bind.port, 443);
|
|
assert_eq!(config.backend.upstreams.len(), 2);
|
|
assert!(config.pow.enabled);
|
|
assert_eq!(config.pow.difficulty, 6);
|
|
}
|
|
|
|
#[test]
|
|
fn empty_upstreams_rejected() {
|
|
let result = Config::parse_str(
|
|
r#"
|
|
[backend]
|
|
upstreams = []
|
|
"#,
|
|
);
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn invalid_upstream_rejected() {
|
|
let result = Config::parse_str(
|
|
r#"
|
|
[backend]
|
|
upstreams = ["not-an-addr"]
|
|
"#,
|
|
);
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn invalid_whitelist_entry_rejected() {
|
|
let result = Config::parse_str("whitelist = [\"999.999.1.1\"]");
|
|
assert!(result.is_err());
|
|
}
|
|
}
|