Skip to content

Commit e27d17e

Browse files
author
Roy Lin
committed
feat(box): CRI applies pod sysctls in the guest
PodSandboxConfig.linux.sysctls now flow CRI → BoxConfig.sysctls → BOX_SYSCTL_<i> boot env → guest-init, which writes each to /proc/sys/<name with '.' as '/'> at VM startup (best-effort: a sysctl the guest kernel does not expose is logged and skipped, not fatal). Verified on the KVM server: the CRI "should support safe sysctls" conformance spec passes. "unsafe sysctls" still fails only because it reads fs.mqueue.msg_max, which the libkrun guest kernel does not compile in (CONFIG_POSIX_MQUEUE absent) — a guest-kernel limitation, not a CRI defect.
1 parent 0214794 commit e27d17e

4 files changed

Lines changed: 93 additions & 2 deletions

File tree

src/core/src/config.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,13 @@ pub struct BoxConfig {
349349
#[serde(default)]
350350
pub security_opt: Vec<String>,
351351

352+
/// Kernel sysctls (name → value) applied in the guest at boot.
353+
///
354+
/// Pod-level sysctls from the CRI `PodSandboxConfig`; the guest writes each
355+
/// to `/proc/sys/<name with '.' as '/'>` once the VM is up.
356+
#[serde(default)]
357+
pub sysctls: Vec<(String, String)>,
358+
352359
/// Run in privileged mode (disables all security restrictions)
353360
#[serde(default)]
354361
pub privileged: bool,
@@ -409,6 +416,7 @@ impl Default for BoxConfig {
409416
cap_add: vec![],
410417
cap_drop: vec![],
411418
security_opt: vec![],
419+
sysctls: vec![],
412420
privileged: false,
413421
read_only: false,
414422
sidecar: None,

src/cri/src/config_mapper.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,27 @@ pub fn pod_sandbox_config_to_box_config(
4646
port_map,
4747
network,
4848
hostname,
49+
sysctls: parse_sysctls(config),
4950
..Default::default()
5051
})
5152
}
5253

54+
/// Extract pod-level sysctls from the CRI sandbox config.
55+
///
56+
/// Sorted by name for deterministic ordering (the guest applies them in order).
57+
fn parse_sysctls(config: &PodSandboxConfig) -> Vec<(String, String)> {
58+
let Some(linux) = config.linux.as_ref() else {
59+
return Vec::new();
60+
};
61+
let mut sysctls: Vec<(String, String)> = linux
62+
.sysctls
63+
.iter()
64+
.map(|(name, value)| (name.clone(), value.clone()))
65+
.collect();
66+
sysctls.sort();
67+
sysctls
68+
}
69+
5370
fn parse_hostname(config: &PodSandboxConfig) -> Result<Option<String>> {
5471
let hostname = config.hostname.trim();
5572
if hostname.is_empty() {
@@ -232,6 +249,40 @@ mod tests {
232249
assert_eq!(box_config.image, DEFAULT_AGENT_IMAGE);
233250
}
234251

252+
#[test]
253+
fn test_sysctls_extracted_and_sorted() {
254+
use crate::cri_api::LinuxPodSandboxConfig;
255+
let mut config = make_config(HashMap::new());
256+
config.linux = Some(LinuxPodSandboxConfig {
257+
sysctls: HashMap::from([
258+
(
259+
"net.ipv4.ip_local_port_range".to_string(),
260+
"1024 65000".to_string(),
261+
),
262+
("kernel.shm_rmid_forced".to_string(), "1".to_string()),
263+
]),
264+
..Default::default()
265+
});
266+
let box_config = pod_sandbox_config_to_box_config(&config, DEFAULT_AGENT_IMAGE).unwrap();
267+
assert_eq!(
268+
box_config.sysctls,
269+
vec![
270+
("kernel.shm_rmid_forced".to_string(), "1".to_string()),
271+
(
272+
"net.ipv4.ip_local_port_range".to_string(),
273+
"1024 65000".to_string()
274+
),
275+
]
276+
);
277+
}
278+
279+
#[test]
280+
fn test_sysctls_empty_without_linux_config() {
281+
let config = make_config(HashMap::new());
282+
let box_config = pod_sandbox_config_to_box_config(&config, DEFAULT_AGENT_IMAGE).unwrap();
283+
assert!(box_config.sysctls.is_empty());
284+
}
285+
235286
#[test]
236287
fn test_empty_default_agent_image_without_annotation_is_rejected() {
237288
let config = make_config(HashMap::new());

src/guest/init/src/host_config.rs

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,41 @@
1-
//! Guest hostname configuration.
1+
//! Guest hostname and sysctl configuration.
22
33
use std::path::Path;
44

5-
/// Apply hostname configuration from `BOX_HOSTNAME`, if present.
5+
/// Apply host configuration from the boot environment: pod sysctls and, if
6+
/// present, the hostname.
67
pub fn apply_from_env() -> Result<(), Box<dyn std::error::Error>> {
8+
apply_sysctls_from_env();
9+
710
let Ok(hostname) = std::env::var("BOX_HOSTNAME") else {
811
return Ok(());
912
};
1013
apply_hostname(&hostname, Path::new("/etc/hostname"))
1114
}
1215

16+
/// Apply pod sysctls passed as `BOX_SYSCTL_<index>=<name>=<value>`.
17+
///
18+
/// Each is written to `/proc/sys/<name with '.' as '/'>`. Best-effort: a sysctl
19+
/// the guest kernel does not expose is logged and skipped rather than aborting
20+
/// VM startup.
21+
fn apply_sysctls_from_env() {
22+
let mut index = 0;
23+
loop {
24+
let Ok(spec) = std::env::var(format!("BOX_SYSCTL_{index}")) else {
25+
break;
26+
};
27+
index += 1;
28+
let Some((name, value)) = spec.split_once('=') else {
29+
continue;
30+
};
31+
let path = format!("/proc/sys/{}", name.trim().replace('.', "/"));
32+
match std::fs::write(&path, value) {
33+
Ok(()) => tracing::info!("Applied sysctl {name}={value}"),
34+
Err(e) => tracing::warn!("Failed to apply sysctl {name}={value} ({path}): {e}"),
35+
}
36+
}
37+
}
38+
1339
fn apply_hostname(hostname: &str, hostname_path: &Path) -> Result<(), Box<dyn std::error::Error>> {
1440
a3s_box_core::dns::validate_hostname(hostname)
1541
.map_err(|e| format!("invalid BOX_HOSTNAME: {e}"))?;

src/runtime/src/vm/spec.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,12 @@ impl VmManager {
175175
env.push((format!("BOX_TMPFS_{}", i), tmpfs_spec.clone()));
176176
}
177177

178+
// Pass pod sysctls to guest init.
179+
// Format: BOX_SYSCTL_<index>=<name>=<value>
180+
for (i, (name, value)) in self.config.sysctls.iter().enumerate() {
181+
env.push((format!("BOX_SYSCTL_{}", i), format!("{}={}", name, value)));
182+
}
183+
178184
// Pass security configuration to guest init
179185
let security_config = a3s_box_core::SecurityConfig::from_options(
180186
&self.config.security_opt,

0 commit comments

Comments
 (0)