third_party: clone libslirp-rs

libslirp-rs cloned from 19553209436ae7b9e036641f4013246111192d5c.

BUG=b:213151463
TEST=n/a

Change-Id: I560bb39ca96e68b3cf2654690bb3feb3af0325a6
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/3650466
Tested-by: kokoro <noreply+kokoro@google.com>
Reviewed-by: Keiichi Watanabe <keiichiw@chromium.org>
Commit-Queue: Noah Gold <nkgold@google.com>
This commit is contained in:
Noah Gold 2022-05-17 10:29:43 -07:00 committed by Chromeos LUCI
parent 39a8c88e79
commit 465479699b
17 changed files with 2195 additions and 0 deletions

43
third_party/libslirp-rs/Cargo.toml vendored Normal file
View file

@ -0,0 +1,43 @@
[package]
name = "libslirp"
version = "4.3.0"
authors = ["Marc-André Lureau <marcandre.lureau@redhat.com>"]
repository = "https://gitlab.freedesktop.org/slirp/libslirp-rs.git"
homepage = "https://gitlab.freedesktop.org/slirp/libslirp-rs"
documentation = "https://docs.rs/libslirp"
description = "High-level bindings & helper process for libslirp."
license-file = "LICENSE"
edition = "2018"
keywords = ["networking", "tcp", "ip", "qemu", "virtualization"]
categories = ["api-bindings", "command-line-utilities", "emulators", "network-programming"]
[features]
default = ["mio", "mio-extras", "ipnetwork", "structopt", "slab"]
helper = ["libc", "zbus", "nix", "libsystemd", "url", "lazy_static", "zvariant", "enumflags2"]
[dependencies]
libslirp-sys = { version = "4.2.0" }
ipnetwork = { version = "0.17", optional = true }
structopt = { version = "0.3.0", optional = true }
mio = { version = "0.6.19", optional = true }
mio-extras = { version = "2.0.5", optional = true }
slab = { version = "0.4.0", optional = true }
libc = { version = "0.2", optional = true }
nix = { version = "0.17", optional = true }
libsystemd = { version = "0.3", optional = true }
url = { version = "2.1", optional = true }
lazy_static = { version = "1.4", optional = true }
zbus = { version = "1.0", optional = true }
zvariant = { version = "2.0", optional = true }
enumflags2 = { version = "0.6.4", optional = true }
[dev-dependencies]
etherparse = "0.8.0"
[[bin]]
name = "libslirp-helper"
required-features = ["default", "helper"]
[[test]]
name = "test-ip"
required-features = ["default"]

21
third_party/libslirp-rs/LICENSE vendored Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019-2020 Red Hat, Inc
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

16
third_party/libslirp-rs/Makefile vendored Normal file
View file

@ -0,0 +1,16 @@
CARGO = cargo
CARGOFLAGS =
SLIRPHELPER = target/debug/libslirp-helper
.PHONY: $(SLIRPHELPER)
$(SLIRPHELPER):
$(CARGO) build --all-features $(CARGOFLAGS)
.PHONY: test
test: $(SLIRPHELPER)
SLIRPHELPER=$(SLIRPHELPER) \
PYTHONPATH=. \
PYTHONIOENCODING=utf-8 \
unshare -Ur \
dbus-run-session --config-file=tests/dbus.conf \
python3 -m unittest -v

1
third_party/libslirp-rs/README.md vendored Normal file
View file

@ -0,0 +1 @@
Fork init'ed from 19553209436ae7b9e036641f4013246111192d5c.

View file

@ -0,0 +1,319 @@
use std::convert::TryInto;
use std::error::Error;
use std::fs::File;
use std::io::{self, Cursor, Read, Write};
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
use std::os::unix::net::UnixDatagram;
use std::path::PathBuf;
use std::process;
use std::rc::Rc;
use enumflags2::BitFlags;
#[cfg(feature = "libsystemd")]
use libsystemd::daemon::{self, NotifyState};
use mio::unix::EventedFd;
use mio::*;
use nix::sched::{setns, CloneFlags};
use structopt::{clap::ArgGroup, StructOpt};
use zbus::dbus_interface;
#[macro_use]
extern crate lazy_static;
mod tun;
#[derive(Debug, StructOpt)]
#[structopt(
name = "libslirp-helper",
about = "slirp helper process",
rename_all = "kebab-case",
group = ArgGroup::with_name("verb").required(true)
)]
struct Opt {
/// Activate debug mode
#[structopt(long)]
debug: bool,
/// Print capabilities
#[structopt(long, group = "verb")]
print_capabilities: bool,
/// Exit with parent process
#[structopt(long)]
exit_with_parent: bool,
/// DBus bus address
#[structopt(long)]
dbus_address: Option<String>,
/// Helper instance ID
#[structopt(long, name = "id")]
dbus_id: Option<String>,
/// Incoming migration data from DBus
#[structopt(long)]
dbus_incoming: bool,
/// Unix datagram socket path
#[structopt(long, parse(from_os_str), group = "verb")]
socket_path: Option<PathBuf>,
/// Unix datagram socket file descriptor
#[structopt(long, group = "verb")]
fd: Option<i32>,
/// Incoming migration data
#[structopt(long)]
incoming_fd: Option<i32>,
/// Set DHCP NBP URL (ex: tftp://10.0.0.1/my-nbp)
#[structopt(long, name = "url")]
dhcp_nbp: Option<String>,
/// Path to network namespace to join.
#[structopt(long)]
netns: Option<PathBuf>,
/// Interface name, such as "tun0".
#[structopt(long, group = "verb")]
interface: Option<String>,
#[structopt(flatten)]
slirp: libslirp::Opt,
}
fn set_exit_with_parent() {
#[cfg(any(target_os = "linux", target_os = "android"))]
unsafe {
libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGTERM, 0, 0, 0);
}
}
const DBUS_TOKEN: Token = Token(10_000_000);
fn slirp_state_read<'a, R: Read>(
slirp: &libslirp::MioHandler<'a>,
reader: &mut R,
) -> Result<(), Box<dyn Error>> {
let mut buf = [0; 4];
reader.read(&mut buf)?;
let in_version = i32::from_be_bytes(buf);
if in_version > libslirp::state_version() {
return Err(format!(
"Incompatible migration data version: {} > {}",
in_version,
libslirp::state_version()
)
.into());
}
slirp.ctxt.state_read(in_version, reader)?;
slirp.register();
Ok(())
}
fn print_capabilities() -> Result<(), Box<dyn Error>> {
io::stdout().write_all(
r#"{
"type": "slirp-helper",
"features": [
"dbus-address",
"dhcp",
"exit-with-parent",
"migrate",
"tftp",
"ipv4",
"ipv6",
"netns",
"notify-socket",
"restrict"
]
}
"#
.as_bytes(),
)?;
Ok(())
}
fn set_netns(fd: RawFd) -> Result<(), nix::Error> {
setns(fd, CloneFlags::CLONE_NEWNET)
}
lazy_static! {
// XXX: when do we get async yet?
static ref POLL: Poll = Poll::new().unwrap();
}
struct Slirp1 {
slirp: Rc<libslirp::MioHandler<'static>>,
}
#[dbus_interface(name = "org.freedesktop.Slirp1.Helper")]
impl Slirp1 {
fn get_info(&self) -> String {
self.slirp.ctxt.connection_info().to_string()
}
}
struct VMState1 {
id: String,
slirp: Rc<libslirp::MioHandler<'static>>,
}
#[dbus_interface(name = "org.qemu.VMState1")]
impl VMState1 {
fn save(&self) -> zbus::fdo::Result<Vec<u8>> {
let mut data = libslirp::state_version().to_be_bytes().to_vec();
let mut state = self
.slirp
.ctxt
.state_get()
.map_err(|e| zbus::fdo::Error::Failed(format!("Failed to save: {}", e)))?;
data.append(&mut state);
Ok(data)
}
fn load(&self, data: &[u8]) -> zbus::fdo::Result<()> {
let mut data = Cursor::new(data);
Ok(slirp_state_read(&self.slirp, &mut data)
.map_err(|e| zbus::fdo::Error::Failed(format!("Failed to load: {}", e)))?)
}
#[dbus_interface(property)]
fn id(&self) -> &str {
&self.id
}
}
fn main() -> Result<(), Box<dyn Error>> {
let m = Opt::clap().get_matches();
let mut opt = Opt::from_clap(&m);
if opt.debug {
dbg!(&opt);
}
if opt.print_capabilities {
return print_capabilities();
}
if m.occurrences_of("dhcp-start") == 0 {
let dhcp_start = opt.slirp.ipv4.net.nth(15).expect("Invalid --net");
opt.slirp.ipv4.dhcp_start = dhcp_start;
}
if let Some(url) = &opt.dhcp_nbp {
let url = url::Url::parse(url)?;
if url.scheme() != "tftp" {
return Err("Invalid NBP URL".into());
}
opt.slirp.tftp.name = Some(url.host_str().unwrap().to_string());
opt.slirp.tftp.bootfile = Some(url.path().to_string());
}
let mut main_netns = None;
if let Some(netns) = &opt.netns {
main_netns = Some(File::open("/proc/self/ns/net")?);
let netns = File::open(netns)?;
set_netns(netns.as_raw_fd())?;
opt.interface.get_or_insert("tun0".to_string());
}
let stream = match &opt {
Opt { fd: Some(fd), .. } => unsafe { UnixDatagram::from_raw_fd(*fd) },
Opt {
socket_path: Some(path),
..
} => UnixDatagram::bind(path)?,
Opt {
interface: Some(tun),
..
} => tun::open(tun)?,
_ => return Err("Missing a socket argument".into()),
};
if let Some(netns) = main_netns {
set_netns(netns.as_raw_fd())?;
}
if opt.exit_with_parent {
set_exit_with_parent();
}
let slirp = Rc::new(libslirp::MioHandler::new(&opt.slirp, &POLL, stream));
let dbus = if let Some(dbus_addr) = opt.dbus_address {
if opt.dbus_id.is_none() {
return Err("You must specify an id with DBus".into());
}
let c = zbus::Connection::new_for_address(&dbus_addr, true)?;
zbus::fdo::DBusProxy::new(&c)?.request_name(
&format!("org.freedesktop.Slirp1_{}", process::id()),
BitFlags::empty(),
)?;
zbus::fdo::DBusProxy::new(&c)?.request_name("org.qemu.VMState1", BitFlags::empty())?;
let dbus_fd = c.as_raw_fd();
POLL.register(
&EventedFd(&dbus_fd),
DBUS_TOKEN,
Ready::readable(),
PollOpt::level(),
)?;
Some(c)
} else {
None
};
let mut s = if let Some(c) = &dbus {
let mut s = zbus::ObjectServer::new(c);
s.at(
&"/org/freedesktop/Slirp1/Helper".try_into()?,
Slirp1 {
slirp: slirp.clone(),
},
)?;
s.at(
&"/org/qemu/VMState1".try_into()?,
VMState1 {
id: opt.dbus_id.unwrap(),
slirp: slirp.clone(),
},
)?;
Some(s)
} else {
None
};
if opt.dbus_incoming && opt.incoming_fd.is_some() {
return Err("Invalid multiple incoming paths.".into());
}
let mut events = Events::with_capacity(1024);
let mut duration = None;
if let Some(fd) = opt.incoming_fd {
let mut f = unsafe { File::from_raw_fd(fd) };
slirp_state_read(&slirp, &mut f)?;
} else if !opt.dbus_incoming {
slirp.register();
}
#[cfg(feature = "libsystemd")]
daemon::notify(true, &[NotifyState::Ready])?;
loop {
if opt.debug {
dbg!(duration);
}
POLL.poll(&mut events, duration)?;
duration = slirp.dispatch(&events)?;
if let Some(dbus) = &dbus {
for event in &events {
match event.token() {
DBUS_TOKEN => {
let m = dbus.receive_message()?;
if let Err(e) = s.as_mut().unwrap().dispatch_message(&m) {
eprintln!("{}", e);
}
}
_ => {
continue;
}
}
}
}
}
}

