arcbox_virtio_vsock/
host.rs1#![cfg(unix)]
4
5use std::collections::HashMap;
6use std::io::{Read, Write};
7use std::os::unix::net::UnixStream;
8
9use arcbox_virtio_core::error::{Result, VirtioError};
10
11use crate::addr::VsockAddr;
12use crate::backend::VsockBackend;
13
14#[derive(Debug)]
17struct VsockChannel {
18 stream: UnixStream,
20}
21
22pub struct HostVsockBackend {
32 port_map: HashMap<u32, String>,
34 channels: HashMap<(u32, u32), VsockChannel>,
36}
37
38impl std::fmt::Debug for HostVsockBackend {
39 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40 f.debug_struct("HostVsockBackend")
41 .field("port_map", &self.port_map)
42 .field("channels_count", &self.channels.len())
43 .finish()
44 }
45}
46
47impl HostVsockBackend {
48 #[must_use]
50 pub fn new() -> Self {
51 Self {
52 port_map: HashMap::new(),
53 channels: HashMap::new(),
54 }
55 }
56
57 pub fn add_port_mapping(&mut self, port: u32, socket_path: String) {
63 self.port_map.insert(port, socket_path);
64 }
65
66 #[must_use]
68 pub fn with_port_map(port_map: HashMap<u32, String>) -> Self {
69 Self {
70 port_map,
71 channels: HashMap::new(),
72 }
73 }
74}
75
76impl Default for HostVsockBackend {
77 fn default() -> Self {
78 Self::new()
79 }
80}
81
82impl VsockBackend for HostVsockBackend {
83 fn on_connect(&mut self, addr: VsockAddr) -> Result<()> {
84 let socket_path = self.port_map.get(&addr.port).ok_or_else(|| {
85 VirtioError::InvalidOperation(format!("No port mapping for vsock port {}", addr.port))
86 })?;
87
88 let stream = UnixStream::connect(socket_path).map_err(|e| {
89 VirtioError::Io(format!(
90 "Failed to connect to Unix socket {}: {}",
91 socket_path, e
92 ))
93 })?;
94
95 stream
96 .set_nonblocking(true)
97 .map_err(|e| VirtioError::Io(format!("Failed to set nonblocking: {}", e)))?;
98
99 tracing::debug!(
100 "HostVsockBackend: connected port {} to {}",
101 addr.port,
102 socket_path
103 );
104
105 self.channels
108 .insert((addr.port, addr.port), VsockChannel { stream });
109 Ok(())
110 }
111
112 fn on_send(&mut self, addr: VsockAddr, data: &[u8]) -> Result<usize> {
113 let channel = self
114 .channels
115 .get_mut(&(addr.port, addr.port))
116 .ok_or_else(|| {
117 VirtioError::InvalidOperation(format!("No channel for vsock port {}", addr.port))
118 })?;
119
120 channel
121 .stream
122 .write(data)
123 .map_err(|e| VirtioError::Io(format!("Failed to write to Unix socket: {}", e)))
124 }
125
126 fn on_recv(&mut self, addr: VsockAddr, buf: &mut [u8]) -> Result<usize> {
127 let channel = self
128 .channels
129 .get_mut(&(addr.port, addr.port))
130 .ok_or_else(|| {
131 VirtioError::InvalidOperation(format!("No channel for vsock port {}", addr.port))
132 })?;
133
134 match channel.stream.read(buf) {
135 Ok(n) => Ok(n),
136 Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => Ok(0),
137 Err(e) => Err(VirtioError::Io(format!(
138 "Failed to read from Unix socket: {}",
139 e
140 ))),
141 }
142 }
143
144 fn on_close(&mut self, addr: VsockAddr) -> Result<()> {
145 self.channels.remove(&(addr.port, addr.port));
146 tracing::debug!("HostVsockBackend: closed port {}", addr.port);
147 Ok(())
148 }
149
150 fn has_pending_data(&self, addr: VsockAddr) -> bool {
151 self.channels.contains_key(&(addr.port, addr.port))
154 }
155}
156
157#[cfg(test)]
158mod tests {
159 use super::*;
160
161 #[test]
162 fn test_host_vsock_backend_new() {
163 let backend = HostVsockBackend::new();
164 assert!(backend.port_map.is_empty());
165 assert!(backend.channels.is_empty());
166 }
167
168 #[test]
169 fn test_host_vsock_backend_add_port_mapping() {
170 let mut backend = HostVsockBackend::new();
171 backend.add_port_mapping(1234, "/tmp/test.sock".to_string());
172 assert_eq!(backend.port_map.get(&1234).unwrap(), "/tmp/test.sock");
173 }
174
175 #[test]
176 fn test_host_vsock_backend_connect_no_mapping() {
177 let mut backend = HostVsockBackend::new();
178 let addr = VsockAddr::new(3, 9999);
179 let result = backend.on_connect(addr);
180 assert!(result.is_err());
181 }
182
183 #[test]
184 fn test_host_vsock_backend_with_unix_socket() {
185 use std::os::unix::net::UnixListener;
186
187 let tmpdir = tempfile::tempdir().unwrap();
188 let sock_path = tmpdir.path().join("test.sock");
189 let sock_path_str = sock_path.to_str().unwrap().to_string();
190
191 let _listener = UnixListener::bind(&sock_path).unwrap();
192
193 let mut backend = HostVsockBackend::new();
194 backend.add_port_mapping(5000, sock_path_str);
195
196 let addr = VsockAddr::new(3, 5000);
197 backend.on_connect(addr).unwrap();
198
199 let data = b"ping";
200 let sent = backend.on_send(addr, data).unwrap();
201 assert_eq!(sent, data.len());
202
203 backend.on_close(addr).unwrap();
204 assert!(!backend.has_pending_data(addr));
205 }
206}