Skip to content

Commit 82d1fb6

Browse files
author
Hang SU
committed
vhost-device-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 0b6314f commit 82d1fb6

File tree

6 files changed

+300
-106
lines changed

6 files changed

+300
-106
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: 15 additions & 7 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

@@ -40,13 +43,18 @@ vhost-device-console --socket-path=<SOCKET_PATH>
4043
The localhost's port to be used for each guest, this part will be increased with
4144
0,1,2..socket_count-1.
4245

43-
-- option:: -b, --backend=nested|network
46+
-- option:: -b, --backend=nested|network|uds
4447

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

53+
.. option:: -v, --vm-sock=uds-file-path
54+
55+
The unix domain socket to be used for each guest, this part will be increased with
56+
0,1,2..socket_count-1.
57+
5058
.. option:: -q, --max-queue-size=SIZE
5159

5260
The maximum size of virtqueues. It is optional, and the default value is
@@ -62,10 +70,10 @@ VIRTIO_CONSOLE_F_SIZE features.
6270
## Features
6371

6472
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.
73+
either by connecting to a localhost server port (network backend) or a unix socket
74+
file (uds backend) or by creating an nested command prompt in the current terminal
75+
(nested backend). This prompt appears as soon as the guest is fully booted and
76+
gives the ability to user run command as a in regular terminal.
6977

7078
## Examples
7179

vhost-device-console/src/backend.rs

Lines changed: 38 additions & 22 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 vm_sock: String,
5053
pub backend: BackendType,
5154
pub tcp_port: String,
5255
pub socket_count: u32,
@@ -73,23 +76,33 @@ impl VuConsoleConfig {
7376
(0..self.socket_count).map(make_socket_path).collect()
7477
}
7578

76-
pub fn generate_tcp_addrs(&self) -> Vec<String> {
77-
let tcp_port_base = self.tcp_port.clone();
78-
79-
let make_tcp_port = |i: u32| -> String {
80-
let port_num: u32 = tcp_port_base.clone().parse().unwrap();
81-
"127.0.0.1:".to_owned() + &(port_num + i).to_string()
82-
};
83-
84-
(0..self.socket_count).map(make_tcp_port).collect()
79+
pub fn generate_vm_sock_addrs(&self) -> Vec<String> {
80+
match self.backend {
81+
// if type is Nested, result will be dropped.
82+
BackendType::Nested => {
83+
vec![String::new()]
84+
}
85+
86+
BackendType::Network => {
87+
let port_base: u32 = self.tcp_port.parse().unwrap();
88+
let make_tcp_port =
89+
|i: u32| -> String { "127.0.0.1:".to_owned() + &(port_base + i).to_string() };
90+
(0..self.socket_count).map(make_tcp_port).collect()
91+
}
92+
93+
BackendType::Uds => {
94+
let make_uds_path = |i: u32| -> String { self.vm_sock.to_owned() + &i.to_string() };
95+
(0..self.socket_count).map(make_uds_path).collect()
96+
}
97+
}
8598
}
8699
}
87100