View file

@ -0,0 +1,75 @@
use nix::fcntl::OFlag;
use nix::ioctl_write_ptr;
use nix::sys::stat::Mode;
use std::error::Error;
use std::os::raw::c_short;
use std::os::unix::io::FromRawFd;
use std::os::unix::net::UnixDatagram;
//pub const IFF_TUN: c_short = 0x0001;
pub const IFF_TAP: c_short = 0x0002;
pub const IFF_NO_PI: c_short = 0x1000;
const INTERFACE_NAME_SIZE: usize = 16;
const INTERFACE_REQUEST_UNION_SIZE: usize = 24;
const TUN_MAGIC: u8 = b'T';
const TUN_SETIFF: u8 = 202;
#[repr(C)]
#[derive(Default)]
pub struct InterfaceRequest {
pub interface_name: [u8; INTERFACE_NAME_SIZE],
pub union: InterfaceRequestUnion,
}
impl InterfaceRequest {
pub fn with_interface_name(name: &str) -> Result<Self, Box<dyn Error>> {
let mut interface_request: Self = Default::default();
interface_request.set_interface_name(name)?;
Ok(interface_request)
}
pub fn set_interface_name(&mut self, name: &str) -> Result<(), Box<dyn Error>> {
let name_len = name.len();
let mut name = Vec::from(name);
if name_len < INTERFACE_NAME_SIZE {
name.resize(INTERFACE_NAME_SIZE, 0);
} else {
return Err("interface name too long".into());
}
assert_eq!(name.len(), INTERFACE_NAME_SIZE);
self.interface_name.clone_from_slice(&name);
Ok(())
}
}
#[repr(C)]
pub union InterfaceRequestUnion {
pub data: [u8; INTERFACE_REQUEST_UNION_SIZE],
pub flags: c_short,
}
impl Default for InterfaceRequestUnion {
fn default() -> Self {
InterfaceRequestUnion {
data: Default::default(),
}
}
}
ioctl_write_ptr!(tun_set_iff, TUN_MAGIC, TUN_SETIFF, libc::c_int);
pub fn open(name: &str) -> Result<UnixDatagram, Box<dyn Error>> {
let flags = IFF_TAP | IFF_NO_PI;
let fd = nix::fcntl::open("/dev/net/tun", OFlag::O_RDWR, Mode::empty())?;
let mut ifr = InterfaceRequest::with_interface_name(name)?;
ifr.union.flags = flags;
unsafe { tun_set_iff(fd, &mut ifr as *mut InterfaceRequest as *mut i32) }?;
Ok(unsafe { UnixDatagram::from_raw_fd(fd) })
}

575
third_party/libslirp-rs/src/context.rs vendored Normal file
View file

