From 1ddf8a22e841aafce0f24d57ca56f85dfe96af60 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Sat, 6 Jul 2024 17:28:26 +0900 Subject: [PATCH 1/2] Add dynamic UNIX socket forwarding Unconditionally forward a range of ports to sockets named `$XDG_RUNTIME_DIR/krun/socket/port-$PORT`. This allows applications to dynamically bind to sockets at those paths and have them forwarded to the guest as vsock ports, after the krun VM is already running. The port range is currently hardcoded as 50000..50200. Signed-off-by: Asahi Lina Signed-off-by: Sasha Finkelstein --- crates/muvm/src/bin/muvm.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/crates/muvm/src/bin/muvm.rs b/crates/muvm/src/bin/muvm.rs index c785f42f..f2bda281 100644 --- a/crates/muvm/src/bin/muvm.rs +++ b/crates/muvm/src/bin/muvm.rs @@ -297,6 +297,7 @@ fn main() -> Result<()> { return Err(err).context("Failed to configure vsock for pulse socket"); } } + let hidpipe_path = Path::new(&run_path).join("hidpipe"); spawn_hidpipe_server(hidpipe_path.clone()).context("Failed to spawn hidpipe thread")?; let hidpipe_path = CString::new( @@ -312,6 +313,25 @@ fn main() -> Result<()> { let err = Errno::from_raw_os_error(-err); return Err(err).context("Failed to configure vsock for hidpipe socket"); } + + let socket_dir = Path::new(&run_path).join("krun/socket"); + std::fs::create_dir_all(&socket_dir)?; + // Dynamic ports: Applications may listen on these sockets as neeeded. + for port in 50000..50200 { + let socket_path = socket_dir.join(format!("port-{}", port)); + let socket_path = CString::new( + socket_path + .to_str() + .expect("socket_path should not contain invalid UTF-8"), + ) + .context("Failed to process dynamic socket path as it contains NUL character")?; + // SAFETY: `socket_path` is a pointer to a `CString` with long enough lifetime. + let err = unsafe { krun_add_vsock_port(ctx_id, port, socket_path.as_ptr()) }; + if err < 0 { + let err = Errno::from_raw_os_error(-err); + return Err(err).context("Failed to configure vsock for dynamic socket"); + } + } } // Forward the native X11 display into the guest as a socket From 203f1c6adc0392db4ee0e6d9092c43a257e819d1 Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Sat, 6 Jul 2024 19:11:24 +0200 Subject: [PATCH 2/2] Add interactive launch mode. After launching a process via the server, krun immediately closes and the launched process' std{in,out,err} go nowhere. Add a --interactive flag that allocates a pseudo-terminal, routes it to the host and connects to it. Somewhat like '-it' in docker/podman exec. Signed-off-by: Sasha Finkelstein --- crates/muvm/src/bin/muvm.rs | 5 ++- crates/muvm/src/cli_options.rs | 7 +++- crates/muvm/src/launch.rs | 72 +++++++++++++++++++++++++++++++++- 3 files changed, 79 insertions(+), 5 deletions(-) diff --git a/crates/muvm/src/bin/muvm.rs b/crates/muvm/src/bin/muvm.rs index f2bda281..ad18ead3 100644 --- a/crates/muvm/src/bin/muvm.rs +++ b/crates/muvm/src/bin/muvm.rs @@ -16,7 +16,7 @@ use muvm::cli_options::options; use muvm::cpu::{get_fallback_cores, get_performance_cores}; use muvm::env::{find_muvm_exec, prepare_env_vars}; use muvm::hidpipe_server::spawn_hidpipe_server; -use muvm::launch::{launch_or_lock, LaunchResult}; +use muvm::launch::{launch_or_lock, LaunchResult, DYNAMIC_PORT_RANGE}; use muvm::monitor::spawn_monitor; use muvm::net::{connect_to_passt, start_passt}; use muvm::types::MiB; @@ -74,6 +74,7 @@ fn main() -> Result<()> { options.command, options.command_args, options.env, + options.interactive, )? { LaunchResult::LaunchRequested => { // There was a muvm instance already running and we've requested it @@ -317,7 +318,7 @@ fn main() -> Result<()> { let socket_dir = Path::new(&run_path).join("krun/socket"); std::fs::create_dir_all(&socket_dir)?; // Dynamic ports: Applications may listen on these sockets as neeeded. - for port in 50000..50200 { + for port in DYNAMIC_PORT_RANGE { let socket_path = socket_dir.join(format!("port-{}", port)); let socket_path = CString::new( socket_path diff --git a/crates/muvm/src/cli_options.rs b/crates/muvm/src/cli_options.rs index d5bd212f..8278c556 100644 --- a/crates/muvm/src/cli_options.rs +++ b/crates/muvm/src/cli_options.rs @@ -17,6 +17,7 @@ pub struct Options { pub server_port: u32, pub fex_images: Vec, pub direct_x11: bool, + pub interactive: bool, pub command: PathBuf, pub command_args: Vec, } @@ -116,7 +117,10 @@ pub fn options() -> OptionParser { "--direct-x11 requires the `x11bridge` crate feature", ) .hide(); - + let interactive = long("interactive") + .short('i') + .help("Allocate a tty guest-side and connect it to the current stdin/out") + .switch(); let command = positional("COMMAND").help("the command you want to execute in the vm"); let command_args = any::("COMMAND_ARGS", |arg| { (!["--help", "-h"].contains(&&*arg)).then_some(arg) @@ -134,6 +138,7 @@ pub fn options() -> OptionParser { server_port, fex_images, direct_x11, + interactive, // positionals command, command_args, diff --git a/crates/muvm/src/launch.rs b/crates/muvm/src/launch.rs index 69e4948b..2071276f 100644 --- a/crates/muvm/src/launch.rs +++ b/crates/muvm/src/launch.rs @@ -11,8 +11,14 @@ use anyhow::{anyhow, Context, Result}; use rustix::fs::{flock, FlockOperation}; use uuid::Uuid; +use super::utils::env::find_in_path; use crate::env::prepare_env_vars; use crate::utils::launch::Launch; +use rustix::path::Arg; +use std::ops::Range; +use std::process::{Child, Command}; + +pub const DYNAMIC_PORT_RANGE: Range = 50000..50200; pub enum LaunchResult { LaunchRequested, @@ -50,18 +56,79 @@ impl Display for LaunchError { } } +fn start_socat() -> Result<(Child, u32)> { + let run_path = env::var("XDG_RUNTIME_DIR") + .map_err(|e| anyhow!("unable to get XDG_RUNTIME_DIR: {:?}", e))?; + let socket_dir = Path::new(&run_path).join("krun/socket"); + let socat_path = + find_in_path("socat")?.ok_or_else(|| anyhow!("Unable to find socat in PATH"))?; + for port in DYNAMIC_PORT_RANGE { + let path = socket_dir.join(format!("port-{}", port)); + if path.exists() { + continue; + } + let child = Command::new(&socat_path) + .arg(format!("unix-l:{}", path.as_os_str().to_string_lossy())) + .arg("-,raw,echo=0") + .spawn()?; + return Ok((child, port)); + } + Err(anyhow!("Ran out of ports.")) +} + +fn escape_for_socat(s: String) -> String { + let mut ret = String::with_capacity(s.len()); + for c in s.chars() { + match c { + ':' | ',' | '!' | '"' | '\'' | '\\' | '(' | '[' | '{' => { + ret.push('\\'); + }, + _ => {}, + } + ret.push(c); + } + ret +} + +fn wrapped_launch( + server_port: u32, + cookie: Uuid, + mut command: PathBuf, + mut command_args: Vec, + env: HashMap, + interactive: bool, +) -> Result<()> { + if !interactive { + return request_launch(server_port, cookie, command, command_args, env); + } + let (mut socat, vsock_port) = start_socat()?; + command_args.insert(0, command.to_string_lossy().into_owned()); + command_args = vec![ + format!("vsock:2:{}", vsock_port), + format!( + "exec:{},pty,setsid,stderr", + escape_for_socat(command_args.join(" ")) + ), + ]; + command = "socat".into(); + request_launch(server_port, cookie, command, command_args, env)?; + socat.wait()?; + Ok(()) +} + pub fn launch_or_lock( server_port: u32, command: PathBuf, command_args: Vec, env: Vec<(String, Option)>, + interactive: bool, ) -> Result { let running_server_port = env::var("MUVM_SERVER_PORT").ok(); if let Some(port) = running_server_port { let port: u32 = port.parse()?; let env = prepare_env_vars(env)?; let cookie = read_cookie()?; - if let Err(err) = request_launch(port, cookie, command, command_args, env) { + if let Err(err) = wrapped_launch(port, cookie, command, command_args, env, interactive) { return Err(anyhow!("could not request launch to server: {err}")); } return Ok(LaunchResult::LaunchRequested); @@ -80,12 +147,13 @@ pub fn launch_or_lock( let env = prepare_env_vars(env)?; let mut tries = 0; loop { - match request_launch( + match wrapped_launch( server_port, cookie, command.clone(), command_args.clone(), env.clone(), + interactive, ) { Err(err) => match err.downcast_ref::() { Some(&LaunchError::Connection(_)) => {