88101
/// This is the public API through which an external program starts the
89102
/// vhost-device-console backend server.
90103
pub fn start_backend_server(
91104
socket: PathBuf,
92-
tcp_addr: String,
105+
vm_sock: String,
93106
backend: BackendType,
94107
max_queue_size: usize,
95108
) -> Result<()> {
@@ -104,7 +117,7 @@ pub fn start_backend_server(
104117
vu_console_backend
105118
.write()
106119
.unwrap()
107-
.assign_input_method(tcp_addr.clone())
120+
.assign_input_method(vm_sock.clone())
108121
.map_err(Error::CouldNotInitBackend)?;
109122

110123
let mut daemon = VhostUserDaemon::new(
@@ -132,26 +145,29 @@ pub fn start_backend_server(
132145
pub fn start_backend(config: VuConsoleConfig) -> Result<()> {
133146
let mut handles = HashMap::new();
134147
let (senders, receiver) = std::sync::mpsc::channel();
135-
let tcp_addrs = config.generate_tcp_addrs();
148+
let vm_sock = config.generate_vm_sock_addrs();
136149
let backend = config.backend;
137150
let max_queue_size = config.max_queue_size;
138151

139-
for (thread_id, (socket, tcp_addr)) in config
152+
for (thread_id, (socket, vm_sock)) in config
140153
.generate_socket_paths()
141154
.into_iter()
142-
.zip(tcp_addrs.iter())
155+
.zip(vm_sock.iter())
143156
.enumerate()
144157
{
145-
let tcp_addr = tcp_addr.clone();
146-
info!("thread_id: {}, socket: {:?}", thread_id, socket);
158+
let vm_sock = vm_sock.clone();
159+
info!(
160+
"thread_id: {}, socket: {:?} mode: {:?} vm_sock: {}",
161+
thread_id, socket, backend, vm_sock
162+
);
147163

148-
let name = format!("vhu-console-{}", tcp_addr);
164+
let name = format!("vhu-console-{}", vm_sock);
149165
let sender = senders.clone();
150166
let handle = Builder::new()
151167
.name(name.clone())
152168
.spawn(move || {
153169
let result = std::panic::catch_unwind(move || {
154-
start_backend_server(socket, tcp_addr.to_string(), backend, max_queue_size)
170+
start_backend_server(socket, vm_sock.to_string(), backend, max_queue_size)
155171
});
156172

157173
// Notify the main thread that we are done.
@@ -257,16 +273,16 @@ mod tests {
257273
fn test_backend_start_and_stop(args: ConsoleArgs) -> Result<()> {
258274
let config = VuConsoleConfig::try_from(args).expect("Wrong config");
259275

260-
let tcp_addrs = config.generate_tcp_addrs();
276+
let vm_sock = config.generate_vm_sock_addrs();
261277
let backend = config.backend;
262278
let max_queue_size = config.max_queue_size;
263279

264-
for (socket, tcp_addr) in config
280+
for (socket, vm_sock) in config
265281
.generate_socket_paths()
266282
.into_iter()
267-
.zip(tcp_addrs.iter())
283+
.zip(vm_sock.iter())
268284
{
269-
start_backend_server(socket, tcp_addr.to_string(), backend, max_queue_size)?;
285+
start_backend_server(socket, vm_sock.to_string(), backend, max_queue_size)?;
270286
}
271287
Ok(())
272288
}

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: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,21 @@ 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(
60+
short = 'v',
61+
long,
62+
value_name = "VM_SOCKET",
63+
default_value = "/tmp/vm.sock"
64+
)]
65+
vm_sock: String,
66+
5767
/// Number of guests (sockets) to connect to.
5868
#[clap(short = 'c', long, default_value_t = 1)]
5969
socket_count: u32,
6070

61-
/// Console backend (Network, Nested) to be used.
71+
/// Console backend (Network, Nested, Uds) to be used.
6272
#[clap(short = 'b', long, value_enum, default_value = "nested")]
6373
backend: BackendType,
6474

@@ -87,14 +97,30 @@ impl TryFrom<ConsoleArgs> for VuConsoleConfig {
8797

8898
let ConsoleArgs {
8999
socket_path,
100+
vm_sock,
90101
backend,
91102
tcp_port,
92103
socket_count,
93104
max_queue_size,
94105
} = args;
95106

107+
// check validation of vm_sock under Uds mode.
108+
if backend == BackendType::Uds {
109+
if vm_sock.is_empty() {
110+
return Err(Error::InvalidUdsFile);
111+
}
112+
113+
let parent = std::path::Path::new(&vm_sock).parent();
114+
if let Some(dir) = parent {
115+
if !dir.exists() {
116+
return Err(Error::InvalidUdsFile);
117+
}
118+
}
119+
}
120+
96121
Ok(Self {
97122
socket_path,
123+
vm_sock,
98124
backend,
99125
tcp_port,
100126
socket_count,

0 commit comments

Comments
 (0)