Skip to content

Commit

Permalink
Add a configuration parameter for port passthrough.
Browse files Browse the repository at this point in the history
Signed-off-by: Sasha Finkelstein <[email protected]>
  • Loading branch information
WhatAmISupposedToPutHere committed Jan 9, 2025
1 parent 050fa1c commit e7a316e
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 6 deletions.
4 changes: 3 additions & 1 deletion crates/muvm/src/bin/muvm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,9 @@ fn main() -> Result<ExitCode> {
.context("Failed to connect to `passt`")?
.into()
} else {
start_passt().context("Failed to start `passt`")?.into()
start_passt(&options.publish_ports)
.context("Failed to start `passt`")?
.into()
};
// SAFETY: `passt_fd` is an `OwnedFd` and consumed to prevent closing on drop.
// See https://doc.rust-lang.org/std/io/index.html#io-safety
Expand Down
11 changes: 11 additions & 0 deletions crates/muvm/src/cli_options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub struct Options {
pub interactive: bool,
pub tty: bool,
pub privileged: bool,
pub publish_ports: Vec<String>,
pub command: PathBuf,
pub command_args: Vec<String>,
}
Expand Down Expand Up @@ -111,6 +112,15 @@ pub fn options() -> OptionParser<Options> {
This notably does not allow root access to the host fs.",
)
.switch();
let publish_ports = long("publish")
.short('p')
.help(
"
Publish a guest’s port, or range of ports, to the host.
The syntax is similar to podman/docker.",
)
.argument::<String>("[[IP:][HOST_PORT]:]GUEST_PORT[/PROTOCOL]")
.many();
let command = positional("COMMAND").help("the command you want to execute in the vm");
let command_args = any::<String, _, _>("COMMAND_ARGS", |arg| {
(!["--help", "-h"].contains(&&*arg)).then_some(arg)
Expand All @@ -129,6 +139,7 @@ pub fn options() -> OptionParser<Options> {
interactive,
tty,
privileged,
publish_ports,
// positionals
command,
command_args,
Expand Down
81 changes: 76 additions & 5 deletions crates/muvm/src/net.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,82 @@ use anyhow::{Context, Result};
use log::debug;
use rustix::io::dup;

struct PublishSpec<'a> {
udp: bool,
guest_range: (u32, u32),
host_range: (u32, u32),
ip: &'a str,
}

fn parse_range(r: &str) -> Result<(u32, u32)> {
Ok(if let Some(pos) = r.find('-') {
(r[..pos].parse()?, r[pos + 1..].parse()?)
} else {
let val = r.parse()?;
(val, val)
})
}

impl PublishSpec<'_> {
fn parse(mut arg: &str) -> Result<PublishSpec> {
let mut udp = false;
if arg.ends_with("/udp") {
udp = true;
}
if let Some(pos) = arg.rfind('/') {
arg = &arg[..pos];
}
let guest_range_start = arg.rfind(':');
let guest_range = parse_range(&arg[guest_range_start.map(|x| x + 1).unwrap_or(0)..])?;
let mut ip = "";
let host_range = match guest_range_start {
None => guest_range,
Some(guest_range_start) => {
arg = &arg[..guest_range_start];
let ip_start = arg.rfind(':');
if let Some(ip_start) = ip_start {
ip = &arg[..ip_start];
arg = &arg[ip_start + 1..];
}
if arg.is_empty() {
guest_range
} else {
parse_range(arg)?
}
},
};
Ok(PublishSpec {
ip,
host_range,
guest_range,
udp,
})
}
fn to_args(&self) -> [String; 2] {
let optslash = if self.ip.is_empty() { "" } else { "/" };
[
if self.udp { "-u" } else { "-t" }.to_owned(),
format!(
"{}{}{}-{}:{}-{}",
self.ip,
optslash,
self.host_range.0,
self.host_range.1,
self.guest_range.0,
self.guest_range.1
),
]
}
}

pub fn connect_to_passt<P>(passt_socket_path: P) -> Result<UnixStream>
where
P: AsRef<Path>,
{
Ok(UnixStream::connect(passt_socket_path)?)
}

pub fn start_passt() -> Result<UnixStream> {
pub fn start_passt(publish_ports: &[String]) -> Result<UnixStream> {
// 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`.
Expand All @@ -35,13 +103,16 @@ pub fn start_passt() -> Result<UnixStream> {

debug!(fd = child_fd.as_raw_fd(); "passing fd to passt");

let mut cmd = Command::new("passt");
// 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", "--fd"])
.arg(format!("{}", child_fd.into_raw_fd()))
.spawn();
cmd.args(["-q", "-f", "--fd"])
.arg(format!("{}", child_fd.into_raw_fd()));
for spec in publish_ports {
cmd.args(PublishSpec::parse(spec)?.to_args());
}
let child = cmd.spawn();
if let Err(err) = child {
return Err(err).context("Failed to execute `passt` as child process");
}
Expand Down

0 comments on commit e7a316e

Please sign in to comment.