@ -0,0 +1,575 @@
use libslirp_sys::*;
use std::io::{Read, Write};
#[cfg(feature = "structopt")]
use crate::Opt;
use std::cell::RefCell;
use std::ffi::{CStr, CString};
use std::io;
use std::net::{Ipv4Addr, Ipv6Addr};
use std::os::raw::{c_char, c_int, c_void};
use std::os::unix::io::RawFd;
use std::path::PathBuf;
use std::rc::Rc;
use std::{fmt, mem, ops, slice, str};
pub struct Context<H> {
pub inner: Box<Inner<H>>,
}
pub struct Inner<H> {
pub context: *mut Slirp,
callbacks: SlirpCb,
handler: H,
}
impl<H> Drop for Context<H> {
fn drop(&mut self) {
unsafe {
slirp_cleanup(self.inner.context);
}
}
}
//unsafe impl<H: Send> Send for Inner<H> {}
pub trait Handler {
type Timer;
fn clock_get_ns(&mut self) -> i64;
fn send_packet(&mut self, buf: &[u8]) -> io::Result<usize>;
fn register_poll_fd(&mut self, fd: RawFd);
fn unregister_poll_fd(&mut self, fd: RawFd);
fn guest_error(&mut self, msg: &str);
fn notify(&mut self);
fn timer_new(&mut self, func: Box<dyn FnMut()>) -> Box<Self::Timer>;
fn timer_mod(&mut self, timer: &mut Box<Self::Timer>, expire_time: i64);
fn timer_free(&mut self, timer: Box<Self::Timer>);
}
impl<T: Handler> Handler for Rc<RefCell<T>> {
type Timer = T::Timer;
fn clock_get_ns(&mut self) -> i64 {
self.borrow_mut().clock_get_ns()
}
fn send_packet(&mut self, buf: &[u8]) -> io::Result<usize> {
self.borrow_mut().send_packet(buf)
}
fn register_poll_fd(&mut self, fd: RawFd) {
self.borrow_mut().register_poll_fd(fd);
}
fn unregister_poll_fd(&mut self, fd: RawFd) {
self.borrow_mut().unregister_poll_fd(fd);
}
fn guest_error(&mut self, msg: &str) {
self.borrow_mut().guest_error(msg);
}
fn notify(&mut self) {
self.borrow_mut().notify();
}
fn timer_new(&mut self, func: Box<dyn FnMut()>) -> Box<Self::Timer> {
self.borrow_mut().timer_new(func)
}
fn timer_mod(&mut self, timer: &mut Box<Self::Timer>, expire_time: i64) {
self.borrow_mut().timer_mod(timer, expire_time)
}
fn timer_free(&mut self, timer: Box<Self::Timer>) {
self.borrow_mut().timer_free(timer)
}
}
extern "C" fn write_handler_cl(buf: *const c_void, len: usize, opaque: *mut c_void) -> isize {
let closure: &mut &mut dyn FnMut(&[u8]) -> isize = unsafe { mem::transmute(opaque) };
let slice = unsafe { slice::from_raw_parts(buf as *const u8, len) };
closure(slice)
}
extern "C" fn read_handler_cl(buf: *mut c_void, len: usize, opaque: *mut c_void) -> isize {
let closure: &mut &mut dyn FnMut(&mut [u8]) -> isize = unsafe { mem::transmute(opaque) };
let slice = unsafe { slice::from_raw_parts_mut(buf as *mut u8, len) };
closure(slice)
}
#[derive(Copy, PartialEq, Eq, Clone, PartialOrd, Ord)]
pub struct PollEvents(usize);
impl PollEvents {
pub fn empty() -> Self {
PollEvents(0)
}
pub fn poll_in() -> Self {
PollEvents(SLIRP_POLL_IN as usize)
}
pub fn poll_out() -> Self {
PollEvents(SLIRP_POLL_OUT as usize)
}
pub fn poll_pri() -> Self {
PollEvents(SLIRP_POLL_PRI as usize)
}
pub fn poll_err() -> Self {
PollEvents(SLIRP_POLL_ERR as usize)
}
pub fn poll_hup() -> Self {
PollEvents(SLIRP_POLL_HUP as usize)
}
pub fn contains<T: Into<Self>>(&self, other: T) -> bool {
let other = other.into();
(*self & other) == other
}
pub fn is_empty(&self) -> bool {
self.0 == 0
}
pub fn has_in(&self) -> bool {
self.contains(PollEvents::poll_in())
}
pub fn has_out(&self) -> bool {
self.contains(PollEvents::poll_out())
}
pub fn has_pri(&self) -> bool {
self.contains(PollEvents::poll_pri())
}
pub fn has_err(&self) -> bool {
self.contains(PollEvents::poll_err())
}
pub fn has_hup(&self) -> bool {
self.contains(PollEvents::poll_hup())
}
}
impl<T: Into<PollEvents>> ops::BitAnd<T> for PollEvents {
type Output = PollEvents;
fn bitand(self, other: T) -> PollEvents {
PollEvents(self.0 & other.into().0)
}
}
impl<T: Into<PollEvents>> ops::BitOr<T> for PollEvents {
type Output = PollEvents;
fn bitor(self, other: T) -> PollEvents {
PollEvents(self.0 | other.into().0)
}
}
impl<T: Into<PollEvents>> ops::BitOrAssign<T> for PollEvents {
fn bitor_assign(&mut self, other: T) {
self.0 |= other.into().0;
}
}
impl fmt::Debug for PollEvents {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
let mut one = false;
let flags = [
(PollEvents(SLIRP_POLL_IN as usize), "IN"),
(PollEvents(SLIRP_POLL_OUT as usize), "OUT"),
(PollEvents(SLIRP_POLL_PRI as usize), "PRI"),
(PollEvents(SLIRP_POLL_ERR as usize), "ERR"),
(PollEvents(SLIRP_POLL_HUP as usize), "HUP"),
];
for &(flag, msg) in &flags {
if self.contains(flag) {
if one {
write!(fmt, " | ")?
}
write!(fmt, "{}", msg)?;
one = true
}
}
if !one {
fmt.write_str("(empty)")?;
}
Ok(())
}
}
extern "C" fn add_poll_handler_cl(fd: c_int, events: c_int, opaque: *mut c_void) -> c_int {
let closure: &mut &mut dyn FnMut(RawFd, PollEvents) -> i32 = unsafe { mem::transmute(opaque) };
closure(fd, PollEvents(events as usize))
}
extern "C" fn get_revents_handler_cl(idx: c_int, opaque: *mut c_void) -> c_int {
let closure: &mut &mut dyn FnMut(i32) -> PollEvents = unsafe { mem::transmute(opaque) };
closure(idx).0 as c_int
}
extern "C" fn send_packet_handler<H: Handler>(
buf: *const c_void,
len: usize,
opaque: *mut c_void,
) -> isize {
let slice = unsafe { slice::from_raw_parts(buf as *const u8, len) };
let res = unsafe { (*(opaque as *mut Inner<H>)).handler.send_packet(slice) };
if res.is_ok() {
res.unwrap() as isize
} else {
eprintln!("send_packet error: {}", res.unwrap_err());
-1
}
}
extern "C" fn guest_error_handler<H: Handler>(msg: *const c_char, opaque: *mut c_void) {
let msg = str::from_utf8(unsafe { CStr::from_ptr(msg) }.to_bytes()).unwrap_or("");
unsafe { (*(opaque as *mut Inner<H>)).handler.guest_error(msg) }
}
extern "C" fn clock_get_ns_handler<H: Handler>(opaque: *mut c_void) -> i64 {
unsafe { (*(opaque as *mut Inner<H>)).handler.clock_get_ns() }
}
extern "C" fn timer_new_handler<H: Handler>(
cb: SlirpTimerCb,
cb_opaque: *mut c_void,
opaque: *mut c_void,
) -> *mut c_void {
let func = Box::new(move || {
if let Some(cb) = cb {
unsafe {
cb(cb_opaque);
}
}
});
let timer = unsafe { (*(opaque as *mut Inner<H>)).handler.timer_new(func) };
Box::into_raw(timer) as *mut c_void
}
extern "C" fn timer_free_handler<H: Handler>(timer: *mut c_void, opaque: *mut c_void) {
unsafe {
let timer = Box::from_raw(timer as *mut H::Timer);
(*(opaque as *mut Inner<H>)).handler.timer_free(timer);
}
}
extern "C" fn timer_mod_handler<H: Handler>(
timer: *mut c_void,
expire_time: i64,
opaque: *mut c_void,
) {
unsafe {
let mut timer = Box::from_raw(timer as *mut H::Timer);
(*(opaque as *mut Inner<H>))
.handler
.timer_mod(&mut timer, expire_time);
Box::into_raw(timer);
}
}
extern "C" fn register_poll_fd_handler<H: Handler>(fd: c_int, opaque: *mut c_void) {
unsafe { (*(opaque as *mut Inner<H>)).handler.register_poll_fd(fd) }
}
extern "C" fn unregister_poll_fd_handler<H: Handler>(fd: c_int, opaque: *mut c_void) {
unsafe { (*(opaque as *mut Inner<H>)).handler.unregister_poll_fd(fd) }
}
extern "C" fn notify_handler<H: Handler>(opaque: *mut c_void) {
unsafe { (*(opaque as *mut Inner<H>)).handler.notify() }
}
impl<H: Handler> Context<H> {
#[cfg(feature = "structopt")]
pub fn new_with_opt(opt: &Opt, handler: H) -> Self {
let cstr_vdns: Vec<_> = opt
.dns_suffixes
.iter()
.map(|arg| CString::new(arg.clone().into_bytes()).unwrap())
.collect();
let mut p_vdns: Vec<_> = cstr_vdns.iter().map(|arg| arg.as_ptr()).collect();
p_vdns.push(std::ptr::null());
let as_ptr = |p: &Option<CString>| p.as_ref().map_or(std::ptr::null(), |s| s.as_ptr());
let tftp_path = opt
.tftp
.root
.as_ref()
.and_then(|s| CString::new(s.to_string_lossy().into_owned()).ok());
let vhostname = opt.hostname.clone().and_then(|s| CString::new(s).ok());
let tftp_server_name = opt.tftp.name.clone().and_then(|s| CString::new(s).ok());
let tftp_bootfile = opt.tftp.bootfile.clone().and_then(|s| CString::new(s).ok());
let vdomainname = opt.domainname.clone().and_then(|s| CString::new(s).ok());
let config = SlirpConfig {
version: 2,
restricted: opt.restrict as i32,
in_enabled: !opt.ipv4.disable,
vnetwork: opt.ipv4.net.ip().into(),
vnetmask: opt.ipv4.net.mask().into(),
vhost: opt.ipv4.host.into(),
in6_enabled: !opt.ipv6.disable,
vprefix_addr6: opt.ipv6.net6.ip().into(),
vprefix_len: opt.ipv6.net6.prefix(),
vhost6: opt.ipv6.host.into(),
vhostname: as_ptr(&vhostname),
tftp_server_name: as_ptr(&tftp_server_name),
tftp_path: as_ptr(&tftp_path),
bootfile: as_ptr(&tftp_bootfile),
vdhcp_start: opt.ipv4.dhcp_start.into(),
vnameserver: opt.ipv4.dns.into(),
vnameserver6: opt.ipv6.dns.into(),
vdnssearch: p_vdns.as_ptr() as *mut *const _,
vdomainname: as_ptr(&vdomainname),
if_mtu: opt.mtu,
if_mru: opt.mtu,
disable_host_loopback: opt.disable_host_loopback,
enable_emu: false,
outbound_addr: std::ptr::null(),
outbound_addr6: std::ptr::null(),
disable_dns: false,
};
Self::new_with_config(&config, handler)
}
pub fn new_with_config(config: &SlirpConfig, handler: H) -> Self {
let mut ret = Context {
inner: Box::new(Inner {
context: std::ptr::null_mut(),
callbacks: SlirpCb {
send_packet: Some(send_packet_handler::<H>),
guest_error: Some(guest_error_handler::<H>),
clock_get_ns: Some(clock_get_ns_handler::<H>),
timer_new: Some(timer_new_handler::<H>),
timer_free: Some(timer_free_handler::<H>),
timer_mod: Some(timer_mod_handler::<H>),
register_poll_fd: Some(register_poll_fd_handler::<H>),
unregister_poll_fd: Some(unregister_poll_fd_handler::<H>),
notify: Some(notify_handler::<H>),
},
handler,
}),
};
let ptr = &*ret.inner as *const _ as *mut _;
ret.inner.context = unsafe { slirp_new(config as *const _, &ret.inner.callbacks, ptr) };
assert!(!ret.inner.context.is_null());
ret
}
pub fn new(
restricted: bool,
ipv4_enabled: bool,
vnetwork: Ipv4Addr,
vnetmask: Ipv4Addr,
vhost: Ipv4Addr,
ipv6_enabled: bool,
vprefix_addr6: Ipv6Addr,
vprefix_len: u8,
vhost6: Ipv6Addr,
vhostname: Option<String>,
tftp_server_name: Option<String>,
tftp_path: Option<PathBuf>,
tftp_bootfile: Option<String>,
vdhcp_start: Ipv4Addr,
vnameserver: Ipv4Addr,
vnameserver6: Ipv6Addr,
vdnssearch: Vec<String>,
vdomainname: Option<String>,
handler: H,
) -> Self {
let mut ret = Context {
inner: Box::new(Inner {
context: std::ptr::null_mut(),
callbacks: SlirpCb {
send_packet: Some(send_packet_handler::<H>),
guest_error: Some(guest_error_handler::<H>),
clock_get_ns: Some(clock_get_ns_handler::<H>),
timer_new: Some(timer_new_handler::<H>),
timer_free: Some(timer_free_handler::<H>),
timer_mod: Some(timer_mod_handler::<H>),
register_poll_fd: Some(register_poll_fd_handler::<H>),
unregister_poll_fd: Some(unregister_poll_fd_handler::<H>),
notify: Some(notify_handler::<H>),
},
handler,
}),
};
let cstr_vdns: Vec<_> = vdnssearch
.iter()
.map(|arg| CString::new(arg.clone().into_bytes()).unwrap())
.collect();
let mut p_vdns: Vec<_> = cstr_vdns.iter().map(|arg| arg.as_ptr()).collect();
p_vdns.push(std::ptr::null());
let as_ptr = |p: &Option<CString>| p.as_ref().map_or(std::ptr::null(), |s| s.as_ptr());
let tftp_path = tftp_path.and_then(|s| CString::new(s.to_string_lossy().into_owned()).ok());
let vhostname = vhostname.and_then(|s| CString::new(s).ok());
let tftp_server_name = tftp_server_name.and_then(|s| CString::new(s).ok());
let tftp_bootfile = tftp_bootfile.and_then(|s| CString::new(s).ok());
let vdomainname = vdomainname.and_then(|s| CString::new(s).ok());
let ptr = &*ret.inner as *const _ as *mut _;
ret.inner.context = unsafe {
slirp_init(
restricted as i32,
ipv4_enabled,
vnetwork.into(),
vnetmask.into(),
vhost.into(),
ipv6_enabled,
vprefix_addr6.into(),
vprefix_len,
vhost6.into(),
as_ptr(&vhostname),
as_ptr(&tftp_server_name),
as_ptr(&tftp_path),
as_ptr(&tftp_bootfile),
vdhcp_start.into(),
vnameserver.into(),
vnameserver6.into(),
p_vdns.as_ptr() as *mut *const _,
as_ptr(&vdomainname),
&ret.inner.callbacks,
ptr,
)
};
assert!(!ret.inner.context.is_null());
ret
}
pub fn input(&self, buf: &[u8]) {
unsafe {
slirp_input(self.inner.context, buf.as_ptr(), buf.len() as i32);
}
}
pub fn connection_info(&self) -> &str {
str::from_utf8(
unsafe { CStr::from_ptr(slirp_connection_info(self.inner.context)) }.to_bytes(),
)
.unwrap_or("")
}
pub fn pollfds_fill<F>(&self, timeout: &mut u32, mut add_poll_cb: F)
where
F: FnMut(RawFd, PollEvents) -> i32,
{
let mut cb: &mut dyn FnMut(RawFd, PollEvents) -> i32 = &mut add_poll_cb;
let cb = &mut cb;
unsafe {
slirp_pollfds_fill(
self.inner.context,
timeout,
Some(add_poll_handler_cl),
cb as *mut _ as *mut c_void,
);
}
}
pub fn pollfds_poll<F>(&self, error: bool, mut get_revents_cb: F)
where
F: FnMut(i32) -> PollEvents,
{
let mut cb: &mut dyn FnMut(i32) -> PollEvents = &mut get_revents_cb;
let cb = &mut cb;
unsafe {
slirp_pollfds_poll(
self.inner.context,
error as i32,
Some(get_revents_handler_cl),
cb as *mut _ as *mut c_void,
);
}
}
pub fn state_save<F>(&self, mut write_cb: F)
where
F: FnMut(&[u8]) -> isize,
{
let mut cb: &mut dyn FnMut(&[u8]) -> isize = &mut write_cb;
let cb = &mut cb;
unsafe {
slirp_state_save(
self.inner.context,
Some(write_handler_cl),
cb as *mut _ as *mut c_void,
);
}
}
pub fn state_write<F: Write>(&self, writer: &mut F) -> std::io::Result<usize> {
let mut res = Ok(0);
self.state_save(|buf| match writer.write(buf) {
Ok(n) => {
res = Ok(*res.as_ref().unwrap() + n);
n as isize
}
Err(e) => {
res = Err(e);
-1
}
});
res
}
pub fn state_get(&self) -> std::io::Result<Vec<u8>> {
let mut state = vec![];
self.state_write(&mut state)?;
Ok(state)
}
pub fn state_load<F>(&self, version_id: i32, mut read_cb: F)
where
F: FnMut(&mut [u8]) -> isize,
{
let mut cb: &mut dyn FnMut(&mut [u8]) -> isize = &mut read_cb;
let cb = &mut cb;
unsafe {
slirp_state_load(
self.inner.context,
version_id,
Some(read_handler_cl),
cb as *mut _ as *mut c_void,
);
}
}
pub fn state_read<R: Read>(&self, version_id: i32, reader: &mut R) -> std::io::Result<usize> {
let mut res = Ok(0);
self.state_load(version_id, |buf| match reader.read(buf) {
Ok(n) => {
res = Ok(*res.as_ref().unwrap() + n);
n as isize
}
Err(e) => {
res = Err(e);
-1
}
});
res
}
}

