diff --git a/Cargo.lock b/Cargo.lock index 368f8e50..086767c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -81,6 +81,12 @@ version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25bdb32cbbdce2b519a9cd7df3a678443100e265d5e25ca763b7572a5104f5f3" +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + [[package]] name = "backtrace" version = "0.3.72" @@ -227,17 +233,94 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] [[package]] name = "getrandom" @@ -372,9 +455,12 @@ dependencies = [ "bpaf", "byteorder", "env_logger", + "futures-util", "krun-sys", "log", - "nix", + "nix 0.28.0", + "regex", + "rtnetlink", "rustix", "serde", "serde_json", @@ -384,6 +470,82 @@ dependencies = [ "uuid", ] +[[package]] +name = "netlink-packet-core" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72724faf704479d67b388da142b186f916188505e7e0b26719019c525882eda4" +dependencies = [ + "anyhow", + "byteorder", + "netlink-packet-utils", +] + +[[package]] +name = "netlink-packet-route" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74c171cd77b4ee8c7708da746ce392440cb7bcf618d122ec9ecc607b12938bf4" +dependencies = [ + "anyhow", + "byteorder", + "libc", + "log", + "netlink-packet-core", + "netlink-packet-utils", +] + +[[package]] +name = "netlink-packet-utils" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ede8a08c71ad5a95cdd0e4e52facd37190977039a4704eb82a283f713747d34" +dependencies = [ + "anyhow", + "byteorder", + "paste", + "thiserror", +] + +[[package]] +name = "netlink-proto" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b33524dc0968bfad349684447bfce6db937a9ac3332a1fe60c0c5a5ce63f21" +dependencies = [ + "bytes", + "futures", + "log", + "netlink-packet-core", + "netlink-sys", + "thiserror", + "tokio", +] + +[[package]] +name = "netlink-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "416060d346fbaf1f23f9512963e3e878f1a78e707cb699ba9215761754244307" +dependencies = [ + "bytes", + "futures", + "libc", + "log", + "tokio", +] + +[[package]] +name = "nix" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" +dependencies = [ + "bitflags", + "cfg-if", + "libc", +] + [[package]] name = "nix" version = "0.28.0" @@ -425,12 +587,24 @@ dependencies = [ "memchr", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "pin-project-lite" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "proc-macro2" version = "1.0.82" @@ -451,9 +625,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.4" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" dependencies = [ "aho-corasick", "memchr", @@ -463,9 +637,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.6" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", @@ -474,9 +648,27 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.3" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rtnetlink" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" +checksum = "b684475344d8df1859ddb2d395dd3dac4f8f3422a1aa0725993cb375fc5caba5" +dependencies = [ + "futures", + "log", + "netlink-packet-core", + "netlink-packet-route", + "netlink-packet-utils", + "netlink-proto", + "netlink-sys", + "nix 0.27.1", + "thiserror", + "tokio", +] [[package]] name = "rustc-demangle" @@ -555,6 +747,15 @@ dependencies = [ "libc", ] +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + [[package]] name = "socket2" version = "0.5.7" @@ -588,6 +789,26 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "thiserror" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tokio" version = "1.38.0" diff --git a/crates/muvm/Cargo.toml b/crates/muvm/Cargo.toml index f9f1d83b..8095a55e 100644 --- a/crates/muvm/Cargo.toml +++ b/crates/muvm/Cargo.toml @@ -13,10 +13,13 @@ anyhow = { version = "1.0.82", default-features = false, features = ["std"] } bpaf = { version = "0.9.11", default-features = false, features = [] } byteorder = { version = "1.5.0", default-features = false, features = ["std"] } env_logger = { version = "0.11.3", default-features = false, features = ["auto-color", "humantime", "unstable-kv"] } +futures-util = { version = "0.3.30", default-features = false, features = [] } krun-sys = { path = "../krun-sys", version = "1.9.1", default-features = false, features = [] } log = { version = "0.4.21", default-features = false, features = ["kv"] } nix = { version = "0.28.0", default-features = false, features = ["user"] } +regex = { version = "1.10.6" } rustix = { version = "0.38.34", default-features = false, features = ["fs", "mount", "process", "std", "system", "use-libc-auxv"] } +rtnetlink = { version = "0.14.1" } serde = { version = "1.0.203", default-features = false, features = ["derive"] } serde_json = { version = "1.0.117", default-features = false, features = ["std"] } tempfile = { version = "3.10.1", default-features = false, features = [] } diff --git a/crates/muvm/src/bin/muvm.rs b/crates/muvm/src/bin/muvm.rs index 5ea49591..49245165 100644 --- a/crates/muvm/src/bin/muvm.rs +++ b/crates/muvm/src/bin/muvm.rs @@ -233,13 +233,19 @@ fn main() -> Result<()> { } } + let mut env = prepare_env_vars(env).context("Failed to prepare environment variables")?; + env.insert( + "MUVM_SERVER_PORT".to_owned(), + options.server_port.to_string(), + ); + { let passt_fd: OwnedFd = if let Some(passt_socket) = options.passt_socket { connect_to_passt(passt_socket) .context("Failed to connect to `passt`")? .into() } else { - start_passt(options.server_port) + start_passt(options.server_port, &mut env) .context("Failed to start `passt`")? .into() }; @@ -355,12 +361,6 @@ fn main() -> Result<()> { muvm_guest_args.push(arg); } - let mut env = prepare_env_vars(env).context("Failed to prepare environment variables")?; - env.insert( - "MUVM_SERVER_PORT".to_owned(), - options.server_port.to_string(), - ); - let mut krun_config = KrunConfig { args: Vec::new(), envs: Vec::new(), diff --git a/crates/muvm/src/guest/bin/muvm-guest.rs b/crates/muvm/src/guest/bin/muvm-guest.rs index 311b2caf..a82040b4 100644 --- a/crates/muvm/src/guest/bin/muvm-guest.rs +++ b/crates/muvm/src/guest/bin/muvm-guest.rs @@ -1,4 +1,5 @@ use std::cmp; +use std::env; use std::os::unix::process::CommandExt as _; use std::process::Command; @@ -7,7 +8,7 @@ use log::debug; use muvm::guest::cli_options::options; use muvm::guest::fex::setup_fex; use muvm::guest::mount::mount_filesystems; -use muvm::guest::net::configure_network; +use muvm::guest::net::{configure_network, NetworkConfig}; use muvm::guest::socket::setup_socket_proxy; use muvm::guest::sommelier::exec_sommelier; use muvm::guest::user::setup_user; @@ -16,6 +17,21 @@ use muvm::utils::env::find_in_path; use rustix::process::{getrlimit, setrlimit, Resource}; fn main() -> Result<()> { + let netconf = NetworkConfig { + address: env::var("MUVM_NETWORK_ADDRESS").ok(), + mask: env::var("MUVM_NETWORK_MASK").ok(), + router: env::var("MUVM_NETWORK_ROUTER").ok(), + dns1: env::var("MUVM_NETWORK_DNS1").ok(), + dns2: env::var("MUVM_NETWORK_DNS2").ok(), + dns3: env::var("MUVM_NETWORK_DNS3").ok(), + search: env::var("MUVM_NETWORK_SEARCH").ok(), + }; + + let rt = tokio::runtime::Runtime::new().unwrap(); + rt.block_on(async { tokio_main(netconf).await }) +} + +async fn tokio_main(netconf: NetworkConfig) -> Result<()> { env_logger::init(); let options = options().run(); @@ -42,7 +58,9 @@ fn main() -> Result<()> { setup_fex()?; - configure_network()?; + if let Err(err) = configure_network(netconf).await { + eprintln!("Couldn't configure the network in the microVM: {err}"); + } if let Some(hidpipe_client_path) = find_in_path("hidpipe-client")? { Command::new(hidpipe_client_path) diff --git a/crates/muvm/src/guest/mount.rs b/crates/muvm/src/guest/mount.rs index a816b3cf..2363d19c 100644 --- a/crates/muvm/src/guest/mount.rs +++ b/crates/muvm/src/guest/mount.rs @@ -138,8 +138,6 @@ pub fn mount_filesystems() -> Result<()> { println!("Failed to mount FEX rootfs, carrying on without.") } - place_etc("resolv.conf", None)?; - mount2( Some("binfmt_misc"), "/proc/sys/fs/binfmt_misc", diff --git a/crates/muvm/src/guest/net.rs b/crates/muvm/src/guest/net.rs index b47f2e1e..0a3bf96b 100644 --- a/crates/muvm/src/guest/net.rs +++ b/crates/muvm/src/guest/net.rs @@ -1,16 +1,26 @@ use std::fs; use std::io::Write; -use std::os::unix::process::ExitStatusExt as _; -use std::process::Command; +use std::net::{IpAddr, Ipv4Addr}; +use std::str::FromStr; -use anyhow::{anyhow, Context, Result}; -use log::debug; +use anyhow::{Context, Result}; +use futures_util::TryStreamExt; +use rtnetlink::new_connection; use rustix::system::sethostname; -use crate::utils::env::find_in_path; -use crate::utils::fs::find_executable; +use super::mount::place_etc; -pub fn configure_network() -> Result<()> { +pub struct NetworkConfig { + pub address: Option, + pub mask: Option, + pub router: Option, + pub dns1: Option, + pub dns2: Option, + pub dns3: Option, + pub search: Option, +} + +pub async fn configure_network(netconf: NetworkConfig) -> Result<()> { // Allow unprivileged users to use ping, as most distros do by default. { let mut file = fs::File::options() @@ -33,63 +43,49 @@ pub fn configure_network() -> Result<()> { sethostname(hostname.as_bytes()).context("Failed to set hostname")?; } - let dhcpcd_path = find_in_path("dhcpcd").context("Failed to check existence of `dhcpcd`")?; - let dhcpcd_path = if let Some(dhcpcd_path) = dhcpcd_path { - Some(dhcpcd_path) - } else { - find_executable("/sbin/dhcpcd").context("Failed to check existence of `/sbin/dhcpcd`")? - }; - if let Some(dhcpcd_path) = dhcpcd_path { - let output = Command::new(dhcpcd_path) - .args(["-M", "--nodev", "eth0"]) - .output() - .context("Failed to execute `dhcpcd` as child process")?; - debug!(output:?; "dhcpcd output"); - if !output.status.success() { - let err = if let Some(code) = output.status.code() { - anyhow!("`dhcpcd` process exited with status code: {code}") - } else { - anyhow!( - "`dhcpcd` process terminated by signal: {}", - output - .status - .signal() - .expect("either one of status code or signal should be set") - ) - }; - Err(err)?; - } + let address = Ipv4Addr::from_str(&netconf.address.context("Missing MUVM_NETWORK_ADDRESS")?)?; + let mask = u32::from(Ipv4Addr::from_str( + &netconf.mask.context("Missing MUVM_NETWORK_MASK")?, + )?); + let prefix = (!mask).leading_zeros() as u8; + let router = netconf.router.context("Missing MUVM_NETWORK_ROUTER")?; + let router = Ipv4Addr::from_str(&router)?; - return Ok(()); + let (connection, handle, _) = new_connection().unwrap(); + tokio::spawn(connection); + let mut links = handle.link().get().match_name("eth0".to_string()).execute(); + if let Some(link) = links.try_next().await? { + handle + .address() + .add(link.header.index, IpAddr::V4(address), prefix) + .execute() + .await?; + handle.link().set(link.header.index).up().execute().await? } + handle.route().add().v4().gateway(router).execute().await?; - let dhclient_path = - find_in_path("dhclient").context("Failed to check existence of `dhclient`")?; - let dhclient_path = if let Some(dhclient_path) = dhclient_path { - Some(dhclient_path) - } else { - find_executable("/sbin/dhclient") - .context("Failed to check existence of `/sbin/dhclient`")? - }; - let dhclient_path = - dhclient_path.ok_or_else(|| anyhow!("could not find required `dhcpcd` or `dhclient`"))?; - let output = Command::new(dhclient_path) - .output() - .context("Failed to execute `dhclient` as child process")?; - debug!(output:?; "dhclient output"); - if !output.status.success() { - let err = if let Some(code) = output.status.code() { - anyhow!("`dhclient` process exited with status code: {code}") - } else { - anyhow!( - "`dhclient` process terminated by signal: {}", - output - .status - .signal() - .expect("either one of status code or signal should be set") - ) - }; - Err(err)?; + // Only override resolv.conf is we have some values to put on it. + if netconf.dns1.is_some() || netconf.dns2.is_some() || netconf.dns3.is_some() { + place_etc("resolv.conf", None)?; + let mut resolv = fs::File::options() + .write(true) + .open("/etc/resolv.conf") + .context("Failed to open resolv.conf")?; + + for ns in [netconf.dns1, netconf.dns2, netconf.dns3] + .into_iter() + .flatten() + { + resolv + .write_all(format!("nameserver {}\n", ns).as_bytes()) + .context("Failed to write resolv.conf")?; + } + + if let Some(search) = netconf.search { + resolv + .write_all(format!("search {}\n", search).as_bytes()) + .context("Failed to write resolv.conf")?; + } } Ok(()) diff --git a/crates/muvm/src/net.rs b/crates/muvm/src/net.rs index d6431bc6..4af65d7c 100644 --- a/crates/muvm/src/net.rs +++ b/crates/muvm/src/net.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use std::os::fd::{AsRawFd, IntoRawFd}; use std::os::unix::net::UnixStream; use std::path::Path; @@ -5,6 +6,7 @@ use std::process::Command; use anyhow::{Context, Result}; use log::debug; +use regex::Regex; use rustix::io::dup; pub fn connect_to_passt

