Skip to content

Commit

Permalink
Add support for starting VMs with passt network
Browse files Browse the repository at this point in the history
This required adding another field to the config. This is done by
migrating the old config to a newer version.
We are backwards compatible on configuration but not forwards compatible
(older versions of krunvm will not be able to use the config from
this version)
If we want forward compatibility, I feel like we need to ditch the confy
crate.

Signed-off-by: Matej Hrica <[email protected]>
  • Loading branch information
mtjhrc committed Nov 22, 2023
1 parent 90a8299 commit 5fc42aa
Show file tree
Hide file tree
Showing 18 changed files with 433 additions and 59 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ libc = "0.2.82"
serde = "1.0.120"
serde_derive = "1.0.120"
text_io = "0.1.8"
nix = {version = "0.27.1", features = ["socket", "fs"]}
nix = {version = "0.27.1", features = ["socket", "fs"]}
2 changes: 2 additions & 0 deletions docs/krunvm-changevm.1.txt
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ host visible in the guest.
An empty string ("") tells krunvm to not set a working directory
explicitly, letting libkrun decide which one should be set.

*--net* _NETWORK_MODE_::
Configures the network connection mode. Supported modes are either PASST or TSI.

SEE ALSO
--------
Expand Down
3 changes: 3 additions & 0 deletions docs/krunvm-config.1.txt
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ OPTIONS
Sets the default mount of RAM, in MiB, that will be configured for
newly created microVMs.

*--net* _NETWORK_MODE_::
Sets the default network connection mode, that will be configured for
newly created microVMs. Supported modes are PASST or TSI.

SEE ALSO
--------
Expand Down
2 changes: 2 additions & 0 deletions docs/krunvm-create.1.txt
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ host visible in the guest.
An empty string ("") tells krunvm to not set a working directory
explicitly, letting libkrun decide which one should be set.

*--net* _NETWORK_MODE_::
Set the network connection mode. Supported modes are either PASST or TSI.

SEE ALSO
--------
Expand Down
11 changes: 8 additions & 3 deletions docs/krunvm.1.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,15 @@ microVM and exposing ports from the guest to the host (and the
networks connected to it).

Networking to the guest running in the microVM is provided by
libkrun's TSI (Transparent Socket Impersonation), enabling a seamless
experience that doesn't require network bridges nor other explicit
network configuration.
either libkrun's TSI (Transparent Socket Impersonation) or PASST.

TSI enables a seamless experience that doesn't require network bridges nor other explicit
network configuration. It only supports impersonating AF_INET SOCK_DGRAM and SOCK_STREAM sockets.
This implies it's not possible to communicate outside the VM with raw sockets.

PASST uses virtio-net guest device and sends all traffic to a passt subprocess.
Support of network protocols is therefore dependent on what passt supports.
Note that currently you need to run a DHCP client in the guest to get an IP address.

