Skip to content

WebRTC streamer and vhost-user-input backend processes communicate with a client-server protocol - #3111

Open
jemoreira wants to merge 6 commits into
google:mainfrom
jemoreira:input
Open

WebRTC streamer and vhost-user-input backend processes communicate with a client-server protocol#3111
jemoreira wants to merge 6 commits into
google:mainfrom
jemoreira:input

Conversation

@jemoreira

Copy link
Copy Markdown
Member

cf_vhost_user_input listens on a UNIX socket server it inherits from run_cvd. webRTC connects to this server to inject input events (mouse, touch, keyboard, etc) to the device backends. This enables the input backend to receive events from sources outside the streamer process.

In addition to the protocol change, the device backend ensures all groups of events sent to the guest driver end in a SYN_REPORT. These SYN_REPORT events are used to group events from multiple sources so that the driver receives a valid flow of events.

The behavior is enabled by run_cvd by the flags it launches webRTC and cf_vhost_user_input with. When the run_cvd binary is not substituted, events are sent via a socket pair as before. It would be a problem if run_cvd was substituted but webRTC or cf_vhost_user_input was not, but this situation is extremely unlikely since the latter have been substituted for a while now.

Bug: b/552079861

@jemoreira
jemoreira requested a review from Databean August 28, 2026 02:38
The webRTC streamer can connect to a unix socket for each input device
to inject input events and receive status feedback, in addition to the
existing approach based on receiving the connection as an inherited file
descriptor. The behavior is chosen base on command line flags passed by
run_cvd.

Bug: b/552079861
and implement it with the existing socket pair connected to stdin.

Bug: b/552079861
The vhost-user-input process accepts a UNIX socket's fd via command line
flag. When given, events are not read from stdin, but instead from
clients connected to that unix socket.

Multiple clients can connect at the same time and the server guarantees
that each group of events ending in a SYN_REPORT event is delivered to
the VMM atomically.

Bug: b/552079861
webrtc connects to a unix socket hosted by vhu-input instead of through
a socket pair inherited from run_cvd. run_cvd unconditionally enables
this behavior, assuming that webRTC and cf_vhost_user_input are already
substituted everywhere run_cvd is substituted.

Bug: b/552079861
@jemoreira jemoreira added the kokoro:force-run Trigger a presubmit build unconditionally. label Aug 31, 2026
@GoogleCuttlefishTesterBot GoogleCuttlefishTesterBot removed the kokoro:force-run Trigger a presubmit build unconditionally. label Aug 31, 2026
Comment on lines +271 to +274
rotary_sockets_ =
CF_EXPECT(NewDeviceSockets(RotaryEventsServerPath(instance_),
RotarySocketPath(instance_)),
"Failed to setup sockets for rotary device");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Confirming my understanding: run_cvd creates the unix server sockets and then launches webrtc with command line flags with the server path. This preserves the ordering guarantee that the server sockets are created before the client sockets try to connect to them.

What is gained by passing paths rather than file descriptors to webrtc? Does webrtc now retry connections to the server?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Your understanding is correct.

WebRTC doesn't retry connections. The main benefit is that if an instance of webRTC crashes mid write it will corrupt that connection since all future events will be read misaligned by the server. The problem goes away by making each instance of webRTC have their own independent connection. This isn't that big a deal right now because a crashed webRTC brings down the entire device, but that may not be the case in the future.

Comment on lines +65 to +68
.events_server = SharedFD::SocketLocalServer(evt_server_path, false,
SOCK_STREAM, 0600),
.vhu_server = SharedFD::SocketLocalServer(vhu_server_path, false,
SOCK_STREAM, 0600),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: this could be written as

.events_server = CF_EXPECT(Fd::SocketLocalServer(...)),
.vhu_server = CF_EXPECT(Fd::SocketLocalServer(...)),

saving the ->IsOpen() calls later.

Comment on lines +118 to 127
std::vector<std::string> touch_paths =
input_paths_provider_.TouchscreenPaths();
for (const std::string& touchpad_path :
input_paths_provider_.TouchpadPaths()) {
touch_paths.push_back(touchpad_path);
}
cmd.AddParameter("-touch_fds=", touch_connections[0]);
for (int i = 1; i < touch_connections.size(); ++i) {
cmd.AppendToLastParameter(",", touch_connections[i]);
cmd.AddParameter("-touch_server_paths=", touch_paths[0]);
for (int i = 1; i < touch_paths.size(); ++i) {
cmd.AppendToLastParameter(",", touch_paths[i]);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This had an awkward construction before to take advantage of Command's special handling of SharedFD, but that is no longer needed. This can now be

cmd.AddParameter("-touch_server_paths=", absl::StrJoin(touch_paths, ","));

Comment on lines +27 to +29
/// Reads available bytes from the underlying reader. Returns number of bytes read during this
/// call (which may be less than the resulting size of the buffer) or 0 on EOF.
pub fn read_ahead(&mut self) -> Result<usize> {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

read_ahead and consume look very similar to methods in std::io::BufRead. Is it possible to implement this trait instead of defining a custom API for these methods?

Implementing BufRead is not mutually exclusive with exposing additional pub functions in a separate impl, so buffer and reader can still be exposed.

use crate::buf_reader::BufReader;
use crate::vio_input::{trim_to_event_size_multiple, VIRTIO_INPUT_EVENT_SIZE};

/// Provides input events to the vhost-user backend. Implementations may can get these events from

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: "may" or "can" but not both.

Comment on lines +24 to +26
fn get_events(&mut self) -> Vec<u8>;
/// Send status feedback such as keyboard LED states back to the source.
fn send_status_feedback(&mut self, status_buffer: Vec<u8>);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It seems like a deficiency of the callers that they can't handle these functions returning errors. Rather than force every implementation to panic or log, can the implementations return errors normally and have the one caller deal with converting the errors to aborts or logs?

In particular the one caller of send_status_feedback already returns a Result.

panic!("Background thread's channel closed unexpectedly");
}
}
std::mem::take(&mut *self.events.lock().unwrap())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: there is also Vec::clear.

mut events_fd: UnixStream,
events: Arc<Mutex<Vec<u8>>>,
status: Arc<Mutex<Vec<u8>>>,
) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

anyhow::Error is Send and Sync so it is possible to return error values rather than aborting everywhere.

let events_clone = events.clone();
let status = Arc::new(Mutex::new(Vec::new()));
let status_clone = events.clone();
std::thread::spawn(move || server_loop(listener, bg_events_fd, events_clone, status_clone));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should the JoinHandle for this thread be associated with the UnixSocketEventSource? Should this thread outlive the UnixSocketEventSource?

Comment on lines +260 to +261
// These events were checked on previous calls.
let skip_len = trim_to_event_size_multiple(reader.buffer().len() - read_len);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Shouldn't events on previous calls be removed from consideration using consume?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Events from previous reads are only removed from the buffer after receiving the SYN_REPORT event. If this code is running on a following read, that means SYN_REPORT was not found in the previously read events so those are skipped in the search.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants