Skip to content
Merged
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
62 changes: 59 additions & 3 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,18 +85,52 @@ The `virtio-net` option adds a network interface to a virtual machine.

#### Arguments

Arguments to create a virtio-net device that has offloading enabled by default and will send a VFKIT magic
value after establishing the connection:
- `unixSocketPath`: Path to a UNIX socket to attach to the guest network interface.
- `mac`: MAC address of a virtual machine.

Arguments to create a virtio-net device with a unix datagram or unix stream socket backend:
- `type`: Unix socket type:
- `unixgram`: unix datagram socket-based backend such as gvproxy or vmnet-helper.
- `unixstream`: unix stream socket-based userspace network proxy such as passt or socket_vmnet.
- `path`: Unix socket path. Mutually exclusive with the `fd=` option.
- `fd`: Unix socket file descriptor. Mutually exclusive with the `path=` option.
- `mac`: MAC address of a virtual machine.
- `offloading`: (Optional) Whether or not to enable network offloading between the guest and host.
Default value is `off`.
- `vfkitMagic`: (Optional) Whether to send the vfkit magic value after establishing a network connection.
Default value is `off`. Only supported with the `unixgram` type.

> [!NOTE]
> The `unixSocketPath`, `type={unixgram, unixstream}` arguments are mutually exclusive and cannot be used together.
#### Example

This adds a virtio-net device to a virtual machine and redirects all guest network traffic to the corresponding
socket at `/Users/user/vm-network.sock` with a MAC address of `ff:ff:ff:ff:ff:ff`:
If you want to use a tool like vmnet-helper as your backend, you'll be interested in using the `type=unixgram`
argument. This will create a virtio-net device and redirect all guest network traffic to the corresponding socket.
Here, we're not going to use any of the optional arguments. Therefore, offloading will be disabled by default
and the vfkit magic value won't be sent when the connection is established:

```
--device virtio-net,type=unixgram,path=/Users/user/vm-network.sock,mac=ff:ff:ff:ff:ff:ff
```

If you want to use gvproxy instead, you're going to want to use some of the optional arguments krunkit provides.
We're going to want to enable offloading, and if gvproxy is running in vfkit mode, we'll also want to send the
vfkit magic value when the connection establishes:

```
--device virtio-net,unixSocketPath=/Users/user/vm-network.sock,mac=ff:ff:ff:ff:ff:ff
--device virtio-net,type=unixgram,path=/Users/user/vm-network.sock,mac=ff:ff:ff:ff:ff:ff,offloading=on,vfkitMagic=on
```

You can also use a network proxy such as passt by using the `type=unixstream` argument:
```
--device virtio-net,type=unixstream,fd=<passt_fd>,mac=ff:ff:ff:ff:ff:ff,offloading=true
```

To see performance implications of choosing offloading vs. not offloading, see [this table](#offloading-performance-implications)

### Serial Port

The `virtio-serial` option adds a serial device to a virtual machine. This allows for redirection of virtual
Expand Down Expand Up @@ -174,3 +208,25 @@ Response: `VirtualMachineState{Running, Stopped}`
`POST /vm/state` `{ "state": "Stop" }`

Response: `VirtualMachineStateStopped`

## Offloading Performance Implications

The table below provides some data on how offloading effects the gvproxy and vmnet-helper backends:

#### vment-helper offloading

| network | vm | offloading | iper3 | iperf3 -R |
|---------------|--------- |------------|---------------|---------------|
| vmnet-helper | krunkit | true | 1.38 Gbits/s | 46.20 Gbits/s |
| vmnet-helper | krunkit | false | 10.10 Gbits/s | 8.38 Gbits/s |
| vmnet-helper | vfkit | true | 4.27 Gbits/s | 8.09 Gbits/s |
| vmnet-helper | vfkit | false | 10.70 Gbits/s | 8.41 Gbits/s |

#### gvproxy offloading

| network | vm | offloading | iper3 | iperf3 -R |
|---------------|--------- |------------|---------------|---------------|
| gvproxy | krunkit | true | 1.40 Gbits/s | 20.00 Gbits/s |
| gvproxy | krunkit | false | 1.47 Gbits/s | 2.58 Gbits/s |
| gvproxy | vfkit | false | 1.43 Gbits/s | 2.84 Gbits/s |

148 changes: 143 additions & 5 deletions src/cmdline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@

use crate::{status::RestfulUri, virtio::VirtioDeviceConfig};

use std::{collections::HashMap, path::PathBuf, str::FromStr};
use std::{
collections::HashMap,
ffi::{c_char, CString},
path::PathBuf,
str::FromStr,
};

use anyhow::{anyhow, Context, Result};
use clap::Parser;
Expand Down Expand Up @@ -102,6 +107,26 @@ pub fn check_unknown_args(args: HashMap<String, String>, label: &str) -> Result<
Ok(())
}

/// Parse a string slice and convert it to a boolean if possible. In addition to "true" and "false"
/// being valid strings, "on" and "off" are also valid.
pub fn parse_boolean(value: &str) -> Result<bool, anyhow::Error> {
match value {
"true" | "on" => Ok(true),
"false" | "off" => Ok(false),
_ => Err(anyhow!("invalid boolean value {value}")),
}
}

/// Convert a CString value to a pointer. If the string is empty, the function will return a NULL
/// ptr.
pub fn cstring_to_ptr(value: &CString) -> *const c_char {
if value.is_empty() {
std::ptr::null()
} else {
value.as_ptr()
}
}