GLOBAL OPTIONS
--------------
Expand Down
1 change: 1 addition & 0 deletions src/bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ extern "C" {
pub fn krun_set_mapped_volumes(ctx: u32, mapped_volumes: *const *const c_char) -> i32;
pub fn krun_set_port_map(ctx: u32, port_map: *const *const c_char) -> i32;
pub fn krun_set_workdir(ctx: u32, workdir_path: *const c_char) -> i32;
pub fn krun_set_passt_fd(ctx: u32, fd: c_int) -> i32;
pub fn krun_set_exec(
ctx: u32,
exec_path: *const c_char,
Expand Down
13 changes: 11 additions & 2 deletions src/commands/changevm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
use clap::Args;
use std::collections::HashMap;

use crate::config::{KrunvmConfig, NetworkMode};
use crate::utils::{path_pairs_to_hash_map, port_pairs_to_hash_map, PathPair, PortPair};
use crate::{KrunvmConfig, APP_NAME};

use super::list::printvm;

Expand Down Expand Up @@ -46,6 +46,10 @@ pub struct ChangeVmCmd {
/// Port(s) in format "host_port:guest_port" to be exposed to the host
#[arg(long = "port")]
ports: Vec<PortPair>,

/// Set the network connection mode for the microVM
#[arg(long)]
net: Option<NetworkMode>,
}

impl ChangeVmCmd {
Expand Down Expand Up @@ -130,12 +134,17 @@ impl ChangeVmCmd {
cfg_changed = true;
}

if let Some(network_mode) = self.net {
vmcfg.network_mode = network_mode;
cfg_changed = true;
}

println!();
printvm(vmcfg);
println!();

if cfg_changed {
confy::store(APP_NAME, &cfg).unwrap();
crate::config::save(cfg).unwrap();
}
}
}
17 changes: 14 additions & 3 deletions src/commands/config.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright 2021 Red Hat, Inc.
// SPDX-License-Identifier: Apache-2.0

use crate::{KrunvmConfig, APP_NAME};
use crate::config::{KrunvmConfig, NetworkMode};
use clap::Args;

/// Configure global values
Expand All @@ -18,6 +18,10 @@ pub struct ConfigCmd {
/// DNS server to use in the microVM
#[arg(long)]
dns: Option<String>,

/// Default network connection mode to use
#[arg(long)]
net: Option<NetworkMode>,
}

impl ConfigCmd {
Expand Down Expand Up @@ -47,11 +51,18 @@ impl ConfigCmd {
cfg_changed = true;
}

if let Some(network_mode) = self.net {
if network_mode != cfg.default_network_mode {
cfg.default_network_mode = network_mode;
cfg_changed = true;
}
}

if cfg_changed {
confy::store(APP_NAME, &cfg).unwrap();
crate::config::save(cfg).unwrap();
}

println!("Global configuration:");
println!("Global config:");
println!(
"Default number of CPUs for newly created VMs: {}",
cfg.default_cpus
Expand Down
10 changes: 8 additions & 2 deletions src/commands/create.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright 2021 Red Hat, Inc.
// SPDX-License-Identifier: Apache-2.0

use crate::config::{KrunvmConfig, NetworkMode, VmConfig};
use crate::APP_NAME;
use clap::Args;
use std::fs;
use std::io::Write;
Expand All @@ -12,8 +14,6 @@ use crate::utils::{
get_buildah_args, mount_container, path_pairs_to_hash_map, port_pairs_to_hash_map,
umount_container, BuildahCommand, PathPair, PortPair,
};
use crate::{KrunvmConfig, VmConfig, APP_NAME};

#[cfg(target_os = "macos")]
const KRUNVM_ROSETTA_FILE: &str = ".krunvm-rosetta";

Expand Down Expand Up @@ -51,6 +51,10 @@ pub struct CreateCmd {
#[arg(long = "port")]
ports: Vec<PortPair>,

/// Network connection mode to use
#[arg(long)]
net: Option<NetworkMode>,

/// Create a x86_64 microVM even on an Aarch64 host
#[arg(short, long)]
#[cfg(target_os = "macos")]
Expand All @@ -68,6 +72,7 @@ impl CreateCmd {
let mapped_ports = port_pairs_to_hash_map(self.ports);
let image = self.image;
let name = self.name;
let network_mode = self.net.unwrap_or_else(|| cfg.default_network_mode.clone());

if let Some(ref name) = name {
if cfg.vmconfig_map.contains_key(name) {
Expand Down Expand Up @@ -160,6 +165,7 @@ https://threedots.ovh/blog/2022/06/quick-look-at-rosetta-on-linux/
workdir: workdir.to_string(),
mapped_volumes,
mapped_ports,
network_mode,
};

let rootfs = mount_container(cfg, &vmcfg).unwrap();
Expand Down
5 changes: 3 additions & 2 deletions src/commands/delete.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// Copyright 2021 Red Hat, Inc.
// SPDX-License-Identifier: Apache-2.0

use crate::{KrunvmConfig, APP_NAME};
use crate::config;
use crate::config::KrunvmConfig;
use clap::Args;

use crate::utils::{remove_container, umount_container};
Expand All @@ -26,6 +27,6 @@ impl DeleteCmd {
umount_container(cfg, &vmcfg).unwrap();
remove_container(cfg, &vmcfg).unwrap();

confy::store(APP_NAME, &cfg).unwrap();
config::save(cfg).unwrap()
}
}
3 changes: 2 additions & 1 deletion src/commands/list.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright 2021 Red Hat, Inc.
// SPDX-License-Identifier: Apache-2.0

use crate::{KrunvmConfig, VmConfig};
use crate::config::{KrunvmConfig, VmConfig};
use clap::Args;

/// List microVMs
Expand Down Expand Up @@ -33,6 +33,7 @@ pub fn printvm(vm: &VmConfig) {
println!(" DNS server: {}", vm.dns);
println!(" Buildah container: {}", vm.container);
println!(" Workdir: {}", vm.workdir);
println!(" Network mode: {:?}", vm.network_mode);
println!(" Mapped volumes: {:?}", vm.mapped_volumes);
println!(" Mapped ports: {:?}", vm.mapped_ports);
}
85 changes: 79 additions & 6 deletions src/commands/start.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,29 @@
// SPDX-License-Identifier: Apache-2.0

use clap::Args;
use libc::c_char;
use libc::{c_char, c_int};
use nix::errno::Errno;
use nix::sys::socket::{socketpair, AddressFamily, SockFlag, SockType};
use std::collections::HashMap;
use std::ffi::CString;

use std::fs::File;
#[cfg(target_os = "linux")]
use std::io::{Error, ErrorKind};

use std::os::fd::{IntoRawFd, OwnedFd};
use std::os::unix::io::AsRawFd;

#[cfg(target_os = "macos")]
use std::path::Path;
use std::process::Stdio;

use nix::fcntl::{fcntl, FcntlArg, FdFlag};

use crate::bindings;
use crate::bindings::krun_set_passt_fd;
use crate::config::{KrunvmConfig, NetworkMode, VmConfig};
use crate::utils::{mount_container, umount_container};
use crate::{KrunvmConfig, VmConfig};

#[derive(Args, Debug)]
/// Start an existing microVM
Expand All @@ -36,6 +47,49 @@ pub struct StartCmd {
mem: Option<usize>, // TODO: implement or remove this
}

fn start_passt(mapped_ports: &HashMap<String, String>) -> Result<OwnedFd, ()> {
let (passt_fd, krun_fd) = socketpair(
AddressFamily::Unix,
SockType::Stream,
None,
SockFlag::empty(),
)
.map_err(|e| {
eprint!("Failed to create socket pair for passt: {e}");
})?;

if let Err(e) = fcntl(krun_fd.as_raw_fd(), FcntlArg::F_SETFD(FdFlag::FD_CLOEXEC)) {
eprint!("Failed to set FD_CLOEXEC: {e}");
}

let mut cmd = std::process::Command::new("passt");
cmd.arg("-q")
.arg("-f")
.arg("-F")
.arg(passt_fd.as_raw_fd().to_string());

if !mapped_ports.is_empty() {
let comma_separated_ports = mapped_ports
.iter()
.map(|(host_port, guest_port)| format!("{}:{}", host_port, guest_port))
.collect::<Vec<String>>()
.join(",");

cmd.arg("-t").arg(comma_separated_ports);
}

cmd.stdout(Stdio::null())
.stderr(Stdio::null())
.stdin(Stdio::null());

if let Err(e) = cmd.spawn() {
eprintln!("Failed to start passt: {e}");
return Err(());
}

Ok(krun_fd)
}

impl StartCmd {
pub fn run(self, cfg: &KrunvmConfig) {
let vmcfg = match cfg.vmconfig_map.get(&self.name) {
Expand Down Expand Up @@ -152,10 +206,29 @@ unsafe fn exec_vm(vmcfg: &VmConfig, rootfs: &str, cmd: Option<&str>, args: Vec<C
}
ps.push(std::ptr::null());

let ret = bindings::krun_set_port_map(ctx, ps.as_ptr());
if ret < 0 {
println!("Error setting VM port map");
std::process::exit(-1);
match vmcfg.network_mode {
NetworkMode::Tsi => {
let ret = bindings::krun_set_port_map(ctx, ps.as_ptr());
if ret < 0 {
println!("Error setting VM port map");
std::process::exit(-1);
}
}
NetworkMode::Passt => {
let Ok(passt_fd) = start_passt(&vmcfg.mapped_ports) else {
std::process::exit(-1);
};
let ret = krun_set_passt_fd(ctx, passt_fd.into_raw_fd() as c_int);
if ret < 0 {
let errno = Errno::from_i32(-ret);
if errno == Errno::ENOTSUP {
println!("Failed to set passt fd: your libkrun build does not support virtio-net/passt mode.");
} else {
println!("Failed to set passt fd: {}", errno);
}
std::process::exit(-1);
}
}
}

if !vmcfg.workdir.is_empty() {
Expand Down
Loading

0 comments on commit 5fc42aa

Please sign in to comment.