(passt_socket_path: P) -> Result @@ -14,7 +16,7 @@ where Ok(UnixStream::connect(passt_socket_path)?) } -pub fn start_passt(server_port: u32) -> Result { +pub fn start_passt(server_port: u32, env: &mut HashMap) -> Result { // SAFETY: The child process should not inherit the file descriptor of // `parent_socket`. There is no documented guarantee of this, but the // implementation as of writing atomically sets `SOCK_CLOEXEC`. @@ -38,14 +40,70 @@ pub fn start_passt(server_port: u32) -> Result { // SAFETY: `child_fd` is an `OwnedFd` and consumed to prevent closing on drop, // as it will now be owned by the child process. // See https://doc.rust-lang.org/std/io/index.html#io-safety - let child = Command::new("passt") - .args(["-q", "-f", "-t"]) + let output = Command::new("passt") + .args(["-t"]) .arg(format!("{server_port}:{server_port}")) .arg("--fd") .arg(format!("{}", child_fd.into_raw_fd())) - .spawn(); - if let Err(err) = child { - return Err(err).context("Failed to execute `passt` as child process"); + .output() + .context("Failed to execute `passt` as child process")?; + + let content = String::from_utf8(output.stderr).context("Failed to parse `passt` output")?; + + let re = + Regex::new(r".*assign: (?

.+)\n.*mask: (?.+)\n.*router: (?.+).*\n") + .unwrap(); + if let Some(caps) = re.captures(&content) { + if let Some(address) = caps.name("address") { + env.insert( + "MUVM_NETWORK_ADDRESS".to_owned(), + address.as_str().to_string(), + ); + } else { + println!("Can't read network address from passt output. Expect degraded networking"); + } + if let Some(mask) = caps.name("mask") { + env.insert("MUVM_NETWORK_MASK".to_owned(), mask.as_str().to_string()); + } else { + println!("Can't read network mask from passt output. Expect degraded networking"); + } + if let Some(router) = caps.name("router") { + env.insert( + "MUVM_NETWORK_ROUTER".to_owned(), + router.as_str().to_string(), + ); + } else { + println!("Can't read network router from passt output. Expect degraded networking"); + } + } + + if let Some(dns_index) = content.find("DNS:") { + let dns_data = &content[dns_index + 5..]; + let mut i = 1; + for line in dns_data.split("\n") { + if line.starts_with(" ") { + env.insert( + format!("MUVM_NETWORK_DNS{}", i).to_owned(), + line.trim().to_string(), + ); + if i == 3 { + // MAXNS == 3, ignore the rest. + break; + } + i += 1; + } else { + break; + } + } + } + + if let Some(search_index) = content.find("DNS search list:") { + let search_data = &content[search_index + 17..]; + for line in search_data.split("\n") { + if line.starts_with(" ") { + env.insert("MUVM_NETWORK_SEARCH".to_owned(), line.trim().to_string()); + } + } } Ok(parent_socket)