13
third_party/libslirp-rs/src/lib.rs vendored Normal file
View file

@ -0,0 +1,13 @@
pub mod context;
#[cfg(all(feature = "structopt", feature = "mio"))]
pub mod mio;
#[cfg(feature = "structopt")]
pub mod opt;
pub mod version;
pub use self::context::{Context, Handler, PollEvents};
#[cfg(all(feature = "structopt", feature = "mio"))]
pub use self::mio::*;
#[cfg(feature = "structopt")]
pub use self::opt::*;
pub use self::version::{state_version, version};

312
third_party/libslirp-rs/src/mio.rs vendored Normal file
View file

@ -0,0 +1,312 @@
use crate::context::{Context, Handler, PollEvents};
use crate::opt::Opt;
use mio::unix::{EventedFd, UnixReady};
use mio::*;
use mio_extras::timer::Timer as MioTimer;
use slab::Slab;
use std::cell::RefCell;
use std::fmt;
use std::fs::File;
use std::io;
use std::io::prelude::*;
use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
use std::os::unix::net::UnixDatagram;
use std::rc::Rc;
use std::time::{Duration, Instant};
struct MyTimer {
func: Rc<RefCell<Box<dyn FnMut()>>>,
timer: MioTimer<()>,
}
impl fmt::Debug for MyTimer {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "MyTimer {{}}")
}
}
#[derive(Debug)]
struct MyFd {
fd: RawFd,
events: PollEvents,
revents: Option<PollEvents>,
}
impl MyFd {
fn new(fd: RawFd, events: PollEvents) -> Self {
Self {
events,
fd,
revents: None,
}
}
}
#[derive(Debug)]
enum MyToken {
Fd(MyFd),
Timer(MyTimer),
}
pub struct Inner<'a> {
start: Instant,
stream: UnixDatagram,
poll: &'a Poll,
tokens: Slab<MyToken>,
}
pub struct MioHandler<'a> {
inner: Rc<RefCell<Inner<'a>>>,
pub ctxt: Context<Rc<RefCell<Inner<'a>>>>,
}
impl<'a> Handler for Inner<'a> {
type Timer = usize;
fn clock_get_ns(&mut self) -> i64 {
const NANOS_PER_SEC: u64 = 1_000_000_000;
let d = self.start.elapsed();
(d.as_secs() * NANOS_PER_SEC + d.subsec_nanos() as u64) as i64
}
fn timer_new(&mut self, func: Box<dyn FnMut()>) -> Box<Self::Timer> {
let timer = MioTimer::default();
let tok = self.tokens.insert(MyToken::Timer(MyTimer {
func: Rc::new(RefCell::new(func)),
timer,
}));
let timer = match &self.tokens[tok] {
MyToken::Timer(MyTimer { timer: t, .. }) => t,
_ => panic!(),
};
self.poll
.register(timer, Token(tok), Ready::readable(), PollOpt::edge())
.unwrap();
Box::new(tok)
}
fn timer_mod(&mut self, timer: &mut Box<Self::Timer>, expire_time: i64) {
let when = Duration::from_millis(expire_time as u64);
let timer = match &mut self.tokens[**timer] {
MyToken::Timer(MyTimer { timer: t, .. }) => t,
_ => panic!(),
};
timer.set_timeout(when, ());
}
fn timer_free(&mut self, timer: Box<Self::Timer>) {
let t = match &self.tokens[*timer] {
MyToken::Timer(MyTimer { timer: t, .. }) => t,
_ => panic!(),
};
self.poll.deregister(t).unwrap();
self.tokens.remove(*timer);
drop(timer); // for clarity
}
fn send_packet(&mut self, buf: &[u8]) -> io::Result<usize> {
self.stream.send(buf)
}
fn guest_error(&mut self, msg: &str) {
eprintln!("guest error: {}", msg);
}
fn register_poll_fd(&mut self, _fd: RawFd) {}
fn unregister_poll_fd(&mut self, _fd: RawFd) {}
fn notify(&mut self) {}
}
fn to_mio_ready(events: PollEvents) -> mio::Ready {
let mut ready = UnixReady::from(Ready::empty());
if events.has_in() {
ready.insert(Ready::readable());
}
if events.has_out() {
ready.insert(Ready::writable());
}
if events.has_hup() {
ready.insert(UnixReady::hup());
}
if events.has_err() {
ready.insert(UnixReady::error());
}
if events.has_pri() {
ready.insert(UnixReady::priority());
}
Ready::from(ready)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn to_mio_ready_test() {
assert_eq!(to_mio_ready(PollEvents::empty()), Ready::empty());
assert_eq!(to_mio_ready(PollEvents::poll_in()), Ready::readable());
assert_eq!(to_mio_ready(PollEvents::poll_out()), Ready::writable());
assert_eq!(
to_mio_ready(PollEvents::poll_err()),
Ready::from(UnixReady::error())
);
assert_eq!(
to_mio_ready(PollEvents::poll_pri()),
Ready::from(UnixReady::priority())
);
assert_eq!(
to_mio_ready(PollEvents::poll_hup()),
Ready::from(UnixReady::hup())
);
let ev = PollEvents::poll_in() | PollEvents::poll_pri();
let ev = to_mio_ready(ev);
assert!(ev.is_readable());
// bug, see https://github.com/carllerche/mio/pull/897
assert!(!ev.is_writable());
}
}
fn from_mio_ready(ready: mio::Ready) -> PollEvents {
let mut events = PollEvents::empty();
let ready = UnixReady::from(ready);
if ready.is_readable() {
events |= PollEvents::poll_in();
}
if ready.is_writable() {
events |= PollEvents::poll_out();
}
if ready.is_hup() {
events |= PollEvents::poll_hup();
}
if ready.is_error() {
events |= PollEvents::poll_err();
}
if ready.is_priority() {
events |= PollEvents::poll_pri();
}
events
}
const SOCKET: Token = Token(1_000_000);
impl<'a> MioHandler<'a> {
pub fn new(opt: &Opt, poll: &'a Poll, stream: UnixDatagram) -> Self {
let inner = Rc::new(RefCell::new(Inner {
start: Instant::now(),
poll,
stream,
tokens: Slab::with_capacity(1024),
}));
Self {
inner: inner.clone(),
ctxt: Context::new_with_opt(opt, inner.clone()),
}
}
pub fn register(&self) {
let inner = self.inner.borrow();
let fd = inner.stream.as_raw_fd();
inner
.poll
.register(&EventedFd(&fd), SOCKET, Ready::readable(), PollOpt::level())
.unwrap();
}
pub fn dispatch(&self, events: &Events) -> io::Result<Option<Duration>> {
let inner = self.inner.clone();
for (_, token) in inner.borrow().tokens.iter() {
if let MyToken::Fd(fd) = token {
let ev = EventedFd(&fd.fd);
inner.borrow().poll.deregister(&ev)?;
}
}
for event in events {
match event.token() {
SOCKET => {
const NET_BUFSIZE: usize = 4096 + 65536; // defined by Emu
let mut buffer = [0; NET_BUFSIZE];
let fd = self.inner.borrow_mut().stream.as_raw_fd();
let mut f = unsafe { File::from_raw_fd(fd) };
let len = f.read(&mut buffer[..]).unwrap();
f.into_raw_fd();
self.ctxt.input(&buffer[..len]);
}
i if i.0 < inner.borrow().tokens.capacity() => {
let events = from_mio_ready(event.readiness());
let mut inner = inner.borrow_mut();
let token = &mut inner.tokens[i.0];
match token {
MyToken::Fd(fd) => {
// libslirp doesn't like getting more events...
fd.revents = Some(events & fd.events);
}
MyToken::Timer(MyTimer { func, .. }) => {
let func = func.clone();
drop(inner);
let func = &mut **func.borrow_mut();
func();
}
}
}
_ => continue,
}
}
self.ctxt.pollfds_poll(false, |idx| {
let token = &mut inner.borrow_mut().tokens[idx as usize];
if let MyToken::Fd(fd) = token {
fd.revents.take().unwrap_or(PollEvents::empty())
} else {
panic!();
}
});
inner
.borrow_mut()
.tokens
.retain(|_, v| if let MyToken::Fd(_) = v { false } else { true });
let mut timeout = u32::MAX;
self.ctxt.pollfds_fill(&mut timeout, |fd, events| {
let ready = to_mio_ready(events);
let tok = inner
.borrow_mut()
.tokens
.insert(MyToken::Fd(MyFd::new(fd, events)));
let ev = EventedFd(&fd);
inner
.borrow()
.poll
.register(&ev, Token(tok), ready, PollOpt::level())
.unwrap();
tok as i32
});
let duration = if timeout == u32::MAX {
None
} else {
Some(Duration::from_millis(timeout as u64))
};
Ok(duration)
}
}

