mod dispatch;
mod fd;
mod memory;
mod restore;
mod runtime;
mod shadow;
mod snapshot;
mod supervisor;
mod tracee;
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
use std::collections::BTreeMap;
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
use std::path::Path;
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
pub(crate) use runtime::{
restore_runtime_snapshot_with_replacements, KboxlikeRuntimeRestoreOptions,
};
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
pub(crate) use supervisor::{
capture_live_ptrace_rootfs_exec_snapshot_after_sigstop_request,
kill_and_reap_live_process_group, KboxlikeRootfsExecConfig,
};
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
pub(crate) use tracee::{LinuxStoppedTraceeReplacementFactory, PtraceDetachRestoredTraceeResumer};
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
const KBOXLIVE_PROXY_ENV_KEYS: &[&str] = &[
"HTTP_PROXY",
"HTTPS_PROXY",
"ALL_PROXY",
"NO_PROXY",
"http_proxy",
"https_proxy",
"all_proxy",
"no_proxy",
"npm_config_proxy",
"npm_config_https_proxy",
"npm_config_noproxy",
];
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
const KBOXLIVE_GUEST_CA_BUNDLE: &str = "/etc/ssl/certs/ca-certificates.crt";
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
const KBOXLIVE_CA_ENV_KEYS: &[&str] = &[
"SSL_CERT_FILE",
"CURL_CA_BUNDLE",
"GIT_SSL_CAINFO",
"REQUESTS_CA_BUNDLE",
"NODE_EXTRA_CA_CERTS",
"NPM_CONFIG_CAFILE",
"npm_config_cafile",
];
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
pub(crate) fn prepare_rootfs_network_config(rootfs: &Path) -> std::io::Result<()> {
if is_host_root(rootfs) {
return Ok(());
}
let etc = rootfs.join("etc");
std::fs::create_dir_all(&etc)?;
if let Some(text) = host_resolv_conf_for_kboxlike() {
atomic_write(&etc.join("resolv.conf"), text.as_bytes())?;
}
ensure_localhost_hosts_entry(&etc.join("hosts"))?;
inject_host_ca_bundle(rootfs)?;
Ok(())
}
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
pub(crate) fn add_host_proxy_env_if_absent(env: &mut BTreeMap<String, String>) {
for key in KBOXLIVE_PROXY_ENV_KEYS {
if env.contains_key(*key) {
continue;
}
if let Ok(value) = std::env::var(key) {
if !value.is_empty() {
env.insert((*key).to_owned(), value);
}
}
}
if has_host_ca_source() {
for key in KBOXLIVE_CA_ENV_KEYS {
env.entry((*key).to_owned())
.or_insert_with(|| KBOXLIVE_GUEST_CA_BUNDLE.to_owned());
}
}
}
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
pub(crate) fn merge_host_proxy_env_if_absent(env: &mut Vec<(String, String)>) {
let mut merged = env.iter().cloned().collect::<BTreeMap<_, _>>();
add_host_proxy_env_if_absent(&mut merged);
*env = merged.into_iter().collect();
}
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
fn is_host_root(path: &Path) -> bool {
path == Path::new("/")
|| path
.canonicalize()
.map(|canonical| canonical == Path::new("/"))
.unwrap_or(false)
}
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
fn host_resolv_conf_for_kboxlike() -> Option<String> {
for path in ["/run/systemd/resolve/resolv.conf", "/etc/resolv.conf"] {
let Ok(text) = std::fs::read_to_string(path) else {
continue;
};
if let Some(text) = sanitize_kboxlike_resolver(&text) {
return Some(text);
}
}
Some(
"# generated by supermachine for kboxlike networking\nnameserver 1.1.1.1\noptions timeout:2 attempts:2 single-request no-aaaa\n"
.to_owned(),
)
}
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
fn sanitize_kboxlike_resolver(text: &str) -> Option<String> {
let nameserver = text.lines().find_map(|line| {
let mut fields = line.split_whitespace();
if !matches!(fields.next(), Some("nameserver")) {
return None;
}
let addr = fields.next()?;
if addr.eq_ignore_ascii_case("localhost") {
return None;
}
Some(addr.to_owned())
})?;
Some(format!(
"# generated by supermachine for kboxlike networking\nnameserver {nameserver}\noptions timeout:2 attempts:2 single-request no-aaaa\n"
))
}
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
fn ensure_localhost_hosts_entry(path: &Path) -> std::io::Result<()> {
let existing = std::fs::read_to_string(path).unwrap_or_default();
if existing
.lines()
.any(|line| line.split_whitespace().any(|field| field == "localhost"))
{
return Ok(());
}
let mut next = existing;
if !next.is_empty() && !next.ends_with('\n') {
next.push('\n');
}
next.push_str("127.0.0.1\tlocalhost\n::1\tlocalhost\n");
atomic_write(path, next.as_bytes())
}
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
fn inject_host_ca_bundle(rootfs: &Path) -> std::io::Result<()> {
let host_pems = collect_host_ca_pems()?;
if host_pems.is_empty() {
return Ok(());
}
let guest_bundle = rootfs.join(KBOXLIVE_GUEST_CA_BUNDLE.trim_start_matches('/'));
let existing = std::fs::read_to_string(&guest_bundle).unwrap_or_default();
let mut merged = existing;
for pem in host_pems {
if !merged.contains(&pem) {
if !merged.is_empty() && !merged.ends_with('\n') {
merged.push('\n');
}
merged.push_str(&pem);
if !merged.ends_with('\n') {
merged.push('\n');
}
}
}
atomic_write(&guest_bundle, merged.as_bytes())
}
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
fn has_host_ca_source() -> bool {
host_ca_candidate_files()
.into_iter()
.any(|path| path.is_file())
|| std::env::var_os("SSL_CERT_DIR")
.map(|dir| Path::new(&dir).is_dir())
.unwrap_or(false)
}
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
fn collect_host_ca_pems() -> std::io::Result<Vec<String>> {
let mut out = Vec::new();
for path in host_ca_candidate_files() {
collect_ca_pems_from_file(&path, &mut out)?;
}
if let Some(dir) = std::env::var_os("SSL_CERT_DIR") {
collect_ca_pems_from_dir(Path::new(&dir), &mut out)?;
}
out.sort();
out.dedup();
Ok(out)
}
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
fn host_ca_candidate_files() -> Vec<std::path::PathBuf> {
let mut paths = Vec::new();
if let Some(path) = std::env::var_os("SSL_CERT_FILE") {
paths.push(path.into());
}
for path in [
"/etc/ssl/certs/ca-certificates.crt",
"/etc/pki/tls/certs/ca-bundle.crt",
"/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem",
"/etc/ssl/cert.pem",
] {
paths.push(path.into());
}
paths
}
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
fn collect_ca_pems_from_dir(path: &Path, out: &mut Vec<String>) -> std::io::Result<()> {
let Ok(entries) = std::fs::read_dir(path) else {
return Ok(());
};
for entry in entries {
let entry = entry?;
let file_type = entry.file_type()?;
if file_type.is_file() || file_type.is_symlink() {
collect_ca_pems_from_file(&entry.path(), out)?;
}
}
Ok(())
}
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
fn collect_ca_pems_from_file(path: &Path, out: &mut Vec<String>) -> std::io::Result<()> {
let Ok(text) = std::fs::read_to_string(path) else {
return Ok(());
};
out.extend(extract_pem_certificates(&text));
Ok(())
}
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
fn extract_pem_certificates(text: &str) -> Vec<String> {
const BEGIN: &str = "-----BEGIN CERTIFICATE-----";
const END: &str = "-----END CERTIFICATE-----";
let mut certs = Vec::new();
let mut rest = text;
while let Some(begin) = rest.find(BEGIN) {
rest = &rest[begin..];
let Some(end) = rest.find(END) else {
break;
};
let pem_end = end + END.len();
certs.push(format!("{}\n", rest[..pem_end].trim()));
rest = &rest[pem_end..];
}
certs
}
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
fn atomic_write(path: &Path, bytes: &[u8]) -> std::io::Result<()> {
let parent = path.parent().ok_or_else(|| {
std::io::Error::new(std::io::ErrorKind::InvalidInput, "path has no parent")
})?;
std::fs::create_dir_all(parent)?;
let tmp = parent.join(format!(
".{}.{}.tmp",
path.file_name()
.and_then(|name| name.to_str())
.unwrap_or("supermachine"),
std::process::id()
));
std::fs::write(&tmp, bytes)?;
if let Err(err) = std::fs::rename(&tmp, path) {
let _ = std::fs::remove_file(&tmp);
return Err(err);
}
Ok(())
}
#[cfg(all(test, target_os = "linux", target_arch = "x86_64"))]
mod tests {
#[test]
fn kboxlike_resolver_keeps_docker_loopback_dns() {
let out = super::sanitize_kboxlike_resolver("nameserver 127.0.0.11\noptions ndots:0\n")
.expect("resolver");
assert!(out.contains("nameserver 127.0.0.11"));
assert!(out.contains("single-request no-aaaa"));
}
#[test]
fn kboxlike_resolver_skips_localhost_name() {
assert!(super::sanitize_kboxlike_resolver("nameserver localhost\n").is_none());
}
#[test]
fn kboxlike_network_prep_skips_host_root() {
assert!(super::is_host_root(std::path::Path::new("/")));
}
#[test]
fn kboxlike_extracts_pem_certificates_from_bundle() {
let certs = super::extract_pem_certificates(
"noise\n-----BEGIN CERTIFICATE-----\nabc\n-----END CERTIFICATE-----\nnoise\n\
-----BEGIN CERTIFICATE-----\ndef\n-----END CERTIFICATE-----",
);
assert_eq!(certs.len(), 2);
assert!(certs[0].contains("abc"));
assert!(certs[1].contains("def"));
}
}