Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 6 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ libc = "0.2"
log = "0.4"
mac_address = "1.1"
merge = "0.2"
nix = { version = "0.31", features = ["mman", "pthread", "signal"] }
nix = { version = "0.31", features = ["mman", "pthread", "signal", "net", "ioctl", "poll"] }
nohash = "0.2"
rftrace = { version = "0.3", optional = true }
rftrace-frontend = { version = "0.3", optional = true }
Expand All @@ -62,18 +62,22 @@ tempfile = "3.26"
thiserror = "2.0.18"
time = "0.3"
toml = "1"
tun-tap = { version = "0.1.3", default-features = false }
uhyve-interface = { version = "0.2.0", path = "uhyve-interface", features = ["std"] }
virtio-bindings = "~0.2.7"
vm-fdt = "0.3"
vm-memory = { version = "0.18", features = ["backend-mmap"] }
uuid = { version = "1.22.0", features = ["fast-rng", "v4"]}
tar-no-std = { version = "0.4", features = ["alloc"] }
bitflags = "2.11"
virtio-queue = "0.17"
zerocopy = { version = "0.8", features = ["derive"] }

[target.'cfg(target_os = "linux")'.dependencies]
kvm-bindings = "0.14"
kvm-ioctls = "0.24"
landlock = "0.4.4"
mac_address = "1.1"
tun-tap = { version = "0.1", default-features = false }
vmm-sys-util = "0.15"

[target.'cfg(target_os = "macos")'.dependencies]
Expand All @@ -88,7 +92,6 @@ memory_addresses = { version = "0.3", default-features = false, features = [
] }