88
third_party/libslirp-rs/src/opt.rs vendored Normal file
View file

@ -0,0 +1,88 @@
use ipnetwork::{Ipv4Network, Ipv6Network};
use std::net::{Ipv4Addr, Ipv6Addr};
use std::path::PathBuf;
use structopt::StructOpt;
#[derive(Debug, StructOpt)]
pub struct OptIpv4 {
/// Whether to disable IPv4
#[structopt(name = "disable-ipv4", long = "disable-ipv4")]
pub disable: bool,
/// IPv4 network CIDR
#[structopt(name = "net", long = "net", default_value = "10.0.2.0/24")]
pub net: Ipv4Network,
/// Guest-visible address of the host
#[structopt(long, default_value = "10.0.2.2")]
pub host: Ipv4Addr,
/// The first of the 16 IPs the built-in DHCP server can assign
#[structopt(
name = "dhcp-start",
long = "dhcp-start",
short,
default_value = "10.0.2.15"
)]
pub dhcp_start: Ipv4Addr,
/// Guest-visible address of the virtual nameserver
#[structopt(long = "dhcp-dns", default_value = "10.0.2.3")]
pub dns: Ipv4Addr,
}
#[derive(Debug, StructOpt)]
pub struct OptIpv6 {
/// Whether to disable IPv6
#[structopt(name = "disable-ipv6", long = "disable-ipv6")]
pub disable: bool,
/// IPv6 network CIDR
#[structopt(name = "net6", long = "net6", default_value = "fec0::/64")]
pub net6: Ipv6Network,
/// Guest-visible IPv6 address of the host
#[structopt(name = "host-ipv6", long = "host-ipv6", default_value = "fec0::2")]
pub host: Ipv6Addr,
/// Guest-visible address of the virtual nameserver
#[structopt(name = "dns-ipv6", long, default_value = "fec0::3")]
pub dns: Ipv6Addr,
}
#[derive(Debug, StructOpt)]
pub struct OptTftp {
/// RFC2132 "TFTP server name" string
#[structopt(name = "name", long = "tftp-name")]
pub name: Option<String>,
/// root directory of the built-in TFTP server
#[structopt(name = "root-path", parse(from_os_str), long = "tftp")]
pub root: Option<PathBuf>,
/// BOOTP filename, for use with tftp
#[structopt(long = "dhcp-bootfile")]
pub bootfile: Option<String>,
}
#[derive(Debug, StructOpt)]
#[structopt(name = "slirp-opt")]
pub struct Opt {
/// Isolate guest from host
#[structopt(long, short)]
pub restrict: bool,
/// Set interface MTU
#[structopt(long, default_value = "1500")]
pub mtu: usize,
/// Prohibit connection to 127.0.0.1
#[structopt(long)]
pub disable_host_loopback: bool,
/// Client hostname reported by the builtin DHCP server
#[structopt(long)]
pub hostname: Option<String>,
/// List of DNS suffixes to search, passed as DHCP option to the guest
#[structopt(long = "dns-suffixes")]
pub dns_suffixes: Vec<String>,
/// Guest-visible domain name of the virtual nameserver from DHCP server
#[structopt(long)]
pub domainname: Option<String>,
#[structopt(flatten)]
pub ipv4: OptIpv4,
#[structopt(flatten)]
pub ipv6: OptIpv6,
#[structopt(flatten)]
pub tftp: OptTftp,
}

