net_util: Add windows slirp support

BUG=b:237011316
TEST=presubmit and tested in wine

Change-Id: I1b6160142b8161d4b09d3fd98dfacde354e238b4
Reviewed-on: https://chromium-review.googlesource.com/c/crosvm/crosvm/+/3934818
Reviewed-by: Dennis Kempin <denniskempin@google.com>
Commit-Queue: Vikram Auradkar <auradkar@google.com>
This commit is contained in:
Vikram Auradkar 2022-10-04 20:29:10 +00:00 committed by crosvm LUCI
parent 75dbd9763e
commit c4a4dc9b23
14 changed files with 145 additions and 73 deletions

2
Cargo.lock generated
View file

@ -1260,6 +1260,7 @@ dependencies = [
name = "net_util"
version = "0.1.0"
dependencies = [
"anyhow",
"base",
"cfg-if",
"cros_async",
@ -1269,6 +1270,7 @@ dependencies = [
"metrics",
"net_sys",
"pcap-file",
"prebuilts",
"remain",
"serde",
"smallvec",

View file

@ -130,7 +130,7 @@ all-linux = [
"wl-dmabuf",
"x",
]
win64 = [ "balloon", "crash_report", "haxm", "stats" ]
win64 = [ "balloon", "crash_report", "haxm", "slirp", "stats" ]
arc_quota = ["devices/arc_quota"]
audio = ["devices/audio"]
audio_cras = ["devices/audio_cras"]
@ -169,7 +169,7 @@ plugin = ["protos/plugin", "crosvm_plugin", "kvm", "kvm_sys", "protobuf"]
plugin-render-server = []
power-monitor-powerd = ["arch/power-monitor-powerd"]
qcow = ["disk/qcow"]
slirp = ["devices/slirp"]
slirp = ["devices/slirp", "net_util/slirp"]
stats = ["devices/stats"]
tpm = ["devices/tpm"]
usb = ["devices/usb"]

View file

@ -15,7 +15,6 @@ mod interrupt;
mod iommu;
mod queue;
mod rng;
#[cfg(unix)]
mod sys;
#[cfg(any(feature = "tpm", feature = "vtpm"))]
mod tpm;
@ -80,6 +79,8 @@ cfg_if::cfg_if! {
#[cfg(feature = "slirp")]
pub use self::net::*;
#[cfg(feature = "slirp")]
pub use self::sys::windows::NetExt;
pub use self::vsock::*;
} else {
compile_error!("Unsupported platform");

View file

@ -163,13 +163,11 @@ pub(in crate::virtio::vhost::user::device::net) fn start_queue<T: 'static + Into
let tap = ex
.async_from(tap)
.context("failed to create async tap device")?;
let read_notifier = base::Event(
overlapped_wrapper
.get_h_event_ref()
.unwrap()
.try_clone()
.unwrap(),
);
let read_notifier = overlapped_wrapper
.get_h_event_ref()
.unwrap()
.try_clone()
.unwrap();
let read_notifier = EventAsync::new_without_reset(read_notifier, ex)
.context("failed to create async read notifier")?;

View file

@ -17,7 +17,6 @@ cfg-if = "1.0.0"
cros_async = { path = "../cros_async" }
data_model = { path = "../common/data_model" }
libc = "*"
libslirp-sys = { version = "4.2.1", optional = true }
net_sys = { path = "../net_sys" }
pcap-file = { version = "1.1.0", optional = true }
@ -30,3 +29,8 @@ virtio_sys = { path = "../virtio_sys" }
[target.'cfg(windows)'.dependencies]
metrics = { path = "../metrics" }
winapi = { version = "*", features = ["everything", "std", "impl-default"] }
libslirp-sys = { version = "4.2.1", optional = true }
[build-dependencies]
anyhow = "*"
prebuilts = { path = "../prebuilts" }

View file

@ -2,33 +2,11 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#[cfg(all(feature = "slirp", windows))]
mod win_slirp {
use std::env;
pub(super) fn main() {
// This must be an absolute path or linking issues will result when a consuming crate
// tries to link since $PWD will be different.
let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
#[cfg(debug_assertions)]
let build_type = "debug";
#[cfg(not(debug_assertions))]
let build_type = "release";
println!(
r#"cargo:rustc-link-search={}\..\..\..\third_party\libslirp\{}"#,
manifest_dir, build_type
);
println!(
r#"cargo:rustc-env=PATH={};{}\..\..\..\third_party\libslirp\{};"#,
env::var("PATH").unwrap(),
manifest_dir,
build_type,
);
}
}
static PREBUILTS_VERSION_FILENAME: &str = "prebuilts_version";
static SLIRP_LIB: &str = "libslirp.lib";
static SLIRP_DLL: &str = "libslirp-0.dll";
#[cfg(unix)]
static GLIB_FILENAME: &str = "libglib-2.0.dll.a";
fn main() {
// We (the Windows crosvm maintainers) submitted upstream patches to libslirp-sys so it doesn't
@ -36,8 +14,27 @@ fn main() {
// to the build system that invokes Cargo (e.g. the crosvm jCI scripts that also produce the
// required libslirp DLL & lib). The integration here (win_slirp::main) is specific to crosvm's
// build process.
#[cfg(all(feature = "slirp", windows))]
win_slirp::main();
if std::env::var("CARGO_CFG_WINDOWS").is_ok() {
let version = std::fs::read_to_string(PREBUILTS_VERSION_FILENAME)
.unwrap()
.trim()
.parse::<u32>()
.unwrap();
// TODO(b:242204245) build libslirp locally on windows from build.rs.
prebuilts::download_prebuilts(
"libslirp",
version,
&[
SLIRP_DLL,
SLIRP_LIB,
#[cfg(unix)]
// When compiling with mingw64 to run under wine64, we need glib as slirp links
// against it.
GLIB_FILENAME,
],
)
.unwrap();
}
// For unix, libslirp-sys's build script will make the appropriate linking calls to pkg_config.
}

View file

@ -0,0 +1 @@
1

View file

@ -33,6 +33,11 @@ use serde::Serialize;
pub use sys::TapT;
use thiserror::Error as ThisError;
#[cfg(all(feature = "slirp"))]
pub mod slirp;
#[cfg(all(feature = "slirp", windows))]
pub use slirp::Slirp;
#[sorted]
#[derive(ThisError, Debug)]
pub enum Error {
@ -51,18 +56,23 @@ pub enum Error {
/// Couldn't open /dev/net/tun.
#[error("failed to open /dev/net/tun: {0}")]
OpenTun(SysError),
#[cfg(all(feature = "slirp", windows))]
#[error("slirp related error")]
Slirp(slirp::SlirpError),
}
pub type Result<T> = std::result::Result<T, Error>;
impl Error {
pub fn sys_error(&self) -> SysError {
match *self {
Error::CreateSocket(e) => e,
Error::OpenTun(e) => e,
Error::CreateTap(e) => e,
Error::CloneTap(e) => e,
Error::IoctlError(e) => e,
match &*self {
Error::CreateSocket(e) => *e,
Error::OpenTun(e) => *e,
Error::CreateTap(e) => *e,
Error::CloneTap(e) => *e,
Error::IoctlError(e) => *e,
#[cfg(all(feature = "slirp", windows))]
Error::Slirp(e) => e.sys_error(),
}
}
}

View file

@ -6,6 +6,8 @@
//! level interfaces to libslirp that are used to implement that loop, and
//! diagnostic tools.
#![cfg(windows)]
#[path = "../../third_party/libslirp-rs/src/context.rs"]
pub mod context;
@ -15,6 +17,44 @@ pub mod packet_ring_buffer;
pub mod sys;
pub use sys::Slirp;
use base::Error as SysError;
use remain::sorted;
use thiserror::Error as ThisError;
/// Length includes space for an ethernet frame & the vnet header. See the virtio spec for details:
/// <http://docs.oasis-open.org/virtio/virtio/v1.1/csprd01/virtio-v1.1-csprd01.html#x1-2050006>
pub const ETHERNET_FRAME_SIZE: usize = 1526;
#[cfg(windows)]
#[sorted]
#[derive(ThisError, Debug)]
pub enum SlirpError {
#[error("pipe was closed: {0}")]
BrokenPipe(std::io::Error),
#[error("failed to clone object: {0}")]
CloneFailed(std::io::Error),
#[error("overlapped operation failed: {0}")]
OverlappedError(std::io::Error),
/// Error encountered while in a Slirp related poll operation.
#[error("slirp poll failed: {0}")]
SlirpIOPollError(std::io::Error),
/// Error encountered while in a Slirp related poll operation.
#[error("slirp poll failed: {0}")]
SlirpPollError(SysError),
#[error("WSAStartup failed with code: {0}")]
WSAStartupError(SysError),
}
#[cfg(windows)]
impl SlirpError {
pub fn sys_error(&self) -> SysError {
match &*self {
SlirpError::BrokenPipe(e) => SysError::new(e.raw_os_error().unwrap_or_default()),
SlirpError::CloneFailed(e) => SysError::new(e.raw_os_error().unwrap_or_default()),
SlirpError::OverlappedError(e) => SysError::new(e.raw_os_error().unwrap_or_default()),
SlirpError::SlirpIOPollError(e) => SysError::new(e.raw_os_error().unwrap_or_default()),
SlirpError::SlirpPollError(e) => *e,
SlirpError::WSAStartupError(e) => *e,
}
}
}

View file

@ -30,6 +30,7 @@ use cros_async::IntoAsync;
use serde::Deserialize;
use serde::Serialize;
use crate::slirp::SlirpError;
use crate::slirp::ETHERNET_FRAME_SIZE;
use crate::Error;
use crate::MacAddress;
@ -69,7 +70,7 @@ impl Slirp {
#[cfg(feature = "slirp-ring-capture")]
let slirp_capture_file_clone = slirp_capture_file.clone();
slirp_thread = thread::spawn(move || {
let disable_access_to_host = !cfg!("guest-to-host-net-loopback");
let disable_access_to_host = !cfg!(feature = "guest-to-host-net-loopback");
handler::start_slirp(
slirp_pipe,
@ -100,7 +101,10 @@ impl Slirp {
fn try_clone(&self) -> Result<Self> {
Ok(Slirp {
guest_pipe: self.guest_pipe.try_clone().map_err(Error::CloneFailed)?,
guest_pipe: self
.guest_pipe
.try_clone()
.map_err(|e| Error::Slirp(SlirpError::CloneFailed(e)))?,
overlapped_wrapper: OverlappedWrapper::new(true).unwrap(),
slirp_thread: None,
})
@ -122,7 +126,7 @@ impl Slirp {
// because libslirp logs *everything* as a debug entry.
std::env::set_var("G_MESSAGES_DEBUG", "all");
let disable_access_to_host = !cfg!("guest-to-host-net-loopback");
let disable_access_to_host = !cfg!(feature = "guest-to-host-net-loopback");
info!("starting slirp loop...");
match handler::start_slirp(
@ -132,7 +136,9 @@ impl Slirp {
#[cfg(feature = "slirp-ring-capture")]
slirp_capture_file.take(),
) {
Err(Error::BrokenPipe(e)) => warn!("exited slirp listening loop: {}", e),
Err(Error::Slirp(SlirpError::BrokenPipe(e))) => {
warn!("exited slirp listening loop: {}", e)
}
Err(e) => panic!("error while running slirp listening loop: {}", e),
_ => {}
}
@ -226,7 +232,7 @@ impl TapTCommon for Slirp {
unimplemented!("not used by Slirp");
}
fn if_flags(&self) -> i32 {
fn if_flags(&self) -> u32 {
// This function is unused by the Slirp code paths.
unimplemented!("not used by Slirp");
}

View file

@ -21,20 +21,20 @@ use base::AsRawDescriptor;
use base::Descriptor;
use base::Error as SysError;
use base::Event;
use base::EventExt;
use base::EventToken;
use base::EventWindows;
use base::RawDescriptor;
use base::Timer;
use base::WaitContext;
use base::WaitContextExt;
use data_model::DataInit;
use data_model::Le16;
use metrics::MetricEventType;
use metrics::PeriodicLogger;
#[cfg(any(feature = "slirp-ring-capture", feature = "slirp-debug"))]
use pcap_file::pcap::PcapWriter;
use smallvec::SmallVec;
use virtio_sys::virtio_net_hdr;
use virtio_sys::virtio_net_hdr_mrg_rxbuf;
use virtio_sys::virtio_net::virtio_net_hdr;
use virtio_sys::virtio_net::virtio_net_hdr_mrg_rxbuf;
use winapi::shared::minwindef::MAKEWORD;
use winapi::um::winnt::LONG;
use winapi::um::winnt::SHORT;
@ -61,6 +61,7 @@ use crate::slirp::context::Context;
use crate::slirp::context::PollEvents;
#[cfg(feature = "slirp-ring-capture")]
use crate::slirp::packet_ring_buffer::PacketRingBuffer;
use crate::slirp::SlirpError;
use crate::slirp::ETHERNET_FRAME_SIZE;
use crate::Error;
use crate::Result;
@ -113,7 +114,7 @@ impl CallbackHandler for Handler {
hdr_len: 0,
csum_start: 0,
csum_offset: 0,
gso_type: virtio_sys::VIRTIO_NET_HDR_GSO_NONE as u8,
gso_type: virtio_sys::virtio_net::VIRTIO_NET_HDR_GSO_NONE as u8,
},
num_buffers: 1,
};
@ -359,7 +360,7 @@ impl<'a> EventSelectedSocket<'a> {
)
};
if res == SOCKET_ERROR {
return Err(Error::SlirpIOPollError(last_wsa_error()));
return Err(Error::Slirp(SlirpError::SlirpIOPollError(last_wsa_error())));
}
Ok(EventSelectedSocket { socket, event })
}
@ -402,28 +403,32 @@ fn poll<'a>(
selected_sockets.push(EventSelectedSocket::new(*socket, socket_event_handle)?);
}
wait_ctx.clear().map_err(Error::SlirpPollError)?;
wait_ctx
.clear()
.map_err(|e| Error::Slirp(SlirpError::SlirpPollError(e)))?;
for (i, handle) in handles.iter().enumerate() {
match wait_ctx.add(*handle, Token::EventHandleReady(i)) {
Ok(v) => v,
Err(e) => {
return Err(Error::SlirpPollError(e));
return Err(Error::Slirp(SlirpError::SlirpPollError(e)));
}
}
}
match wait_ctx.add(socket_event_handle, Token::SocketReady) {
Ok(v) => v,
Err(e) => {
return Err(Error::SlirpPollError(e));
return Err(Error::Slirp(SlirpError::SlirpPollError(e)));
}
}
let events = if let Some(timeout) = timeout {
wait_ctx
.wait_timeout(timeout)
.map_err(Error::SlirpPollError)?
.map_err(|e| Error::Slirp(SlirpError::SlirpPollError(e)))?
} else {
wait_ctx.wait().map_err(Error::SlirpPollError)?
wait_ctx
.wait()
.map_err(|e| Error::Slirp(SlirpError::SlirpPollError(e)))?
};
let tokens: Vec<Token> = events
@ -446,7 +451,7 @@ fn poll<'a>(
let socket_results = if sockets.is_empty() {
Vec::new()
} else {
poll_sockets(sockets).map_err(Error::SlirpIOPollError)?
poll_sockets(sockets).map_err(|e| Error::Slirp(SlirpError::SlirpIOPollError(e)))?
};
Ok((handle_results, socket_results))
@ -466,7 +471,9 @@ impl WSAContext {
// Safe because ctx.data is guaranteed to exist, and we check the return code.
let err = unsafe { WSAStartup(MAKEWORD(2, 0), &mut ctx.data) };
if err != 0 {
Err(Error::WSAStartupError(SysError::new(err)))
Err(Error::Slirp(SlirpError::WSAStartupError(SysError::new(
err,
))))
} else {
Ok(ctx)
}
@ -506,8 +513,10 @@ pub fn start_slirp(
let shutdown_event_handle = shutdown_event.as_raw_descriptor();
// Stack data for the poll function.
let wait_ctx: WaitContext<Token> = WaitContext::new().map_err(Error::SlirpPollError)?;
let socket_event_handle = Event::new_auto_reset().map_err(Error::SlirpPollError)?;
let wait_ctx: WaitContext<Token> =
WaitContext::new().map_err(|e| Error::Slirp(SlirpError::SlirpPollError(e)))?;
let socket_event_handle =
Event::new_auto_reset().map_err(|e| Error::Slirp(SlirpError::SlirpPollError(e)))?;
'slirp: loop {
// Request the FDs that we should poll from Slirp. Slirp provides them to us by way of a

View file

@ -74,6 +74,7 @@ pub(crate) fn run_slirp(args: RunSlirpCommand) -> Result<()> {
let slirp_config = bootstrap_tube.recv::<SlirpStartupConfig>().unwrap();
#[cfg(feature = "sandbox")]
if let Some(mut target) = sandbox::TargetServices::get()
.exit_context(Exit::SandboxError, "sandbox operation failed")?
{

View file

@ -52,6 +52,7 @@ use base::error;
use base::RawDescriptor;
use libslirp_sys::*;
use crate::slirp::SlirpError;
use crate::Error;
use crate::Result;
@ -512,10 +513,10 @@ impl<H: CallbackHandler> Context<H> {
assert!(!slirp.is_null());
match ret.callback_handler.begin_read_from_guest() {
Err(e) if e.kind() == std::io::ErrorKind::BrokenPipe => {
return Err(Error::BrokenPipe(e));
return Err(Error::Slirp(SlirpError::BrokenPipe(e)));
}
Err(e) => {
return Err(Error::OverlappedError(e));
return Err(Error::Slirp(SlirpError::OverlappedError(e)));
}
_ => {}
}
@ -545,15 +546,15 @@ impl<H: CallbackHandler> Context<H> {
break;
}
Err(e) if e.kind() == std::io::ErrorKind::BrokenPipe => {
return Err(Error::BrokenPipe(e));
return Err(Error::Slirp(SlirpError::BrokenPipe(e)));
}
Err(_) => {
match self.callback_handler.begin_read_from_guest() {
Err(e) if e.kind() == std::io::ErrorKind::BrokenPipe => {
return Err(Error::BrokenPipe(e));
return Err(Error::Slirp(SlirpError::BrokenPipe(e)));
}
Err(e) => {
return Err(Error::OverlappedError(e));
return Err(Error::Slirp(SlirpError::OverlappedError(e)));
}
_ => {}
}
@ -562,10 +563,10 @@ impl<H: CallbackHandler> Context<H> {
}
match self.callback_handler.begin_read_from_guest() {
Err(e) if e.kind() == std::io::ErrorKind::BrokenPipe => {
return Err(Error::BrokenPipe(e));
return Err(Error::Slirp(SlirpError::BrokenPipe(e)));
}
Err(e) => {
return Err(Error::OverlappedError(e));
return Err(Error::Slirp(SlirpError::OverlappedError(e)));
}
_ => {}
}

View file

@ -22,7 +22,9 @@ sudo apt-get install --yes --no-install-recommends \
libdbus-1-dev \
libdrm-dev \
libepoxy-dev \
libglib2.0-dev \
libguestfs-tools \
libslirp-dev \
libssl-dev \
libswscale-dev \
libudev-dev \