Skip to content

Commit

Permalink
Add support for force vsock on firecracker pilot
Browse files Browse the repository at this point in the history
The vsock communication between guest and host was established
with the firecracker pilot only for resume type registrations.
For those it's required because you cannot mux one serial
terminal between parallel applications running at the same
time in the guest. So in resume mode each call has its own
socket and channels. However, there is no reason to not also
support that type of data flow between guest and host for one
time calls which fires up the VM, starts the application and
shuts down the VM. This commit adds support for forcing the
vsock communication also for the simple app registration.
The default stays on serial though.
  • Loading branch information
schaefi committed Jan 26, 2024
1 parent 51e88e3 commit ac11bb5
Show file tree
Hide file tree
Showing 9 changed files with 227 additions and 139 deletions.
7 changes: 7 additions & 0 deletions doc/flake-ctl-firecracker-register.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ SYNOPSIS
--include-path <INCLUDE_PATH>...
--no-net
--resume
--force-vsock
--overlay-size <OVERLAY_SIZE>
--run-as <RUN_AS>
--target <TARGET>
Expand Down Expand Up @@ -75,6 +76,12 @@ OPTIONS
Resume the VM from previous execution. If the VM is still running,
the app will be executed inside of this VM instance

--force-vsock

Force using a vsock to communicate between guest and
host if resume is set to false. In resume mode the vsock
setup is always required.

--run-as <RUN_AS>

Name of the user to run firecracker
Expand Down
267 changes: 148 additions & 119 deletions firecracker-pilot/guestvm-tools/sci/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,25 @@ fn main() {
do_exec = true;
}

// check for resume mode
let resume;
match env::var("sci_resume").ok() {
Some(_) => { resume = true },
None => { resume = false }
}

// check for console setting
let mut console_vsock = false;
if resume {
// In resume mode we have to use vsocks to allow multiple
// commands at the same time
console_vsock = true
}
match env::var("sci_force_vsock").ok() {
Some(_) => { console_vsock = true },
None => { }
}

// mount /proc, /sys and /run, skip if already mounted
mount_basic_fs();

