Skip to content

Commit 49a0346

Browse files
WhatAmISupposedToPutHereslp
authored andcommitted
Add a configuration parameter for port passthrough.
Signed-off-by: Sasha Finkelstein <[email protected]>
1 parent 51439ac commit 49a0346

3 files changed

Lines changed: 90 additions & 6 deletions

File tree

crates/muvm/src/bin/muvm.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,9 @@ fn main() -> Result<ExitCode> {
264264
.context("Failed to connect to `passt`")?
265265
.into()
266266
} else {
267-
start_passt().context("Failed to start `passt`")?.into()
267+
start_passt(&options.publish_ports)
268+
.context("Failed to start `passt`")?
269+
.into()
268270
};
269271
// SAFETY: `passt_fd` is an `OwnedFd` and consumed to prevent closing on drop.
270272
// See https://doc.rust-lang.org/std/io/index.html#io-safety

crates/muvm/src/cli_options.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ pub struct Options {
1818
pub interactive: bool,
1919
pub tty: bool,
2020
pub privileged: bool,
21+
pub publish_ports: Vec<String>,
2122
pub command: PathBuf,
2223
pub command_args: Vec<String>,
2324
}
@@ -111,6 +112,15 @@ pub fn options() -> OptionParser<Options> {
111112
This notably does not allow root access to the host fs.",
112113
)
113114
.switch();
115+
let publish_ports = long("publish")
116+
.short('p')
117+
.help(
118+
"
119+
Publish a guest’s port, or range of ports, to the host.
120+
The syntax is similar to podman/docker.",
121+
)
122+
.argument::<String>("[[IP:][HOST_PORT]:]GUEST_PORT[/PROTOCOL]")
123+
.many();
114124
let command = positional("COMMAND").help("the command you want to execute in the vm");
115125
let command_args = any::<String, _, _>("COMMAND_ARGS", |arg| {
116126
(!["--help", "-h"].contains(&&*arg)).then_some(arg)
@@ -129,6 +139,7 @@ pub fn options() -> OptionParser<Options> {
129139
interactive,
130140
tty,
131141
privileged,
142+
publish_ports,
132143
// positionals
133144
command,
134145
command_args,

crates/muvm/src/net.rs

Lines changed: 76 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,82 @@ use anyhow::{Context, Result};
77
use log::debug;
88
use rustix::io::dup;
99

10+
struct PublishSpec<'a> {
11+
udp: bool,
12+
guest_range: (u32, u32),
13+
host_range: (u32, u32),
14+
ip: &'a str,
15+
}
16+
17+
fn parse_range(r: &str) -> Result<(u32, u32)> {
18+
Ok(if let Some(pos) = r.find('-') {
19+
(r[..pos].parse()?, r[pos + 1..].parse()?)
20+
} else {
21+
let val = r.parse()?;
22+
(val, val)
23+
})
24+
}
25+
26+
impl PublishSpec<'_> {
27+
fn parse(mut arg: &str) -> Result<PublishSpec> {
28+
let mut udp = false;
29+
if arg.ends_with("/udp") {
30+
udp = true;
31+
}
32+
if let Some(pos) = arg.rfind('/') {
33+
arg = &arg[..pos];
34+
}
35+
let guest_range_start = arg.rfind(':');
36+
let guest_range = parse_range(&arg[guest_range_start.map(|x| x + 1).unwrap_or(0)..])?;
37+
let mut ip = "";
38+
let host_range = match guest_range_start {
39+
None => guest_range,
40+
Some(guest_range_start) => {
41+
arg = &arg[..guest_range_start];
42+
let ip_start = arg.rfind(':');
43+
if let Some(ip_start) = ip_start {
44+
ip = &arg[..ip_start];
45+
arg = &arg[ip_start + 1..];
46+
}
47+
if arg.is_empty() {
48+
guest_range
49+
} else {
50+
parse_range(arg)?
51+
}
52+
},
53+
};
54+
Ok(PublishSpec {
55+
ip,
56+
host_range,
57+
guest_range,
58+
udp,
59+
})
60+
}
61+
fn to_args(&self) -> [String; 2] {
62+
let optslash = if self.ip.is_empty() { "" } else { "/" };
63+
[
64+
if self.udp { "-u" } else { "-t" }.to_owned(),
65+
format!(
66+
"{}{}{}-{}:{}-{}",
67+
self.ip,
68+
optslash,
69+
self.host_range.0,
70+
self.host_range.1,
71+
self.guest_range.0,
72+
self.guest_range.1
73+
),
74+
]
75+
}
76+
}
77+
1078
pub fn connect_to_passt<P>(passt_socket_path: P) -> Result<UnixStream>
1179
where
1280
P: AsRef<Path>,
1381
{
1482
Ok(UnixStream::connect(passt_socket_path)?)
1583
}
1684

17-
pub fn start_passt() -> Result<UnixStream> {
85+
pub fn start_passt(publish_ports: &[String]) -> Result<UnixStream> {
1886
// SAFETY: The child process should not inherit the file descriptor of
1987
// `parent_socket`. There is no documented guarantee of this, but the
2088
// implementation as of writing atomically sets `SOCK_CLOEXEC`.
@@ -35,13 +103,16 @@ pub fn start_passt() -> Result<UnixStream> {
35103

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

106+
let mut cmd = Command::new("passt");
38107
// SAFETY: `child_fd` is an `OwnedFd` and consumed to prevent closing on drop,
39108
// as it will now be owned by the child process.
40109
// See https://doc.rust-lang.org/std/io/index.html#io-safety
41-
let child = Command::new("passt")
42-
.args(["-q", "-f", "--fd"])
43-
.arg(format!("{}", child_fd.into_raw_fd()))
44-
.spawn();
110+
cmd.args(["-q", "-f", "--fd"])
111+
.arg(format!("{}", child_fd.into_raw_fd()));
112+
for spec in publish_ports {
113+
cmd.args(PublishSpec::parse(spec)?.to_args());
114+
}
115+
let child = cmd.spawn();
45116
if let Err(err) = child {
46117
return Err(err).context("Failed to execute `passt` as child process");
47118
}

0 commit comments

Comments
 (0)