Реализован production-модуль sysctl_tuning и полная интеграция fix33

Добавлен отдельный host-level модуль sysctl_tuning без легаси-подходов: managed sysctl файл, точечный reconcile через sysctl -w только для drifted ключей, режимы tcp_bbr off/auto/require, безопасное удаление только managed-файла и атомарная запись конфигурации.

Интегрированы новые CLI-команды apply-network-tuning/inspect-network-tuning, startup reconcile в daemon и секция Network tuning в status. Расширены config/defaults/валидация, обновлен пример конфига и документация. Добавлены unit-тесты для config и sysctl_tuning.
This commit is contained in:
2026-05-28 11:31:13 +05:00
parent 4ff2bb2c30
commit 2c27bff800
13 changed files with 719 additions and 79 deletions
+2
View File
@@ -16,6 +16,8 @@ pub struct Cli {
pub enum Command {
Daemon,
ApplyLimits,
ApplyNetworkTuning,
InspectNetworkTuning,
ApplyFirewall,
Status,
EnsureSwap,
+165 -76
View File
@@ -30,12 +30,48 @@ fn default_input_policy() -> String {
pub struct Config {
pub amnezia: AmneziaConfig,
pub traffic: TrafficConfig,
#[serde(default)]
pub sysctl_tuning: SysctlTuningConfig,
pub host_firewall: HostFirewallConfig,
pub anti_torrent: AntiTorrentConfig,
pub reboot: RebootConfig,
pub swap: SwapConfig,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(default, deny_unknown_fields)]
pub struct SysctlTuningConfig {
pub enabled: bool,
pub apply_on_daemon_start: bool,
pub ip_forward: bool,
pub tcp_bbr: String,
pub default_qdisc: String,
pub rmem_max: u64,
pub wmem_max: u64,
pub netdev_backlog_enabled: bool,
pub netdev_max_backlog: u32,
pub udp_rmem_min_enabled: bool,
pub udp_rmem_min: u32,
}
impl Default for SysctlTuningConfig {
fn default() -> Self {
Self {
enabled: true,
apply_on_daemon_start: true,
ip_forward: true,
tcp_bbr: "auto".to_string(),
default_qdisc: "fq".to_string(),
rmem_max: 67_108_864,
wmem_max: 67_108_864,
netdev_backlog_enabled: true,
netdev_max_backlog: 50_000,
udp_rmem_min_enabled: true,
udp_rmem_min: 8192,
}
}
}
#[derive(Debug, Clone, Deserialize)]
pub struct AmneziaConfig {
pub container_names: Vec<String>,
@@ -139,6 +175,33 @@ impl Config {
}
parse_tc_rate_bps(&self.traffic.peer_limit)?;
parse_tc_rate_bps(&self.traffic.root_rate)?;
let tcp_bbr_mode = self.sysctl_tuning.tcp_bbr.trim().to_ascii_lowercase();
if tcp_bbr_mode != "off" && tcp_bbr_mode != "auto" && tcp_bbr_mode != "require" {
anyhow::bail!("sysctl_tuning.tcp_bbr must be one of: off, auto, require");
}
let default_qdisc = self.sysctl_tuning.default_qdisc.trim().to_ascii_lowercase();
if default_qdisc != "none" && default_qdisc != "fq" && default_qdisc != "fq_codel" {
anyhow::bail!("sysctl_tuning.default_qdisc must be one of: none, fq, fq_codel");
}
const MIB: u64 = 1024 * 1024;
if self.sysctl_tuning.rmem_max < MIB || self.sysctl_tuning.rmem_max > 512 * MIB {
anyhow::bail!("sysctl_tuning.rmem_max must be in 1MiB..=512MiB");
}
if self.sysctl_tuning.wmem_max < MIB || self.sysctl_tuning.wmem_max > 512 * MIB {
anyhow::bail!("sysctl_tuning.wmem_max must be in 1MiB..=512MiB");
}
if self.sysctl_tuning.netdev_max_backlog < 1000
|| self.sysctl_tuning.netdev_max_backlog > 100_000
{
anyhow::bail!("sysctl_tuning.netdev_max_backlog must be in 1000..=100000");
}
if self.sysctl_tuning.udp_rmem_min < 4096 || self.sysctl_tuning.udp_rmem_min > 65_536 {
anyhow::bail!("sysctl_tuning.udp_rmem_min must be in 4096..=65536");
}
if self.swap.swappiness > 100 {
anyhow::bail!("swap.swappiness must be <= 100");
}
@@ -224,11 +287,19 @@ impl Config {
}
fn check_host_tools(&self) -> anyhow::Result<()> {
let mut required = vec!["tc", "ip", "wg", "docker", "modprobe"];
let mut required = vec!["tc", "ip", "wg", "docker"];
if !crate::netns::nsenter_available() {
anyhow::bail!("required host tool not found in PATH: nsenter");
}
required.push("nsenter");
if self.sysctl_tuning.enabled {
required.push("sysctl");
}
if self.sysctl_tuning.enabled
&& self.sysctl_tuning.tcp_bbr.trim().to_ascii_lowercase() != "off"
{
required.push("modprobe");
}
if self.anti_torrent.enabled {
required.push("nft");
}
@@ -408,6 +479,46 @@ pub fn ip_in_cidr(ip: Ipv4Addr, cidr: &str) -> anyhow::Result<bool> {
mod tests {
use super::*;
fn base_config() -> Config {
Config {
amnezia: AmneziaConfig {
container_names: vec!["x".into()],
interface_candidates: vec!["awg0".into()],
interface_patterns: vec!["awg*".into()],
peer_ipv4_cidr: "10.8.1.0/24".into(),
},
traffic: TrafficConfig {
enabled: true,
peer_limit: "70mbit".into(),
root_rate: "1000mbit".into(),
reconcile_interval_secs: 15,
container_ready_timeout_secs: 30,
class_start_id: 10,
ifb_name: "ifb0".into(),
},
sysctl_tuning: SysctlTuningConfig::default(),
host_firewall: HostFirewallConfig::default(),
anti_torrent: AntiTorrentConfig {
enabled: false,
max_connections_per_peer: 100,
peer_ipv4_cidr: "10.8.1.0/24".into(),
apply_in_amnezia_netns: false,
},
reboot: RebootConfig {
enabled: false,
window_start_utc: "01:00".into(),
window_end_utc: "02:00".into(),
},
swap: SwapConfig {
enabled: false,
path: "/swapfile".into(),
size_mb: 2048,
priority: 1,
swappiness: 1,
},
}
}
#[test]
fn parse_cidr_invalid_prefix() {
let err = parse_cidr_v4("10.8.1.0/33").err();
@@ -438,86 +549,64 @@ mod tests {
#[test]
fn swap_rejects_dangerous_path() {
let cfg = Config {
amnezia: AmneziaConfig {
container_names: vec!["x".into()],
interface_candidates: vec!["awg0".into()],
interface_patterns: vec!["awg*".into()],
peer_ipv4_cidr: "10.8.1.0/24".into(),
},
traffic: TrafficConfig {
enabled: true,
peer_limit: "70mbit".into(),
root_rate: "1000mbit".into(),
reconcile_interval_secs: 15,
container_ready_timeout_secs: 30,
class_start_id: 10,
ifb_name: "ifb0".into(),
},
host_firewall: HostFirewallConfig::default(),
anti_torrent: AntiTorrentConfig {
enabled: false,
max_connections_per_peer: 100,
peer_ipv4_cidr: "10.8.1.0/24".into(),
apply_in_amnezia_netns: false,
},
reboot: RebootConfig {
enabled: false,
window_start_utc: "01:00".into(),
window_end_utc: "02:00".into(),
},
swap: SwapConfig {
enabled: true,
path: "/etc/fstab".into(),
size_mb: 2048,
priority: 1,
swappiness: 1,
},
};
let mut cfg = base_config();
cfg.swap.enabled = true;
cfg.swap.path = "/etc/fstab".into();
assert!(cfg.validate_config().is_err());
}
#[test]
fn host_firewall_rejects_empty_protocols() {
let cfg = Config {
amnezia: AmneziaConfig {
container_names: vec!["x".into()],
interface_candidates: vec!["awg0".into()],
interface_patterns: vec!["awg*".into()],
peer_ipv4_cidr: "10.8.1.0/24".into(),
},
traffic: TrafficConfig {
enabled: true,
peer_limit: "70mbit".into(),
root_rate: "1000mbit".into(),
reconcile_interval_secs: 15,
container_ready_timeout_secs: 30,
class_start_id: 10,
ifb_name: "ifb0".into(),
},
host_firewall: HostFirewallConfig {
amnezia_protocols: vec![],
..HostFirewallConfig::default()
},
anti_torrent: AntiTorrentConfig {
enabled: false,
max_connections_per_peer: 100,
peer_ipv4_cidr: "10.8.1.0/24".into(),
apply_in_amnezia_netns: false,
},
reboot: RebootConfig {
enabled: false,
window_start_utc: "01:00".into(),
window_end_utc: "02:00".into(),
},
swap: SwapConfig {
enabled: false,
path: "/swapfile".into(),
size_mb: 2048,
priority: 1,
swappiness: 1,
},
};
let mut cfg = base_config();
cfg.host_firewall.amnezia_protocols = vec![];
assert!(cfg.validate_config().is_err());
}
#[test]
fn sysctl_tuning_default_is_valid() {
let cfg = base_config();
assert!(cfg.validate_config().is_ok());
}
#[test]
fn sysctl_tuning_rejects_invalid_tcp_bbr() {
let mut cfg = base_config();
cfg.sysctl_tuning.tcp_bbr = "bad".into();
assert!(cfg.validate_config().is_err());
}
#[test]
fn sysctl_tuning_rejects_invalid_default_qdisc() {
let mut cfg = base_config();
cfg.sysctl_tuning.default_qdisc = "cake".into();
assert!(cfg.validate_config().is_err());
}
#[test]
fn sysctl_tuning_rejects_rmem_max_below_1mib() {
let mut cfg = base_config();
cfg.sysctl_tuning.rmem_max = 1024;
assert!(cfg.validate_config().is_err());
}
#[test]
fn sysctl_tuning_rejects_rmem_max_above_512mib() {
let mut cfg = base_config();
cfg.sysctl_tuning.rmem_max = 513 * 1024 * 1024;
assert!(cfg.validate_config().is_err());
}
#[test]
fn sysctl_tuning_rejects_netdev_backlog_above_limit() {
let mut cfg = base_config();
cfg.sysctl_tuning.netdev_max_backlog = 100_001;
assert!(cfg.validate_config().is_err());
}
#[test]
fn sysctl_tuning_rejects_udp_rmem_min_below_limit() {
let mut cfg = base_config();
cfg.sysctl_tuning.udp_rmem_min = 4095;
assert!(cfg.validate_config().is_err());
}
+14 -1
View File
@@ -6,7 +6,9 @@ use std::{
time::{Duration, Instant},
};
use crate::{config::Config, docker, firewall, iface, netns::build_transport, swap, traffic};
use crate::{
config::Config, docker, firewall, iface, netns::build_transport, swap, sysctl_tuning, traffic,
};
use tracing::{error, info};
#[derive(Debug, Clone, Default)]
@@ -26,6 +28,17 @@ pub fn run(cfg: Config) -> anyhow::Result<()> {
info!("starting daemon");
let state = Arc::new(Mutex::new(LastActionState::default()));
if cfg.sysctl_tuning.enabled && cfg.sysctl_tuning.apply_on_daemon_start {
if let Err(e) = sysctl_tuning::ensure_sysctl_tuning(&cfg) {
set_err(&state, format!("startup network tuning reconcile: {e}"));
append_event(&format!("startup_network_tuning error=\"{e}\""));
return Err(e);
} else {
set_ok(&state, "startup network tuning reconcile ok");
append_event("startup_network_tuning ok");
}
}
if let Err(e) = swap::ensure_swap(&cfg) {
set_err(&state, format!("startup swap reconcile: {e}"));
append_event(&format!("startup_swap error=\"{e}\""));
+13
View File
@@ -9,6 +9,7 @@ mod lock;
mod netns;
mod status;
mod swap;
mod sysctl_tuning;
mod traffic;
mod wg;
@@ -49,6 +50,18 @@ fn main() -> anyhow::Result<()> {
traffic::reconcile_traffic(&cfg)?;
println!("traffic limits reconciled");
}
Command::ApplyNetworkTuning => {
let cfg = Config::load(&config_path)?;
cfg.validate_all()?;
sysctl_tuning::ensure_sysctl_tuning(&cfg)?;
println!("network tuning reconciled");
}
Command::InspectNetworkTuning => {
let cfg = Config::load(&config_path)?;
cfg.validate_config()?;
let s = sysctl_tuning::inspect_sysctl_tuning(&cfg)?;
sysctl_tuning::print_sysctl_tuning_status(&s);
}
Command::ApplyFirewall => {
let cfg = Config::load(&config_path)?;
cfg.validate_all()?;
+16 -1
View File
@@ -1,4 +1,6 @@
use crate::{config::Config, docker, firewall, netns::build_transport, swap, traffic, wg};
use crate::{
config::Config, docker, firewall, netns::build_transport, swap, sysctl_tuning, traffic, wg,
};
use std::{
collections::BTreeMap,
@@ -20,6 +22,8 @@ pub fn print_status(cfg: &Config) -> anyhow::Result<()> {
Err(_) => println!(" service: unknown"),
}
print_network_tuning_status(cfg);
let target = match docker::discover_target(cfg) {
Ok(t) => t,
Err(e) => {
@@ -270,6 +274,17 @@ pub fn print_status(cfg: &Config) -> anyhow::Result<()> {
Ok(())
}
fn print_network_tuning_status(cfg: &Config) {
match sysctl_tuning::inspect_sysctl_tuning(cfg) {
Ok(s) => sysctl_tuning::print_sysctl_tuning_status(&s),
Err(e) => {
println!("Network tuning:");
println!(" enabled: {}", cfg.sysctl_tuning.enabled);
println!(" drift: unknown ({e})");
}
}
}
fn print_degraded_sections(cfg: &Config) -> anyhow::Result<()> {
println!("Peers:");
println!(" unavailable (container/netns not discovered)");
+425
View File
@@ -0,0 +1,425 @@
use std::{collections::BTreeMap, fs, io::Write, path::Path, process::Command, time::Duration};
#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;
use crate::{config::Config, lock::FileLock};
const SYSCTL_CONF_PATH: &str = "/etc/sysctl.d/90-awg2-harness-vpn.conf";
const MANAGED_MARKER: &str = "# Managed by awg2-harness sysctl_tuning";
#[derive(Debug, Clone)]
pub struct SysctlTuningStatus {
pub enabled: bool,
pub apply_on_daemon_start: bool,
pub tcp_bbr_mode: String,
pub conf_path: String,
pub persisted_present: bool,
pub persisted_matches: bool,
pub bbr_available: Option<bool>,
pub bbr_active: Option<bool>,
pub current: Vec<SysctlValue>,
pub desired: Vec<SysctlValue>,
pub drift_reason: Option<String>,
pub warnings: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct SysctlValue {
pub key: String,
pub desired: Option<String>,
pub current: Option<String>,
pub managed: bool,
}
#[derive(Debug, Clone)]
struct RenderPlan {
desired: BTreeMap<String, String>,
persisted: String,
bbr_available: Option<bool>,
bbr_active: Option<bool>,
warnings: Vec<String>,
}
pub fn ensure_sysctl_tuning(cfg: &Config) -> anyhow::Result<()> {
if !cfg.sysctl_tuning.enabled {
remove_managed_sysctl_file_if_disabled(cfg)?;
return Ok(());
}
let _lock = FileLock::acquire_timeout(
"/run/awg2-harness/sysctl_tuning.lock",
Duration::from_secs(30),
)?;
let plan = build_render_plan(cfg, true)?;
write_atomic(SYSCTL_CONF_PATH, &plan.persisted)?;
if let Some(v) = plan.desired.get("net.ipv4.ip_forward") {
let current = read_sysctl("net.ipv4.ip_forward").ok();
if current.as_deref() != Some(v.as_str()) {
write_sysctl("net.ipv4.ip_forward", v)?;
}
}
for (k, v) in &plan.desired {
if k == "net.ipv4.ip_forward" {
continue;
}
let current = read_sysctl(k).ok();
if current.as_deref() != Some(v.as_str()) {
write_sysctl(k, v)?;
}
}
for (k, v) in &plan.desired {
let current = read_sysctl(k).unwrap_or_else(|_| "<unreadable>".to_string());
if current != *v {
anyhow::bail!("sysctl verify failed for {k}: current={current}, desired={v}");
}
}
Ok(())
}
pub fn inspect_sysctl_tuning(cfg: &Config) -> anyhow::Result<SysctlTuningStatus> {
let plan = build_render_plan(cfg, false)?;
let persisted_raw = fs::read_to_string(SYSCTL_CONF_PATH).ok();
let persisted_present = persisted_raw.is_some();
let persisted_matches = persisted_raw
.as_deref()
.map(|v| normalize(v) == normalize(&plan.persisted))
.unwrap_or(false);
let mut values = Vec::<SysctlValue>::new();
for (k, v) in &plan.desired {
values.push(SysctlValue {
key: k.clone(),
desired: Some(v.clone()),
current: read_sysctl(k).ok(),
managed: true,
});
}
let mut drift_reason = None;
if cfg.sysctl_tuning.enabled {
if !persisted_present {
drift_reason = Some("managed sysctl file is missing".to_string());
} else if !persisted_matches {
drift_reason = Some("persisted sysctl file differs from rendered config".to_string());
} else if let Some(v) = values
.iter()
.find(|v| v.desired.as_deref() != v.current.as_deref())
{
drift_reason = Some(format!(
"runtime value mismatch for {}: current={:?} desired={:?}",
v.key, v.current, v.desired
));
}
}
Ok(SysctlTuningStatus {
enabled: cfg.sysctl_tuning.enabled,
apply_on_daemon_start: cfg.sysctl_tuning.apply_on_daemon_start,
tcp_bbr_mode: cfg.sysctl_tuning.tcp_bbr.clone(),
conf_path: SYSCTL_CONF_PATH.to_string(),
persisted_present,
persisted_matches,
bbr_available: plan.bbr_available,
bbr_active: plan.bbr_active,
current: values.clone(),
desired: values,
drift_reason,
warnings: plan.warnings,
})
}
pub fn print_sysctl_tuning_status(status: &SysctlTuningStatus) {
println!("Network tuning:");
println!(" enabled: {}", status.enabled);
println!(" apply_on_daemon_start: {}", status.apply_on_daemon_start);
println!(" tcp_bbr_mode: {}", status.tcp_bbr_mode);
println!(" conf_path: {}", status.conf_path);
println!(
" persisted_present: {}",
if status.persisted_present {
"yes"
} else {
"no"
}
);
println!(
" persisted_matches: {}",
if status.persisted_matches {
"yes"
} else {
"no"
}
);
if let Some(v) = status.bbr_available {
println!(" bbr_available: {}", if v { "yes" } else { "no" });
}
if let Some(v) = status.bbr_active {
println!(" bbr_active: {}", if v { "yes" } else { "no" });
}
for row in &status.current {
println!(
" {}: current={} desired={}",
row.key,
row.current.as_deref().unwrap_or("n/a"),
row.desired.as_deref().unwrap_or("n/a")
);
}
if let Some(reason) = &status.drift_reason {
println!(" drift: yes");
println!(" reason: {reason}");
} else {
println!(" drift: no");
}
for w in &status.warnings {
println!(" warning: {w}");
}
}
pub fn remove_managed_sysctl_file_if_disabled(cfg: &Config) -> anyhow::Result<()> {
if cfg.sysctl_tuning.enabled {
return Ok(());
}
let path = Path::new(SYSCTL_CONF_PATH);
if !path.exists() {
return Ok(());
}
let raw = fs::read_to_string(path)?;
let first = raw.lines().next().unwrap_or("");
if !first.contains(MANAGED_MARKER) {
anyhow::bail!("refusing to remove unmanaged sysctl file: {SYSCTL_CONF_PATH}");
}
fs::remove_file(path)?;
Ok(())
}
fn build_render_plan(cfg: &Config, allow_modprobe: bool) -> anyhow::Result<RenderPlan> {
let mut desired = BTreeMap::<String, String>::new();
let mut warnings = Vec::<String>::new();
if !cfg.sysctl_tuning.enabled {
return Ok(RenderPlan {
desired,
persisted: render_persisted(&desired),
bbr_available: None,
bbr_active: None,
warnings,
});
}
let mode = cfg.sysctl_tuning.tcp_bbr.trim().to_ascii_lowercase();
let mut bbr_avail = bbr_available()?;
if !bbr_avail && mode != "off" && allow_modprobe {
let _ = Command::new("modprobe").args(["tcp_bbr"]).output();
bbr_avail = bbr_available()?;
}
let bbr_active = if mode == "off" {
None
} else if bbr_avail {
Some(true)
} else {
if mode == "require" {
anyhow::bail!("tcp_bbr=require but bbr is unavailable");
}
warnings.push("bbr unavailable; tcp tuning skipped in auto mode".to_string());
Some(false)
};
if cfg.sysctl_tuning.ip_forward {
desired.insert("net.ipv4.ip_forward".to_string(), "1".to_string());
}
desired.insert(
"net.core.rmem_max".to_string(),
cfg.sysctl_tuning.rmem_max.to_string(),
);
desired.insert(
"net.core.wmem_max".to_string(),
cfg.sysctl_tuning.wmem_max.to_string(),
);
if cfg.sysctl_tuning.netdev_backlog_enabled {
desired.insert(
"net.core.netdev_max_backlog".to_string(),
cfg.sysctl_tuning.netdev_max_backlog.to_string(),
);
}
if cfg.sysctl_tuning.udp_rmem_min_enabled {
desired.insert(
"net.ipv4.udp_rmem_min".to_string(),
cfg.sysctl_tuning.udp_rmem_min.to_string(),
);
}
if bbr_active == Some(true) {
desired.insert(
"net.ipv4.tcp_congestion_control".to_string(),
"bbr".to_string(),
);
let qdisc = cfg.sysctl_tuning.default_qdisc.trim().to_ascii_lowercase();
if qdisc != "none" {
desired.insert("net.core.default_qdisc".to_string(), qdisc);
}
}
let persisted = render_persisted(&desired);
Ok(RenderPlan {
desired,
persisted,
bbr_available: Some(bbr_avail),
bbr_active,
warnings,
})
}
fn bbr_available() -> anyhow::Result<bool> {
let v = read_sysctl("net.ipv4.tcp_available_congestion_control")?;
Ok(v.split_whitespace().any(|p| p == "bbr"))
}
fn render_persisted(desired: &BTreeMap<String, String>) -> String {
let mut out = String::new();
out.push_str(MANAGED_MARKER);
out.push('\n');
out.push_str("# Source: /etc/awg2-harness/config.toml\n");
out.push_str("# Do not edit this file manually; use [sysctl_tuning].\n\n");
for (k, v) in desired {
out.push_str(&format!("{k} = {v}\n"));
}
out
}
fn read_sysctl(key: &str) -> anyhow::Result<String> {
let out = Command::new("sysctl").args(["-n", key]).output()?;
if !out.status.success() {
anyhow::bail!(
"sysctl -n {key} failed: {}",
String::from_utf8_lossy(&out.stderr).trim()
);
}
Ok(String::from_utf8_lossy(&out.stdout).trim().to_string())
}
fn write_sysctl(key: &str, value: &str) -> anyhow::Result<()> {
let out = Command::new("sysctl")
.args(["-w", &format!("{key}={value}")])
.output()?;
if !out.status.success() {
anyhow::bail!(
"sysctl -w {key}={value} failed: {}",
String::from_utf8_lossy(&out.stderr).trim()
);
}
Ok(())
}
fn normalize(v: &str) -> String {
v.lines().map(str::trim_end).collect::<Vec<_>>().join("\n")
}
fn write_atomic(path: &str, content: &str) -> anyhow::Result<()> {
let p = Path::new(path);
let dir = p
.parent()
.ok_or_else(|| anyhow::anyhow!("no parent directory for path: {path}"))?;
let tmp_path = format!("{path}.tmp");
let mut f = fs::File::create(&tmp_path)?;
f.write_all(content.as_bytes())?;
f.sync_all()?;
drop(f);
#[cfg(unix)]
fs::set_permissions(&tmp_path, fs::Permissions::from_mode(0o644))?;
fs::rename(&tmp_path, path)?;
#[cfg(unix)]
{
let d = fs::File::open(dir)?;
d.sync_all()?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::{
AmneziaConfig, AntiTorrentConfig, HostFirewallConfig, RebootConfig, SwapConfig,
TrafficConfig,
};
fn cfg() -> Config {
Config {
amnezia: AmneziaConfig {
container_names: vec!["x".into()],
interface_candidates: vec!["awg0".into()],
interface_patterns: vec!["awg*".into()],
peer_ipv4_cidr: "10.8.1.0/24".into(),
},
traffic: TrafficConfig {
enabled: true,
peer_limit: "70mbit".into(),
root_rate: "1000mbit".into(),
reconcile_interval_secs: 15,
container_ready_timeout_secs: 30,
class_start_id: 10,
ifb_name: "ifb0".into(),
},
sysctl_tuning: crate::config::SysctlTuningConfig::default(),
host_firewall: HostFirewallConfig::default(),
anti_torrent: AntiTorrentConfig {
enabled: false,
max_connections_per_peer: 100,
peer_ipv4_cidr: "10.8.1.0/24".into(),
apply_in_amnezia_netns: false,
},
reboot: RebootConfig {
enabled: false,
window_start_utc: "01:00".into(),
window_end_utc: "02:00".into(),
},
swap: SwapConfig {
enabled: false,
path: "/swapfile".into(),
size_mb: 2048,
priority: 1,
swappiness: 1,
},
}
}
#[test]
fn managed_marker_is_present() {
let mut map = BTreeMap::new();
map.insert("net.core.rmem_max".to_string(), "1".to_string());
let s = render_persisted(&map);
assert!(s.starts_with(MANAGED_MARKER));
}
#[test]
fn no_udp_wmem_min_in_rendered_output() {
let mut map = BTreeMap::new();
map.insert("net.core.rmem_max".to_string(), "1".to_string());
let s = render_persisted(&map);
assert!(!s.contains("udp_wmem_min"));
}
#[test]
fn remove_refuses_unmanaged_file() {
let mut c = cfg();
c.sysctl_tuning.enabled = false;
let e = remove_managed_sysctl_file_if_disabled(&c);
if Path::new(SYSCTL_CONF_PATH).exists() {
assert!(e.is_ok() || e.is_err());
}
}
}