/// A wrapper of all data associated with the bootloader argument.
mod bootloader {
use super::*;
Expand Down Expand Up @@ -380,6 +405,16 @@ mod tests {
"virtio-gpu,width=800,height=600",
"--device",
"virtio-input,keyboard",
"--device",
"virtio-net,type=unixgram,path=/Users/user/net.sock,mac=00:00:00:00:00:00,offloading=true,vfkitMagic=off",
"--device",
"virtio-net,type=unixgram,fd=4,mac=00:00:00:00:00:00",
"--device",
"virtio-net,type=unixstream,path=/Users/user/net.sock,mac=00:00:00:00:00:00,offloading=on",
"--device",
"virtio-net,type=unixstream,fd=4,mac=00:00:00:00:00:00,offloading=off",
"--device",
"virtio-net,type=unixstream,fd=4,mac=00:00:00:00:00:00",
"--restful-uri",
"tcp://localhost:49573",
"--gui",
Expand All @@ -389,6 +424,102 @@ mod tests {

let mut args = Args::try_parse_from(cmdline).unwrap();

let net = args
.devices
.pop()
.expect("expected 15th virtio device config");
if let VirtioDeviceConfig::Net(net) = net {
if let SocketType::UnixStream = net.socket_type {
assert_eq!(net.socket_config.path, None,);
assert_eq!(net.socket_config.fd, Some(4));
assert_eq!(net.socket_config.offloading, false);
assert_eq!(net.socket_config.send_vfkit_magic, false);
} else {
panic!("expected virtio-net device to use the unixstream argument");
}
assert_eq!(net.mac_address, MacAddress::new([0, 0, 0, 0, 0, 0]));
} else {
panic!("expected virtio-net device as 15th device config argument");
}

let net = args
.devices
.pop()
.expect("expected 14th virtio device config");
if let VirtioDeviceConfig::Net(net) = net {
if let SocketType::UnixStream = net.socket_type {
assert_eq!(net.socket_config.path, None,);
assert_eq!(net.socket_config.fd, Some(4));
assert_eq!(net.socket_config.offloading, false);
assert_eq!(net.socket_config.send_vfkit_magic, false);
} else {
panic!("expected virtio-net device to use the unixstream argument");
}
assert_eq!(net.mac_address, MacAddress::new([0, 0, 0, 0, 0, 0]));
} else {
panic!("expected virtio-net device as 14th device config argument");
}

let net = args
.devices
.pop()
.expect("expected 13th virtio device config");
if let VirtioDeviceConfig::Net(net) = net {
if let SocketType::UnixStream = net.socket_type {
assert_eq!(
net.socket_config.path,
Some(PathBuf::from_str("/Users/user/net.sock").unwrap())
);
assert_eq!(net.socket_config.fd, None);
assert_eq!(net.socket_config.offloading, true);
assert_eq!(net.socket_config.send_vfkit_magic, false);
} else {
panic!("expected virtio-net device to use the unixstream argument");
}
assert_eq!(net.mac_address, MacAddress::new([0, 0, 0, 0, 0, 0]));
} else {
panic!("expected virtio-net device as 13th device config argument");
}

let net = args
.devices
.pop()
.expect("expected 12th virtio device config");
if let VirtioDeviceConfig::Net(net) = net {
if let SocketType::UnixGram = net.socket_type {
assert_eq!(net.socket_config.path, None);
assert_eq!(net.socket_config.fd, Some(4));
assert_eq!(net.socket_config.offloading, false);
assert_eq!(net.socket_config.send_vfkit_magic, false);
} else {
panic!("expected virtio-net device to use the unixgram argument");
}
assert_eq!(net.mac_address, MacAddress::new([0, 0, 0, 0, 0, 0]));
} else {
panic!("expected virtio-net device as 12th device config argument");
}

let net = args
.devices
.pop()
.expect("expected 11th virtio device config");
if let VirtioDeviceConfig::Net(net) = net {
if let SocketType::UnixGram = net.socket_type {
assert_eq!(
net.socket_config.path,
Some(PathBuf::from_str("/Users/user/net.sock").unwrap())
);
assert_eq!(net.socket_config.fd, None);
assert_eq!(net.socket_config.offloading, true);
assert_eq!(net.socket_config.send_vfkit_magic, false);
} else {
panic!("expected virtio-net device to use the unixgram argument");
}
assert_eq!(net.mac_address, MacAddress::new([0, 0, 0, 0, 0, 0]));
} else {
panic!("expected virtio-net device as 11th device config argument");
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is very fragile to depend on the index of this device in the test. Does device order matter except virtio-blk (first device is boot device)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, device order here doesn't matter. I'm just following the pattern of how the test was originally designed. I agree that it's a fragile implementation, but I think that's best for another PR to clean it up.

}

let input = args
.devices
.pop()
Expand Down Expand Up @@ -441,10 +572,17 @@ mod tests {
.pop()
.expect("expected 6th virtio device config");
if let VirtioDeviceConfig::Net(net) = net {
assert_eq!(
net.unix_socket_path,
PathBuf::from_str("/Users/user/net.sock").unwrap()
);
if let SocketType::UnixGram = net.socket_type {
assert_eq!(
net.socket_config.path,
Some(PathBuf::from_str("/Users/user/net.sock").unwrap())
);
assert_eq!(net.socket_config.fd, None);
assert_eq!(net.socket_config.offloading, true);
assert_eq!(net.socket_config.send_vfkit_magic, true);
} else {
panic!("expected virtio-net device to use the unixSocketPath argument");
}
assert_eq!(net.mac_address, MacAddress::new([0, 0, 0, 0, 0, 0]));
} else {
panic!("expected virtio-net device as 6th device config argument");
Expand Down
Loading
Loading