use crate::error::{VZError, VZResult};
use crate::shim_ffi;
use std::ffi::{c_char, c_void};
use std::os::unix::io::RawFd;
use std::sync::mpsc as std_mpsc;
use std::time::Duration;
struct VsockConnectionInfo {
fd: RawFd,
source_port: u32,
destination_port: u32,
}
impl Drop for VsockConnectionInfo {
fn drop(&mut self) {
if self.fd >= 0 {
unsafe { libc::close(self.fd) };
}
}
}
type VsockResult = Result<VsockConnectionInfo, String>;
unsafe extern "C" fn vsock_trampoline(
ctx: *mut c_void,
fd: i32,
src_port: u32,
dst_port: u32,
err: *mut c_char,
) {
unsafe {
let sender = Box::from_raw(ctx.cast::<std_mpsc::Sender<VsockResult>>());
let result = if err.is_null() {
Ok(VsockConnectionInfo {
fd,
source_port: src_port,
destination_port: dst_port,
})
} else {
Err(shim_ffi::take_error_string(err))
};
let _ = sender.send(result);
}
}
pub struct VirtioSocketDevice {
device_box: *mut c_void,
}
unsafe impl Send for VirtioSocketDevice {}
unsafe impl Sync for VirtioSocketDevice {}
impl VirtioSocketDevice {
pub(crate) fn from_box(device_box: *mut c_void) -> Self {
Self { device_box }
}
pub fn connect_blocking(
&self,
port: u32,
timeout: Duration,
) -> VZResult<VirtioSocketConnection> {
tracing::debug!("VirtioSocketDevice::connect_blocking(port={})", port);
let (tx, rx) = std_mpsc::channel::<VsockResult>();
let ctx: *mut c_void = Box::into_raw(Box::new(tx)).cast();
unsafe {
shim_ffi::abx_vsock_connect(self.device_box, port, ctx, vsock_trampoline);
}
match rx.recv_timeout(timeout) {
Ok(Ok(mut info)) => {
tracing::info!(
"Vsock connected: fd={}, src_port={}, dst_port={}",
info.fd,
info.source_port,
info.destination_port
);
let fd = info.fd;
info.fd = -1;
Ok(VirtioSocketConnection {
fd,
source_port: info.source_port,
destination_port: info.destination_port,
})
}
Ok(Err(message)) => {
if is_transient_connect_error(&message) {
tracing::debug!(port, error = %message, "Vsock connection not ready yet");
} else {
tracing::warn!(port, error = %message, "Vsock connection failed");
}
Err(VZError::ConnectionFailed(message))
}
Err(std_mpsc::RecvTimeoutError::Timeout) => {
tracing::warn!("Vsock connection timed out after {:?}", timeout);
Err(VZError::Timeout(format!(
"Vsock connection to port {port} timed out"
)))
}
Err(std_mpsc::RecvTimeoutError::Disconnected) => Err(VZError::Internal {
code: -1,
message: "Connection channel closed unexpectedly".into(),
}),
}
}
}
impl Drop for VirtioSocketDevice {
fn drop(&mut self) {
if !self.device_box.is_null() {
unsafe { shim_ffi::abx_object_release(self.device_box) };
}
}
}
fn is_transient_connect_error(message: &str) -> bool {
let msg = message.to_ascii_lowercase();
msg.contains("connection reset")
|| msg.contains("connection refused")
|| msg.contains("connection aborted")
|| msg.contains("broken pipe")
}
pub struct VirtioSocketConnection {
fd: RawFd,
source_port: u32,
destination_port: u32,
}
impl VirtioSocketConnection {
#[inline]
#[must_use]
pub fn as_raw_fd(&self) -> RawFd {
self.fd
}
#[inline]
#[must_use]
pub fn source_port(&self) -> u32 {
self.source_port
}
#[inline]
#[must_use]
pub fn destination_port(&self) -> u32 {
self.destination_port
}
pub fn read(&self, buf: &mut [u8]) -> std::io::Result<usize> {
let n = unsafe { libc::read(self.fd, buf.as_mut_ptr().cast::<c_void>(), buf.len()) };
if n < 0 {
Err(std::io::Error::last_os_error())
} else {
Ok(n as usize)
}
}
pub fn write(&self, buf: &[u8]) -> std::io::Result<usize> {
let n = unsafe { libc::write(self.fd, buf.as_ptr().cast::<c_void>(), buf.len()) };
if n < 0 {
Err(std::io::Error::last_os_error())
} else {
Ok(n as usize)
}
}
#[must_use]
pub fn into_raw_fd(self) -> RawFd {
let fd = self.fd;
std::mem::forget(self);
fd
}
}
impl Drop for VirtioSocketConnection {
fn drop(&mut self) {
if self.fd >= 0 {
unsafe {
libc::close(self.fd);
}
}
}
}