Expand Down Expand Up @@ -239,140 +258,150 @@ fn main() {
if ! ok {
do_reboot(ok)
}
match env::var("sci_resume").ok() {
Some(_) => {
// resume mode; check if vhost transport is loaded
let mut modprobe = Command::new(defaults::PROBE_MODULE);
modprobe.arg(defaults::VHOST_TRANSPORT);
debug(&format!(
"SCI CALL: {} -> {:?}", defaults::PROBE_MODULE, modprobe.get_args()
));
match modprobe.status() {
Ok(_) => { },
Err(error) => {
debug(&format!(
"Loading {} module failed: {}",
defaults::VHOST_TRANSPORT, error
));
}
if console_vsock {
// vsock required; check if vhost transport is loaded
let mut modprobe = Command::new(defaults::PROBE_MODULE);
modprobe.arg(defaults::VHOST_TRANSPORT);
debug(&format!(
"SCI CALL: {} -> {:?}", defaults::PROBE_MODULE, modprobe.get_args()
));
match modprobe.status() {
Ok(_) => { },
Err(error) => {
debug(&format!(
"Loading {} module failed: {}",
defaults::VHOST_TRANSPORT, error
));
}
// start vsock listener on VM_PORT, wait for command(s) in a loop
// A received command turns into a vsock stream process calling
// the command with an expected listener.
debug(&format!(
"Binding vsock CID={} on port={}",
defaults::GUEST_CID, defaults::VM_PORT
));
match VsockListener::bind_with_cid_port(
defaults::GUEST_CID, defaults::VM_PORT
) {
Ok(listener) => {
// Enter main loop
loop {
match listener.accept() {
Ok((mut stream, addr)) => {
// read command string from incoming connection
debug(&format!(
"Accepted incoming connection from: {}:{}",
addr.cid(), addr.port()
));
let mut call_str = String::new();
let mut call_buf = Vec::new();
match stream.read_to_end(&mut call_buf) {
Ok(_) => {
call_str = String::from_utf8(
call_buf.to_vec()
).unwrap();
let len_to_truncate = call_str
.trim_end()
.len();
call_str.truncate(len_to_truncate);
},
Err(error) => {
debug(&format!(
"Failed to read data {}", error
));
}
};
stream.shutdown(Shutdown::Both).unwrap();
if call_str.is_empty() {
// Caused by handshake check from the
// pilot, if the vsock connection between
// guest and host can be established
continue
}
// start vsock listener on VM_PORT, wait for command(s) in a loop
// A received command turns into a vsock stream process calling
// the command with an expected listener.
debug(&format!(
"Binding vsock CID={} on port={}",
defaults::GUEST_CID, defaults::VM_PORT
));
match VsockListener::bind_with_cid_port(
defaults::GUEST_CID, defaults::VM_PORT
) {
Ok(listener) => {
// Enter main loop
loop {
ok = true;
match listener.accept() {
Ok((mut stream, addr)) => {
// read command string from incoming connection
debug(&format!(
"Accepted incoming connection from: {}:{}",
addr.cid(), addr.port()
));
let mut call_str = String::new();
let mut call_buf = Vec::new();
match stream.read_to_end(&mut call_buf) {
Ok(_) => {
call_str = String::from_utf8(
call_buf.to_vec()
).unwrap();
let len_to_truncate = call_str
.trim_end()
.len();
call_str.truncate(len_to_truncate);
},
Err(error) => {
debug(&format!(
"Failed to read data {}", error
));
ok = false
}
};
stream.shutdown(Shutdown::Both).unwrap();
if call_str.is_empty() {
// Caused by handshake check from the
// pilot, if the vsock connection between
// guest and host can be established
continue
}
debug(&format!(
"SCI CALL RAW BUF: {:?}", call_str
));
let mut call_stack: Vec<&str> =
call_str.split(' ').collect();
let exec_port = call_stack.pop().unwrap();
let exec_cmd = call_stack.join(" ");
let mut exec_port_num = 0;
match exec_port.parse::<u32>() {
Ok(num) => { exec_port_num = num },
Err(error) => {
debug(&format!(
"Failed to parse port: {}: {}",
exec_port, error
));
ok = false
}
debug(&format!(
"SCI CALL RAW BUF: {:?}", call_str
));
let mut call_stack: Vec<&str> =
call_str.split(' ').collect();
let exec_port = call_stack.pop().unwrap();
let exec_cmd = call_stack.join(" ");
let mut exec_port_num = 0;
match exec_port.parse::<u32>() {
Ok(num) => { exec_port_num = num },
}
debug(&format!(
"CALL SCI: {}", exec_cmd
));

// Establish a VSOCK connection with the farend
let thread_handle = thread::spawn(move || {
match VsockStream::connect_with_cid_port(
2, exec_port_num
) {
Ok(vsock_stream) => {
redirect_command(
&exec_cmd, vsock_stream
);
},
Err(error) => {
debug(&format!(
"Failed to parse port: {}: {}",
exec_port, error
"VSOCK-CONNECT failed with: {}",
error
));
}
}
debug(&format!(
"CALL SCI: {}", exec_cmd
));

// Establish a VSOCK connection with the farend
thread::spawn(move || {
match VsockStream::connect_with_cid_port(
2, exec_port_num
) {
Ok(vsock_stream) => {
redirect_command(
&exec_cmd, vsock_stream
);
},
Err(error) => {
debug(&format!(
"VSOCK-CONNECT failed with: {}",
error
));
}
}
});
},
Err(error) => {
debug(&format!(
"Failed to accept incoming connection: {}",
error
));
});
if ! resume {
// Wait for the thread to finish if not in resume mode
let _ = thread_handle.join();
}
},
Err(error) => {
debug(&format!(
"Failed to accept incoming connection: {}",
error
));
ok = false
}
}
},
Err(error) => {
debug(&format!(
"Failed to bind vsock: CID: {}: {}",
defaults::GUEST_CID, error
));
ok = false

// we are not in resume mode, exit after the command is done
if ! resume {
break
}
}
}
},
None => {
// run regular command and close vm
if do_exec {
// replace ourselves
debug(&format!("EXEC: {} -> {:?}", &args[0], call.get_args()));
call.exec();
} else {
// call a command and keep control
},
Err(error) => {
debug(&format!(
"SCI CALL: {} -> {:?}", &args[0], call.get_args()
"Failed to bind vsock: CID: {}: {}",
defaults::GUEST_CID, error
));
let _ = call.status();
ok = false
}
}
} else {
// run regular command and close vm
if do_exec {
// replace ourselves
debug(&format!("EXEC: {} -> {:?}", &args[0], call.get_args()));
call.exec();
} else {
// call a command and keep control
debug(&format!(
"SCI CALL: {} -> {:?}", &args[0], call.get_args()
));
let _ = call.status();
}
}

// Close firecracker session
Expand Down
8 changes: 8 additions & 0 deletions firecracker-pilot/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,14 @@ pub struct RuntimeSection<'a> {
#[serde(default)]
pub resume: bool,

/// Force using a vsock to communicate between guest and
/// host if resume is set to false. In resume mode the
/// vsock setup is always required.
///
/// Default: false
#[serde(default)]
pub force_vsock: bool,

pub firecracker: EngineSection<'a>,
}

Expand Down
Loading

0 comments on commit ac11bb5

Please sign in to comment.