12
third_party/libslirp-rs/src/version.rs vendored Normal file
View file

@ -0,0 +1,12 @@
use libslirp_sys::*;
use std::ffi::CStr;
use std::str;
pub fn version() -> &'static str {
str::from_utf8(unsafe { CStr::from_ptr(slirp_version_string()) }.to_bytes()).unwrap_or("")
}
pub fn state_version() -> i32 {
unsafe { slirp_state_version() }
}

View file

298
third_party/libslirp-rs/tests/base.py vendored Normal file
View file

@ -0,0 +1,298 @@
import ctypes
import functools
import io
import json
import os
import pathlib
import shlex
import signal
import socket
import subprocess
import tempfile
import unittest
from scapy.all import StreamSocket, sndrcv, Ether, conf, Route, ARP
SLIRPHELPER = os.environ.get("SLIRPHELPER")
LIBC = ctypes.CDLL("libc.so.6")
CLONE_NEWNET = 0x40000000
ORIGINAL_NET_NS = open("/proc/self/ns/net", "rb")
THISDIR = pathlib.Path(__file__).parent.absolute()
DBUS_SESSION_BUS_ADDRESS = os.environ.get("DBUS_SESSION_BUS_ADDRESS")
@functools.lru_cache()
def helper_capabilities():
p = subprocess.run(
[SLIRPHELPER, "--print-capabilities"], stdout=subprocess.PIPE, text=True
)
return json.loads(p.stdout)
def has_cap(cap):
return cap in helper_capabilities()["features"]
class Process:
def __init__(self, argv, close_fds=True, env=None):
self.p = subprocess.Popen(
argv,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
close_fds=close_fds,
env=env,
)
self.rc = None
def stdout_all(self):
return self.p.stdout.read()
def stdout_line(self):
return self.p.stdout.readline()
def stderr_all(self):
return self.p.stderr.read()
def stderr_line(self):
return self.p.stderr.readline()
def close(self, kill=True):
"""Returns process return code."""
if self.p:
if kill:
# Ensure the process registers two signals by sending a combo of
# SIGINT and SIGTERM. Sending the same signal two times is racy
# because the process can't reliably detect how many times the
# signal was sent.
self.p.send_signal(signal.SIGINT)
self.p.send_signal(signal.SIGTERM)
self.rc = self.p.wait()
self.p.stderr.close()
self.p.stdout.close()
self.p = None
return self.rc
def graceful_stop(self, wait=True):
self.p.send_signal(signal.SIGINT)
if wait:
self.p.wait()
class TestCase(unittest.TestCase):
has_notify_socket = None
execno = 0
def setUp(self):
if self.has_notify_socket is None:
self.has_notify_socket = has_cap("notify-socket")
self.cleanups = None
prev_net_fd = open("/proc/self/ns/net", "rb")
r = LIBC.unshare(CLONE_NEWNET)
if r != 0:
self.fail('Are you running within "unshare -Ur" ? Need unshare() syscall.')
self.guest_net_fd = open("/proc/self/ns/net", "rb")
self._add_teardown(self.guest_net_fd)
# mode tap, means ethernet headers
os.system(
"ip link set lo up;"
"ip tuntap add mode tap name tun0;"
"ip link set tun0 mtu 65521;"
"ip link set tun0 up;"
"ip addr add 10.0.2.100/24 dev tun0;"
"ip addr add 2001:2::100/32 dev tun0 nodad;"
"ip route add 0.0.0.0/0 via 10.0.2.2 dev tun0;"
"ip route add ::/0 via 2001:2::2 dev tun0;"
)
w = subprocess.Popen(["/bin/sleep", "1073741824"])
self.guest_ns_pid = w.pid
self._add_teardown(w)
LIBC.setns(prev_net_fd.fileno(), CLONE_NEWNET)
prev_net_fd.close()
self._tmpdir = tempfile.TemporaryDirectory()
self._add_teardown(self._tmpdir)
def tearDown(self):
while self.cleanups:
item = self.cleanups.pop()
if isinstance(item, subprocess.Popen):
item.send_signal(signal.SIGINT)
item.wait()
elif isinstance(item, Process):
item.close()
if getattr(item, "stdout", None):
item.stdout.close()
if getattr(item, "stderr", None):
item.stderr.close()
elif isinstance(item, io.BufferedReader):
item.close()
elif isinstance(item, tempfile.TemporaryDirectory):
item.cleanup()
else:
print("Unknown cleanup type")
print(type(item))
def run_helper(self, argv1=[], wait_ready=True, netns=True):
if isinstance(argv1, str):
argv1 = shlex.split(argv1)
a = [SLIRPHELPER] + argv1
if netns:
a = a + ["--netns", self.net_ns_path(), "--interface", "tun0"]
sn = None
env = None
if self.has_notify_socket and wait_ready:
self.execno += 1
sn = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
path = self.get_tmp_filename("sn-%d" % self.execno)
sn.bind(path)
env = dict(os.environ, NOTIFY_SOCKET="%s" % path)
p = Process(a, close_fds=False, env=env)
if sn:
sn.settimeout(1) # FIXME: remove timeout, end if process exit
try:
self.assertIn("READY=1", sn.recv(4096).decode())
except:
print(p.stderr_all())
sn.close()
self._add_teardown(p)
return p
def skipIfNotCapable(self, cap):
if not has_cap(cap):
self.skipTest("since '%s' capability is missing" % cap)
def start_echo(self, udp=False):
cmd = [THISDIR / "echo.py"]
if udp:
cmd += ["-u"]
p = Process(cmd)
self._add_teardown(p)
return int(p.stdout_line())
def assertTcpEcho(self, ip, port):
data = os.getrandom(16)
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect((ip, port))
s.sendall(data)
self.assertEqual(s.recv(len(data)), data)
def assertUdpEcho(self, ip, port):
data = os.getrandom(16)
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
s.sendto(data, (ip, port))
self.assertEqual(s.recv(len(data)), data)
def get_tmp_filename(self, name):
return os.path.join(self._tmpdir.name, name)
def _add_teardown(self, item):
if not self.cleanups:
self.cleanups = []
self.cleanups.append(item)
def net_ns_path(self):
return "/proc/%s/ns/net" % self.guest_ns_pid
def guest_netns(self):
xself = self
class controlled_execution:
def __enter__(self):
self.prev_net_fd = open("/proc/self/ns/net", "rb")
LIBC.setns(xself.guest_net_fd.fileno(), CLONE_NEWNET)
def __exit__(self, type, value, traceback):
LIBC.setns(self.prev_net_fd.fileno(), CLONE_NEWNET)
self.prev_net_fd.close()
return controlled_execution()
class testScapySocket:
def __init__(self, fd):
ss = StreamSocket(fd)
ss.basecls = Ether
self.ss = ss
conf.route = Route() # reinitializes the route based on the NS
self.e = Ether(src="52:55:0a:00:02:42")
def send(self, x):
self.ss.send(self.e / x)
def recv(self, x):
# this is not symmetrical with send, which appends Ether
# header, but ss.basecls will strip it of: not sure if that's
# the best way of doing things in fact, but that seem to work..
return self.ss.recv(x)
def fileno(self):
return self.ss.fileno()
def sr1(self, x, checkIPaddr=True, *args, **kwargs):
conf.checkIPaddr = checkIPaddr
kwargs.setdefault("verbose", False)
ans, _ = sndrcv(self.ss, self.e / x, *args, **kwargs)
return ans[0][1]
def sr(self, x, checkIPaddr=True, *args, **kwargs):
conf.checkIPaddr = checkIPaddr
kwargs.setdefault("verbose", False)
return sndrcv(self.ss, self.e / x, *args, **kwargs)
def withScapy():
def decorate(fn):
@functools.wraps(fn)
def maybe(*args, **kw):
sp = socket.socketpair(type=socket.SOCK_DGRAM)
os.set_inheritable(sp[0].fileno(), True)
self = args[0]
arg = kw.pop("parg", "")
p = self.run_helper(arg + " --fd %d" % sp[0].fileno(), netns=False)
s = testScapySocket(sp[1])
# gratious advertizing ARP
s.send(ARP(psrc="10.0.2.100", pdst="10.0.2.100", hwsrc=s.e.src))
kw["s"] = s
ret = fn(*args, **kw)
sp[0].close()
sp[1].close()
return ret
return maybe
return decorate
def isolateHostNetwork():
def decorate(fn):
@functools.wraps(fn)
def maybe(*args, **kw):
prev_net_fd = open("/proc/self/ns/net", "rb")
r = LIBC.unshare(CLONE_NEWNET)
if r != 0:
self.fail(
'Are you running within "unshare -Ur" ? Need unshare() syscall.'
)
# mode tun, since we don't actually plan on anyone reading the other side.
os.system(
"ip link set lo up;"
"ip tuntap add mode tun name eth0;"
"ip link set eth0 mtu 65521;"
"ip link set eth0 up;"
"ip addr add 192.168.1.100/24 dev eth0;"
"ip addr add 3ffe::100/16 dev eth0 nodad;"
"ip route add 0.0.0.0/0 via 192.168.1.1 dev eth0;"
"ip route add ::/0 via 3ffe::1 dev eth0;"
)
ret = fn(*args, **kw)
LIBC.setns(prev_net_fd.fileno(), CLONE_NEWNET)
prev_net_fd.close()
return ret
return maybe
return decorate

