microsandbox_vsock/
stream.rs1use std::io;
2use std::os::fd::{AsRawFd, OwnedFd, RawFd};
3use std::path::{Path, PathBuf};
4use std::sync::atomic::{AtomicBool, Ordering};
5
6use msb_krun::backends::vsock::{
7 VsockConnectRequest, VsockConnectState, VsockNotifier, VsockPortBackend, VsockShutdown,
8 VsockStreamBackend,
9};
10use nix::errno::Errno;
11use nix::sys::socket::{
12 MsgFlags, Shutdown, SockType, UnixAddr, connect, getsockopt, recv, send, shutdown, sockopt,
13};
14
15use crate::common::{
16 DEFAULT_MAX_ACTIVE_PEERS, PeerLease, PeerLimit, nonblocking_unix_socket, validate_socket_path,
17};
18
19pub struct UnixStreamPortBackend {
25 path: PathBuf,
26 peers: PeerLimit,
27}
28
29struct UnixStreamBackend {
30 fd: OwnedFd,
31 connected: AtomicBool,
32 defer_connect_check: AtomicBool,
33 _lease: PeerLease,
34}
35
36impl UnixStreamPortBackend {
41 pub fn new(path: impl AsRef<Path>) -> io::Result<Self> {
43 Self::with_max_active_peers(path, DEFAULT_MAX_ACTIVE_PEERS)
44 }
45
46 pub fn with_max_active_peers(path: impl AsRef<Path>, max: usize) -> io::Result<Self> {
48 let path = path.as_ref().to_path_buf();
49 validate_socket_path(&path)?;
50 if max == 0 {
51 return Err(io::Error::new(
52 io::ErrorKind::InvalidInput,
53 "vsock stream peer limit must be non-zero",
54 ));
55 }
56 Ok(Self {
57 path,
58 peers: PeerLimit::new(max),
59 })
60 }
61}
62
63impl VsockPortBackend for UnixStreamPortBackend {
68 fn connect(
69 &self,
70 _request: VsockConnectRequest,
71 _notifier: VsockNotifier,
72 ) -> io::Result<Box<dyn VsockStreamBackend>> {
73 let lease = self.peers.acquire()?;
74 let fd = nonblocking_unix_socket(SockType::Stream)?;
75 let address = UnixAddr::new(&self.path)
76 .map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, err))?;
77
78 let (connected, pending) = match connect(fd.as_raw_fd(), &address) {
79 Ok(()) => (true, false),
80 Err(Errno::EINPROGRESS | Errno::EAGAIN) => (false, true),
83 Err(err) => return Err(io::Error::from(err)),
84 };
85
86 Ok(Box::new(UnixStreamBackend {
87 fd,
88 connected: AtomicBool::new(connected),
89 defer_connect_check: AtomicBool::new(pending),
93 _lease: lease,
94 }))
95 }
96}
97
98impl VsockStreamBackend for UnixStreamBackend {
99 fn connect_state(&self) -> io::Result<VsockConnectState> {
100 if self.connected.load(Ordering::Acquire) {
101 return Ok(VsockConnectState::Connected);
102 }
103 if self.defer_connect_check.swap(false, Ordering::AcqRel) {
104 return Ok(VsockConnectState::Connecting);
105 }
106
107 let error = getsockopt(&self.fd, sockopt::SocketError).map_err(io::Error::from)?;
108 if error != 0 {
109 return Err(io::Error::from_raw_os_error(error));
110 }
111 self.connected.store(true, Ordering::Release);
112 Ok(VsockConnectState::Connected)
113 }
114
115 fn read(&self, buf: &mut [u8]) -> io::Result<usize> {
116 recv(self.fd.as_raw_fd(), buf, MsgFlags::MSG_DONTWAIT).map_err(io::Error::from)
117 }
118
119 fn write(&self, buf: &[u8]) -> io::Result<usize> {
120 #[cfg(target_os = "linux")]
121 let flags = MsgFlags::MSG_NOSIGNAL;
122 #[cfg(not(target_os = "linux"))]
123 let flags = MsgFlags::empty();
124 send(self.fd.as_raw_fd(), buf, flags).map_err(io::Error::from)
125 }
126
127 fn shutdown(&self, how: VsockShutdown) -> io::Result<()> {
128 let how = match how {
129 VsockShutdown::Read => Shutdown::Read,
130 VsockShutdown::Write => Shutdown::Write,
131 VsockShutdown::Both => Shutdown::Both,
132 };
133 shutdown(self.fd.as_raw_fd(), how).map_err(io::Error::from)
134 }
135
136 fn pollable(&self) -> Option<RawFd> {
137 Some(self.fd.as_raw_fd())
138 }
139}
140
141#[cfg(test)]
146mod tests {
147 use std::io::{Read, Write};
148 use std::os::unix::net::UnixListener;
149
150 use msb_krun::backends::vsock::{VsockConnectRequest, VsockPortBackend};
151
152 use super::*;
153
154 #[test]
155 fn stream_backend_connects_and_moves_bytes_in_both_directions() {
156 let dir = tempfile::tempdir().unwrap();
157 let path = dir.path().join("service.sock");
158 let listener = UnixListener::bind(&path).unwrap();
159 let service = UnixStreamPortBackend::new(&path).unwrap();
160 let endpoint = service
161 .connect(
162 VsockConnectRequest {
163 guest_cid: 3,
164 guest_port: 4000,
165 host_port: 5000,
166 },
167 VsockNotifier::new().unwrap(),
168 )
169 .unwrap();
170 let (mut host, _) = listener.accept().unwrap();
171
172 for _ in 0..100 {
173 if endpoint.connect_state().unwrap() == VsockConnectState::Connected {
174 break;
175 }
176 std::thread::yield_now();
177 }
178 assert_eq!(
179 endpoint.connect_state().unwrap(),
180 VsockConnectState::Connected
181 );
182
183 endpoint.write(b"guest").unwrap();
184 let mut request = [0; 5];
185 host.read_exact(&mut request).unwrap();
186 assert_eq!(&request, b"guest");
187
188 host.write_all(b"host").unwrap();
189 let mut response = [0; 4];
190 for _ in 0..100 {
191 match endpoint.read(&mut response) {
192 Ok(4) => break,
193 Err(err) if err.kind() == io::ErrorKind::WouldBlock => std::thread::yield_now(),
194 result => panic!("unexpected stream read result: {result:?}"),
195 }
196 }
197 assert_eq!(&response, b"host");
198 }
199}