Skip to content

Commit e3b6b54

Browse files
author
Hang SU
committed
console: add Unix domain socket backend support
- Introduce `BackendType::Uds` for VM communication via Unix sockets - Refactor address generation logic with `generate_vm_sock_addrs` - Add CLI parameter `--vm-sock` to specify UDS path - Update documentation and error handling Signed-off-by: Hang SU <[email protected]> Suggested-by: Xuewei Niu <[email protected]>
1 parent 085dd79 commit e3b6b54

File tree

6 files changed

+166
-13
lines changed

6 files changed

+166
-13
lines changed

vhost-device-console/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
### Added
55

66
- [#811](https://github.com/rust-vmm/vhost-device/pull/811) Being able to specify max queue size
7+
- [#821](https://github.com/rust-vmm/vhost-device/pull/821) Add Unix domain socket backend support
78

89
### Changed
910

vhost-device-console/README.md

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,17 @@ This program is a vhost-user backend that emulates a VirtIO Console device.
66
The device's binary takes as parameters a socket path, a socket number which
77
is the number of connections, commonly used across all vhost-devices to
88
communicate with the vhost-user frontend devices, and the backend type
9-
"nested" or "network".
9+
"nested" or "network" or "uds".
1010

1111
The "nested" backend allows input/output to the guest console through the
1212
current terminal.
1313

1414
The "network" backend creates a local TCP port (specified on vhost-device-console
1515
arguments) and allows input/output to the guest console via that socket.
1616

17+
The "uds" backend creates a unix domain socket (specified on vhost-device-console
18+
arguments) and allows input/output to the guest console via that socket.
19+
1720
This program is tested with QEMU's `vhost-user-device-pci` device.
1821
Examples' section below.
1922

@@ -39,14 +42,23 @@ vhost-device-console --socket-path=<SOCKET_PATH>
3942

4043
The localhost's port to be used for each guest, this part will be increased with
4144
0,1,2..socket_count-1.
45+
And the option is only valid when backend mode is "network".
4246

43-
-- option:: -b, --backend=nested|network
47+
-- option:: -b, --backend=nested|network|uds
4448

4549
The backend type vhost-device-console to be used. The current implementation
46-
supports two types of backends: "nested", "network" (described above).
50+
supports 3 types of backends: "nested", "network", "uds".
4751
Note: The nested backend is selected by default and can be used only when
4852
socket_count equals 1.
4953

54+
.. option:: --uds-path=uds-file-path
55+
56+
The unix domain socket to be used for each guest, this path will be suffixed with
57+
0,1,2..socket_count-1. e.g.: `--uds-path=/tmp/vm.sock --socket-count=2`
58+
leads to two connectable vhost-user sockets:
59+
/tmp/vm.sock0, /tmp/vm.sock1
60+
And the option is only valid when backend mode is "uds".
61+
5062
.. option:: -q, --max-queue-size=SIZE
5163

5264
The maximum size of virtqueues. It is optional, and the default value is
@@ -62,10 +74,10 @@ VIRTIO_CONSOLE_F_SIZE features.
6274
## Features
6375

6476
The current device gives access to multiple QEMU guest by providing a login prompt
65-
either by connecting to a localhost server port (network backend) or by creating an
66-
nested command prompt in the current terminal (nested backend). This prompt appears
67-
as soon as the guest is fully booted and gives the ability to user run command as a
68-
in regular terminal.
77+
either by connecting to a localhost server port (network backend) or a unix socket
78+
file (uds backend) or by creating an nested command prompt in the current terminal
79+
(nested backend). This prompt appears as soon as the guest is fully booted and
80+
gives the ability to user run command as a in regular terminal.
6981

7082
## Examples
7183

@@ -83,10 +95,15 @@ For testing the device the required dependencies are:
8395
The daemon should be started first:
8496
```shell
8597
host# vhost-device-console --socket-path=/tmp/console.sock --socket-count=1 \
86-
--tcp-port=12345 --backend=network
98+
--tcp-port=12345 --backend=network # for network backend
99+
```
100+
or
101+
```shell
102+
host# vhost-device-console --socket-path=/tmp/console.sock --socket-count=1 \
103+
--uds-path=/tmp/vm.sock --backend=uds # for uds backend
87104
```
88105
>Note: In case the backend is "nested" there is no need to provide
89-
"--socket-count" and "--tcp-port" parameters.
106+
"--socket-count", "--tcp-port" and "--uds-path" parameters.
90107

91108
The QEMU invocation needs to create a chardev socket the device can
92109
use to communicate as well as share the guests memory over a memfd.
@@ -119,9 +136,26 @@ host# qemu-system
119136
...
120137
```
121138

139+
#### Test the device with UML
140+
Start the daemon as above section, then run the following cmdline for
141+
Kernel Mode Linux [`virtio console`](https://github.com/torvalds/linux/blob/848e076317446f9c663771ddec142d7c2eb4cb43/include/uapi/linux/virtio_ids.h#L34):
142+
```text
143+
host# linux root=/dev/ubda1 rw ubd0=$YOUR-PATH/kata-ubuntu-latest.image \
144+
<normal UML options> \
145+
virtio_uml.device=/tmp/console.sock0:3 console=tty0 console=hvc0 \
146+
init=/bin/systemd \
147+
systemd.unit=kata-containers.target agent.debug_console
148+
```
149+
Test with [kata-ubuntu-latest.image](https://github.com/kata-containers/kata-containers/releases/),
150+
you can also use systemd to setup pty manually without kata-agent help.
151+
122152
Eventually, the user can connect to the console by running:
123153
```test
124-
host# stty -icanon -echo && nc localhost 12345 && stty echo
154+
host# stty -icanon -echo && nc localhost 12345 && stty echo # for network backend
155+
```
156+
or
157+
```test
158+
host# stty -icanon -echo && nc -U /tmp/vm.sock0 && stty echo # for uds backend
125159
```
126160

127161
>Note: `stty -icanon -echo` is used to force the tty layer to disable buffering and send / receive each character individually. After closing the connection please run `stty echo` so character are printed back on the local terminal console.

vhost-device-console/src/backend.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,14 @@ pub enum Error {
4242
ThreadPanic(String, Box<dyn Any + Send>),
4343
#[error("Error using multiple sockets with Nested backend")]
4444
WrongBackendSocket,
45+
#[error("Invalid uds file")]
46+
InvalidUdsFile,
4547
}
4648

4749
#[derive(PartialEq, Eq, Debug)]
4850
pub struct VuConsoleConfig {
4951
pub socket_path: PathBuf,
52+
pub uds_path: PathBuf,
5053
pub backend: BackendType,
5154
pub tcp_port: String,
5255
pub socket_count: u32,
@@ -86,6 +89,26 @@ impl VuConsoleConfig {
8689
|i: u32| -> String { "127.0.0.1:".to_owned() + &(port_base + i).to_string() };
8790
(0..self.socket_count).map(make_tcp_port).collect()
8891
}
92+
93+
BackendType::Uds => {
94+
let uds_filename = self.uds_path.file_name().expect("uds has no filename.");
95+
let uds_parent = self
96+
.uds_path
97+
.parent()
98+
.expect("uds has no parent directory.");
99+
100+
let make_uds_path = |i: u32| -> String {
101+
let mut filename = uds_filename.to_os_string();
102+
filename.push(std::ffi::OsStr::new(&i.to_string()));
103+
uds_parent
104+
.join(&filename)
105+
.to_str()
106+
.expect("Path contains invalid UTF-8 characters")
107+
.to_string()
108+
};
109+
110+
(0..self.socket_count).map(make_uds_path).collect()
111+
}
89112
}
90113
}
91114
}
@@ -192,6 +215,7 @@ mod tests {
192215
fn test_console_valid_configuration_nested() {
193216
let args = ConsoleArgs {
194217
socket_path: String::from("/tmp/vhost.sock").into(),
218+
uds_path: None,
195219
backend: BackendType::Nested,
196220
tcp_port: String::from("12345"),
197221
socket_count: 1,
@@ -205,6 +229,7 @@ mod tests {
205229
fn test_console_invalid_configuration_nested_1() {
206230
let args = ConsoleArgs {
207231
socket_path: String::from("/tmp/vhost.sock").into(),
232+
uds_path: None,
208233
backend: BackendType::Nested,
209234
tcp_port: String::from("12345"),
210235
socket_count: 0,
@@ -221,6 +246,7 @@ mod tests {
221246
fn test_console_invalid_configuration_nested_2() {
222247
let args = ConsoleArgs {
223248
socket_path: String::from("/tmp/vhost.sock").into(),
249+
uds_path: None,
224250
backend: BackendType::Nested,
225251
tcp_port: String::from("12345"),
226252
socket_count: 2,
@@ -237,6 +263,7 @@ mod tests {
237263
fn test_console_valid_configuration_network_1() {
238264
let args = ConsoleArgs {
239265
socket_path: String::from("/tmp/vhost.sock").into(),
266+
uds_path: None,
240267
backend: BackendType::Network,
241268
tcp_port: String::from("12345"),
242269
socket_count: 1,
@@ -250,6 +277,7 @@ mod tests {
250277
fn test_console_valid_configuration_network_2() {
251278
let args = ConsoleArgs {
252279
socket_path: String::from("/tmp/vhost.sock").into(),
280+
uds_path: None,
253281
backend: BackendType::Network,
254282
tcp_port: String::from("12345"),
255283
socket_count: 2,
@@ -280,6 +308,7 @@ mod tests {
280308
fn test_start_backend_server_success() {
281309
let args = ConsoleArgs {
282310
socket_path: String::from("/not_a_dir/vhost.sock").into(),
311+
uds_path: None,
283312
backend: BackendType::Network,
284313
tcp_port: String::from("12345"),
285314
socket_count: 1,
@@ -293,6 +322,7 @@ mod tests {
293322
fn test_start_backend_success() {
294323
let config = VuConsoleConfig {
295324
socket_path: String::from("/not_a_dir/vhost.sock").into(),
325+
uds_path: PathBuf::new(),
296326
backend: BackendType::Network,
297327
tcp_port: String::from("12346"),
298328
socket_count: 1,

vhost-device-console/src/console.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ pub enum BackendType {
1515
#[default]
1616
Nested,
1717
Network,
18+
Uds,
1819
}
1920

2021
#[derive(Debug)]

vhost-device-console/src/main.rs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,16 @@ struct ConsoleArgs {
5454
#[clap(short = 's', long, value_name = "SOCKET")]
5555
socket_path: PathBuf,
5656

57+
/// Virtual machine communication endpoint.
58+
/// Unix domain socket path (e.g., "/tmp/vm.sock").
59+
#[clap(long, required(false), value_name = "VM_SOCKET")]
60+
uds_path: Option<PathBuf>,
61+
5762
/// Number of guests (sockets) to connect to.
5863
#[clap(short = 'c', long, default_value_t = 1)]
5964
socket_count: u32,
6065

61-
/// Console backend (Network, Nested) to be used.
66+
/// Console backend (Network, Nested, Uds) to be used.
6267
#[clap(short = 'b', long, value_enum, default_value = "nested")]
6368
backend: BackendType,
6469

@@ -87,14 +92,30 @@ impl TryFrom<ConsoleArgs> for VuConsoleConfig {
8792

8893
let ConsoleArgs {
8994
socket_path,
95+
uds_path,
9096
backend,
9197
tcp_port,
9298
socket_count,
9399
max_queue_size,
94100
} = args;
95101

102+
// check validation of uds_path under Uds mode.
103+
if backend == BackendType::Uds {
104+
let path = uds_path
105+
.as_ref()
106+
.filter(|p| !p.as_os_str().is_empty())
107+
.ok_or(Error::InvalidUdsFile)?;
108+
109+
if let Some(parent_dir) = path.parent() {
110+
if !parent_dir.exists() {
111+
return Err(Error::InvalidUdsFile);
112+
}
113+
}
114+
}
115+
96116
Ok(Self {
97117
socket_path,
118+
uds_path: uds_path.unwrap_or_default(),
98119
backend,
99120
tcp_port,
100121
socket_count,

0 commit comments

Comments
 (0)