use std::collections::{HashMap, VecDeque};
use std::num::Wrapping;
#[cfg(test)]
use std::os::unix::io::FromRawFd;
use std::os::unix::io::{AsRawFd, OwnedFd, RawFd};
use std::sync::Arc;
use std::sync::atomic::{AtomicU32, Ordering};
use crate::VsockHostConnections;
pub type VsockDoorbell = Arc<dyn Fn() + Send + Sync>;
#[derive(Debug, Clone, Copy, Default)]
pub struct RxOps(u8);
impl RxOps {
pub const REQUEST: u8 = 0x01;
pub const RW: u8 = 0x02;
pub const RESPONSE: u8 = 0x04;
pub const CREDIT_UPDATE: u8 = 0x08;
pub const RESET: u8 = 0x10;
pub const CREDIT_REQUEST: u8 = 0x20;
pub fn pending(&self) -> bool {
self.0 != 0
}
pub fn enqueue(&mut self, op: u8) {
self.0 |= op;
}
pub fn dequeue(&mut self) -> u8 {
if self.0 == 0 {
return 0;
}
let op = self.0 & self.0.wrapping_neg();
self.0 &= !op;
op
}
pub fn peek(&self) -> u8 {
if self.0 == 0 {
return 0;
}
self.0 & self.0.wrapping_neg()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct VsockConnectionId {
pub host_port: u32,
pub guest_port: u32,
}
pub const TX_BUFFER_SIZE: u32 = 64 * 1024;
pub const CREDIT_UPDATE_THRESHOLD: u32 = 4096;
pub const VSOCK_SHUTDOWN_F_RECEIVE: u32 = 1 << 0;
pub const VSOCK_SHUTDOWN_F_SEND: u32 = 1 << 1;
pub const VSOCK_SHUTDOWN_F_BOTH: u32 = VSOCK_SHUTDOWN_F_RECEIVE | VSOCK_SHUTDOWN_F_SEND;
pub struct VsockConnection {
pub id: VsockConnectionId,
pub internal_fd: OwnedFd,
pub injected_notify: Option<std::sync::mpsc::Sender<()>>,
pub guest_cid: u64,
pub connect: bool,
pub rx_queue: RxOps,
pub fwd_cnt: Wrapping<u32>,
last_fwd_cnt: Wrapping<u32>,
pub peer_buf_alloc: u32,
pub peer_fwd_cnt: Wrapping<u32>,
pub rx_cnt: Wrapping<u32>,
credit_request_pending: bool,
peer_no_recv: bool,
}
impl VsockConnection {
pub fn new_local_init(
id: VsockConnectionId,
guest_cid: u64,
fd: OwnedFd,
injected_notify: std::sync::mpsc::Sender<()>,
) -> Self {
let mut conn = Self {
id,
internal_fd: fd,
guest_cid,
connect: false,
injected_notify: Some(injected_notify),
rx_queue: RxOps::default(),
fwd_cnt: Wrapping(0),
last_fwd_cnt: Wrapping(0),
peer_buf_alloc: 0,
peer_fwd_cnt: Wrapping(0),
rx_cnt: Wrapping(0),
credit_request_pending: false,
peer_no_recv: false,
};
conn.rx_queue.enqueue(RxOps::REQUEST);
conn
}
pub fn peer_avail_credit(&self) -> usize {
(Wrapping(self.peer_buf_alloc) - (self.rx_cnt - self.peer_fwd_cnt)).0 as usize
}
pub fn update_peer_credit(&mut self, buf_alloc: u32, fwd_cnt: u32) {
self.peer_buf_alloc = buf_alloc;
self.peer_fwd_cnt = Wrapping(fwd_cnt);
self.credit_request_pending = false;
}
pub fn maybe_request_credit(&mut self) {
if self.credit_request_pending || self.peer_buf_alloc == 0 {
return;
}
let half = (self.peer_buf_alloc / 2) as usize;
if self.peer_avail_credit() < half {
self.rx_queue.enqueue(RxOps::CREDIT_REQUEST);
self.credit_request_pending = true;
}
}
pub fn note_credit_request_sent(&mut self) {
self.credit_request_pending = true;
}
#[must_use]
pub fn credit_request_pending(&self) -> bool {
self.credit_request_pending
}
pub fn mark_peer_no_recv(&mut self) {
self.peer_no_recv = true;
}
#[must_use]
pub const fn peer_no_recv(&self) -> bool {
self.peer_no_recv
}
#[must_use]
pub const fn accepts_data(&self) -> bool {
self.connect && !self.peer_no_recv
}
pub fn advance_fwd_cnt(&mut self, bytes: u32) {
self.fwd_cnt += Wrapping(bytes);
let consumed = (self.fwd_cnt - self.last_fwd_cnt).0;
if consumed >= CREDIT_UPDATE_THRESHOLD {
self.rx_queue.enqueue(RxOps::CREDIT_UPDATE);
}
}
pub fn record_rx(&mut self, bytes: u32) {
self.rx_cnt += Wrapping(bytes);
}
pub fn mark_credit_sent(&mut self) {
self.last_fwd_cnt = self.fwd_cnt;
}
}
pub struct VsockConnectionManager {
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);
}
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 VsockHostConnections for VsockConnectionManager {
fn fd_for(&self, guest_port: u32, host_port: u32) -> Option<RawFd> {
let id = VsockConnectionId {
host_port,
guest_port,
};
self.connections
.get(&id)
.filter(|c| c.connect)
.map(|c| c.internal_fd.as_raw_fd())
}
fn mark_connected(&mut self, guest_port: u32, host_port: u32) {
let id = VsockConnectionId {
host_port,
guest_port,
};
if let Some(conn) = self.connections.get_mut(&id) {
conn.connect = true;
self.ring_doorbell();
tracing::info!("VsockConnectionManager: connection {:?} now Connected", id,);
} else {
tracing::warn!(
"VsockConnectionManager: mark_connected for unknown connection \
guest_port={} host_port={}",
guest_port,
host_port,
);
}
}
fn remove_connection(&mut self, guest_port: u32, host_port: u32) {
let id = VsockConnectionId {
host_port,
guest_port,
};
self.remove(&id);
}
fn update_peer_credit(
&mut self,
guest_port: u32,
host_port: u32,
buf_alloc: u32,
fwd_cnt: u32,
) {
let id = VsockConnectionId {
host_port,
guest_port,
};
if let Some(conn) = self.connections.get_mut(&id) {
conn.update_peer_credit(buf_alloc, fwd_cnt);
}
}
fn advance_fwd_cnt(&mut self, guest_port: u32, host_port: u32, bytes: u32) -> bool {
let id = VsockConnectionId {
host_port,
guest_port,
};
if let Some(conn) = self.connections.get_mut(&id) {
conn.advance_fwd_cnt(bytes);
if conn.rx_queue.pending() {
self.backend_rxq.push_back(id);
self.ring_doorbell();
return true;
}
}
false
}
fn enqueue_credit_update(&mut self, guest_port: u32, host_port: u32) {
let id = VsockConnectionId {
host_port,
guest_port,
};
if let Some(conn) = self.connections.get_mut(&id) {
conn.rx_queue.enqueue(RxOps::CREDIT_UPDATE);
self.backend_rxq.push_back(id);
self.ring_doorbell();
}
}
fn handle_shutdown(&mut self, guest_port: u32, host_port: u32, flags: u32) {
if flags == 0 || flags & VSOCK_SHUTDOWN_F_BOTH == VSOCK_SHUTDOWN_F_BOTH {
self.remove_connection(guest_port, host_port);
return;
}
let id = VsockConnectionId {
host_port,
guest_port,
};
if flags & VSOCK_SHUTDOWN_F_RECEIVE != 0 {
if let Some(conn) = self.connections.get_mut(&id) {
conn.mark_peer_no_recv();
}
}
if flags & VSOCK_SHUTDOWN_F_SEND != 0 {
if let Some(conn) = self.connections.get(&id) {
let fd = conn.internal_fd.as_raw_fd();
let r = unsafe { libc::shutdown(fd, libc::SHUT_WR) };
if r != 0 {
let err = std::io::Error::last_os_error();
if !matches!(err.raw_os_error(), Some(libc::ENOTCONN | libc::EINVAL)) {
tracing::warn!(
guest_port,
host_port,
"shutdown(internal_fd, SHUT_WR) for F_SEND failed: {}",
err,
);
}
}
}
}
}
}
impl Default for VsockConnectionManager {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_socketpair() -> (OwnedFd, OwnedFd) {
let mut fds: [libc::c_int; 2] = [0; 2];
let ret =
unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) };
assert_eq!(ret, 0);
unsafe { (OwnedFd::from_raw_fd(fds[0]), OwnedFd::from_raw_fd(fds[1])) }
}
#[test]
fn rx_ops_priority_order() {
let mut ops = RxOps::default();
ops.enqueue(RxOps::RESET);
ops.enqueue(RxOps::REQUEST);
ops.enqueue(RxOps::RW);
ops.enqueue(RxOps::CREDIT_UPDATE);
assert_eq!(ops.dequeue(), RxOps::REQUEST);
assert_eq!(ops.dequeue(), RxOps::RW);
assert_eq!(ops.dequeue(), RxOps::CREDIT_UPDATE);
assert_eq!(ops.dequeue(), RxOps::RESET);
assert_eq!(ops.dequeue(), 0);
}
#[test]
fn rx_ops_dedup() {
let mut ops = RxOps::default();
ops.enqueue(RxOps::RW);
ops.enqueue(RxOps::RW);
ops.enqueue(RxOps::RW);
assert_eq!(ops.dequeue(), RxOps::RW);
assert_eq!(ops.dequeue(), 0); }
#[test]
fn allocate_unique_host_ports() {
let mut mgr = VsockConnectionManager::new();
let (_, internal1) = make_socketpair();
let (_, internal2) = make_socketpair();
let (id1, _rx1) = mgr.allocate(1024, 3, internal1);
let (id2, _rx2) = mgr.allocate(1024, 3, internal2);
assert_ne!(id1.host_port, id2.host_port);
assert_eq!(id1.guest_port, 1024);
assert_eq!(id2.guest_port, 1024);
assert_eq!(mgr.len(), 2);
}
#[test]
fn allocate_enqueues_request() {
let mut mgr = VsockConnectionManager::new();
let (_, internal) = make_socketpair();
let (id, _rx) = mgr.allocate(1024, 3, internal);
assert_eq!(mgr.backend_rxq.len(), 1);
assert_eq!(mgr.backend_rxq[0], id);
let conn = mgr.get(&id).unwrap();
assert_eq!(conn.rx_queue.peek(), RxOps::REQUEST);
assert!(!conn.connect);
}
#[test]
fn connected_fds_only_returns_connected() {
let mut mgr = VsockConnectionManager::new();
let (_, internal1) = make_socketpair();
let (_, internal2) = make_socketpair();
let (id1, _rx1) = mgr.allocate(1024, 3, internal1);
let (_id2, _rx2) = mgr.allocate(1024, 3, internal2);
assert!(mgr.connected_fds().is_empty());
mgr.mark_connected(id1.guest_port, id1.host_port);
let fds = mgr.connected_fds();
assert_eq!(fds.len(), 1);
assert_eq!(fds[0].0, id1);
}
#[test]
fn remove_closes_fd() {
let mut mgr = VsockConnectionManager::new();
let (_, internal) = make_socketpair();
let fd_raw = internal.as_raw_fd();
let (id, _rx) = mgr.allocate(1024, 3, internal);
mgr.mark_connected(id.guest_port, id.host_port);
assert!(mgr.fd_for(1024, id.host_port).is_some());
mgr.remove_connection(id.guest_port, id.host_port);
assert!(mgr.fd_for(1024, id.host_port).is_none());
assert_eq!(mgr.len(), 0);
let ret = unsafe { libc::fcntl(fd_raw, libc::F_GETFD) };
assert_eq!(ret, -1);
}
#[test]
fn credit_flow_control() {
let mut mgr = VsockConnectionManager::new();
let (_, internal) = make_socketpair();
let (id, _rx) = mgr.allocate(1024, 3, internal);
let conn = mgr.get_mut(&id).unwrap();
conn.update_peer_credit(128 * 1024, 0);
assert_eq!(conn.peer_avail_credit(), 128 * 1024);
conn.record_rx(64 * 1024);
assert_eq!(conn.peer_avail_credit(), 64 * 1024);
conn.update_peer_credit(128 * 1024, 32 * 1024);
assert_eq!(conn.peer_avail_credit(), 96 * 1024);
}
#[test]
fn fwd_cnt_triggers_credit_update() {
let mut mgr = VsockConnectionManager::new();
let (_, internal) = make_socketpair();
let (id, _rx) = mgr.allocate(1024, 3, internal);
let conn = mgr.get_mut(&id).unwrap();
conn.rx_queue.dequeue();
conn.advance_fwd_cnt(CREDIT_UPDATE_THRESHOLD);
assert_eq!(conn.rx_queue.peek(), RxOps::CREDIT_UPDATE);
}
#[test]
fn fwd_cnt_below_threshold_does_not_trigger_credit_update() {
let mut mgr = VsockConnectionManager::new();
let (_, internal) = make_socketpair();
let (id, _rx) = mgr.allocate(1024, 3, internal);
let conn = mgr.get_mut(&id).unwrap();
conn.rx_queue.dequeue();
conn.advance_fwd_cnt(CREDIT_UPDATE_THRESHOLD - 1);
assert!(!conn.rx_queue.pending());
}
#[test]
fn maybe_request_credit_fires_below_half_window() {
let mut mgr = VsockConnectionManager::new();
let (_, internal) = make_socketpair();
let (id, _rx) = mgr.allocate(1024, 3, internal);
let conn = mgr.get_mut(&id).unwrap();
conn.rx_queue.dequeue();
conn.update_peer_credit(8192, 0);
conn.record_rx(5000); conn.maybe_request_credit();
assert_eq!(conn.rx_queue.peek(), RxOps::CREDIT_REQUEST);
assert!(conn.credit_request_pending());
}
#[test]
fn maybe_request_credit_noop_above_half_window() {
let mut mgr = VsockConnectionManager::new();
let (_, internal) = make_socketpair();
let (id, _rx) = mgr.allocate(1024, 3, internal);
let conn = mgr.get_mut(&id).unwrap();
conn.rx_queue.dequeue();
conn.update_peer_credit(8192, 0);
conn.record_rx(3000); conn.maybe_request_credit();
assert!(!conn.rx_queue.pending());
assert!(!conn.credit_request_pending());
}
#[test]
fn maybe_request_credit_dedupes_while_pending() {
let mut mgr = VsockConnectionManager::new();
let (_, internal) = make_socketpair();
let (id, _rx) = mgr.allocate(1024, 3, internal);
let conn = mgr.get_mut(&id).unwrap();
conn.rx_queue.dequeue();
conn.update_peer_credit(8192, 0);
conn.record_rx(5000);
conn.maybe_request_credit();
conn.rx_queue.dequeue();
conn.record_rx(100); conn.maybe_request_credit();
assert!(!conn.rx_queue.pending(), "second request would be a dup");
}
#[test]
fn update_peer_credit_clears_pending_flag() {
let mut mgr = VsockConnectionManager::new();
let (_, internal) = make_socketpair();
let (id, _rx) = mgr.allocate(1024, 3, internal);
let conn = mgr.get_mut(&id).unwrap();
conn.rx_queue.dequeue();
conn.update_peer_credit(8192, 0);
conn.record_rx(5000);
conn.maybe_request_credit();
assert!(conn.credit_request_pending());
conn.update_peer_credit(8192, 5000);
assert!(!conn.credit_request_pending());
assert_eq!(conn.rx_queue.dequeue(), RxOps::CREDIT_REQUEST);
conn.maybe_request_credit();
assert!(!conn.rx_queue.pending());
assert!(!conn.credit_request_pending());
}
#[test]
fn shutdown_both_bits_removes_connection() {
let mut mgr = VsockConnectionManager::new();
let (_, internal) = make_socketpair();
let (id, _rx) = mgr.allocate(1024, 3, internal);
assert!(mgr.get(&id).is_some());
mgr.handle_shutdown(id.guest_port, id.host_port, VSOCK_SHUTDOWN_F_BOTH);
assert!(mgr.get(&id).is_none());
}
#[test]
fn shutdown_receive_bit_marks_half_close() {
let mut mgr = VsockConnectionManager::new();
let (_, internal) = make_socketpair();
let (id, _rx) = mgr.allocate(1024, 3, internal);
mgr.handle_shutdown(id.guest_port, id.host_port, VSOCK_SHUTDOWN_F_RECEIVE);
let conn = mgr.get(&id).expect("conn must survive half-close");
assert!(conn.peer_no_recv());
assert!(!conn.accepts_data() || !conn.connect); }
#[test]
fn shutdown_send_bit_only_is_informational() {
let mut mgr = VsockConnectionManager::new();
let (_, internal) = make_socketpair();
let (id, _rx) = mgr.allocate(1024, 3, internal);
mgr.handle_shutdown(id.guest_port, id.host_port, VSOCK_SHUTDOWN_F_SEND);
let conn = mgr.get(&id).expect("conn must survive");
assert!(
!conn.peer_no_recv(),
"F_SEND alone does not block host→peer RW"
);
}
#[test]
fn shutdown_send_bit_propagates_eof_to_daemon_fd() {
use std::io::Read;
use std::os::fd::IntoRawFd;
let mut mgr = VsockConnectionManager::new();
let (daemon_end, internal) = make_socketpair();
let (id, _rx) = mgr.allocate(1024, 3, internal);
let mut daemon_stream =
unsafe { std::os::unix::net::UnixStream::from_raw_fd(daemon_end.into_raw_fd()) };
daemon_stream
.set_read_timeout(Some(std::time::Duration::from_secs(2)))
.unwrap();
mgr.handle_shutdown(id.guest_port, id.host_port, VSOCK_SHUTDOWN_F_SEND);
let mut buf = [0u8; 8];
let n = daemon_stream
.read(&mut buf)
.expect("read on daemon fd should not error");
assert_eq!(n, 0, "daemon fd must read EOF after F_SEND propagation");
use std::io::Write;
daemon_stream
.write_all(b"still-alive")
.expect("daemon→internal write should still succeed");
}
#[test]
fn doorbell_rings_on_producer_paths() {
use std::sync::atomic::AtomicUsize;
let rings = Arc::new(AtomicUsize::new(0));
let mut mgr = VsockConnectionManager::new();
let rings_cb = Arc::clone(&rings);
mgr.set_doorbell(Arc::new(move || {
rings_cb.fetch_add(1, Ordering::SeqCst);
}));
let (_, internal) = make_socketpair();
let (id, _rx) = mgr.allocate(1024, 3, internal);
assert_eq!(rings.load(Ordering::SeqCst), 1, "allocate rings");
mgr.mark_connected(id.guest_port, id.host_port);
assert_eq!(rings.load(Ordering::SeqCst), 2, "mark_connected rings");
mgr.enqueue_credit_update(id.guest_port, id.host_port);
assert_eq!(
rings.load(Ordering::SeqCst),
3,
"enqueue_credit_update rings"
);
assert!(mgr.advance_fwd_cnt(id.guest_port, id.host_port, CREDIT_UPDATE_THRESHOLD));
assert_eq!(
rings.load(Ordering::SeqCst),
4,
"advance_fwd_cnt rings on push"
);
}
#[test]
fn doorbell_silent_on_injection_driver_paths() {
use std::sync::atomic::AtomicUsize;
let rings = Arc::new(AtomicUsize::new(0));
let mut mgr = VsockConnectionManager::new();
let (_, internal) = make_socketpair();
let (id, _rx) = mgr.allocate(1024, 3, internal);
mgr.get_mut(&id).unwrap().rx_queue.dequeue();
let rings_cb = Arc::clone(&rings);
mgr.set_doorbell(Arc::new(move || {
rings_cb.fetch_add(1, Ordering::SeqCst);
}));
assert!(!mgr.advance_fwd_cnt(id.guest_port, id.host_port, 1));
assert_eq!(rings.load(Ordering::SeqCst), 0);
mgr.enqueue_rw(id);
mgr.enqueue_reset(id);
assert_eq!(rings.load(Ordering::SeqCst), 0);
}
#[test]
fn shutdown_flags_zero_removes_connection_conservatively() {
let mut mgr = VsockConnectionManager::new();
let (_, internal) = make_socketpair();
let (id, _rx) = mgr.allocate(1024, 3, internal);
mgr.handle_shutdown(id.guest_port, id.host_port, 0);
assert!(mgr.get(&id).is_none());
}
}