mod connection;
mod host_connections;
mod ops;
#[cfg(test)]
mod tests;
pub use connection::{
CREDIT_UPDATE_THRESHOLD, TX_BUFFER_SIZE, VSOCK_SHUTDOWN_F_BOTH, VSOCK_SHUTDOWN_F_RECEIVE,
VSOCK_SHUTDOWN_F_SEND, VsockConnection, VsockConnectionId,
};
pub use ops::RxOps;
use std::collections::{HashMap, VecDeque};
use std::os::unix::io::{AsRawFd, OwnedFd, RawFd};
use std::sync::Arc;
use std::sync::atomic::{AtomicU32, Ordering};
pub type VsockDoorbell = Arc<dyn Fn() + Send + Sync>;
pub struct VsockConnectionManager {
pub(super) connections: HashMap<VsockConnectionId, VsockConnection>,
pub backend_rxq: VecDeque<VsockConnectionId>,
next_host_port: AtomicU32,
doorbell: Option<VsockDoorbell>,
}
impl VsockConnectionManager {
const EPHEMERAL_PORT_BASE: u32 = 50_000;
pub fn new() -> Self {
Self {
connections: HashMap::new(),
backend_rxq: VecDeque::new(),
next_host_port: AtomicU32::new(Self::EPHEMERAL_PORT_BASE),
doorbell: None,
}
}
pub fn set_doorbell(&mut self, doorbell: VsockDoorbell) {
self.doorbell = Some(doorbell);
}
pub(super) fn ring_doorbell(&self) {
if let Some(doorbell) = &self.doorbell {
doorbell();
}
}
pub fn allocate(
&mut self,
guest_port: u32,
guest_cid: u64,
internal_fd: OwnedFd,
) -> (VsockConnectionId, std::sync::mpsc::Receiver<()>) {
let host_port = self.next_host_port.fetch_add(1, Ordering::Relaxed);
let id = VsockConnectionId {
host_port,
guest_port,
};
let (tx, rx) = std::sync::mpsc::channel();
let conn = VsockConnection::new_local_init(id, guest_cid, internal_fd, tx);
self.connections.insert(id, conn);
self.backend_rxq.push_back(id);
self.ring_doorbell();
tracing::info!(
"VsockConnectionManager: allocated connection guest_port={} host_port={} — \
OP_REQUEST enqueued",
guest_port,
host_port,
);
(id, rx)
}
pub fn connected_fds(&self) -> Vec<(VsockConnectionId, RawFd)> {
self.connections
.values()
.filter(|c| c.connect)
.map(|c| (c.id, c.internal_fd.as_raw_fd()))
.collect()
}
pub fn get_mut(&mut self, id: &VsockConnectionId) -> Option<&mut VsockConnection> {
self.connections.get_mut(id)
}
pub fn get(&self, id: &VsockConnectionId) -> Option<&VsockConnection> {
self.connections.get(id)
}
pub fn enqueue_rw(&mut self, id: VsockConnectionId) {
if let Some(conn) = self.connections.get_mut(&id) {
conn.rx_queue.enqueue(RxOps::RW);
self.backend_rxq.push_back(id);
}
}
pub fn enqueue_reset(&mut self, id: VsockConnectionId) {
if let Some(conn) = self.connections.get_mut(&id) {
conn.rx_queue.enqueue(RxOps::RESET);
self.backend_rxq.push_back(id);
}
}
pub fn remove(&mut self, id: &VsockConnectionId) {
if let Some(mut conn) = self.connections.remove(id) {
if let Some(tx) = conn.injected_notify.take() {
let _ = tx.send(());
}
self.backend_rxq.retain(|qid| qid != id);
tracing::info!(
"VsockConnectionManager: removed connection guest_port={} host_port={} — fd closed",
id.guest_port,
id.host_port,
);
}
}
pub fn connections_with_pending_rx(&self) -> Vec<VsockConnectionId> {
let in_queue: std::collections::HashSet<_> = self.backend_rxq.iter().copied().collect();
self.connections
.values()
.filter(|c| c.rx_queue.pending() && !in_queue.contains(&c.id))
.map(|c| c.id)
.collect()
}
#[cfg(test)]
pub fn len(&self) -> usize {
self.connections.len()
}
#[cfg(test)]
pub fn is_empty(&self) -> bool {
self.connections.is_empty()
}
}
impl Default for VsockConnectionManager {
fn default() -> Self {
Self::new()
}
}