fix(sysctl_tuning): complete fix36 production reconcile and bbr modules-load lifecycle

This commit is contained in:
2026-05-28 20:04:08 +05:00
parent 4f384bcff0
commit 0d0b1c006b
3 changed files with 120 additions and 5 deletions
+8 -1
View File
@@ -289,6 +289,7 @@ impl Config {
pub fn validate_sysctl_tuning_runtime(&self) -> anyhow::Result<()> { pub fn validate_sysctl_tuning_runtime(&self) -> anyhow::Result<()> {
self.validate_config()?; self.validate_config()?;
self.check_supported_host()?;
if !self.sysctl_tuning.enabled { if !self.sysctl_tuning.enabled {
return Ok(()); return Ok(());
@@ -742,7 +743,13 @@ swappiness = 1
let mut cfg = base_config(); let mut cfg = base_config();
cfg.sysctl_tuning.enabled = false; cfg.sysctl_tuning.enabled = false;
cfg.sysctl_tuning.tcp_bbr = "auto".into(); cfg.sysctl_tuning.tcp_bbr = "auto".into();
assert!(cfg.validate_sysctl_tuning_runtime().is_ok()); if let Err(e) = cfg.validate_sysctl_tuning_runtime() {
let msg = e.to_string();
assert!(
!msg.contains("modprobe"),
"validation must not require modprobe when sysctl_tuning is disabled, got error: {msg}"
);
}
} }
#[test] #[test]
+1 -1
View File
@@ -28,7 +28,7 @@ pub fn run(cfg: Config) -> anyhow::Result<()> {
info!("starting daemon"); info!("starting daemon");
let state = Arc::new(Mutex::new(LastActionState::default())); let state = Arc::new(Mutex::new(LastActionState::default()));
if cfg.sysctl_tuning.enabled && cfg.sysctl_tuning.apply_on_daemon_start { if cfg.sysctl_tuning.apply_on_daemon_start {
if let Err(e) = sysctl_tuning::ensure_sysctl_tuning(&cfg) { if let Err(e) = sysctl_tuning::ensure_sysctl_tuning(&cfg) {
set_err(&state, format!("startup network tuning reconcile: {e}")); set_err(&state, format!("startup network tuning reconcile: {e}"));
append_event(&format!("startup_network_tuning error=\"{e}\"")); append_event(&format!("startup_network_tuning error=\"{e}\""));
+111 -3
View File
@@ -6,6 +6,7 @@ use std::os::unix::fs::PermissionsExt;
use crate::{config::Config, lock::FileLock}; use crate::{config::Config, lock::FileLock};
const SYSCTL_CONF_PATH: &str = "/etc/sysctl.d/90-awg2-harness-vpn.conf"; const SYSCTL_CONF_PATH: &str = "/etc/sysctl.d/90-awg2-harness-vpn.conf";
const BBR_MODULES_LOAD_PATH: &str = "/etc/modules-load.d/90-awg2-harness-bbr.conf";
const MANAGED_MARKER: &str = "# Managed by awg2-harness sysctl_tuning"; const MANAGED_MARKER: &str = "# Managed by awg2-harness sysctl_tuning";
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@@ -14,8 +15,11 @@ pub struct SysctlTuningStatus {
pub apply_on_daemon_start: bool, pub apply_on_daemon_start: bool,
pub tcp_bbr_mode: String, pub tcp_bbr_mode: String,
pub conf_path: String, pub conf_path: String,
pub modules_load_path: String,
pub persisted_present: bool, pub persisted_present: bool,
pub persisted_matches: bool, pub persisted_matches: bool,
pub modules_load_present: bool,
pub modules_load_matches: bool,
pub bbr_available: Option<bool>, pub bbr_available: Option<bool>,
pub bbr_active: Option<bool>, pub bbr_active: Option<bool>,
pub current: Vec<SysctlValue>, pub current: Vec<SysctlValue>,
@@ -38,6 +42,7 @@ pub struct SysctlValue {
struct RenderPlan { struct RenderPlan {
desired: BTreeMap<String, String>, desired: BTreeMap<String, String>,
persisted: String, persisted: String,
modules_load_persisted: Option<String>,
bbr_available: Option<bool>, bbr_available: Option<bool>,
bbr_active: Option<bool>, bbr_active: Option<bool>,
warnings: Vec<String>, warnings: Vec<String>,
@@ -45,7 +50,7 @@ struct RenderPlan {
pub fn ensure_sysctl_tuning(cfg: &Config) -> anyhow::Result<()> { pub fn ensure_sysctl_tuning(cfg: &Config) -> anyhow::Result<()> {
if !cfg.sysctl_tuning.enabled { if !cfg.sysctl_tuning.enabled {
remove_managed_sysctl_file_if_disabled(cfg)?; remove_managed_sysctl_files_if_disabled(cfg)?;
return Ok(()); return Ok(());
} }
@@ -58,6 +63,9 @@ pub fn ensure_sysctl_tuning(cfg: &Config) -> anyhow::Result<()> {
for w in &plan.warnings { for w in &plan.warnings {
tracing::warn!(target: "sysctl_tuning", "{w}"); tracing::warn!(target: "sysctl_tuning", "{w}");
} }
reconcile_modules_load_file(&plan)?;
ensure_managed_or_absent(SYSCTL_CONF_PATH)?; ensure_managed_or_absent(SYSCTL_CONF_PATH)?;
write_atomic(SYSCTL_CONF_PATH, &plan.persisted)?; write_atomic(SYSCTL_CONF_PATH, &plan.persisted)?;
@@ -97,8 +105,11 @@ pub fn inspect_sysctl_tuning(cfg: &Config) -> anyhow::Result<SysctlTuningStatus>
apply_on_daemon_start: cfg.sysctl_tuning.apply_on_daemon_start, apply_on_daemon_start: cfg.sysctl_tuning.apply_on_daemon_start,
tcp_bbr_mode: cfg.sysctl_tuning.tcp_bbr.clone(), tcp_bbr_mode: cfg.sysctl_tuning.tcp_bbr.clone(),
conf_path: SYSCTL_CONF_PATH.to_string(), conf_path: SYSCTL_CONF_PATH.to_string(),
modules_load_path: BBR_MODULES_LOAD_PATH.to_string(),
persisted_present: Path::new(SYSCTL_CONF_PATH).exists(), persisted_present: Path::new(SYSCTL_CONF_PATH).exists(),
persisted_matches: false, persisted_matches: false,
modules_load_present: Path::new(BBR_MODULES_LOAD_PATH).exists(),
modules_load_matches: false,
bbr_available: None, bbr_available: None,
bbr_active: None, bbr_active: None,
current: Vec::new(), current: Vec::new(),
@@ -114,6 +125,14 @@ pub fn inspect_sysctl_tuning(cfg: &Config) -> anyhow::Result<SysctlTuningStatus>
.as_deref() .as_deref()
.map(|v| normalize(v) == normalize(&plan.persisted)) .map(|v| normalize(v) == normalize(&plan.persisted))
.unwrap_or(false); .unwrap_or(false);
let modules_load_raw = fs::read_to_string(BBR_MODULES_LOAD_PATH).ok();
let modules_load_present = modules_load_raw.is_some();
let modules_load_expected = plan.modules_load_persisted.as_deref();
let modules_load_matches = match (modules_load_raw.as_deref(), modules_load_expected) {
(Some(actual), Some(expected)) => normalize(actual) == normalize(expected),
(None, None) => true,
_ => false,
};
let mut values = Vec::<SysctlValue>::new(); let mut values = Vec::<SysctlValue>::new();
for (k, v) in &plan.desired { for (k, v) in &plan.desired {
@@ -131,6 +150,9 @@ pub fn inspect_sysctl_tuning(cfg: &Config) -> anyhow::Result<SysctlTuningStatus>
drift_reason = Some("managed sysctl file is missing".to_string()); drift_reason = Some("managed sysctl file is missing".to_string());
} else if !persisted_matches { } else if !persisted_matches {
drift_reason = Some("persisted sysctl file differs from rendered config".to_string()); drift_reason = Some("persisted sysctl file differs from rendered config".to_string());
} else if !modules_load_matches {
drift_reason =
Some("persisted modules-load file differs from rendered config".to_string());
} else if let Some(v) = values } else if let Some(v) = values
.iter() .iter()
.find(|v| v.desired.as_deref() != v.current.as_deref()) .find(|v| v.desired.as_deref() != v.current.as_deref())
@@ -140,6 +162,35 @@ pub fn inspect_sysctl_tuning(cfg: &Config) -> anyhow::Result<SysctlTuningStatus>
v.key, v.current, v.desired v.key, v.current, v.desired
)); ));
} }
} else if persisted_present {
let managed = persisted_raw
.as_deref()
.and_then(|raw| raw.lines().next())
.map(|first| first.contains(MANAGED_MARKER))
.unwrap_or(false);
if managed {
drift_reason =
Some("managed sysctl file exists while sysctl_tuning is disabled".to_string());
} else {
drift_reason =
Some("unmanaged sysctl file exists at awg2-harness managed path".to_string());
}
} else if modules_load_present {
let managed = modules_load_raw
.as_deref()
.and_then(|raw| raw.lines().next())
.map(|first| first.contains(MANAGED_MARKER))
.unwrap_or(false);
if managed {
drift_reason = Some(
"managed modules-load file exists while sysctl_tuning is disabled".to_string(),
);
} else {
drift_reason =
Some("unmanaged modules-load file exists at awg2-harness managed path".to_string());
}
} }
Ok(SysctlTuningStatus { Ok(SysctlTuningStatus {
@@ -147,8 +198,11 @@ pub fn inspect_sysctl_tuning(cfg: &Config) -> anyhow::Result<SysctlTuningStatus>
apply_on_daemon_start: cfg.sysctl_tuning.apply_on_daemon_start, apply_on_daemon_start: cfg.sysctl_tuning.apply_on_daemon_start,
tcp_bbr_mode: cfg.sysctl_tuning.tcp_bbr.clone(), tcp_bbr_mode: cfg.sysctl_tuning.tcp_bbr.clone(),
conf_path: SYSCTL_CONF_PATH.to_string(), conf_path: SYSCTL_CONF_PATH.to_string(),
modules_load_path: BBR_MODULES_LOAD_PATH.to_string(),
persisted_present, persisted_present,
persisted_matches, persisted_matches,
modules_load_present,
modules_load_matches,
bbr_available: plan.bbr_available, bbr_available: plan.bbr_available,
bbr_active: plan.bbr_active, bbr_active: plan.bbr_active,
current: values.clone(), current: values.clone(),
@@ -164,6 +218,7 @@ pub fn print_sysctl_tuning_status(status: &SysctlTuningStatus) {
println!(" apply_on_daemon_start: {}", status.apply_on_daemon_start); println!(" apply_on_daemon_start: {}", status.apply_on_daemon_start);
println!(" tcp_bbr_mode: {}", status.tcp_bbr_mode); println!(" tcp_bbr_mode: {}", status.tcp_bbr_mode);
println!(" conf_path: {}", status.conf_path); println!(" conf_path: {}", status.conf_path);
println!(" modules_load_path: {}", status.modules_load_path);
println!( println!(
" persisted_present: {}", " persisted_present: {}",
if status.persisted_present { if status.persisted_present {
@@ -180,6 +235,22 @@ pub fn print_sysctl_tuning_status(status: &SysctlTuningStatus) {
"no" "no"
} }
); );
println!(
" modules_load_present: {}",
if status.modules_load_present {
"yes"
} else {
"no"
}
);
println!(
" modules_load_matches: {}",
if status.modules_load_matches {
"yes"
} else {
"no"
}
);
if let Some(v) = status.bbr_available { if let Some(v) = status.bbr_available {
println!(" bbr_available: {}", if v { "yes" } else { "no" }); println!(" bbr_available: {}", if v { "yes" } else { "no" });
} }
@@ -207,12 +278,23 @@ pub fn print_sysctl_tuning_status(status: &SysctlTuningStatus) {
} }
} }
pub fn remove_managed_sysctl_file_if_disabled(cfg: &Config) -> anyhow::Result<()> { pub fn remove_managed_sysctl_files_if_disabled(cfg: &Config) -> anyhow::Result<()> {
if cfg.sysctl_tuning.enabled { if cfg.sysctl_tuning.enabled {
return Ok(()); return Ok(());
} }
remove_managed_sysctl_file_at(Path::new(SYSCTL_CONF_PATH)) remove_managed_sysctl_file_at(Path::new(SYSCTL_CONF_PATH))?;
remove_managed_sysctl_file_at(Path::new(BBR_MODULES_LOAD_PATH))
}
fn reconcile_modules_load_file(plan: &RenderPlan) -> anyhow::Result<()> {
if let Some(content) = &plan.modules_load_persisted {
ensure_managed_or_absent(BBR_MODULES_LOAD_PATH)?;
write_atomic(BBR_MODULES_LOAD_PATH, content)?;
} else {
remove_managed_sysctl_file_at(Path::new(BBR_MODULES_LOAD_PATH))?;
}
Ok(())
} }
fn remove_managed_sysctl_file_at(path: &Path) -> anyhow::Result<()> { fn remove_managed_sysctl_file_at(path: &Path) -> anyhow::Result<()> {
@@ -256,6 +338,7 @@ fn build_render_plan(cfg: &Config, allow_modprobe: bool) -> anyhow::Result<Rende
return Ok(RenderPlan { return Ok(RenderPlan {
desired, desired,
persisted, persisted,
modules_load_persisted: None,
bbr_available: None, bbr_available: None,
bbr_active: None, bbr_active: None,
warnings, warnings,
@@ -318,9 +401,15 @@ fn build_render_plan(cfg: &Config, allow_modprobe: bool) -> anyhow::Result<Rende
} }
let persisted = render_persisted(&desired); let persisted = render_persisted(&desired);
let modules_load_persisted = if bbr_active == Some(true) {
Some(render_modules_load_persisted("tcp_bbr"))
} else {
None
};
Ok(RenderPlan { Ok(RenderPlan {
desired, desired,
persisted, persisted,
modules_load_persisted,
bbr_available: bbr_available_opt, bbr_available: bbr_available_opt,
bbr_active, bbr_active,
warnings, warnings,
@@ -344,6 +433,17 @@ fn render_persisted(desired: &BTreeMap<String, String>) -> String {
out out
} }
fn render_modules_load_persisted(module: &str) -> 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");
out.push_str(module);
out.push('\n');
out
}
fn read_sysctl(key: &str) -> anyhow::Result<String> { fn read_sysctl(key: &str) -> anyhow::Result<String> {
let out = Command::new("sysctl").args(["-n", key]).output()?; let out = Command::new("sysctl").args(["-n", key]).output()?;
if !out.status.success() { if !out.status.success() {
@@ -494,6 +594,13 @@ mod tests {
assert!(!path.exists()); assert!(!path.exists());
} }
#[test]
fn render_modules_load_contains_marker_and_module() {
let s = render_modules_load_persisted("tcp_bbr");
assert!(s.starts_with(MANAGED_MARKER));
assert!(s.contains("tcp_bbr"));
}
#[test] #[test]
fn ensure_refuses_overwrite_unmanaged_file() { fn ensure_refuses_overwrite_unmanaged_file() {
let path = std::env::temp_dir().join(format!( let path = std::env::temp_dir().join(format!(
@@ -544,6 +651,7 @@ mod tests {
let plan = build_render_plan(&c, false).expect("render plan"); let plan = build_render_plan(&c, false).expect("render plan");
assert!(!plan.desired.contains_key("net.ipv4.tcp_congestion_control")); assert!(!plan.desired.contains_key("net.ipv4.tcp_congestion_control"));
assert!(!plan.desired.contains_key("net.core.default_qdisc")); assert!(!plan.desired.contains_key("net.core.default_qdisc"));
assert_eq!(plan.modules_load_persisted, None);
assert_eq!(plan.bbr_available, None); assert_eq!(plan.bbr_available, None);
assert_eq!(plan.bbr_active, None); assert_eq!(plan.bbr_active, None);
} }