use std::io;
use std::os::fd::{AsRawFd, OwnedFd, RawFd};
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicBool, Ordering};
use msb_krun::backends::vsock::{
VsockConnectRequest, VsockConnectState, VsockNotifier, VsockPortBackend, VsockShutdown,
VsockStreamBackend,
};
use nix::errno::Errno;
use nix::sys::socket::{
MsgFlags, Shutdown, SockType, UnixAddr, connect, getsockopt, recv, send, shutdown, sockopt,
};
use crate::common::{
DEFAULT_MAX_ACTIVE_PEERS, PeerLease, PeerLimit, nonblocking_unix_socket, validate_socket_path,
};
pub struct UnixStreamPortBackend {
path: PathBuf,
peers: PeerLimit,
}
struct UnixStreamBackend {
fd: OwnedFd,
connected: AtomicBool,
defer_connect_check: AtomicBool,
_lease: PeerLease,
}
impl UnixStreamPortBackend {
pub fn new(path: impl AsRef<Path>) -> io::Result<Self> {
Self::with_max_active_peers(path, DEFAULT_MAX_ACTIVE_PEERS)
}
pub fn with_max_active_peers(path: impl AsRef<Path>, max: usize) -> io::Result<Self> {
let path = path.as_ref().to_path_buf();
validate_socket_path(&path)?;
if max == 0 {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"vsock stream peer limit must be non-zero",
));
}
Ok(Self {
path,
peers: PeerLimit::new(max),
})
}
}
impl VsockPortBackend for UnixStreamPortBackend {
fn connect(
&self,
_request: VsockConnectRequest,
_notifier: VsockNotifier,
) -> io::Result<Box<dyn VsockStreamBackend>> {
let lease = self.peers.acquire()?;
let fd = nonblocking_unix_socket(SockType::Stream)?;
let address = UnixAddr::new(&self.path)
.map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, err))?;
let (connected, pending) = match connect(fd.as_raw_fd(), &address) {
Ok(()) => (true, false),
Err(Errno::EINPROGRESS | Errno::EAGAIN) => (false, true),
Err(err) => return Err(io::Error::from(err)),
};
Ok(Box::new(UnixStreamBackend {
fd,
connected: AtomicBool::new(connected),
defer_connect_check: AtomicBool::new(pending),
_lease: lease,
}))
}
}
impl VsockStreamBackend for UnixStreamBackend {
fn connect_state(&self) -> io::Result<VsockConnectState> {
if self.connected.load(Ordering::Acquire) {
return Ok(VsockConnectState::Connected);
}
if self.defer_connect_check.swap(false, Ordering::AcqRel) {
return Ok(VsockConnectState::Connecting);
}
let error = getsockopt(&self.fd, sockopt::SocketError).map_err(io::Error::from)?;
if error != 0 {
return Err(io::Error::from_raw_os_error(error));
}
self.connected.store(true, Ordering::Release);
Ok(VsockConnectState::Connected)
}
fn read(&self, buf: &mut [u8]) -> io::Result<usize> {
recv(self.fd.as_raw_fd(), buf, MsgFlags::MSG_DONTWAIT).map_err(io::Error::from)
}
fn write(&self, buf: &[u8]) -> io::Result<usize> {
#[cfg(target_os = "linux")]
let flags = MsgFlags::MSG_NOSIGNAL;
#[cfg(not(target_os = "linux"))]
let flags = MsgFlags::empty();
send(self.fd.as_raw_fd(), buf, flags).map_err(io::Error::from)
}
fn shutdown(&self, how: VsockShutdown) -> io::Result<()> {
let how = match how {
VsockShutdown::Read => Shutdown::Read,
VsockShutdown::Write => Shutdown::Write,
VsockShutdown::Both => Shutdown::Both,
};
shutdown(self.fd.as_raw_fd(), how).map_err(io::Error::from)
}
fn pollable(&self) -> Option<RawFd> {
Some(self.fd.as_raw_fd())
}
}
#[cfg(test)]
mod tests {
use std::io::{Read, Write};
use std::os::unix::net::UnixListener;
use msb_krun::backends::vsock::{VsockConnectRequest, VsockPortBackend};
use super::*;
#[test]
fn stream_backend_connects_and_moves_bytes_in_both_directions() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("service.sock");
let listener = UnixListener::bind(&path).unwrap();
let service = UnixStreamPortBackend::new(&path).unwrap();
let endpoint = service
.connect(
VsockConnectRequest {
guest_cid: 3,
guest_port: 4000,
host_port: 5000,
},
VsockNotifier::new().unwrap(),
)
.unwrap();
let (mut host, _) = listener.accept().unwrap();
for _ in 0..100 {
if endpoint.connect_state().unwrap() == VsockConnectState::Connected {
break;
}
std::thread::yield_now();
}
assert_eq!(
endpoint.connect_state().unwrap(),
VsockConnectState::Connected
);
endpoint.write(b"guest").unwrap();
let mut request = [0; 5];
host.read_exact(&mut request).unwrap();
assert_eq!(&request, b"guest");
host.write_all(b"host").unwrap();
let mut response = [0; 4];
for _ in 0..100 {
match endpoint.read(&mut response) {
Ok(4) => break,
Err(err) if err.kind() == io::ErrorKind::WouldBlock => std::thread::yield_now(),
result => panic!("unexpected stream read result: {result:?}"),
}
}
assert_eq!(&response, b"host");
}
}