[target.'cfg(target_arch = "aarch64")'.dependencies]
bitflags = "2.11"
memory_addresses = { version = "0.3", default-features = false, features = [
"aarch64",
] }
Expand Down
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,30 @@ For more options, the default values, and the corresponding environment variable
uhyve --help
```

### Networking

**Network support is currently unstable and tested only on Linux.**

If you require uhyve to create its own virtual ethernet interface, you will need to provide it with the `CAP_NET_ADMIN` capability:

```
# as root
setcap cap_net_admin+ep /path/to/uhyve # ./target/debug/uhyve
```

~~You can set the pre-created tap device name via an environment variable `TAP`~~

Currently, the device is hard-coded with the name `tap10`. You will need to create the device and connect it to a bridge (such as virbr0):

```
ip tuntap add tap10 mode tap user "$(whoami)"
ip link set tap10 master virbr0
ip link set dev tap10 up
```

And, if desired, set the IP address and gateway of your RustyHermit instance via `HERMIT_IP` and `HERMIT_GATEWAY`.


### Contributing

If you are interested in contributing to Uhyve, make sure to check out the [Uhyve wiki][uhyve-wiki]!
Expand Down
1 change: 1 addition & 0 deletions benches/benches/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod complete_binary;
pub mod network;
pub mod vm;
244 changes: 244 additions & 0 deletions benches/benches/network.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
use std::{
io::{Read, Write},
net::{Shutdown, TcpListener, TcpStream},
path::PathBuf,
thread,
time::Instant,
};

use byte_unit::{Byte, Unit};
use criterion::{Criterion, criterion_group, measurement::Measurement};
use log::debug;
use regex::Regex;
#[cfg(target_os = "linux")]
use uhyvelib::params::FileSandboxMode;
use uhyvelib::{
params::{NetworkMode, Output, Params},
vm::UhyveVm,
};

use crate::common::{BuildMode, HERMIT_GATEWAY, HERMIT_IP, build_hermit_bin, check_result};

const TOTAL_BYTES: u64 = 1024 * 1024 * 1024;

/// Custom struct for throughput measurements in criterion. Must be used in connection with `iter_custom`
pub struct ThroughputMeasurement;

impl Measurement for ThroughputMeasurement {
type Intermediate = ();
type Value = u64;

fn start(&self) -> Self::Intermediate {}

fn end(&self, _i: Self::Intermediate) -> Self::Value {
unreachable!("This measurement uses iter_custom")
}

fn add(&self, v1: &Self::Value, v2: &Self::Value) -> Self::Value {
*v1 + *v2
}

fn zero(&self) -> Self::Value {
0
}

fn to_f64(&self, value: &Self::Value) -> f64 {
*value as f64
}

fn formatter(&self) -> &dyn criterion::measurement::ValueFormatter {
&ThroughputFormatter
}
}

struct ThroughputFormatter;

impl criterion::measurement::ValueFormatter for ThroughputFormatter {
fn scale_values(&self, typical_value: f64, values: &mut [f64]) -> &'static str {
let (factor, unitstr) = match typical_value {
0.0..1000.0 => (1.0, "bits/s"),
1000.0..1000000.0 => (1000.0, "Kbits/s"),
1000000.0..1000000000.0 => (1000000.0, "Mbits/s"),
1000000000.0.. => (1000000000.0, "Gbits/s"),
_ => unreachable!("Negative Throughput???"),
};
values.iter_mut().for_each(|v| *v /= factor);
unitstr
}

fn scale_throughputs(
&self,
_typical_value: f64,
_throughput: &criterion::Throughput,
_throughputs: &mut [f64],
) -> &'static str {
"bits/s"
}

fn scale_for_machines(&self, _values: &mut [f64]) -> &'static str {
"bits/s"
}
}

fn network_receive_bench(kernel_path: PathBuf) -> u64 {
let params = Params {
cpu_count: 1.try_into().unwrap(),
memory_size: Byte::from_u64_with_unit(64, Unit::MiB)
.unwrap()
.try_into()
.unwrap(),
output: Output::Buffer,
stats: true,
aslr: false,
#[cfg(target_os = "linux")]
file_isolation: FileSandboxMode::None,
network: Some(NetworkMode::Tap {
name: "tap10".to_string(),
}),
kernel_args: vec![
"--".to_owned(),
"testname=receive_bench".to_owned(),
"test_argument=".to_owned(),
],
..Default::default()
};

let t = thread::spawn(move || {
let mut hermit_ip = String::from(HERMIT_IP);
hermit_ip.push_str(":9975");
let mut stream = TcpStream::connect(hermit_ip).unwrap();

let buf = vec![123u8; 64 * 1024]; // Bytes without meaning
let mut sent: u64 = 0;

let start = Instant::now();

while sent < TOTAL_BYTES {
let remaining = (TOTAL_BYTES - sent) as usize;
let to_send = remaining.min(buf.len());
stream.write_all(&buf[..to_send]).unwrap();
sent += to_send as u64;
}

stream.shutdown(Shutdown::Write).unwrap();
let elapsed = start.elapsed();
let secs = elapsed.as_secs_f64();

debug!("Sent {sent} bytes in {secs:.3} s");
let mbit = (sent as f64 * 8.0) / (secs * 1_000_000.0);
debug!("Throughput (sending): {mbit:.2} Mbit/s");
});

let res = UhyveVm::new(kernel_path.clone(), params).unwrap().run(None);

check_result(&res);

let re =
Regex::new(r"(?m)^Throughput \(receiving\):\s*([0-9]+(?:\.[0-9]+)?)\s+Mbit/s").unwrap();

let caps = re.captures(res.output.as_ref().unwrap()).unwrap();
let throughput: f64 = caps[1].parse().expect("invalid number");

t.join().unwrap();
(throughput * 1000000.0) as u64
}

fn network_send_bench(kernel_path: PathBuf) -> u64 {
let params = Params {
cpu_count: 1.try_into().unwrap(),
memory_size: Byte::from_u64_with_unit(64, Unit::MiB)
.unwrap()
.try_into()
.unwrap(),
output: Output::Buffer,
stats: true,
aslr: false,
#[cfg(target_os = "linux")]
file_isolation: FileSandboxMode::None,
network: Some(NetworkMode::Tap {
name: "tap10".to_string(),
}),
kernel_args: vec![
"--".to_owned(),
"testname=send_bench".to_owned(),
format!("test_argument={HERMIT_GATEWAY}:9975/{TOTAL_BYTES}").to_owned(),
],
..Default::default()
};

let t = thread::spawn(move || {
let listener = TcpListener::bind(HERMIT_GATEWAY.to_string() + ":9975").unwrap();
debug!("socket bound");
let (mut stream, peer) = listener.accept().unwrap();
debug!("Got connection from {}", peer);

stream.set_nodelay(true).unwrap();

let mut buf = vec![0u8; 8192];
let mut received: u64 = 0;

let start = Instant::now();
loop {
let n = stream.read(&mut buf).unwrap();
if n == 0 {
// connection terminated
break;
}
received += n as u64;
}

let elapsed = start.elapsed();
let secs = elapsed.as_secs_f64();

debug!("Received {received} bytes in {secs:.3} s");
let mbit = (received as f64 * 8.0) / (secs * 1_000_000.0);
debug!("Throughput (receiving): {mbit:.2} Mbit/s");
});

let res = UhyveVm::new(kernel_path.clone(), params).unwrap().run(None);

check_result(&res);

let re = Regex::new(r"(?m)^Throughput \(sending\):\s*([0-9]+(?:\.[0-9]+)?)\s+Mbit/s").unwrap();

let caps = re.captures(res.output.as_ref().unwrap()).unwrap();
let throughput: f64 = caps[1].parse().expect("invalid number");

t.join().unwrap();
(throughput * 1000000.0) as u64
}

pub fn network_receive_throughput(c: &mut Criterion<ThroughputMeasurement>) {
env_logger::try_init().ok();
let kernel_path = build_hermit_bin("network_test", BuildMode::Release);
c.bench_function("network_receive_throughput", |b| {
b.iter_custom(|iters| {
let mut total: u64 = 0;
for _ in 0..iters {
total += network_receive_bench(kernel_path.clone());
}
total / iters
});
});
}

pub fn network_send_throughput(c: &mut Criterion<ThroughputMeasurement>) {
env_logger::try_init().ok();
let kernel_path = build_hermit_bin("network_test", BuildMode::Release);

c.bench_function("network_send_throughput", |b| {
b.iter_custom(|iters| {
let mut total: u64 = 0;
for _ in 0..iters {
total += network_send_bench(kernel_path.clone());
}
total / iters
});
});
}

criterion_group!(
name = network_benchmark_group;
config = Criterion::default().with_measurement(ThroughputMeasurement).sample_size(10);
targets = network_receive_throughput, network_send_throughput
);
15 changes: 13 additions & 2 deletions benches/benchmarks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,18 @@ use criterion::criterion_main;

pub mod benches;

use benches::{complete_binary::run_complete_binaries_group, vm::load_kernel_benchmark_group};
use benches::{
complete_binary::run_complete_binaries_group, network::network_benchmark_group,
vm::load_kernel_benchmark_group,
};

#[path = "../tests/common.rs"]
pub(crate) mod common;
pub use common::build_hermit_bin;

// Add the benchmark groups that should be run
criterion_main!(load_kernel_benchmark_group, run_complete_binaries_group);
criterion_main!(
load_kernel_benchmark_group,
run_complete_binaries_group,
network_benchmark_group
);
Loading
Loading