mirror of
https://chromium.googlesource.com/crosvm/crosvm
synced 2024-11-25 05:03:05 +00:00
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:
parent
39a8c88e79
commit
465479699b
17 changed files with 2195 additions and 0 deletions
43
third_party/libslirp-rs/Cargo.toml
vendored
Normal file
43
third_party/libslirp-rs/Cargo.toml
vendored
Normal 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
21
third_party/libslirp-rs/LICENSE
vendored
Normal 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
16
third_party/libslirp-rs/Makefile
vendored
Normal 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
1
third_party/libslirp-rs/README.md
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
Fork init'ed from 19553209436ae7b9e036641f4013246111192d5c.
|
319
third_party/libslirp-rs/src/bin/libslirp-helper/main.rs
vendored
Normal file
319
third_party/libslirp-rs/src/bin/libslirp-helper/main.rs
vendored
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
75
third_party/libslirp-rs/src/bin/libslirp-helper/tun.rs
vendored
Normal file
75
third_party/libslirp-rs/src/bin/libslirp-helper/tun.rs
vendored
Normal 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
575
third_party/libslirp-rs/src/context.rs
vendored
Normal 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
13
third_party/libslirp-rs/src/lib.rs
vendored
Normal 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
312
third_party/libslirp-rs/src/mio.rs
vendored
Normal 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
88
third_party/libslirp-rs/src/opt.rs
vendored
Normal 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
12
third_party/libslirp-rs/src/version.rs
vendored
Normal 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() }
|
||||
}
|
0
third_party/libslirp-rs/tests/__init__.py
vendored
Normal file
0
third_party/libslirp-rs/tests/__init__.py
vendored
Normal file
298
third_party/libslirp-rs/tests/base.py
vendored
Normal file
298
third_party/libslirp-rs/tests/base.py
vendored
Normal 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
22
third_party/libslirp-rs/tests/dbus.conf
vendored
Normal 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
35
third_party/libslirp-rs/tests/echo.py
vendored
Executable 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
119
third_party/libslirp-rs/tests/test-ip.rs
vendored
Normal 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
246
third_party/libslirp-rs/tests/test.py
vendored
Normal 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/")
|
Loading…
Reference in a new issue