22
third_party/libslirp-rs/tests/dbus.conf vendored Normal file
View file

@ -0,0 +1,22 @@
<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-Bus Bus Configuration 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
<type>slirpnetstack-test</type>
<!-- Workaround abstract namespace and NEWNS issue -->
<listen>unix:dir=/tmp</listen>
<auth>EXTERNAL</auth>
<policy context='default'>
<!-- Allow everything to be sent -->
<allow send_destination='*' eavesdrop='true'/>
<!-- Allow everything to be received -->
<allow eavesdrop='true'/>
<!-- Allow anyone to own anything -->
<allow own='*'/>
</policy>
<include if_selinux_enabled='yes' selinux_root_relative='yes'>contexts/dbus_contexts</include>
</busconfig>

35
third_party/libslirp-rs/tests/echo.py vendored Executable file
View file

@ -0,0 +1,35 @@
#!/usr/bin/env python3
import socket
import sys
import getopt
def main(argv):
stype = socket.SOCK_STREAM
opts, args = getopt.getopt(argv, "u")
for opt, arg in opts:
if opt == "-u":
stype = socket.SOCK_DGRAM
s = socket.socket(socket.AF_INET, stype)
s.bind(("", 0))
print(s.getsockname()[1], flush=True)
if stype == socket.SOCK_STREAM:
s.listen(1)
s, _ = s.accept()
while 1:
data, addr = s.recvfrom(1024)
if not data:
break
if addr:
s.sendto(data, addr)
else:
s.sendall(data)
s.close()
if __name__ == "__main__":
main(sys.argv[1:])

119
third_party/libslirp-rs/tests/test-ip.rs vendored Normal file
View file

@ -0,0 +1,119 @@
use etherparse::{PacketBuilder, TcpOptionElement};
use libslirp;
use std::io;
use std::os::unix::io::RawFd;
use std::time::Instant;
use structopt::StructOpt;
impl libslirp::Handler for App {
type Timer = usize;
fn clock_get_ns(&mut self) -> i64 {
const NANOS_PER_SEC: u64 = 1_000_000_000;
let d = self.start.elapsed();
(d.as_secs() * NANOS_PER_SEC + d.subsec_nanos() as u64) as i64
}
fn timer_new(&mut self, _func: Box<dyn FnMut()>) -> Box<Self::Timer> {
Box::new(0)
}
fn timer_mod(&mut self, _timer: &mut Box<Self::Timer>, _expire_time: i64) {}
fn timer_free(&mut self, timer: Box<Self::Timer>) {
drop(timer);
}
fn send_packet(&mut self, buf: &[u8]) -> io::Result<usize> {
//self.stream.send(buf).unwrap() as isize
Ok(buf.len())
}
fn guest_error(&mut self, msg: &str) {
eprintln!("guest error: {}", msg);
}
fn register_poll_fd(&mut self, fd: RawFd) {
println!("register_poll_fd: fd={:?}", fd);
}
fn unregister_poll_fd(&mut self, fd: RawFd) {
println!("unregister_poll_fd: fd={:?}", fd);
}
fn notify(&mut self) {
println!("notify");
}
}
struct App {
start: Instant,
}
#[test]
fn ip() {
let opt = libslirp::Opt::from_args();
let app = App {
start: Instant::now(),
};
let ctxt = libslirp::Context::new_with_opt(&opt, app);
{
let builder = PacketBuilder::ethernet2(
[1, 2, 3, 4, 5, 6], //source mac
[7, 8, 9, 10, 11, 12], //destination mac
)
.ipv4(
[192, 168, 1, 1], //source ip
[192, 168, 1, 2], //desitination ip
20, //time to life
)
.udp(
21, //source port
1234, //desitnation port
);
//payload of the udp packet
let payload = [1, 2, 3, 4, 5, 6, 7, 8];
let mut buffer = Vec::<u8>::with_capacity(builder.size(payload.len()));
builder.write(&mut buffer, &payload).unwrap();
ctxt.input(&buffer);
}
{
let builder = PacketBuilder::ethernet2(
[1, 2, 3, 4, 5, 6], //source mac
[7, 8, 9, 10, 11, 12], //destionation mac
)
.ipv4(
[192, 168, 1, 1], //source ip
[192, 168, 1, 2], //desitionation ip
20, //time to life
)
.tcp(
21, //source port
1234, //desitnation port
1, //sequence number
26180, //window size
)
//set additional tcp header fields
.ns() //set the ns flag
//supported flags: ns(), fin(), syn(), rst(), psh(), ece(), cwr()
.ack(123) //ack flag + the ack number
.urg(23) //urg flag + urgent pointer
//tcp header options
.options(&[
TcpOptionElement::Nop,
TcpOptionElement::MaximumSegmentSize(1234),
])
.unwrap();
//payload of the tcp packet
let payload = [1, 2, 3, 4, 5, 6, 7, 8];
//get some memory to store the result
let mut buffer = Vec::<u8>::with_capacity(builder.size(payload.len()));
builder.write(&mut buffer, &payload).unwrap();
ctxt.input(&buffer);
}
}

246
third_party/libslirp-rs/tests/test.py vendored Normal file
View file

