virtual-machine 0.0.3 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/net_tap.rs DELETED
@@ -1,164 +0,0 @@
1
- //! TAP network backend for native (non-WASM) builds.
2
- //!
3
- //! This module provides a network backend that uses a TAP interface
4
- //! to communicate with the host network stack on Linux/macOS.
5
-
6
- use crate::net::NetworkBackend;
7
- use std::io::Write;
8
- use std::os::unix::io::AsRawFd;
9
-
10
- /// TAP network backend using the tun-tap crate.
11
- pub struct TapBackend {
12
- name: String,
13
- iface: Option<tun_tap::Iface>,
14
- mac: [u8; 6],
15
- }
16
-
17
- impl TapBackend {
18
- /// Create a new TAP backend with the given interface name.
19
- ///
20
- /// The interface will not be opened until `init()` is called.
21
- /// Creating the TAP interface typically requires root privileges.
22
- ///
23
- /// # Example
24
- /// ```ignore
25
- /// let mut tap = TapBackend::new("tap0");
26
- /// tap.init()?;
27
- /// ```
28
- pub fn new(name: &str) -> Self {
29
- Self {
30
- name: name.to_string(),
31
- iface: None,
32
- // Default MAC - locally administered, unicast
33
- mac: [0x52, 0x54, 0x00, 0x12, 0x34, 0x56],
34
- }
35
- }
36
-
37
- /// Create a TAP backend with a custom MAC address.
38
- pub fn with_mac(name: &str, mac: [u8; 6]) -> Self {
39
- Self {
40
- name: name.to_string(),
41
- iface: None,
42
- mac,
43
- }
44
- }
45
-
46
- /// Set the interface to non-blocking mode.
47
- fn set_nonblocking(&self) -> Result<(), String> {
48
- if let Some(ref iface) = self.iface {
49
- let fd = iface.as_raw_fd();
50
-
51
- // Get current flags
52
- let flags = unsafe { libc::fcntl(fd, libc::F_GETFL) };
53
- if flags < 0 {
54
- return Err(format!(
55
- "Failed to get fd flags: {}",
56
- std::io::Error::last_os_error()
57
- ));
58
- }
59
-
60
- // Set non-blocking flag
61
- let result = unsafe { libc::fcntl(fd, libc::F_SETFL, flags | libc::O_NONBLOCK) };
62
- if result < 0 {
63
- return Err(format!(
64
- "Failed to set non-blocking mode: {}",
65
- std::io::Error::last_os_error()
66
- ));
67
- }
68
- }
69
- Ok(())
70
- }
71
- }
72
-
73
- impl NetworkBackend for TapBackend {
74
- fn init(&mut self) -> Result<(), String> {
75
- // Open the TAP interface
76
- let iface = tun_tap::Iface::without_packet_info(&self.name, tun_tap::Mode::Tap)
77
- .map_err(|e| format!("Failed to open TAP interface '{}': {}", self.name, e))?;
78
-
79
- self.iface = Some(iface);
80
-
81
- // Set non-blocking mode for recv polling
82
- self.set_nonblocking()?;
83
-
84
- log::info!("[TapBackend] Opened TAP interface '{}'", self.name);
85
- Ok(())
86
- }
87
-
88
- fn recv(&mut self) -> Result<Option<Vec<u8>>, String> {
89
- let iface = self.iface.as_mut().ok_or("TAP interface not initialized")?;
90
-
91
- // Buffer for maximum Ethernet frame size (MTU 1500 + headers)
92
- let mut buf = vec![0u8; 1514];
93
-
94
- match iface.recv(&mut buf) {
95
- Ok(n) => {
96
- buf.truncate(n);
97
- log::trace!("[TapBackend] Received {} byte packet", n);
98
- Ok(Some(buf))
99
- }
100
- Err(e) => {
101
- // Check if it's a would-block error (no data available)
102
- if e.kind() == std::io::ErrorKind::WouldBlock {
103
- Ok(None)
104
- } else {
105
- Err(format!("TAP recv error: {}", e))
106
- }
107
- }
108
- }
109
- }
110
-
111
- fn send(&self, buf: &[u8]) -> Result<(), String> {
112
- let iface = self.iface.as_ref().ok_or("TAP interface not initialized")?;
113
-
114
- // tun_tap::Iface doesn't implement Send on the write side,
115
- // so we need to use the raw fd for writing. However, for simplicity
116
- // we'll just write directly. Note: this may need adjustment for
117
- // thread safety in the future.
118
- let mut iface_clone = unsafe {
119
- // This is safe because we're only writing and the fd is valid
120
- std::fs::File::from_raw_fd(std::os::unix::io::AsRawFd::as_raw_fd(iface))
121
- };
122
-
123
- let result = iface_clone.write_all(buf);
124
-
125
- // Don't drop the file - it doesn't own the fd
126
- std::mem::forget(iface_clone);
127
-
128
- result.map_err(|e| format!("TAP send error: {}", e))?;
129
- log::trace!("[TapBackend] Sent {} byte packet", buf.len());
130
- Ok(())
131
- }
132
-
133
- fn mac_address(&self) -> [u8; 6] {
134
- self.mac
135
- }
136
- }
137
-
138
- // Manual implementation to handle the Iface not being Send
139
- unsafe impl Send for TapBackend {}
140
-
141
- use std::os::unix::io::FromRawFd;
142
-
143
- #[cfg(test)]
144
- mod tests {
145
- use super::*;
146
-
147
- #[test]
148
- fn test_tap_backend_creation() {
149
- let tap = TapBackend::new("test0");
150
- assert_eq!(tap.name, "test0");
151
- assert!(tap.iface.is_none());
152
- }
153
-
154
- #[test]
155
- fn test_tap_backend_custom_mac() {
156
- let mac = [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF];
157
- let tap = TapBackend::with_mac("test0", mac);
158
- assert_eq!(tap.mac_address(), mac);
159
- }
160
-
161
- // Note: Actually opening a TAP interface requires root privileges,
162
- // so we can't test init() in regular unit tests.
163
- }
164
-
package/src/net_ws.rs DELETED
@@ -1,396 +0,0 @@
1
- //! WebSocket network backend for cross-platform networking.
2
- //!
3
- //! This backend tunnels Ethernet frames over WebSocket, enabling
4
- //! networking on platforms without TAP support (macOS, WASM/browser).
5
-
6
- use crate::net::NetworkBackend;
7
- use std::sync::mpsc::{channel, Receiver, Sender, TryRecvError};
8
- use std::sync::{Arc, Mutex};
9
-
10
- #[cfg(not(target_arch = "wasm32"))]
11
- mod native {
12
- use super::*;
13
- use std::thread;
14
- use tungstenite::{connect, Message, WebSocket};
15
- use tungstenite::stream::MaybeTlsStream;
16
- use std::net::TcpStream;
17
-
18
- /// WebSocket backend for native platforms (macOS, Linux, Windows).
19
- pub struct WsBackend {
20
- url: String,
21
- mac: [u8; 6],
22
- tx_to_ws: Option<Sender<Vec<u8>>>,
23
- rx_from_ws: Option<Receiver<Vec<u8>>>,
24
- connected: Arc<Mutex<bool>>,
25
- error_message: Arc<Mutex<Option<String>>>,
26
- }
27
-
28
- impl WsBackend {
29
- pub fn new(url: &str) -> Self {
30
- // Generate a random-ish MAC based on URL hash
31
- let mut mac = [0x52, 0x54, 0x00, 0x00, 0x00, 0x00];
32
- let hash: u32 = url.bytes().fold(0u32, |acc, b| acc.wrapping_mul(31).wrapping_add(b as u32));
33
- mac[3] = ((hash >> 16) & 0xff) as u8;
34
- mac[4] = ((hash >> 8) & 0xff) as u8;
35
- mac[5] = (hash & 0xff) as u8;
36
-
37
- Self {
38
- url: url.to_string(),
39
- mac,
40
- tx_to_ws: None,
41
- rx_from_ws: None,
42
- connected: Arc::new(Mutex::new(false)),
43
- error_message: Arc::new(Mutex::new(None)),
44
- }
45
- }
46
-
47
- /// Check if the backend is currently connected.
48
- pub fn is_connected(&self) -> bool {
49
- *self.connected.lock().unwrap()
50
- }
51
-
52
- /// Get any error message from the connection.
53
- pub fn error_message(&self) -> Option<String> {
54
- self.error_message.lock().unwrap().clone()
55
- }
56
-
57
- /// Reader thread - reads from WebSocket and sends to channel
58
- fn reader_thread(
59
- mut socket: WebSocket<MaybeTlsStream<TcpStream>>,
60
- tx_received: Sender<Vec<u8>>,
61
- rx_to_send: Receiver<Vec<u8>>,
62
- connected: Arc<Mutex<bool>>,
63
- ) {
64
- // Set socket to blocking mode for reliable reads
65
- if let MaybeTlsStream::Plain(ref stream) = *socket.get_ref() {
66
- let _ = stream.set_nonblocking(false);
67
- // Set a read timeout so we can also check for outgoing messages
68
- let _ = stream.set_read_timeout(Some(std::time::Duration::from_millis(10)));
69
- }
70
-
71
- loop {
72
- // First, check if we have outgoing messages to send
73
- loop {
74
- match rx_to_send.try_recv() {
75
- Ok(data) => {
76
- log::trace!("[WsBackend] Sending {} bytes", data.len());
77
- if let Err(e) = socket.send(Message::Binary(data.into())) {
78
- log::warn!("[WsBackend] Send error: {}", e);
79
- *connected.lock().unwrap() = false;
80
- return;
81
- }
82
- }
83
- Err(TryRecvError::Empty) => break,
84
- Err(TryRecvError::Disconnected) => {
85
- log::info!("[WsBackend] Send channel closed, exiting");
86
- *connected.lock().unwrap() = false;
87
- return;
88
- }
89
- }
90
- }
91
-
92
- // Flush any pending writes
93
- if let Err(e) = socket.flush() {
94
- if !matches!(e, tungstenite::Error::Io(ref io) if io.kind() == std::io::ErrorKind::WouldBlock) {
95
- log::warn!("[WsBackend] Flush error: {}", e);
96
- }
97
- }
98
-
99
- // Try to read from WebSocket
100
- match socket.read() {
101
- Ok(Message::Binary(data)) => {
102
- log::debug!("[WsBackend] Received {} bytes from relay", data.len());
103
- if tx_received.send(data.into()).is_err() {
104
- log::info!("[WsBackend] Receiver dropped, exiting");
105
- *connected.lock().unwrap() = false;
106
- return;
107
- }
108
- }
109
- Ok(Message::Close(_)) => {
110
- log::info!("[WsBackend] WebSocket closed by server");
111
- *connected.lock().unwrap() = false;
112
- return;
113
- }
114
- Ok(Message::Ping(data)) => {
115
- // Respond to ping with pong
116
- let _ = socket.send(Message::Pong(data));
117
- }
118
- Ok(_) => {} // Ignore text, pong
119
- Err(tungstenite::Error::Io(ref e)) if e.kind() == std::io::ErrorKind::WouldBlock => {
120
- // Timeout - no data available, continue loop
121
- }
122
- Err(tungstenite::Error::Io(ref e)) if e.kind() == std::io::ErrorKind::TimedOut => {
123
- // Timeout - no data available, continue loop
124
- }
125
- Err(e) => {
126
- log::warn!("[WsBackend] Read error: {}", e);
127
- *connected.lock().unwrap() = false;
128
- return;
129
- }
130
- }
131
- }
132
- }
133
- }
134
-
135
- impl NetworkBackend for WsBackend {
136
- fn init(&mut self) -> Result<(), String> {
137
- log::info!("[WsBackend] Connecting to {}", self.url);
138
-
139
- let (socket, _response) = connect(&self.url)
140
- .map_err(|e| {
141
- let msg = format!("Failed to connect: {}", e);
142
- log::error!("[WsBackend] {}", msg);
143
- *self.error_message.lock().unwrap() = Some(msg.clone());
144
- msg
145
- })?;
146
-
147
- log::info!("[WsBackend] Connected successfully!");
148
- *self.connected.lock().unwrap() = true;
149
- *self.error_message.lock().unwrap() = None;
150
-
151
- let (tx_to_ws, rx_to_send) = channel();
152
- let (tx_received, rx_from_ws) = channel();
153
-
154
- self.tx_to_ws = Some(tx_to_ws);
155
- self.rx_from_ws = Some(rx_from_ws);
156
-
157
- let connected = self.connected.clone();
158
-
159
- thread::spawn(move || {
160
- Self::reader_thread(socket, tx_received, rx_to_send, connected);
161
- });
162
-
163
- log::info!("[WsBackend] Initialized successfully");
164
- Ok(())
165
- }
166
-
167
- fn recv(&mut self) -> Result<Option<Vec<u8>>, String> {
168
- if let Some(ref rx) = self.rx_from_ws {
169
- match rx.try_recv() {
170
- Ok(data) => {
171
- log::trace!("[WsBackend] recv() returning {} bytes", data.len());
172
- Ok(Some(data))
173
- }
174
- Err(TryRecvError::Empty) => Ok(None),
175
- Err(TryRecvError::Disconnected) => {
176
- Err("WebSocket disconnected".to_string())
177
- }
178
- }
179
- } else {
180
- Ok(None)
181
- }
182
- }
183
-
184
- fn send(&self, buf: &[u8]) -> Result<(), String> {
185
- if !*self.connected.lock().unwrap() {
186
- return Err("Not connected".to_string());
187
- }
188
-
189
- if let Some(ref tx) = self.tx_to_ws {
190
- tx.send(buf.to_vec()).map_err(|e| format!("Send failed: {}", e))
191
- } else {
192
- Err("WebSocket not initialized".to_string())
193
- }
194
- }
195
-
196
- fn mac_address(&self) -> [u8; 6] {
197
- self.mac
198
- }
199
- }
200
- }
201
-
202
- #[cfg(target_arch = "wasm32")]
203
- mod wasm {
204
- use super::*;
205
- use wasm_bindgen::prelude::*;
206
- use wasm_bindgen::JsCast;
207
- use web_sys::{WebSocket, MessageEvent, ErrorEvent, CloseEvent, BinaryType};
208
- use js_sys::{ArrayBuffer, Uint8Array};
209
- use std::cell::RefCell;
210
- use std::rc::Rc;
211
- use std::collections::VecDeque;
212
-
213
- /// Maximum number of packets to buffer before dropping
214
- const MAX_RX_QUEUE_SIZE: usize = 256;
215
-
216
- /// Connection state for tracking
217
- #[derive(Clone, Copy, PartialEq, Eq, Debug)]
218
- pub enum ConnectionState {
219
- Disconnected,
220
- Connecting,
221
- Connected,
222
- Error,
223
- }
224
-
225
- /// WebSocket backend for WASM/browser.
226
- pub struct WsBackend {
227
- url: String,
228
- mac: [u8; 6],
229
- ws: Option<WebSocket>,
230
- rx_queue: Rc<RefCell<VecDeque<Vec<u8>>>>,
231
- state: Rc<RefCell<ConnectionState>>,
232
- error_message: Rc<RefCell<Option<String>>>,
233
- packets_dropped: Rc<RefCell<u64>>,
234
- }
235
-
236
- // WASM types are !Send by default, but we need Send for NetworkBackend.
237
- // This is safe because WASM is single-threaded.
238
- unsafe impl Send for WsBackend {}
239
-
240
- impl WsBackend {
241
- pub fn new(url: &str) -> Self {
242
- let mut mac = [0x52, 0x54, 0x00, 0x00, 0x00, 0x00];
243
- let hash: u32 = url.bytes().fold(0u32, |acc, b| acc.wrapping_mul(31).wrapping_add(b as u32));
244
- mac[3] = ((hash >> 16) & 0xff) as u8;
245
- mac[4] = ((hash >> 8) & 0xff) as u8;
246
- mac[5] = (hash & 0xff) as u8;
247
-
248
- Self {
249
- url: url.to_string(),
250
- mac,
251
- ws: None,
252
- rx_queue: Rc::new(RefCell::new(VecDeque::with_capacity(MAX_RX_QUEUE_SIZE))),
253
- state: Rc::new(RefCell::new(ConnectionState::Disconnected)),
254
- error_message: Rc::new(RefCell::new(None)),
255
- packets_dropped: Rc::new(RefCell::new(0)),
256
- }
257
- }
258
-
259
- /// Check if the backend is currently connected.
260
- pub fn is_connected(&self) -> bool {
261
- *self.state.borrow() == ConnectionState::Connected
262
- }
263
-
264
- /// Get the current connection state.
265
- pub fn connection_state(&self) -> ConnectionState {
266
- *self.state.borrow()
267
- }
268
-
269
- /// Get any error message.
270
- pub fn error_message(&self) -> Option<String> {
271
- self.error_message.borrow().clone()
272
- }
273
-
274
- /// Get the number of packets dropped due to buffer overflow.
275
- pub fn packets_dropped(&self) -> u64 {
276
- *self.packets_dropped.borrow()
277
- }
278
- }
279
-
280
- impl NetworkBackend for WsBackend {
281
- fn init(&mut self) -> Result<(), String> {
282
- *self.state.borrow_mut() = ConnectionState::Connecting;
283
- *self.error_message.borrow_mut() = None;
284
-
285
- let ws = WebSocket::new(&self.url)
286
- .map_err(|e| {
287
- *self.state.borrow_mut() = ConnectionState::Error;
288
- let msg = format!("Failed to create WebSocket: {:?}", e);
289
- *self.error_message.borrow_mut() = Some(msg.clone());
290
- msg
291
- })?;
292
-
293
- ws.set_binary_type(BinaryType::Arraybuffer);
294
-
295
- // Set up message handler with buffer overflow protection
296
- let rx_queue = self.rx_queue.clone();
297
- let packets_dropped = self.packets_dropped.clone();
298
- let onmessage_callback = Closure::<dyn FnMut(_)>::new(move |e: MessageEvent| {
299
- if let Ok(abuf) = e.data().dyn_into::<ArrayBuffer>() {
300
- let array = Uint8Array::new(&abuf);
301
- let mut data = vec![0u8; array.length() as usize];
302
- array.copy_to(&mut data);
303
-
304
- let mut queue = rx_queue.borrow_mut();
305
- // Handle buffer overflow - drop oldest packets if queue is full
306
- while queue.len() >= MAX_RX_QUEUE_SIZE {
307
- queue.pop_front();
308
- *packets_dropped.borrow_mut() += 1;
309
- }
310
- queue.push_back(data);
311
- }
312
- });
313
- ws.set_onmessage(Some(onmessage_callback.as_ref().unchecked_ref()));
314
- onmessage_callback.forget();
315
-
316
- // Set up open handler
317
- let state_open = self.state.clone();
318
- let error_clear = self.error_message.clone();
319
- let onopen_callback = Closure::<dyn FnMut()>::new(move || {
320
- *state_open.borrow_mut() = ConnectionState::Connected;
321
- *error_clear.borrow_mut() = None;
322
- log::info!("[WsBackend] WebSocket connected!");
323
- });
324
- ws.set_onopen(Some(onopen_callback.as_ref().unchecked_ref()));
325
- onopen_callback.forget();
326
-
327
- // Set up error handler
328
- let state_error = self.state.clone();
329
- let error_message = self.error_message.clone();
330
- let onerror_callback = Closure::<dyn FnMut(_)>::new(move |e: ErrorEvent| {
331
- *state_error.borrow_mut() = ConnectionState::Error;
332
- let msg = format!("WebSocket error: {}", e.message());
333
- *error_message.borrow_mut() = Some(msg.clone());
334
- log::error!("[WsBackend] {}", msg);
335
- });
336
- ws.set_onerror(Some(onerror_callback.as_ref().unchecked_ref()));
337
- onerror_callback.forget();
338
-
339
- // Set up close handler
340
- let state_close = self.state.clone();
341
- let onclose_callback = Closure::<dyn FnMut(_)>::new(move |e: CloseEvent| {
342
- *state_close.borrow_mut() = ConnectionState::Disconnected;
343
- log::info!("[WsBackend] WebSocket closed (code: {}, reason: {})",
344
- e.code(), e.reason());
345
- });
346
- ws.set_onclose(Some(onclose_callback.as_ref().unchecked_ref()));
347
- onclose_callback.forget();
348
-
349
- self.ws = Some(ws);
350
-
351
- log::info!("[WsBackend] Initialized, connecting to {}", self.url);
352
- Ok(())
353
- }
354
-
355
- fn recv(&mut self) -> Result<Option<Vec<u8>>, String> {
356
- // Check connection state
357
- let state = *self.state.borrow();
358
- if state == ConnectionState::Error {
359
- if let Some(msg) = self.error_message.borrow().clone() {
360
- return Err(msg);
361
- }
362
- return Err("WebSocket in error state".to_string());
363
- }
364
-
365
- Ok(self.rx_queue.borrow_mut().pop_front())
366
- }
367
-
368
- fn send(&self, buf: &[u8]) -> Result<(), String> {
369
- let state = *self.state.borrow();
370
-
371
- if state != ConnectionState::Connected {
372
- // Silently drop if not connected (don't spam errors)
373
- return Ok(());
374
- }
375
-
376
- if let Some(ref ws) = self.ws {
377
- let array = Uint8Array::from(buf);
378
- ws.send_with_array_buffer(&array.buffer())
379
- .map_err(|e| format!("Send failed: {:?}", e))
380
- } else {
381
- Err("WebSocket not initialized".to_string())
382
- }
383
- }
384
-
385
- fn mac_address(&self) -> [u8; 6] {
386
- self.mac
387
- }
388
- }
389
- }
390
-
391
- // Re-export the appropriate backend based on target
392
- #[cfg(not(target_arch = "wasm32"))]
393
- pub use native::WsBackend;
394
-
395
- #[cfg(target_arch = "wasm32")]
396
- pub use wasm::WsBackend;