microsandbox_vsock/
dgram.rs1use std::io;
2use std::os::fd::{AsRawFd, OwnedFd, RawFd};
3use std::os::unix::net::UnixDatagram as StdUnixDatagram;
4use std::path::{Path, PathBuf};
5use std::sync::atomic::{AtomicU64, Ordering};
6
7use msb_krun::backends::vsock::{
8 VsockDatagramBackend, VsockDatagramPeer, VsockDatagramPortBackend, VsockDatagramRead,
9 VsockNotifier,
10};
11use nix::sys::socket::{MsgFlags, recv, send};
12use tempfile::TempDir;
13
14use crate::common::{DEFAULT_MAX_ACTIVE_PEERS, PeerLease, PeerLimit, validate_socket_path};
15
16pub struct UnixDatagramPortBackend {
22 path: PathBuf,
23 peer_dir: TempDir,
24 next_peer: AtomicU64,
25 peers: PeerLimit,
26}
27
28struct UnixDatagramBackend {
29 fd: OwnedFd,
30 bound_path: PathBuf,
31 _lease: PeerLease,
32}
33
34impl UnixDatagramPortBackend {
39 pub fn new(path: impl AsRef<Path>) -> io::Result<Self> {
41 Self::with_max_active_peers(path, DEFAULT_MAX_ACTIVE_PEERS)
42 }
43
44 pub fn with_max_active_peers(path: impl AsRef<Path>, max: usize) -> io::Result<Self> {
46 let path = path.as_ref().to_path_buf();
47 validate_socket_path(&path)?;
48 if max == 0 {
49 return Err(io::Error::new(
50 io::ErrorKind::InvalidInput,
51 "vsock datagram peer limit must be non-zero",
52 ));
53 }
54
55 let peer_dir = tempfile::Builder::new()
61 .prefix("msb-vsock-")
62 .tempdir_in("/tmp")?;
63 Ok(Self {
64 path,
65 peer_dir,
66 next_peer: AtomicU64::new(1),
67 peers: PeerLimit::new(max),
68 })
69 }
70
71 fn next_peer_path(&self) -> PathBuf {
72 let id = self.next_peer.fetch_add(1, Ordering::Relaxed);
73 self.peer_dir.path().join(format!("{id:x}.sock"))
74 }
75}
76
77impl VsockDatagramPortBackend for UnixDatagramPortBackend {
82 fn open_peer(
83 &self,
84 _peer: VsockDatagramPeer,
85 _notifier: VsockNotifier,
86 ) -> io::Result<Box<dyn VsockDatagramBackend>> {
87 let lease = self.peers.acquire()?;
88 let bound_path = self.next_peer_path();
89 let socket = StdUnixDatagram::bind(&bound_path)?;
93 if let Err(err) = socket.set_nonblocking(true) {
94 let _ = std::fs::remove_file(&bound_path);
95 return Err(err);
96 }
97 if let Err(err) = socket.connect(&self.path) {
98 let _ = std::fs::remove_file(&bound_path);
99 return Err(err);
100 }
101 let fd = socket.into();
102
103 Ok(Box::new(UnixDatagramBackend {
104 fd,
105 bound_path,
106 _lease: lease,
107 }))
108 }
109}
110
111impl VsockDatagramBackend for UnixDatagramBackend {
112 fn send(&self, payload: &[u8]) -> io::Result<()> {
113 let written =
114 send(self.fd.as_raw_fd(), payload, MsgFlags::empty()).map_err(io::Error::from)?;
115 if written != payload.len() {
116 return Err(io::Error::new(
117 io::ErrorKind::WriteZero,
118 "Unix datagram send did not consume the complete message",
119 ));
120 }
121 Ok(())
122 }
123
124 fn receive(&self, buf: &mut [u8]) -> io::Result<VsockDatagramRead> {
125 let received = recv(
126 self.fd.as_raw_fd(),
127 buf,
128 MsgFlags::MSG_DONTWAIT | MsgFlags::MSG_TRUNC,
129 )
130 .map_err(io::Error::from)?;
131 Ok(VsockDatagramRead {
132 len: received.min(buf.len()),
133 truncated: received > buf.len(),
134 })
135 }
136
137 fn pollable(&self) -> Option<RawFd> {
138 Some(self.fd.as_raw_fd())
139 }
140}
141
142impl Drop for UnixDatagramBackend {
143 fn drop(&mut self) {
144 if let Err(err) = std::fs::remove_file(&self.bound_path)
145 && err.kind() != io::ErrorKind::NotFound
146 {
147 tracing::debug!(
148 path = %self.bound_path.display(),
149 %err,
150 "failed to remove vsock datagram peer socket"
151 );
152 }
153 }
154}
155
156#[cfg(test)]
161mod tests {
162 use std::os::unix::net::UnixDatagram;
163
164 use super::*;
165
166 #[test]
167 fn datagram_backend_preserves_messages_and_reply_address() {
168 let dir = tempfile::tempdir().unwrap();
169 let path = dir.path().join("service.sock");
170 let host = UnixDatagram::bind(&path).unwrap();
171 host.set_read_timeout(Some(std::time::Duration::from_secs(1)))
172 .unwrap();
173
174 let service = UnixDatagramPortBackend::new(&path).unwrap();
175 let endpoint = service
176 .open_peer(
177 VsockDatagramPeer {
178 guest_cid: 3,
179 guest_port: 4000,
180 host_port: 5000,
181 },
182 VsockNotifier::new().unwrap(),
183 )
184 .unwrap();
185
186 endpoint.send(b"guest-event").unwrap();
187 let mut request = [0; 32];
188 let (len, peer) = host.recv_from(&mut request).unwrap();
189 assert_eq!(&request[..len], b"guest-event");
190 let reply_path = peer.as_pathname().unwrap();
191 assert!(
192 reply_path.exists(),
193 "reply path missing: {}",
194 reply_path.display()
195 );
196 host.send_to(b"host-event", reply_path).unwrap();
197
198 let mut response = [0; 32];
199 let mut read = None;
200 for _ in 0..100 {
201 match endpoint.receive(&mut response) {
202 Ok(received) => {
203 read = Some(received);
204 break;
205 }
206 Err(err) if err.kind() == io::ErrorKind::WouldBlock => std::thread::yield_now(),
207 result => panic!("unexpected datagram receive result: {result:?}"),
208 }
209 }
210 let read = read.expect("reply datagram should become readable");
211 assert_eq!(&response[..read.len], b"host-event");
212 assert!(!read.truncated);
213 }
214}