Skip to content

Commit 43ae636

Browse files
jakecorrentitylerfanelli
authored andcommitted
Add support for none and unix URI schemes
Vfkit supports the schemes `unix` and `none` in addition to `tcp`. Krunkit currently only implements `tcp`, so implement the remaining schemes to meet vfkit parity. Signed-off-by: Jake Correnti <jakecorrenti+github@proton.me>
1 parent bb5cb8f commit 43ae636

5 files changed

Lines changed: 215 additions & 62 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@ mac_address = "1.1.5"
1515
sysinfo = "0.31.4"
1616
log = "0.4.0"
1717
env_logger = "0.11.8"
18+
regex = "1.11.1"

src/cmdline.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// SPDX-License-Identifier: Apache-2.0
22

3-
use crate::{status::RestfulUriAddr, virtio::VirtioDeviceConfig};
3+
use crate::{status::RestfulUri, virtio::VirtioDeviceConfig};
44

55
use std::{collections::HashMap, path::PathBuf, str::FromStr};
66

@@ -29,7 +29,7 @@ pub struct Args {
2929

3030
/// URI of the status/shutdown listener.
3131
#[arg(long = "restful-uri")]
32-
pub restful_uri: Option<RestfulUriAddr>,
32+
pub restful_uri: Option<RestfulUri>,
3333

3434
/// GUI option for compatibility with vfkit (ignored).
3535
#[arg(long, default_value_t = false)]
@@ -514,8 +514,10 @@ mod tests {
514514

515515
let restful_uri = args.restful_uri.expect("restful-uri argument not found");
516516

517-
assert_eq!(restful_uri.ip_addr, Ipv4Addr::new(127, 0, 0, 1));
518-
assert_eq!(restful_uri.port, 49573);
517+
assert_eq!(
518+
restful_uri,
519+
RestfulUri::Tcp(Ipv4Addr::new(127, 0, 0, 1), 49573)
520+
);
519521

520522
assert_eq!(args.gui, true);
521523
assert_eq!(args.krun_log_level, 5);

src/context.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
use super::*;
44

55
use crate::{
6-
status::{get_shutdown_eventfd, status_listener},
6+
status::{get_shutdown_eventfd, status_listener, RestfulUri},
77
virtio::KrunContextSet,
88
};
99

@@ -125,7 +125,9 @@ impl KrunContext {
125125
let shutdown_eventfd = unsafe { get_shutdown_eventfd(self.id) };
126126
let uri = self.args.restful_uri.clone();
127127

128-
thread::spawn(move || status_listener(shutdown_eventfd, uri).unwrap());
128+
if uri != Some(RestfulUri::None) {
129+
thread::spawn(move || status_listener(shutdown_eventfd, uri).unwrap());
130+
}
129131

130132
// Run the workload.
131133
if unsafe { krun_start_enter(self.id) } < 0 {

src/status.rs

Lines changed: 203 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,16 @@
22

33
use std::{
44
fs::File,
5-
io::{Read, Write},
5+
io::{ErrorKind, Read, Write},
66
net::{Ipv4Addr, TcpListener},
7-
os::fd::{FromRawFd, RawFd},
7+
os::{
8+
fd::{FromRawFd, RawFd},
9+
unix::net::UnixListener,
10+
},
811
str::FromStr,
912
};
1013

1114
use anyhow::{anyhow, Context};
12-
use clap::Parser;
1315

1416
#[link(name = "krun-efi")]
1517
extern "C" {
@@ -22,51 +24,84 @@ const HTTP_RUNNING: &str =
2224
const HTTP_STOPPING: &str =
2325
"HTTP/1.1 200 OK\r\nContent-type: application/json\r\n\r\n{\"state\": \"VirtualMachineStateStopping\"}\0";
2426

25-
/// Socket address in which the restful URI socket should listen on. Identical to Rust's
26-
/// SocketAddrV4, but requires a modified FromStr implementation due to how the address is
27-
/// presented on the command line.
28-
#[derive(Clone, Debug, Parser)]
29-
pub struct RestfulUriAddr {
30-
pub ip_addr: Ipv4Addr,
31-
pub port: u16,
27+
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
28+
pub enum UriScheme {
29+
#[default]
30+
Tcp,
31+
Unix,
32+
None,
3233
}
3334

34-
impl FromStr for RestfulUriAddr {
35+
impl FromStr for UriScheme {
3536
type Err = anyhow::Error;
3637

3738
fn from_str(s: &str) -> Result<Self, Self::Err> {
38-
let mut string = String::from(s);
39-
40-
if let Some(removed) = string.strip_prefix("tcp://") {
41-
string = String::from(removed);
39+
match s {
40+
"tcp" => Ok(Self::Tcp),
41+
"unix" => Ok(Self::Unix),
42+
"none" => Ok(Self::None),
43+
_ => Err(anyhow!("invalid scheme")),
4244
}
45+
}
46+
}
4347

44-
let mut parts: Vec<String> = string.split(':').map(|s| s.to_string()).collect();
45-
if parts.len() != 2 {
46-
return Err(anyhow!("restful URI formatted incorrectly"));
47-
}
48+
/// Socket address in which the restful URI socket should listen on. Identical to Rust's
49+
/// SocketAddrV4, but requires a modified FromStr implementation due to how the address is
50+
/// presented on the command line.
51+
#[derive(Clone, Debug, PartialEq)]
52+
pub enum RestfulUri {
53+
Tcp(Ipv4Addr, u16),
54+
Unix(String),
55+
None,
56+
}
4857

49-
// Ipv4Address's FromStr does not understand that the "localhost" IP address translates to
50-
// 127.0.0.1, this must be manually translated.
51-
if &parts[0][..] == "localhost" {
52-
parts[0] = String::from("127.0.0.1");
58+
impl FromStr for RestfulUri {
59+
type Err = anyhow::Error;
60+
61+
fn from_str(s: &str) -> Result<Self, Self::Err> {
62+
let expression = regex::Regex::new(r"^(?P<scheme>none|tcp|unix)://(?P<value>.*)").unwrap();
63+
let Some(cap) = expression.captures(s) else {
64+
return Err(anyhow!("invalid scheme input"));
65+
};
66+
let scheme = &cap["scheme"];
67+
let value = &cap["value"];
68+
match UriScheme::from_str(scheme)? {
69+
UriScheme::Tcp => {
70+
let (ip_addr, port) = parse_tcp_input(value)?;
71+
Ok(Self::Tcp(ip_addr, port))
72+
}
73+
UriScheme::Unix => {
74+
if value.is_empty() {
75+
return Err(anyhow!("empty unix socket path"));
76+
}
77+
Ok(Self::Unix(value.to_string()))
78+
}
79+
UriScheme::None => Ok(Self::None),
5380
}
81+
}
82+
}
5483

55-
let ip_addr = Ipv4Addr::from_str(&parts[0])
56-
.context("restful URI IP address formatted incorrectly")?;
57-
let port =
58-
u16::from_str(&parts[1]).context("restful URI port number formatted incorrectly")?;
84+
fn parse_tcp_input(input: &str) -> Result<(Ipv4Addr, u16), anyhow::Error> {
85+
let mut parts: Vec<String> = input.split(':').map(|s| s.to_string()).collect();
86+
if parts.len() != 2 {
87+
return Err(anyhow!("restful URI formatted incorrectly"));
88+
}
5989

60-
Ok(Self { ip_addr, port })
90+
// Ipv4Address's FromStr does not understand that the "localhost" IP address translates to
91+
// 127.0.0.1, this must be manually translated.
92+
if &parts[0][..] == "localhost" {
93+
parts[0] = String::from("127.0.0.1");
6194
}
95+
96+
let ip_addr =
97+
Ipv4Addr::from_str(&parts[0]).context("restful URI IP address formatted incorrectly")?;
98+
let port = u16::from_str(&parts[1]).context("restful URI port number formatted incorrectly")?;
99+
Ok((ip_addr, port))
62100
}
63101

64-
impl Default for RestfulUriAddr {
102+
impl Default for RestfulUri {
65103
fn default() -> Self {
66-
Self {
67-
ip_addr: Ipv4Addr::new(127, 0, 0, 1),
68-
port: 8081,
69-
}
104+
Self::Tcp(Ipv4Addr::new(127, 0, 0, 1), 8081)
70105
}
71106
}
72107

@@ -79,42 +114,154 @@ pub unsafe fn get_shutdown_eventfd(ctx_id: u32) -> i32 {
79114
fd
80115
}
81116

117+
fn handle_incoming_stream<T: Read + Write>(stream: &mut T, shutdown_fd: &mut File) {
118+
let mut buf = [0u8; 4096];
119+
match stream.read(&mut buf) {
120+
Ok(_sz) => {
121+
let request = String::from_utf8_lossy(&buf);
122+
if request.contains("POST") {
123+
// Send a VirtualMachineStateStopping message to the client.
124+
if let Err(e) = stream.write_all(HTTP_STOPPING.as_bytes()) {
125+
println!("Error writing POST response: {e}");
126+
}
127+
128+
// Shut down the VM.
129+
if let Err(e) = shutdown_fd.write_all(&1u64.to_le_bytes()) {
130+
println!("Error writing to shutdown fd: {e}");
131+
}
132+
} else if let Err(e) = stream.write_all(HTTP_RUNNING.as_bytes()) {
133+
println!("Error writing GET response: {e}");
134+
}
135+
}
136+
Err(e) => println!("Error reading stream: {}", e),
137+
}
138+
}
139+
82140
/// Listen for status and shutdown requests from the client. Shut down the krun VM when prompted.
83141
pub fn status_listener(
84142
shutdown_eventfd: RawFd,
85-
addr: Option<RestfulUriAddr>,
143+
addr: Option<RestfulUri>,
86144
) -> Result<(), anyhow::Error> {
87145
// VM is shut down by writing to the shutdown event file.
88146
let mut shutdown = unsafe { File::from_raw_fd(shutdown_eventfd) };
89147

90148
let addr = addr.unwrap_or_default();
91149

92-
let listener = TcpListener::bind((addr.ip_addr, addr.port)).unwrap();
93-
94-
for stream in listener.incoming() {
95-
let mut buf = [0u8; 4096];
96-
let mut stream = stream.unwrap();
97-
98-
match stream.read(&mut buf) {
99-
Ok(_sz) => {
100-
let request = String::from_utf8_lossy(&buf);
101-
if request.contains("POST") {
102-
// Send a VirtualMachineStateStopping message to the client.
103-
if let Err(e) = stream.write_all(HTTP_STOPPING.as_bytes()) {
104-
println!("Error writting POST response: {e}");
105-
}
106-
107-
// Shut down the VM.
108-
if let Err(e) = shutdown.write_all(&1u64.to_le_bytes()) {
109-
println!("Error writting to shutdown fd: {e}");
110-
}
111-
} else if let Err(e) = stream.write_all(HTTP_RUNNING.as_bytes()) {
112-
println!("Error writting GET response: {e}");
150+
match addr {
151+
RestfulUri::Tcp(addr, port) => {
152+
let listener = TcpListener::bind((addr, port))
153+
.map_err(|e| anyhow!("Unable to bind to TCP listener: {}", e))?;
154+
155+
for stream in listener.incoming() {
156+
handle_incoming_stream(&mut stream.unwrap(), &mut shutdown)
157+
}
158+
}
159+
RestfulUri::Unix(path) => {
160+
if let Err(e) = std::fs::remove_file(&path) {
161+
if e.kind() != ErrorKind::NotFound {
162+
return Err(anyhow!("failed to remove socket with error {e}"));
113163
}
114164
}
115-
Err(e) => println!("Error reading stream: {}", e),
165+
let listener = UnixListener::bind(path)
166+
.map_err(|e| anyhow!("Unable to bind to unix socket: {}", e))?;
167+
168+
for stream in listener.incoming() {
169+
handle_incoming_stream(&mut stream.unwrap(), &mut shutdown)
170+
}
116171
}
172+
RestfulUri::None => unreachable!(),
117173
}
118174

119175
Ok(())
120176
}
177+
178+
#[allow(unused_imports)]
179+
mod tests {
180+
use super::*;
181+
182+
#[test]
183+
fn parse_valid_unix_scheme() {
184+
assert_eq!(
185+
RestfulUri::Unix("/tmp/path".to_string()),
186+
RestfulUri::from_str("unix:///tmp/path").unwrap()
187+
);
188+
}
189+
190+
#[test]
191+
fn parse_unix_scheme_missing_path() {
192+
assert_eq!(
193+
anyhow!("empty unix socket path").to_string(),
194+
RestfulUri::from_str("unix://").err().unwrap().to_string()
195+
);
196+
}
197+
198+
#[test]
199+
fn parse_unix_scheme_missing_slashes() {
200+
assert_eq!(
201+
anyhow!("invalid scheme input").to_string(),
202+
RestfulUri::from_str("unix:").err().unwrap().to_string()
203+
);
204+
}
205+
206+
#[test]
207+
fn parse_unix_scheme_misspelling() {
208+
assert_eq!(
209+
anyhow!("invalid scheme input").to_string(),
210+
RestfulUri::from_str("uni://path")
211+
.err()
212+
.unwrap()
213+
.to_string()
214+
);
215+
}
216+
217+
#[test]
218+
fn parse_valid_tcp_scheme() {
219+
assert_eq!(
220+
RestfulUri::Tcp(Ipv4Addr::new(127, 0, 0, 1), 8080),
221+
RestfulUri::from_str("tcp://localhost:8080").unwrap(),
222+
);
223+
}
224+
225+
#[test]
226+
fn parse_tcp_scheme_missing_port() {
227+
assert_eq!(
228+
anyhow!("restful URI formatted incorrectly").to_string(),
229+
RestfulUri::from_str("tcp://localhost")
230+
.err()
231+
.unwrap()
232+
.to_string()
233+
);
234+
}
235+
236+
#[test]
237+
fn parse_tcp_scheme_with_unix_path() {
238+
assert_eq!(
239+
anyhow!("restful URI formatted incorrectly").to_string(),
240+
RestfulUri::from_str("tcp:///tmp/path")
241+
.err()
242+
.unwrap()
243+
.to_string(),
244+
);
245+
}
246+
247+
#[test]
248+
fn parse_valid_none_scheme() {
249+
assert_eq!(RestfulUri::None, RestfulUri::from_str("none://").unwrap());
250+
}
251+
252+
#[test]
253+
fn parse_none_scheme_missing_postfix() {
254+
assert_eq!(
255+
anyhow!("invalid scheme input").to_string(),
256+
RestfulUri::from_str("none").err().unwrap().to_string(),
257+
);
258+
}
259+
260+
#[test]
261+
fn parse_random_string_scheme() {
262+
assert_eq!(
263+
anyhow!("invalid scheme input").to_string(),
264+
RestfulUri::from_str("foobar").err().unwrap().to_string(),
265+
);
266+
}
267+
}

0 commit comments

Comments
 (0)