@ -0,0 +1,246 @@
import json
import unittest
from . import base
from scapy.all import *
from ipaddress import IPv4Address
from pydbus import SessionBus
class CLITest(base.TestCase):
def test_help(self):
""" Test if -h prints stuff looking like help screen. """
p = self.run_helper("-h", netns=False, wait_ready=False)
e = p.stderr_all()
self.assertFalse(e)
o = p.stdout_all().lower()
self.assertIn("usage:", o)
def test_print_capabilities(self):
""" Test if --print-capabilities output valid json. """
p = self.run_helper("--print-capabilities", netns=False, wait_ready=False)
e = p.stderr_all()
self.assertFalse(e)
o = p.stdout_all()
j = json.loads(o)
self.assertEqual(j["type"], "slirp-helper")
if "features" in j:
self.assertIsInstance(j["features"], list)
f = set(j["features"])
unknown = f.difference(
{
"dbus-address",
"dhcp",
"exit-with-parent",
"ipv4",
"ipv6",
"migrate",
"netns",
"notify-socket",
"restrict",
"tftp",
}
)
for cap in unknown:
if not cap.startswith("x-"):
self.fail("Unknown capability: %s" % cap)
def test_restrict(self):
""" Basic test if 'restrict' options exists. """
self.skipIfNotCapable("restrict")
self.run_helper("--restrict")
def test_ipv4(self):
""" Basic test if 'ipv4' options exists. """
self.skipIfNotCapable("ipv4")
self.run_helper("--disable-ipv4 --net 12.12.0.1/8")
def test_ipv4(self):
""" Basic test if 'ipv6' options exists. """
self.skipIfNotCapable("ipv6")
self.run_helper("--disable-ipv6 --net6 fec0::/64")
def test_exit_with_parent(self):
""" Basic test if 'exit-with-parent' option exists. """
self.skipIfNotCapable("exit-with-parent")
self.run_helper("--exit-with-parent")
def test_tftp(self):
""" Basic test if 'tftp' options exists. """
self.skipIfNotCapable("tftp")
self.run_helper("--tftp .")
def test_net(self):
""" Basic test if --net parses successfully. """
p = self.run_helper("--net 12.12.0.1/23")
p.graceful_stop()
p = self.run_helper("--net wefo/23", wait_ready=False)
e = p.stderr_all()
self.assertTrue(e)
p.graceful_stop()
def test_dbus(self):
""" Test if --dbus-address works. """
self.skipIfNotCapable("dbus-address")
if not base.DBUS_SESSION_BUS_ADDRESS:
self.skipTest("DBUS_SESSION_BUS_ADDRESS unset")
p = self.run_helper(
"--dbus-id TestId --dbus-address %s" % base.DBUS_SESSION_BUS_ADDRESS
)
bus = SessionBus()
iface = bus.get(".Slirp1_%u" % p.p.pid, "/org/freedesktop/Slirp1/Helper")
info = iface.GetInfo()
self.assertIn("Protocol[State]", info)
class ConnTest(base.TestCase):
@base.withScapy()
def test_ping(self, s):
""" Test Scapy ping """
pkt = s.sr1(IP(dst="10.0.2.2") / ICMP())
self.assertEqual(pkt.sprintf("%ICMP.type%"), "echo-reply")
@base.isolateHostNetwork()
def test_restrict(self):
""" Test --restrict behaviour """
port = self.start_echo()
self.run_helper("--restrict")
with self.guest_netns():
with self.assertRaises((ConnectionError, ConnectionRefusedError)):
self.assertTcpEcho("192.168.1.100", port)
@base.isolateHostNetwork()
def test_tcp_echo(self):
""" Test TCP echo """
port = self.start_echo()
self.run_helper()
with self.guest_netns():
self.assertTcpEcho("192.168.1.100", port)
@base.isolateHostNetwork()
def test_udp_echo(self):
""" Test UDP echo """
port = self.start_echo(udp=True)
self.run_helper()
with self.guest_netns():
self.assertUdpEcho("192.168.1.100", port)
@unittest.skipUnless(base.has_cap("dhcp"), "Missing 'dhcp' feature")
class DHCPTest(base.TestCase):
@base.withScapy()
def test_dhcp_v4(self, s):
""" Test DHCPv4 discover """
bootp = BOOTP(xid=RandInt())
dhcp = DHCP(options=[("message-type", "discover"), "end"])
p = (
IP(src="0.0.0.0", dst="255.255.255.255")
/ UDP(sport=68, dport=67)
/ bootp
/ dhcp
)
pkt = s.sr1(p, checkIPaddr=False)
self.assertEqual(pkt.sprintf("%BOOTP.op%"), "BOOTREPLY")
addr = IPv4Address(pkt[BOOTP].yiaddr)
self.assertGreaterEqual(addr, IPv4Address("10.0.2.15"))
self.assertLess(addr, IPv4Address("10.0.2.100"))
for o in pkt[DHCP].options:
if o[0] in ("router", "server_id"):
self.assertEqual(o[1], "10.0.2.2")
opts = [o[0] for o in pkt[DHCP].options if isinstance(o, tuple)]
self.assertIn("router", opts)
self.assertIn("name_server", opts)
self.assertIn("lease_time", opts)
self.assertIn("server_id", opts)
@base.withScapy()
def dhcp_and_net(self, s):
bootp = BOOTP(xid=RandInt())
dhcp = DHCP(options=[("message-type", "discover"), "end"])
p = (
IP(src="0.0.0.0", dst="255.255.255.255")
/ UDP(sport=68, dport=67)
/ bootp
/ dhcp
)
pkt = s.sr1(p, checkIPaddr=False)
self.assertEqual(pkt.sprintf("%BOOTP.op%"), "BOOTREPLY")
addr = IPv4Address(pkt[BOOTP].yiaddr)
self.assertGreaterEqual(addr, IPv4Address("12.34.56.15"))
self.assertLess(addr, IPv4Address("12.34.56.100"))
def test_dhcp_and_net(self):
""" Test DHCPv4 and -net """
self.dhcp_and_net(parg="--net 12.34.56.1/24")
@base.withScapy()
def dhcp_dns(self, s):
bootp = BOOTP(xid=RandInt())
dhcp = DHCP(options=[("message-type", "discover"), "end"])
p = (
IP(src="0.0.0.0", dst="255.255.255.255")
/ UDP(sport=68, dport=67)
/ bootp
/ dhcp
)
pkt = s.sr1(p, checkIPaddr=False)
# BOOTREPLY
for o in pkt[DHCP].options:
if o[0] == "name_server":
self.assertEqual(o[1], "8.8.8.8")
return
self.fail()
def test_dhcp_dns(self):
""" Test DHCPv4 DNS option """
self.dhcp_dns(parg="--dhcp-dns 8.8.8.8")
@base.withScapy()
def dhcp_nbp(self, s):
bootp = BOOTP(xid=RandInt())
dhcp = DHCP(options=[("message-type", "discover"), "end"])
p = (
IP(src="0.0.0.0", dst="255.255.255.255")
/ UDP(sport=68, dport=67)
/ bootp
/ dhcp
)
pkt = s.sr1(p, checkIPaddr=False)
# BOOTREPLY
bootFileName = pkt[BOOTP].file.partition(b"\0")[0].decode()
tftpServerName = None
for o in pkt[DHCP].options:
if o[0] == "boot-file-name":
bootFileName = o[1].decode() # Higher precedence?
elif o[0] in (
66,
"tftp-server-name",
"tftp_server_name",
): # FIXME: scapy doesn't know that field?
tftpServerName = o[1].decode()
self.assertEqual(tftpServerName, "10.0.0.1")
self.assertEqual(bootFileName, "/my-nbp")
def test_dhcp_nbp(self):
""" Test DHCPv4 NBP option """
self.dhcp_nbp(parg="--dhcp-nbp tftp://10.0.0.1/my-nbp")
@base.withScapy()
def dhcp_bootfile(self, s):
bootp = BOOTP(xid=RandInt())
dhcp = DHCP(options=[("message-type", "discover"), "end"])
p = (
IP(src="0.0.0.0", dst="255.255.255.255")
/ UDP(sport=68, dport=67)
/ bootp
/ dhcp
)
pkt = s.sr1(p, checkIPaddr=False)
# BOOTREPLY
self.assertEqual(
pkt[BOOTP].file.partition(b"\0")[0].decode(), "http://boot.netboot.xyz/"
)
def test_dhcp_bootfile(self):
""" Test DHCPv4 bootfile option """
self.dhcp_bootfile(parg="--dhcp-bootfile http://boot.netboot.xyz/")