1 // Copyright 2024 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 //! # This module provides a safe Rust wrapper for the libslirp library.
16
17 //! It allows to embed a virtual network stack within your Rust applications.
18 //!
19 //! ## Features
20 //!
21 //! * **Safe API:** Wraps the libslirp C API in a safe and idiomatic Rust interface.
22 //! * **Networking:** Provides functionality for virtual networking, including TCP/IP, UDP, and ICMP.
23 //! * **Proxy Support:** Allows integration with proxy managers for handling external connections.
24 //! * **Threading:** Handles communication between the Rust application and the libslirp event loop.
25 //!
26 //! ## Usage
27 //!
28 //! ```
29 //! use bytes::Bytes;
30 //! use libslirp_rs::libslirp_config::SlirpConfig;
31 //! use libslirp_rs::libslirp::LibSlirp;
32 //! use std::net::Ipv4Addr;
33 //! use std::sync::mpsc;
34 //!
35 //! let (tx_cmds, _) = mpsc::channel();
36 //! // Create a LibSlirp instance with default configuration
37 //! let libslirp = LibSlirp::new(
38 //! SlirpConfig::default(),
39 //! tx_cmds,
40 //! None
41 //! );
42 //!
43 //! let data = vec![0x01, 0x02, 0x03];
44 //! // Input network data into libslirp
45 //! libslirp.input(Bytes::from(data));
46 //!
47 //! // ... other operations ...
48 //!
49 //! // Shutdown libslirp
50 //! libslirp.shutdown();
51 //! ```
52 //!
53 //! ## Example with Proxy
54 //!
55 //! ```
56 //! use libslirp_rs::libslirp::LibSlirp;
57 //! use libslirp_rs::libslirp_config::SlirpConfig;
58 //! use libslirp_rs::libslirp::{ProxyManager, ProxyConnect};
59 //! use std::sync::mpsc;
60 //! use std::net::SocketAddr;
61 //! // Implement the ProxyManager trait for your proxy logic
62 //! struct MyProxyManager;
63 //!
64 //! impl ProxyManager for MyProxyManager {
65 //! // ... implementation ...
66 //! fn try_connect(
67 //! &self,
68 //! sockaddr: SocketAddr,
69 //! connect_id: usize,
70 //! connect_func: Box<dyn ProxyConnect + Send>,
71 //! ) -> bool {
72 //! todo!()
73 //! }
74 //! fn remove(&self, connect_id: usize) {
75 //! todo!()
76 //! }
77 //! }
78 //! let (tx_cmds, _) = mpsc::channel();
79 //! // Create a LibSlirp instance with a proxy manager
80 //! let libslirp = LibSlirp::new(
81 //! SlirpConfig::default(),
82 //! tx_cmds,
83 //! Some(Box::new(MyProxyManager)),
84 //! );
85 //!
86 //! // ...
87 //! ```
88 //!
89 //! This module abstracts away the complexities of interacting with the libslirp C library,
90 //! providing a more convenient and reliable way to use it in your Rust projects.
91
92 use crate::libslirp_config;
93 use crate::libslirp_config::SlirpConfigs;
94 use crate::libslirp_sys::{
95 self, SlirpPollType, SlirpProxyConnectFunc, SlirpTimerId, SLIRP_POLL_ERR, SLIRP_POLL_HUP,
96 SLIRP_POLL_IN, SLIRP_POLL_OUT, SLIRP_POLL_PRI,
97 };
98
99 use bytes::Bytes;
100 use core::sync::atomic::{AtomicUsize, Ordering};
101 use log::{debug, info, warn};
102 use std::cell::RefCell;
103 use std::collections::HashMap;
104 use std::ffi::{c_char, c_int, c_void, CStr};
105 use std::mem::ManuallyDrop;
106 use std::net::SocketAddr;
107 use std::rc::Rc;
108 use std::sync::mpsc;
109 use std::thread;
110 use std::time::Duration;
111 use std::time::Instant;
112
113 type TimerOpaque = usize;
114
115 const TIMEOUT_SECS: u64 = 1;
116
117 struct TimerManager {
118 clock: RefCell<Instant>,
119 map: RefCell<HashMap<TimerOpaque, Timer>>,
120 timers: AtomicUsize,
121 }
122
123 #[derive(Clone)]
124 struct Timer {
125 id: SlirpTimerId,
126 cb_opaque: usize,
127 expire_time: u64,
128 }
129
130 /// The operations performed on the slirp thread
131 #[derive(Debug)]
132 enum SlirpCmd {
133 Input(Bytes),
134 PollResult(Vec<PollFd>, c_int),
135 TimerModified,
136 Shutdown,
137 ProxyConnect(SlirpProxyConnectFunc, usize, i32, i32),
138 }
139
140 /// Alias for io::fd::RawFd on Unix or RawSocket on Windows (converted to i32)
141 pub type RawFd = i32;
142
143 /// HTTP Proxy callback trait
144 pub trait ProxyManager: Send {
145 /// Attempts to establish a connection through the proxy.
try_connect( &self, sockaddr: SocketAddr, connect_id: usize, connect_func: Box<dyn ProxyConnect + Send>, ) -> bool146 fn try_connect(
147 &self,
148 sockaddr: SocketAddr,
149 connect_id: usize,
150 connect_func: Box<dyn ProxyConnect + Send>,
151 ) -> bool;
152 /// Removes a proxy connection.
remove(&self, connect_id: usize)153 fn remove(&self, connect_id: usize);
154 }
155
156 struct CallbackContext {
157 tx_bytes: mpsc::Sender<Bytes>,
158 tx_cmds: mpsc::Sender<SlirpCmd>,
159 poll_fds: Rc<RefCell<Vec<PollFd>>>,
160 proxy_manager: Option<Box<dyn ProxyManager>>,
161 tx_proxy_bytes: Option<mpsc::Sender<Bytes>>,
162 timer_manager: Rc<TimerManager>,
163 }
164
165 /// A poll thread request has a poll_fds and a timeout
166 type PollRequest = (Vec<PollFd>, u32);
167
168 /// API to LibSlirp
169
170 pub struct LibSlirp {
171 tx_cmds: mpsc::Sender<SlirpCmd>,
172 }
173
174 impl TimerManager {
next_timer(&self) -> TimerOpaque175 fn next_timer(&self) -> TimerOpaque {
176 self.timers.fetch_add(1, Ordering::SeqCst) as TimerOpaque
177 }
178
179 /// Finds expired Timers, clears then clones them
collect_expired(&self) -> Vec<Timer>180 fn collect_expired(&self) -> Vec<Timer> {
181 let now_ms = self.get_elapsed().as_millis() as u64;
182 self.map
183 .borrow_mut()
184 .iter_mut()
185 .filter(|(_, timer)| timer.expire_time < now_ms)
186 .map(|(_, &mut ref mut timer)| {
187 timer.expire_time = u64::MAX;
188 timer.clone()
189 })
190 .collect()
191 }
192
193 /// Return the minimum duration until the next timer
min_duration(&self) -> Duration194 fn min_duration(&self) -> Duration {
195 match self.map.borrow().iter().min_by_key(|(_, timer)| timer.expire_time) {
196 Some((_, timer)) => {
197 let now_ms = self.get_elapsed().as_millis() as u64;
198 // Duration is >= 0
199 Duration::from_millis(timer.expire_time.saturating_sub(now_ms))
200 }
201 None => Duration::from_millis(u64::MAX),
202 }
203 }
204
get_elapsed(&self) -> Duration205 fn get_elapsed(&self) -> Duration {
206 self.clock.borrow().elapsed()
207 }
208
remove(&self, timer_key: &TimerOpaque) -> Option<Timer>209 fn remove(&self, timer_key: &TimerOpaque) -> Option<Timer> {
210 self.map.borrow_mut().remove(timer_key)
211 }
212
insert(&self, timer_key: TimerOpaque, value: Timer)213 fn insert(&self, timer_key: TimerOpaque, value: Timer) {
214 self.map.borrow_mut().insert(timer_key, value);
215 }
216
timer_mod(&self, timer_key: &TimerOpaque, expire_time: u64)217 fn timer_mod(&self, timer_key: &TimerOpaque, expire_time: u64) {
218 if let Some(&mut ref mut timer) = self.map.borrow_mut().get_mut(timer_key) {
219 // expire_time is >= 0
220 timer.expire_time = expire_time;
221 } else {
222 warn!("Unknown timer {timer_key}");
223 }
224 }
225 }
226
227 impl LibSlirp {
228 /// Creates a new `LibSlirp` instance.
new( config: libslirp_config::SlirpConfig, tx_bytes: mpsc::Sender<Bytes>, proxy_manager: Option<Box<dyn ProxyManager>>, tx_proxy_bytes: Option<mpsc::Sender<Bytes>>, ) -> LibSlirp229 pub fn new(
230 config: libslirp_config::SlirpConfig,
231 tx_bytes: mpsc::Sender<Bytes>,
232 proxy_manager: Option<Box<dyn ProxyManager>>,
233 tx_proxy_bytes: Option<mpsc::Sender<Bytes>>,
234 ) -> LibSlirp {
235 let (tx_cmds, rx_cmds) = mpsc::channel::<SlirpCmd>();
236 let (tx_poll, rx_poll) = mpsc::channel::<PollRequest>();
237
238 // Create channels for polling thread and launch
239 let tx_cmds_poll = tx_cmds.clone();
240 if let Err(e) = thread::Builder::new()
241 .name("slirp_poll".to_string())
242 .spawn(move || slirp_poll_thread(rx_poll, tx_cmds_poll))
243 {
244 warn!("Failed to start slirp poll thread: {}", e);
245 }
246
247 let tx_cmds_slirp = tx_cmds.clone();
248 // Create channels for command processor thread and launch
249 if let Err(e) = thread::Builder::new().name("slirp".to_string()).spawn(move || {
250 slirp_thread(
251 config,
252 tx_bytes,
253 tx_cmds_slirp,
254 rx_cmds,
255 tx_poll,
256 proxy_manager,
257 tx_proxy_bytes,
258 )
259 }) {
260 warn!("Failed to start slirp thread: {}", e);
261 }
262
263 LibSlirp { tx_cmds }
264 }
265
266 /// Shuts down the `LibSlirp` instance.
shutdown(self)267 pub fn shutdown(self) {
268 if let Err(e) = self.tx_cmds.send(SlirpCmd::Shutdown) {
269 warn!("Failed to send Shutdown cmd: {}", e);
270 }
271 }
272
273 /// Inputs network data into the `LibSlirp` instance.
input(&self, bytes: Bytes)274 pub fn input(&self, bytes: Bytes) {
275 if let Err(e) = self.tx_cmds.send(SlirpCmd::Input(bytes)) {
276 warn!("Failed to send Input cmd: {}", e);
277 }
278 }
279 }
280
281 struct ConnectRequest {
282 tx_cmds: mpsc::Sender<SlirpCmd>,
283 connect_func: SlirpProxyConnectFunc,
284 connect_id: usize,
285 af: i32,
286 start: Instant,
287 }
288
289 /// Trait for handling proxy connection results.
290 pub trait ProxyConnect: Send {
291 /// Notifies libslirp about the result of a proxy connection attempt.
proxy_connect(&self, fd: i32, addr: SocketAddr)292 fn proxy_connect(&self, fd: i32, addr: SocketAddr);
293 }
294
295 impl ProxyConnect for ConnectRequest {
proxy_connect(&self, fd: i32, addr: SocketAddr)296 fn proxy_connect(&self, fd: i32, addr: SocketAddr) {
297 // Send it to Slirp after try_connect() completed
298 let duration = self.start.elapsed().as_secs();
299 if duration > TIMEOUT_SECS {
300 warn!(
301 "ConnectRequest for connection ID {} to {} took too long: {:?}",
302 self.connect_id, addr, duration
303 );
304 }
305 let _ = self.tx_cmds.send(SlirpCmd::ProxyConnect(
306 self.connect_func,
307 self.connect_id,
308 fd,
309 self.af,
310 ));
311 }
312 }
313
314 /// Converts a libslirp callback's `opaque` handle into a
315 /// `CallbackContext.`
316 ///
317 /// Wrapped in a `ManuallyDrop` because we do not want to release the
318 /// storage when the callback returns.
319 ///
320 /// # Safety
321 ///
322 /// * `opaque` must be a valid pointer to a `CallbackContext` originally passed
323 /// to the slirp API.
callback_context_from_raw(opaque: *mut c_void) -> ManuallyDrop<Box<CallbackContext>>324 unsafe fn callback_context_from_raw(opaque: *mut c_void) -> ManuallyDrop<Box<CallbackContext>> {
325 ManuallyDrop::new(
326 // Safety:
327 //
328 // * `opaque` is a valid pointer to a `CallbackContext` originally passed
329 // to the slirp API. The `callback_context_from_raw` function itself
330 // is marked `unsafe` to enforce this precondition on its callers.
331 unsafe { Box::from_raw(opaque as *mut CallbackContext) },
332 )
333 }
334
335 /// A Rust struct for the fields held by `slirp` C library through its
336 /// lifetime.
337 ///
338 /// All libslirp C calls are impl on this struct.
339 struct Slirp {
340 slirp: *mut libslirp_sys::Slirp,
341 // These fields are held by slirp C library
342 #[allow(dead_code)]
343 configs: Box<SlirpConfigs>,
344 #[allow(dead_code)]
345 callbacks: Box<libslirp_sys::SlirpCb>,
346 // Passed to API calls and then to callbacks
347 callback_context: Box<CallbackContext>,
348 }
349
350 impl Slirp {
new(config: libslirp_config::SlirpConfig, callback_context: Box<CallbackContext>) -> Slirp351 fn new(config: libslirp_config::SlirpConfig, callback_context: Box<CallbackContext>) -> Slirp {
352 let callbacks = Box::new(libslirp_sys::SlirpCb {
353 send_packet: Some(send_packet_cb),
354 guest_error: Some(guest_error_cb),
355 clock_get_ns: Some(clock_get_ns_cb),
356 timer_new: None,
357 timer_free: Some(timer_free_cb),
358 timer_mod: Some(timer_mod_cb),
359 register_poll_fd: Some(register_poll_fd_cb),
360 unregister_poll_fd: Some(unregister_poll_fd_cb),
361 notify: Some(notify_cb),
362 init_completed: Some(init_completed_cb),
363 timer_new_opaque: Some(timer_new_opaque_cb),
364 try_connect: Some(try_connect_cb),
365 remove: Some(remove_cb),
366 });
367 let configs = Box::new(SlirpConfigs::new(&config));
368
369 // Call libslrip "C" library to create a new instance of a slirp
370 // protocol stack.
371 //
372 // Safety: We ensure that:
373 //
374 // * `configs.c_slirp_config` is a valid pointer to the "C" config struct. It is
375 // held by the "C" slirp library for lifetime of the slirp instance.
376 //
377 // * `callbacks` is a valid pointer to an array of callback functions.
378 // It is held by the "C" slirp library for the lifetime of the slirp instance.
379 //
380 // * `callback_context` is an arbitrary opaque type passed back to
381 // callback functions by libslirp.
382 let slirp = unsafe {
383 libslirp_sys::slirp_new(
384 &configs.c_slirp_config,
385 &*callbacks,
386 &*callback_context as *const CallbackContext as *mut c_void,
387 )
388 };
389
390 Slirp { slirp, configs, callbacks, callback_context }
391 }
392
handle_timer(&self, timer: Timer)393 fn handle_timer(&self, timer: Timer) {
394 // Safety: We ensure that:
395 //
396 // * self.slirp is a valid state returned by `slirp_new()`
397 //
398 // * timer.id is a valid c_uint from "C" slirp library calling `timer_new_opaque_cb()`
399 //
400 // * timer.cb_opaque is an usize representing a pointer to callback function from
401 // "C" slirp library calling `timer_new_opaque_cb()`
402 unsafe {
403 libslirp_sys::slirp_handle_timer(self.slirp, timer.id, timer.cb_opaque as *mut c_void);
404 };
405 }
406 }
407
408 impl Drop for Slirp {
409 /// # Safety
410 ///
411 /// * self.slirp is always slirp pointer initialized by slirp_new
412 /// to the slirp API.
drop(&mut self)413 fn drop(&mut self) {
414 // Safety:
415 //
416 // * self.slirp is a slirp pointer initialized by slirp_new;
417 // it's private to the struct and is only constructed that
418 // way.
419 unsafe { libslirp_sys::slirp_cleanup(self.slirp) };
420 }
421 }
422
slirp_thread( config: libslirp_config::SlirpConfig, tx_bytes: mpsc::Sender<Bytes>, tx_cmds: mpsc::Sender<SlirpCmd>, rx: mpsc::Receiver<SlirpCmd>, tx_poll: mpsc::Sender<PollRequest>, proxy_manager: Option<Box<dyn ProxyManager>>, tx_proxy_bytes: Option<mpsc::Sender<Bytes>>, )423 fn slirp_thread(
424 config: libslirp_config::SlirpConfig,
425 tx_bytes: mpsc::Sender<Bytes>,
426 tx_cmds: mpsc::Sender<SlirpCmd>,
427 rx: mpsc::Receiver<SlirpCmd>,
428 tx_poll: mpsc::Sender<PollRequest>,
429 proxy_manager: Option<Box<dyn ProxyManager>>,
430 tx_proxy_bytes: Option<mpsc::Sender<Bytes>>,
431 ) {
432 // Data structures wrapped in an RC are referenced through the
433 // libslirp callbacks and this code (both in the same thread).
434
435 let timer_manager = Rc::new(TimerManager {
436 clock: RefCell::new(Instant::now()),
437 map: RefCell::new(HashMap::new()),
438 timers: AtomicUsize::new(1),
439 });
440
441 let poll_fds = Rc::new(RefCell::new(Vec::new()));
442
443 let callback_context = Box::new(CallbackContext {
444 tx_bytes,
445 tx_cmds,
446 poll_fds: poll_fds.clone(),
447 proxy_manager,
448 tx_proxy_bytes,
449 timer_manager: timer_manager.clone(),
450 });
451
452 let slirp = Slirp::new(config, callback_context);
453
454 slirp.pollfds_fill_and_send(&poll_fds, &tx_poll);
455
456 let min_duration = timer_manager.min_duration();
457 loop {
458 let command = rx.recv_timeout(min_duration);
459 let start = Instant::now();
460
461 let cmd_str = format!("{:?}", command);
462 match command {
463 // The dance to tell libslirp which FDs have IO ready
464 // starts with a response from a worker thread sending a
465 // PollResult, followed by pollfds_poll forwarding the FDs
466 // to libslirp, followed by giving the worker thread
467 // another set of fds to poll (and block).
468 Ok(SlirpCmd::PollResult(poll_fds_result, select_error)) => {
469 poll_fds.borrow_mut().clone_from_slice(&poll_fds_result);
470 slirp.pollfds_poll(select_error);
471 slirp.pollfds_fill_and_send(&poll_fds, &tx_poll);
472 }
473 Ok(SlirpCmd::Input(bytes)) => slirp.input(&bytes),
474
475 // A timer has been modified, new expired_time value
476 Ok(SlirpCmd::TimerModified) => continue,
477
478 // Exit the while loop and shutdown
479 Ok(SlirpCmd::Shutdown) => break,
480
481 Ok(SlirpCmd::ProxyConnect(func, connect_id, fd, af)) => match func {
482 // Safety: we ensure that func (`SlirpProxyConnectFunc`)
483 // and `connect_opaque` are valid because they originated
484 // from the libslirp call to `try_connect_cb.`
485 //
486 // Parameter `fd` will be >= 0 and the descriptor for the
487 // active socket to use, `af` will be either AF_INET or
488 // AF_INET6. On failure `fd` will be negative.
489 Some(func) => unsafe { func(connect_id as *mut c_void, fd as c_int, af as c_int) },
490 None => warn!("Proxy connect function not found"),
491 },
492
493 // Timeout... process any timers
494 Err(mpsc::RecvTimeoutError::Timeout) => continue,
495
496 // Error
497 _ => break,
498 }
499
500 // Explicitly store expired timers to release lock
501 let timers = timer_manager.collect_expired();
502 // Handle any expired timers' callback in the slirp thread
503 for timer in timers {
504 slirp.handle_timer(timer);
505 }
506 let duration = start.elapsed().as_secs();
507 if duration > TIMEOUT_SECS {
508 warn!("libslirp command '{cmd_str}' took too long to complete: {duration:?}");
509 }
510 }
511 // Shuts down the instance of a slirp stack and release slirp storage. No callbacks
512 // occur after this since it calls slirp_cleanup.
513 drop(slirp);
514
515 // Shutdown slirp_poll_thread -- worst case it sends a PollResult that is ignored
516 // since this thread is no longer processing Slirp commands.
517 drop(tx_poll);
518 }
519
520 #[derive(Clone, Debug)]
521 struct PollFd {
522 fd: c_int,
523 events: SlirpPollType,
524 revents: SlirpPollType,
525 }
526
527 impl Slirp {
528 /// Fill the pollfds from libslirp and pass the request to the polling thread.
529 ///
530 /// This is called by the application when it is about to sleep through
531 /// poll(). *timeout is set to the amount of virtual time (in ms) that
532 /// the application intends to wait (UINT32_MAX if
533 /// infinite). slirp_pollfds_fill updates it according to e.g. TCP
534 /// timers, so the application knows it should sleep a smaller amount
535 /// of time. slirp_pollfds_fill calls add_poll for each file descriptor
536 /// that should be monitored along the sleep. The opaque pointer is
537 /// passed as such to add_poll, and add_poll returns an index.
538 ///
539 /// # Safety
540 ///
541 /// `slirp` must be a valid Slirp state returned by `slirp_new()`
pollfds_fill_and_send( &self, poll_fds: &RefCell<Vec<PollFd>>, tx: &mpsc::Sender<PollRequest>, )542 fn pollfds_fill_and_send(
543 &self,
544 poll_fds: &RefCell<Vec<PollFd>>,
545 tx: &mpsc::Sender<PollRequest>,
546 ) {
547 let mut timeout: u32 = u32::MAX;
548 poll_fds.borrow_mut().clear();
549
550 // Safety: we ensure that:
551 //
552 // * self.slirp has a slirp pointer initialized by slirp_new,
553 // as it's private to the struct and is only constructed that way.
554 //
555 // * timeout is a valid ptr to a mutable u32. The "C" slirp
556 // library stores into timeout.
557 //
558 // * slirp_add_poll_cb is a valid `SlirpAddPollCb` function.
559 //
560 // * self.callback_context is a CallbackContext
561 unsafe {
562 libslirp_sys::slirp_pollfds_fill(
563 self.slirp,
564 &mut timeout,
565 Some(slirp_add_poll_cb),
566 &*self.callback_context as *const CallbackContext as *mut c_void,
567 );
568 }
569 if let Err(e) = tx.send((poll_fds.borrow().to_vec(), timeout)) {
570 warn!("Failed to send poll fds: {}", e);
571 }
572 }
573 }
574
575 /// "C" library callback that is called for each file descriptor that
576 /// should be monitored.
577 ///
578 /// # Safety
579 ///
580 /// * opaque must be a CallbackContext
slirp_add_poll_cb(fd: c_int, events: c_int, opaque: *mut c_void) -> c_int581 unsafe extern "C" fn slirp_add_poll_cb(fd: c_int, events: c_int, opaque: *mut c_void) -> c_int {
582 // Safety:
583 //
584 // * opaque is a CallbackContext
585 unsafe { callback_context_from_raw(opaque) }.add_poll(fd, events)
586 }
587
588 impl CallbackContext {
add_poll(&mut self, fd: c_int, events: c_int) -> c_int589 fn add_poll(&mut self, fd: c_int, events: c_int) -> c_int {
590 let idx = self.poll_fds.borrow().len();
591 self.poll_fds.borrow_mut().push(PollFd { fd, events: events as SlirpPollType, revents: 0 });
592 idx as i32
593 }
594 }
595
596 impl Slirp {
597 /// Pass the result from the polling thread back to libslirp.
598 ///
599 /// * select_error should be 1 if poll() returned an error, else 0.
pollfds_poll(&self, select_error: c_int)600 fn pollfds_poll(&self, select_error: c_int) {
601 // Call libslrip "C" library to fill poll return event information
602 // using slirp_get_revents_cb callback function.
603 //
604 // Safety: we ensure that:
605 //
606 // * self.slirp has a slirp pointer initialized by slirp_new,
607 // as it's private to the struct and is only constructed that way.
608 //
609 // * slirp_get_revents_cb is a valid `SlirpGetREventsCb` callback
610 // function.
611 //
612 // * select_error should be 1 if poll() returned an error, else 0.
613 //
614 // * self.callback_context is a CallbackContext
615 unsafe {
616 libslirp_sys::slirp_pollfds_poll(
617 self.slirp,
618 select_error,
619 Some(slirp_get_revents_cb),
620 &*self.callback_context as *const CallbackContext as *mut c_void,
621 );
622 }
623 }
624 }
625
626 /// "C" library callback that is called on each file descriptor, giving
627 /// it the index that add_poll returned.
628 ///
629 /// # Safety
630 ///
631 /// * opaque must be a CallbackContext
slirp_get_revents_cb(idx: c_int, opaque: *mut c_void) -> c_int632 unsafe extern "C" fn slirp_get_revents_cb(idx: c_int, opaque: *mut c_void) -> c_int {
633 // Safety:
634 //
635 // * opaque is a CallbackContext
636 unsafe { callback_context_from_raw(opaque) }.get_events(idx)
637 }
638
639 impl CallbackContext {
get_events(&self, idx: c_int) -> c_int640 fn get_events(&self, idx: c_int) -> c_int {
641 if let Some(poll_fd) = self.poll_fds.borrow().get(idx as usize) {
642 poll_fd.revents as c_int
643 } else {
644 0
645 }
646 }
647 }
648
649 macro_rules! ternary {
650 ($cond:expr, $true_expr:expr) => {
651 if $cond != 0 {
652 $true_expr
653 } else {
654 0
655 }
656 };
657 }
658
659 /// Worker thread that performs blocking `poll` operations on file descriptors.
660 ///
661 /// It receives polling requests from the `rx` channel, performs the `poll`, and sends the results
662 /// back to the slirp thread via the `tx` channel. This allows the slirp stack to be notified about
663 /// network events without busy waiting.
664 ///
665 /// The function handles platform-specific differences in polling mechanisms between Linux/macOS
666 /// and Windows. It also converts between Slirp's `SlirpPollType` and the OS-specific poll event types.
slirp_poll_thread(rx: mpsc::Receiver<PollRequest>, tx: mpsc::Sender<SlirpCmd>)667 fn slirp_poll_thread(rx: mpsc::Receiver<PollRequest>, tx: mpsc::Sender<SlirpCmd>) {
668 #[cfg(any(target_os = "linux", target_os = "macos"))]
669 use libc::{
670 nfds_t as OsPollFdsLenType, poll, pollfd, POLLERR as OS_POLL_ERR, POLLHUP as OS_POLL_HUP,
671 POLLIN as OS_POLL_IN, POLLNVAL as OS_POLL_NVAL, POLLOUT as OS_POLL_OUT,
672 POLLPRI as OS_POLL_PRI,
673 };
674 #[cfg(target_os = "windows")]
675 use winapi::{
676 shared::minwindef::ULONG as OsPollFdsLenType,
677 um::winsock2::{
678 WSAPoll as poll, POLLERR as OS_POLL_ERR, POLLHUP as OS_POLL_HUP,
679 POLLNVAL as OS_POLL_NVAL, POLLRDBAND as OS_POLL_PRI, POLLRDNORM as OS_POLL_IN,
680 POLLWRNORM as OS_POLL_OUT, SOCKET as FdType, WSAPOLLFD as pollfd,
681 },
682 };
683 #[cfg(any(target_os = "linux", target_os = "macos"))]
684 type FdType = c_int;
685
686 // Convert Slirp poll (input) events to OS events definitions
687 fn to_os_events(events: SlirpPollType) -> i16 {
688 ternary!(events & SLIRP_POLL_IN, OS_POLL_IN)
689 | ternary!(events & SLIRP_POLL_OUT, OS_POLL_OUT)
690 | ternary!(events & SLIRP_POLL_PRI, OS_POLL_PRI)
691 }
692 // Convert OS (input) "events" to Slirp (input) events definitions
693 fn to_slirp_events(events: i16) -> SlirpPollType {
694 ternary!(events & OS_POLL_IN, SLIRP_POLL_IN)
695 | ternary!(events & OS_POLL_OUT, SLIRP_POLL_OUT)
696 | ternary!(events & OS_POLL_PRI, SLIRP_POLL_PRI)
697 }
698 // Convert OS (output) "revents" to Slirp revents definitions which includes ERR and HUP
699 fn to_slirp_revents(revents: i16) -> SlirpPollType {
700 to_slirp_events(revents)
701 | ternary!(revents & OS_POLL_ERR, SLIRP_POLL_ERR)
702 | ternary!(revents & OS_POLL_HUP, SLIRP_POLL_HUP)
703 }
704
705 let mut prev_poll_fds_len = 0;
706 while let Ok((poll_fds, timeout)) = rx.recv() {
707 if poll_fds.len() != prev_poll_fds_len {
708 prev_poll_fds_len = poll_fds.len();
709 debug!("slirp_poll_thread recv poll_fds.len(): {:?}", prev_poll_fds_len);
710 }
711 // Create a c format array with the same size as poll
712 let mut os_poll_fds: Vec<pollfd> = Vec::with_capacity(poll_fds.len());
713 for fd in &poll_fds {
714 os_poll_fds.push(pollfd {
715 fd: fd.fd as FdType,
716 events: to_os_events(fd.events),
717 revents: 0,
718 });
719 }
720
721 let mut poll_result = 0;
722 // WSAPoll requires an array of one or more POLLFD structures.
723 // When nfds == 0, WSAPoll returns immediately with result -1, ignoring the timeout.
724 // (This is different from poll on Linux/macOS, which will wait for the timeout.)
725 // Therefore when nfds == 0 we will explicitly sleep for the timeout regardless of OS.
726 if os_poll_fds.is_empty() {
727 // If there are no FDs to poll, sleep for the specified timeout.
728 thread::sleep(Duration::from_millis(timeout as u64));
729 } else {
730 // Safety: we ensure that:
731 //
732 // `os_poll_fds` is a valid ptr to a vector of pollfd which
733 // the `poll` system call can write into. Note `os_poll_fds`
734 // is created and allocated above.
735 poll_result = unsafe {
736 poll(
737 os_poll_fds.as_mut_ptr(),
738 os_poll_fds.len() as OsPollFdsLenType,
739 timeout as i32,
740 )
741 };
742 }
743 // POLLHUP and POLLERR are always allowed revents.
744 // if other events were not requested, then don't return them in the revents.
745 let allowed_revents = OS_POLL_HUP | OS_POLL_ERR;
746 let mut slirp_poll_fds: Vec<PollFd> = Vec::with_capacity(poll_fds.len());
747 for &fd in &os_poll_fds {
748 // Slrip does not handle POLLNVAL - print warning and skip
749 if fd.events & OS_POLL_NVAL != 0 {
750 warn!("POLLNVAL event - Skip poll for fd: {:?}", fd.fd);
751 continue;
752 }
753 slirp_poll_fds.push(PollFd {
754 fd: fd.fd as c_int,
755 events: to_slirp_events(fd.events),
756 revents: to_slirp_revents(fd.revents & (fd.events | allowed_revents)),
757 });
758 }
759
760 // 'select_error' should be 1 if poll() returned an error, else 0.
761 if let Err(e) = tx.send(SlirpCmd::PollResult(slirp_poll_fds, (poll_result < 0) as i32)) {
762 warn!("Failed to send slirp PollResult cmd: {}", e);
763 }
764 }
765 }
766
767 impl Slirp {
768 /// Sends raw input bytes to the slirp stack.
769 ///
770 /// This function is called by the application to inject network data into the virtual network
771 /// stack. The `bytes` slice contains the raw packet data that should be processed by slirp.
input(&self, bytes: &[u8])772 fn input(&self, bytes: &[u8]) {
773 // Safety: The "C" library ensure that the memory is not
774 // referenced after the call and `bytes` does not need to remain
775 // valid after the function returns.
776 unsafe { libslirp_sys::slirp_input(self.slirp, bytes.as_ptr(), bytes.len() as i32) };
777 }
778 }
779
780 /// Callback function invoked by the slirp stack to send an ethernet frame to the guest network.
781 ///
782 /// This function is called by the slirp stack when it has a network packet that needs to be
783 /// delivered to the guest network. The `buf` pointer points to the raw packet data, and `len`
784 /// specifies the length of the packet.
785 ///
786 /// If the guest is not ready to receive the packet, the function can drop the data. TCP will
787 /// handle retransmissions as needed.
788 ///
789 /// # Safety
790 ///
791 /// * `buf` must be a valid pointer to `len` bytes of memory.
792 /// * `len` must be greater than 0.
793 /// * `opaque` must be a valid `CallbackContext` pointer.
794 ///
795 /// # Returns
796 ///
797 /// The number of bytes sent (which should be equal to `len`).
send_packet_cb( buf: *const c_void, len: usize, opaque: *mut c_void, ) -> libslirp_sys::slirp_ssize_t798 unsafe extern "C" fn send_packet_cb(
799 buf: *const c_void,
800 len: usize,
801 opaque: *mut c_void,
802 ) -> libslirp_sys::slirp_ssize_t {
803 // Safety:
804 //
805 // * `buf` is a valid pointer to `len` bytes of memory.
806 // * `len` is greater than 0.
807 // * `opaque` is a valid `CallbackContext` pointer.
808 unsafe { callback_context_from_raw(opaque) }.send_packet(buf, len)
809 }
810
811 impl CallbackContext {
send_packet(&self, buf: *const c_void, len: usize) -> libslirp_sys::slirp_ssize_t812 fn send_packet(&self, buf: *const c_void, len: usize) -> libslirp_sys::slirp_ssize_t {
813 // Safety: The caller ensures that `buf` is contains `len` bytes of data.
814 let c_slice = unsafe { std::slice::from_raw_parts(buf as *const u8, len) };
815 // Bytes::from(slice: &'static [u8]) creates a Bytes object without copying the data.
816 // To own its data, copy &'static [u8] to Vec<u8> before converting to Bytes.
817 let bytes = Bytes::from(c_slice.to_vec());
818 let _ = self.tx_bytes.send(bytes.clone());
819 // When HTTP Proxy is enabled, it tracks DNS packets.
820 if let Some(tx_proxy) = &self.tx_proxy_bytes {
821 let _ = tx_proxy.send(bytes);
822 }
823 len as libslirp_sys::slirp_ssize_t
824 }
825 }
826
827 /// Callback function invoked by the slirp stack to report an error caused by guest misbehavior.
828 ///
829 /// This function is called by the slirp stack when it encounters an error condition that is
830 /// attributed to incorrect or unexpected behavior from the guest network. The `msg` parameter
831 /// contains a human-readable error message describing the issue.
832 ///
833 /// # Safety
834 ///
835 /// * `msg` must be a valid C string.
836 /// * `opaque` must be a valid `CallbackContext` pointer.
guest_error_cb(msg: *const c_char, opaque: *mut c_void)837 unsafe extern "C" fn guest_error_cb(msg: *const c_char, opaque: *mut c_void) {
838 // Safety:
839 // * `msg` is guaranteed to be a valid C string by the caller.
840 let msg = String::from_utf8_lossy(unsafe { CStr::from_ptr(msg) }.to_bytes());
841 // Safety:
842 // * `opaque` is guaranteed to be a valid, non-null pointer to a `CallbackContext` struct that was originally passed
843 // to `slirp_new()` and is guaranteed to be valid for the lifetime of the Slirp instance.
844 // * `callback_context_from_raw()` safely converts the raw `opaque` pointer back to a
845 // `CallbackContext` reference. This is safe because the `opaque` pointer is guaranteed to be valid.
846 unsafe { callback_context_from_raw(opaque) }.guest_error(msg.to_string());
847 }
848
849 impl CallbackContext {
guest_error(&self, msg: String)850 fn guest_error(&self, msg: String) {
851 warn!("libslirp: {msg}");
852 }
853 }
854
855 /// Callback function invoked by the slirp stack to get the current time in nanoseconds.
856 ///
857 /// This function is called by the slirp stack to obtain the current time, which is used for
858 /// various timing-related operations within the virtual network stack.
859 ///
860 /// # Safety
861 ///
862 /// * `opaque` must be a valid `CallbackContext` pointer.
863 ///
864 /// # Returns
865 ///
866 /// The current time in nanoseconds.
clock_get_ns_cb(opaque: *mut c_void) -> i64867 unsafe extern "C" fn clock_get_ns_cb(opaque: *mut c_void) -> i64 {
868 // Safety:
869 //
870 // * `opaque` is a valid `CallbackContext` pointer.
871 //
872 unsafe { callback_context_from_raw(opaque) }.clock_get_ns()
873 }
874
875 impl CallbackContext {
clock_get_ns(&self) -> i64876 fn clock_get_ns(&self) -> i64 {
877 self.timer_manager.get_elapsed().as_nanos() as i64
878 }
879 }
880
881 /// Callback function invoked by the slirp stack to signal that initialization is complete.
882 ///
883 /// This function is called by the slirp stack once it has finished its initialization process
884 /// and is ready to handle network traffic.
885 ///
886 /// # Safety
887 ///
888 /// * `_slirp` is a raw pointer to the slirp instance, but it's not used in this callback.
889 /// * `opaque` must be a valid `CallbackContext` pointer.
init_completed_cb(_slirp: *mut libslirp_sys::Slirp, opaque: *mut c_void)890 unsafe extern "C" fn init_completed_cb(_slirp: *mut libslirp_sys::Slirp, opaque: *mut c_void) {
891 // Safety:
892 //
893 // * `_slirp` is a raw pointer to the slirp instance, but it's not used in this callback.
894 // * `opaque` is a valid `CallbackContext` pointer.
895 unsafe { callback_context_from_raw(opaque) }.init_completed();
896 }
897
898 impl CallbackContext {
init_completed(&self)899 fn init_completed(&self) {
900 info!("libslirp: initialization completed.");
901 }
902 }
903
904 /// Callback function invoked by the slirp stack to create a new timer.
905 ///
906 /// This function is called by the slirp stack when it needs to create a new timer. The `id`
907 /// parameter is a unique identifier for the timer, and `cb_opaque` is an opaque pointer that
908 /// will be passed back to the timer callback function when the timer expires.
909 ///
910 /// # Safety
911 ///
912 /// * `opaque` must be a valid `CallbackContext` pointer.
913 /// * `cb_opaque` should be a valid pointer that can be passed back to libslirp.
914 ///
915 /// # Returns
916 ///
917 /// An opaque pointer to the newly created timer.
timer_new_opaque_cb( id: SlirpTimerId, cb_opaque: *mut c_void, opaque: *mut c_void, ) -> *mut c_void918 unsafe extern "C" fn timer_new_opaque_cb(
919 id: SlirpTimerId,
920 cb_opaque: *mut c_void,
921 opaque: *mut c_void,
922 ) -> *mut c_void {
923 // Safety:
924 // * `opaque` is a valid, non-null pointer to a `CallbackContext` struct that was originally passed
925 // to `slirp_new()` and is guaranteed to be valid for the lifetime of the Slirp instance.
926 // * `callback_context_from_raw()` safely converts the raw `opaque` pointer back to a
927 // `CallbackContext` reference. This is safe because the `opaque` pointer is guaranteed to be valid.
928 unsafe { callback_context_from_raw(opaque).timer_new_opaque(id, cb_opaque) }
929 }
930
931 impl CallbackContext {
932 /// Creates a new timer and stores it in the timer manager.
933 ///
934 /// # Safety
935 ///
936 /// * `cb_opaque` should be a valid pointer that can be passed back to libslirp.
timer_new_opaque(&self, id: SlirpTimerId, cb_opaque: *mut c_void) -> *mut c_void937 unsafe fn timer_new_opaque(&self, id: SlirpTimerId, cb_opaque: *mut c_void) -> *mut c_void {
938 let timer = self.timer_manager.next_timer();
939 self.timer_manager
940 .insert(timer, Timer { expire_time: u64::MAX, id, cb_opaque: cb_opaque as usize });
941 timer as *mut c_void
942 }
943 }
944
945 /// Callback function invoked by the slirp stack to free a timer.
946 ///
947 /// This function is called by the slirp stack when a timer is no longer needed and should be
948 /// removed. The `timer` parameter is an opaque pointer to the timer that was created previously
949 /// using `timer_new_opaque_cb`.
950 ///
951 /// # Safety
952 ///
953 /// * `timer` must be a valid `TimerOpaque` key that was previously returned by `timer_new_opaque_cb`.
954 /// * `opaque` must be a valid `CallbackContext` pointer.
timer_free_cb(timer: *mut c_void, opaque: *mut c_void)955 unsafe extern "C" fn timer_free_cb(timer: *mut c_void, opaque: *mut c_void) {
956 // Safety:
957 //
958 // * `timer` is a valid `TimerOpaque` key that was previously returned by `timer_new_opaque_cb`.
959 // * `opaque` is a valid `CallbackContext` pointer.
960 unsafe { callback_context_from_raw(opaque) }.timer_free(timer);
961 }
962
963 impl CallbackContext {
964 /// Removes a timer from the timer manager.
965 ///
966 /// If the timer is not found in the manager, a warning is logged.
timer_free(&self, timer: *mut c_void)967 fn timer_free(&self, timer: *mut c_void) {
968 let timer = timer as TimerOpaque;
969 if self.timer_manager.remove(&timer).is_none() {
970 warn!("Unknown timer {timer}");
971 }
972 }
973 }
974
975 /// Callback function invoked by the slirp stack to modify an existing timer.
976 ///
977 /// This function is called by the slirp stack when it needs to change the expiration time of
978 /// an existing timer. The `timer` parameter is an opaque pointer to the timer that was created
979 /// previously using `timer_new_opaque_cb`. The `expire_time` parameter specifies the new
980 /// expiration time for the timer, in nanoseconds.
981 ///
982 /// # Safety
983 ///
984 /// * `timer` must be a valid `TimerOpaque` key that was previously returned by `timer_new_opaque_cb`.
985 /// * `opaque` must be a valid `CallbackContext` pointer.
timer_mod_cb(timer: *mut c_void, expire_time: i64, opaque: *mut c_void)986 unsafe extern "C" fn timer_mod_cb(timer: *mut c_void, expire_time: i64, opaque: *mut c_void) {
987 // Safety:
988 //
989 // * `timer` is a valid `TimerOpaque` key that was previously returned by `timer_new_opaque_cb`.
990 // * `opaque` is a valid `CallbackContext` pointer.
991 unsafe { callback_context_from_raw(opaque) }.timer_mod(timer, expire_time);
992 }
993
994 impl CallbackContext {
995 /// Modifies the expiration time of a timer in the timer manager.
996 ///
997 /// This function updates the expiration time of the specified timer. It also sends a
998 /// notification to the slirp command thread to wake it up and reset its sleep duration,
timer_mod(&self, timer: *mut c_void, expire_time: i64)999 fn timer_mod(&self, timer: *mut c_void, expire_time: i64) {
1000 let timer_key = timer as TimerOpaque;
1001 let expire_time = std::cmp::max(expire_time, 0) as u64;
1002 self.timer_manager.timer_mod(&timer_key, expire_time);
1003 // Wake up slirp command thread to reset sleep duration
1004 let _ = self.tx_cmds.send(SlirpCmd::TimerModified);
1005 }
1006 }
1007
register_poll_fd_cb(_fd: c_int, _opaque: *mut c_void)1008 extern "C" fn register_poll_fd_cb(_fd: c_int, _opaque: *mut c_void) {
1009 //TODO: Need implementation for Windows
1010 }
1011
unregister_poll_fd_cb(_fd: c_int, _opaque: *mut c_void)1012 extern "C" fn unregister_poll_fd_cb(_fd: c_int, _opaque: *mut c_void) {
1013 //TODO: Need implementation for Windows
1014 }
1015
notify_cb(_opaque: *mut c_void)1016 extern "C" fn notify_cb(_opaque: *mut c_void) {
1017 //TODO: Un-implemented
1018 }
1019
1020 /// Callback function invoked by the slirp stack to initiate a proxy connection.
1021 ///
1022 /// This function is called by the slirp stack when it needs to establish a connection
1023 /// through a proxy. The `addr` parameter points to the address to connect to, `connect_func`
1024 /// is a callback function that should be called to notify libslirp of the connection result,
1025 /// and `connect_opaque` is an opaque pointer that will be passed back to `connect_func`.
1026 ///
1027 /// # Safety
1028 ///
1029 /// * `addr` must be a valid pointer to a `sockaddr_storage` structure.
1030 /// * `connect_func` must be a valid callback function pointer.
1031 /// * `connect_opaque` should be a valid pointer that can be passed back to libslirp.
1032 /// * `opaque` must be a valid `CallbackContext` pointer.
1033 ///
1034 /// # Returns
1035 ///
1036 /// `true` if the proxy connection request was initiated successfully, `false` otherwise.
try_connect_cb( addr: *const libslirp_sys::sockaddr_storage, connect_func: SlirpProxyConnectFunc, connect_opaque: *mut c_void, opaque: *mut c_void, ) -> bool1037 unsafe extern "C" fn try_connect_cb(
1038 addr: *const libslirp_sys::sockaddr_storage,
1039 connect_func: SlirpProxyConnectFunc,
1040 connect_opaque: *mut c_void,
1041 opaque: *mut c_void,
1042 ) -> bool {
1043 // Safety:
1044 //
1045 // * `addr` is a valid pointer to a `sockaddr_storage` structure.
1046 // * `connect_func` is a valid callback function pointer.
1047 // * `connect_opaque` is a valid pointer that can be passed back to libslirp.
1048 // * `opaque` is a valid `CallbackContext` pointer.
1049 unsafe {
1050 callback_context_from_raw(opaque).try_connect(addr, connect_func, connect_opaque as usize)
1051 }
1052 }
1053
1054 impl CallbackContext {
1055 /// Attempts to establish a proxy connection.
1056 ///
1057 /// This function uses the `proxy_manager` to initiate a connection to the specified address.
1058 /// If the proxy manager is not available, it returns `false`.
1059 ///
1060 /// # Safety
1061 ///
1062 /// * `addr` must be a valid pointer to a `sockaddr_storage` structure.
try_connect( &self, addr: *const libslirp_sys::sockaddr_storage, connect_func: SlirpProxyConnectFunc, connect_id: usize, ) -> bool1063 unsafe fn try_connect(
1064 &self,
1065 addr: *const libslirp_sys::sockaddr_storage,
1066 connect_func: SlirpProxyConnectFunc,
1067 connect_id: usize,
1068 ) -> bool {
1069 if let Some(proxy_manager) = &self.proxy_manager {
1070 // Safety:
1071 //
1072 // * `addr` is a valid pointer to a `sockaddr_storage` structure, as guaranteed by the caller
1073 // * Obtaining the `ss_family` field from a valid `sockaddr_storage` struct is safe
1074 let storage = unsafe { *addr };
1075 let af = storage.ss_family as i32;
1076 let socket_addr: SocketAddr = storage.into();
1077 proxy_manager.try_connect(
1078 socket_addr,
1079 connect_id,
1080 Box::new(ConnectRequest {
1081 tx_cmds: self.tx_cmds.clone(),
1082 connect_func,
1083 connect_id,
1084 af,
1085 start: Instant::now(),
1086 }),
1087 )
1088 } else {
1089 false
1090 }
1091 }
1092 }
1093
1094 /// Callback function invoked by the slirp stack to remove a proxy connection.
1095 ///
1096 /// This function is called by the slirp stack when a proxy connection is no longer needed
1097 /// and should be removed. The `connect_opaque` parameter is an opaque pointer that was
1098 /// originally passed to `try_connect_cb` when the connection was initiated.
1099 ///
1100 /// # Safety
1101 ///
1102 /// * `connect_opaque` must be a valid pointer that was previously passed to `try_connect_cb`.
1103 /// * `opaque` must be a valid `CallbackContext` pointer.
remove_cb(connect_opaque: *mut c_void, opaque: *mut c_void)1104 unsafe extern "C" fn remove_cb(connect_opaque: *mut c_void, opaque: *mut c_void) {
1105 // Safety:
1106 //
1107 // * `connect_opaque` is a valid pointer that was previously passed to `try_connect_cb`.
1108 // * `opaque` is a valid `CallbackContext` pointer.
1109 unsafe { callback_context_from_raw(opaque) }.remove(connect_opaque as usize);
1110 }
1111
1112 impl CallbackContext {
1113 /// Removes a proxy connection from the proxy manager.
1114 ///
1115 /// This function calls the `remove` method on the `proxy_manager` to remove the
1116 /// connection associated with the given `connect_id`.
remove(&self, connect_id: usize)1117 fn remove(&self, connect_id: usize) {
1118 if let Some(proxy_connector) = &self.proxy_manager {
1119 proxy_connector.remove(connect_id);
1120 }
1121 }
1122 }
1123
1124 #[cfg(test)]
1125 mod tests {
1126 use super::*;
1127 use std::io::{Read, Write};
1128 use std::net::{TcpListener, TcpStream};
1129 #[cfg(any(target_os = "linux", target_os = "macos"))]
1130 use std::os::unix::io::AsRawFd;
1131 #[cfg(target_os = "windows")]
1132 use std::os::windows::io::AsRawSocket;
1133
1134 #[test]
test_version_string()1135 fn test_version_string() {
1136 // Safety:
1137 // Function returns a constant c_str
1138 let c_version_str = unsafe { CStr::from_ptr(crate::libslirp_sys::slirp_version_string()) };
1139 assert_eq!("4.7.0", c_version_str.to_str().unwrap());
1140 }
1141
1142 // Utility function to create and launch a slirp polling thread
launch_polling_thread() -> ( mpsc::Sender<SlirpCmd>, mpsc::Receiver<SlirpCmd>, mpsc::Sender<PollRequest>, thread::JoinHandle<()>, )1143 fn launch_polling_thread() -> (
1144 mpsc::Sender<SlirpCmd>,
1145 mpsc::Receiver<SlirpCmd>,
1146 mpsc::Sender<PollRequest>,
1147 thread::JoinHandle<()>,
1148 ) {
1149 let (tx_cmds, rx_cmds) = mpsc::channel::<SlirpCmd>();
1150 let (tx_poll, rx_poll) = mpsc::channel::<PollRequest>();
1151
1152 let tx_cmds_clone = tx_cmds.clone();
1153 let handle = thread::Builder::new()
1154 .name(format!("test_slirp_poll"))
1155 .spawn(move || slirp_poll_thread(rx_poll, tx_cmds_clone))
1156 .unwrap();
1157
1158 (tx_cmds, rx_cmds, tx_poll, handle)
1159 }
1160
1161 #[cfg(any(target_os = "linux", target_os = "macos"))]
to_os_fd(stream: &impl AsRawFd) -> i321162 fn to_os_fd(stream: &impl AsRawFd) -> i32 {
1163 return stream.as_raw_fd() as i32;
1164 }
1165 #[cfg(target_os = "windows")]
to_os_fd(stream: &impl AsRawSocket) -> i321166 fn to_os_fd(stream: &impl AsRawSocket) -> i32 {
1167 return stream.as_raw_socket() as i32;
1168 }
1169
1170 // Utility function to send a poll request and receive the result
poll_and_assert_result( tx_poll: &mpsc::Sender<PollRequest>, rx_cmds: &mpsc::Receiver<SlirpCmd>, fd: i32, poll_events: SlirpPollType, expected_revents: SlirpPollType, )1171 fn poll_and_assert_result(
1172 tx_poll: &mpsc::Sender<PollRequest>,
1173 rx_cmds: &mpsc::Receiver<SlirpCmd>,
1174 fd: i32,
1175 poll_events: SlirpPollType,
1176 expected_revents: SlirpPollType,
1177 ) {
1178 assert!(
1179 tx_poll.send((vec![PollFd { fd, events: poll_events, revents: 0 }], 1000)).is_ok(),
1180 "Failed to send poll request"
1181 );
1182 if let Ok(SlirpCmd::PollResult(poll_fds, select_error)) = rx_cmds.recv() {
1183 assert_eq!(poll_fds.len(), 1, "poll_fds len is not 1.");
1184 let poll_fd = poll_fds.get(0).unwrap();
1185 assert_eq!(poll_fd.fd, fd, "poll file descriptor mismatch.");
1186 assert_eq!(poll_fd.revents, expected_revents, "poll revents mismatch.");
1187 } else {
1188 assert!(false, "Received unexpected command poll result");
1189 }
1190 }
1191
1192 // Create and return TcpListener and TcpStream of a connected pipe
create_stream_pipe() -> (TcpListener, TcpStream)1193 fn create_stream_pipe() -> (TcpListener, TcpStream) {
1194 // Create a TcpStream pipe for testing.
1195 let listener = TcpListener::bind("127.0.0.1:0").unwrap();
1196 let addr = listener.local_addr().unwrap();
1197 let writer = TcpStream::connect(addr).unwrap();
1198 (listener, writer)
1199 }
1200
1201 // Create and return reader and writer TcpStreams of an accepted pipe
create_accepted_stream_pipe() -> (TcpStream, TcpStream)1202 fn create_accepted_stream_pipe() -> (TcpStream, TcpStream) {
1203 // Create a TcpStream pipe
1204 let (listener, writer) = create_stream_pipe();
1205 // Accept the connection
1206 let (reader, _) = listener.accept().unwrap();
1207 (reader, writer)
1208 }
1209
1210 // Initialize an accepted TcpStream pipe with initial data
init_pipe() -> (TcpStream, TcpStream)1211 fn init_pipe() -> (TcpStream, TcpStream) {
1212 // Create an accepted TcpStream pipe
1213 let (reader, mut writer) = create_accepted_stream_pipe();
1214 // Write initial data to pipe
1215 #[cfg(any(target_os = "linux", target_os = "macos"))]
1216 writer.write_all(&[1]).unwrap();
1217 #[cfg(target_os = "windows")]
1218 writer.write_all(b"1").unwrap();
1219
1220 (reader, writer)
1221 }
1222
1223 #[test]
test_slirp_poll_thread_exit()1224 fn test_slirp_poll_thread_exit() {
1225 let (_tx_cmds, _rx_cmds, tx_poll, handle) = launch_polling_thread();
1226 // Drop the sender to end the polling thread and wait for the polling thread to exit
1227 drop(tx_poll);
1228 handle.join().unwrap();
1229 }
1230
1231 #[test]
test_poll_invalid_fd()1232 fn test_poll_invalid_fd() {
1233 // Launch the slirp polling thread.
1234 let (_tx_cmds, rx_cmds, tx_poll, handle) = launch_polling_thread();
1235
1236 let invalid_fd = -1;
1237 // Check that the poll result indicates 0 (fd not ready).
1238 poll_and_assert_result(&tx_poll, &rx_cmds, invalid_fd, SLIRP_POLL_IN, 0);
1239
1240 // Drop the sender to end the polling thread and wait for the polling thread to exit
1241 drop(tx_poll);
1242 handle.join().unwrap();
1243 }
1244
1245 #[test]
test_close_fd_before_accept()1246 fn test_close_fd_before_accept() {
1247 // Launch the slirp polling thread.
1248 let (_tx_cmds, rx_cmds, tx_poll, handle) = launch_polling_thread();
1249
1250 // Init a "broken" pipe that is closed before being accepted
1251 let (listener, writer) = create_stream_pipe();
1252
1253 // Close the listener before accepting the connection
1254 drop(listener);
1255
1256 // Check the expected result when file descriptor is not ready.
1257 #[cfg(target_os = "linux")]
1258 let expected_revents = SLIRP_POLL_IN | SLIRP_POLL_HUP | SLIRP_POLL_ERR;
1259 // TODO: Identify way to trigger and test POLL_ERR for macOS
1260 #[cfg(target_os = "macos")]
1261 let expected_revents = SLIRP_POLL_IN | SLIRP_POLL_HUP;
1262 #[cfg(target_os = "windows")]
1263 let expected_revents = SLIRP_POLL_HUP | SLIRP_POLL_ERR;
1264 poll_and_assert_result(
1265 &tx_poll,
1266 &rx_cmds,
1267 to_os_fd(&writer),
1268 SLIRP_POLL_IN,
1269 expected_revents,
1270 );
1271
1272 // Drop the sender to end the polling thread and wait for the polling thread to exit
1273 drop(tx_poll);
1274 handle.join().unwrap();
1275 }
1276
1277 #[test]
test_accept_close_before_write()1278 fn test_accept_close_before_write() {
1279 // Launch the slirp polling thread.
1280 let (_tx_cmds, rx_cmds, tx_poll, handle) = launch_polling_thread();
1281 // Init a "broken" pipe that is accepted but no initial data is written
1282 let (mut reader, writer) = create_accepted_stream_pipe();
1283 let reader_fd = to_os_fd(&reader);
1284 // Close the writer end of the pipe
1285 drop(writer);
1286
1287 // Check the expected poll result when writer is closed before data is written
1288 #[cfg(target_os = "linux")]
1289 let expected_revents = SLIRP_POLL_IN;
1290 #[cfg(target_os = "macos")]
1291 let expected_revents = SLIRP_POLL_IN | SLIRP_POLL_HUP;
1292 #[cfg(target_os = "windows")]
1293 let expected_revents = SLIRP_POLL_HUP;
1294 poll_and_assert_result(&tx_poll, &rx_cmds, reader_fd, SLIRP_POLL_IN, expected_revents);
1295
1296 // Drop the sender to end the polling thread and wait for the polling thread to exit
1297 drop(tx_poll);
1298 handle.join().unwrap();
1299 }
1300
1301 #[test]
test_accept_write_close()1302 fn test_accept_write_close() {
1303 // Launch the slirp polling thread.
1304 let (_tx_cmds, rx_cmds, tx_poll, handle) = launch_polling_thread();
1305 // Init a pipe for testing and get its reader file descriptor.
1306 let (mut reader, writer) = init_pipe();
1307 let reader_fd = to_os_fd(&reader);
1308
1309 // --- Test polling for POLLIN event ---
1310
1311 // Send a poll request and check that the poll result has POLLIN only
1312 poll_and_assert_result(&tx_poll, &rx_cmds, reader_fd, SLIRP_POLL_IN, SLIRP_POLL_IN);
1313
1314 // Read / remove the data from the pipe.
1315 let mut buf = [0; 1];
1316 reader.read_exact(&mut buf).unwrap();
1317
1318 // --- Test polling for no event after reading ---
1319
1320 // Check that the poll result contains no event since there is no more data
1321 poll_and_assert_result(&tx_poll, &rx_cmds, reader_fd, SLIRP_POLL_IN, 0);
1322
1323 // --- Test polling for POLLHUP event when writer is closed ---
1324
1325 // Close the writer
1326 drop(writer);
1327
1328 // Shutdown the write half of the reader
1329 reader.shutdown(std::net::Shutdown::Write).unwrap();
1330
1331 // Check that expected poll result when writer end is dropped
1332 #[cfg(any(target_os = "linux", target_os = "macos"))]
1333 let expected_revents = SLIRP_POLL_IN | SLIRP_POLL_HUP;
1334 #[cfg(target_os = "windows")]
1335 let expected_revents = SLIRP_POLL_HUP;
1336 poll_and_assert_result(&tx_poll, &rx_cmds, reader_fd, SLIRP_POLL_IN, expected_revents);
1337
1338 // Drop the sender to end the polling thread and wait for the polling thread to exit
1339 drop(tx_poll);
1340 handle.join().unwrap();
1341 }
1342
1343 // TODO: Add testing for POLLNVAL case
1344 }
1345