-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial revision of vmnet, Apple's vmnet.framework bindings for Rust
- Loading branch information
0 parents
commit cc9592c
Showing
22 changed files
with
1,315 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
[target.aarch64-apple-darwin] | ||
runner = 'sudo -E' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
/target | ||
/Cargo.lock |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
[package] | ||
name = "vmnet" | ||
version = "0.1.0" | ||
edition = "2021" | ||
|
||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||
|
||
[workspace] | ||
members = ["vmnet-derive"] | ||
|
||
[dev-dependencies] | ||
smoltcp = "0.8.1" | ||
|
||
[dependencies] | ||
libc = "0.2.126" | ||
block = "0.1.6" | ||
uuid = { version = "1.1.1", features = ["v4"] } | ||
hexdump = "0.1.1" | ||
lazy_static = "1.4.0" | ||
bitflags = "1.3.2" | ||
num_enum = "0.5.7" | ||
thiserror = "1.0.31" | ||
enum-iterator = "1.1.1" | ||
vmnet-derive = { path = "vmnet-derive" } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
# vmnet | ||
|
||
Apple's [`vmnet.framework`](https://developer.apple.com/documentation/vmnet) bindings for Rust. | ||
|
||
## Installation | ||
|
||
Add this to your `Cargo.toml`: | ||
|
||
```toml | ||
[dependencies] | ||
vmnet = "0.1.0" | ||
``` | ||
|
||
## Usage | ||
|
||
Ensure that your software either has an `com.apple.vm.networking` entitlement or is running with elevated privileges. | ||
|
||
Start a NAT interface and receive some packets destined to it: | ||
|
||
```rust | ||
fn main() { | ||
let shared_mode = Shared { | ||
subnet_options: None, | ||
..Default::default() | ||
}; | ||
|
||
let mut iface = Interface::new(Mode::Shared(shared_mode), Options::default()).unwrap(); | ||
|
||
let (tx, rx) = sync::mpsc::sync_channel(0); | ||
|
||
iface.set_event_callback(Events::PACKETS_AVAILABLE, move |events, params| { | ||
if let Some(Parameter::EstimatedPacketsAvailable(pkts)) = params.get(ParameterKind::EstimatedPacketsAvailable) { | ||
tx.send(pkts); | ||
} | ||
}).unwrap(); | ||
|
||
let pkts = rx.recv().unwrap(); | ||
println!("receiving {} packets...", pkts); | ||
for _ in 0..pkts { | ||
let mut buf: [u8; 1514] = [0; 1514]; | ||
println!("{:?}", iface.read(&mut buf)); | ||
} | ||
|
||
drop(rx); | ||
iface.finalize().unwrap(); | ||
} | ||
``` | ||
|
||
## Quirks and missing functionality | ||
|
||
* due to Apple's usage of [blocks](https://en.wikipedia.org/wiki/Blocks_(C_language_extension)) as a way to retrieve API call results, some methods like `set_event_callback()` require the provided closure to have a `'static` lifetime | ||
* this manifests itself in not being able to use `Interface` from such closure | ||
* however, this can be easily worked around by using [interior mutability pattern](https://doc.rust-lang.org/book/ch15-05-interior-mutability.html) or simply by using the callback as a signal carrier | ||
* no [port forwarding](https://developer.apple.com/documentation/vmnet/vmnet_functions) support | ||
* [`vmnet_copy_shared_interface_list()`](https://developer.apple.com/documentation/vmnet/3152677-vmnet_copy_shared_interface_list) is not yet implemented | ||
* due to `API_AVAILABLE` macro not being supported it is assumed that this package is running on macOS 11.0 or newer |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
use crate::ffi::vmnet; | ||
|
||
#[derive(thiserror::Error, Debug)] | ||
pub enum Error { | ||
#[error("vmnet returned an error status")] | ||
VmnetErrorStatus(vmnet::Status), | ||
#[error("vmnet returned an unknown status: {0}")] | ||
VmnetUnknownStatus(u32), | ||
#[error("vmnet_start_interface() failed")] | ||
VmnetStartInterfaceFailed, | ||
#[error("vmnet_read() received no packets at this time")] | ||
VmnetReadNothing, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
use std::ffi::c_void; | ||
|
||
pub type DispatchQueueGlobalT = *mut c_void; | ||
|
||
extern "C" { | ||
pub fn dispatch_get_global_queue(identifier: isize, flags: usize) -> DispatchQueueGlobalT; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
pub mod dispatch; | ||
pub mod vmnet; | ||
pub mod xpc; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
use crate::error; | ||
use crate::ffi::dispatch::DispatchQueueGlobalT; | ||
use crate::ffi::xpc::XpcObjectT; | ||
use bitflags::bitflags; | ||
use libc::{iovec, size_t}; | ||
use num_enum::{IntoPrimitive, TryFromPrimitive}; | ||
use std::ffi::c_void; | ||
use std::os::raw::{c_char, c_int}; | ||
|
||
pub type InterfaceRef = *mut c_void; | ||
pub type InterfaceEventT = u32; | ||
pub type VmnetReturnT = u32; | ||
|
||
#[repr(C)] | ||
#[derive(Debug)] | ||
pub struct vmpktdesc { | ||
pub vm_pkt_size: size_t, | ||
pub vm_pkt_iov: *mut iovec, | ||
pub vm_pkt_iovcnt: u32, | ||
pub vm_flags: u32, | ||
} | ||
|
||
#[link(name = "vmnet", kind = "framework")] | ||
extern "C" { | ||
pub fn vmnet_start_interface( | ||
interface_desc: XpcObjectT, | ||
queue: DispatchQueueGlobalT, | ||
handler: *mut c_void, | ||
) -> InterfaceRef; | ||
pub fn vmnet_interface_set_event_callback( | ||
interface: InterfaceRef, | ||
event_mask: InterfaceEventT, | ||
queue: DispatchQueueGlobalT, | ||
handler: *mut c_void, | ||
) -> VmnetReturnT; | ||
pub fn vmnet_read( | ||
interface: InterfaceRef, | ||
packets: *mut vmpktdesc, | ||
pktcnt: *mut c_int, | ||
) -> VmnetReturnT; | ||
pub fn vmnet_write( | ||
interface: InterfaceRef, | ||
packets: *mut vmpktdesc, | ||
pktcnt: *mut c_int, | ||
) -> VmnetReturnT; | ||
pub fn vmnet_stop_interface( | ||
interface: InterfaceRef, | ||
queue: DispatchQueueGlobalT, | ||
handler: *mut c_void, | ||
) -> VmnetReturnT; | ||
|
||
// Mode selector | ||
pub static vmnet_operation_mode_key: *const c_char; | ||
|
||
// Generic options and parameters | ||
pub static vmnet_mac_address_key: *const c_char; | ||
pub static vmnet_allocate_mac_address_key: *const c_char; | ||
pub static vmnet_interface_id_key: *const c_char; | ||
pub static vmnet_max_packet_size_key: *const c_char; | ||
pub static vmnet_enable_checksum_offload_key: *const c_char; | ||
pub static vmnet_enable_isolation_key: *const c_char; | ||
pub static vmnet_enable_tso_key: *const c_char; | ||
|
||
// Host mode | ||
pub static vmnet_network_identifier_key: *const c_char; | ||
pub static vmnet_host_ip_address_key: *const c_char; | ||
pub static vmnet_host_ipv6_address_key: *const c_char; | ||
pub static vmnet_host_subnet_mask_key: *const c_char; | ||
|
||
// Bridged mode | ||
pub static vmnet_shared_interface_name_key: *const c_char; | ||
|
||
// Shared and host modes | ||
pub static vmnet_start_address_key: *const c_char; | ||
pub static vmnet_end_address_key: *const c_char; | ||
pub static vmnet_subnet_mask_key: *const c_char; | ||
pub static vmnet_mtu_key: *const c_char; | ||
|
||
// Shared mode | ||
pub static vmnet_nat66_prefix_key: *const c_char; | ||
|
||
// Event callback | ||
pub static vmnet_estimated_packets_available_key: *const c_char; | ||
} | ||
|
||
#[derive(TryFromPrimitive)] | ||
#[repr(u32)] | ||
#[derive(Debug, Eq, PartialEq)] | ||
pub enum Status { | ||
Success = 1000, | ||
Failure = 1001, | ||
MemFailure = 1002, | ||
InvalidArgument = 1003, | ||
SetupIncomplete = 1004, | ||
InvalidAccess = 1005, | ||
PacketTooBig = 1006, | ||
BufferExhausted = 1007, | ||
TooManyPackets = 1008, | ||
SharingServiceBusy = 1009, | ||
} | ||
|
||
impl Status { | ||
pub fn from_ffi(status: u32) -> Result<(), error::Error> { | ||
let status = | ||
Status::try_from(status).map_err(|_x| error::Error::VmnetUnknownStatus(status))?; | ||
|
||
match status { | ||
Status::Success => Ok(()), | ||
_ => Err(error::Error::VmnetErrorStatus(status)), | ||
} | ||
} | ||
} | ||
|
||
#[repr(u64)] | ||
#[derive(IntoPrimitive)] | ||
pub enum Mode { | ||
Host = 1000, | ||
Shared = 1001, | ||
Bridged = 1002, | ||
} | ||
|
||
bitflags! { | ||
pub struct Events: u32 { | ||
const PACKETS_AVAILABLE = 1 << 0; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
use crate::ffi::xpc::{ | ||
xpc_dictionary_apply, xpc_dictionary_create_empty, xpc_dictionary_get_value, | ||
xpc_dictionary_set_bool, xpc_dictionary_set_string, xpc_dictionary_set_uint64, | ||
xpc_dictionary_set_uuid, xpc_release, xpc_retain, XpcData, XpcObjectT, | ||
}; | ||
use block::ConcreteBlock; | ||
use std::cell::RefCell; | ||
use std::collections::HashMap; | ||
use std::ffi::{CStr, CString}; | ||
use std::os::raw::c_char; | ||
use std::rc::Rc; | ||
|
||
pub struct Dictionary { | ||
xdict: XpcObjectT, | ||
} | ||
|
||
impl Dictionary { | ||
pub fn new() -> Self { | ||
Dictionary { | ||
xdict: unsafe { xpc_dictionary_create_empty() }, | ||
} | ||
} | ||
|
||
pub unsafe fn from_xpc(xdict: XpcObjectT) -> Self { | ||
Dictionary { | ||
xdict: xpc_retain(xdict), | ||
} | ||
} | ||
|
||
pub unsafe fn to_xpc(&self) -> XpcObjectT { | ||
self.xdict | ||
} | ||
|
||
pub fn get(&self, key: String) -> Option<XpcData> { | ||
let key_ = CString::new(key).unwrap(); | ||
let key = key_.as_ptr(); | ||
|
||
let xpc_value = unsafe { xpc_dictionary_get_value(self.xdict, key) }; | ||
if xpc_value.is_null() { | ||
return None; | ||
} | ||
|
||
unsafe { XpcData::from_xpc_value(xpc_value) } | ||
} | ||
|
||
pub fn set(&mut self, key: String, value: XpcData) { | ||
let key_ = CString::new(key).unwrap(); | ||
let key = key_.as_ptr(); | ||
|
||
match value { | ||
XpcData::Uint64(value) => unsafe { xpc_dictionary_set_uint64(self.xdict, key, value) }, | ||
XpcData::String(value) => { | ||
let c_string = CString::new(value).unwrap(); | ||
unsafe { xpc_dictionary_set_string(self.xdict, key, c_string.as_ptr()) } | ||
} | ||
XpcData::Bool(value) => unsafe { xpc_dictionary_set_bool(self.xdict, key, value) }, | ||
XpcData::Uuid(value) => unsafe { | ||
xpc_dictionary_set_uuid(self.xdict, key, value.as_bytes().as_ptr()) | ||
}, | ||
}; | ||
} | ||
} | ||
|
||
impl Default for Dictionary { | ||
fn default() -> Self { | ||
Self::new() | ||
} | ||
} | ||
|
||
impl Clone for Dictionary { | ||
fn clone(&self) -> Self { | ||
Dictionary { | ||
xdict: unsafe { xpc_retain(self.xdict) }, | ||
} | ||
} | ||
} | ||
|
||
impl Drop for Dictionary { | ||
fn drop(&mut self) { | ||
unsafe { xpc_release(self.xdict) } | ||
} | ||
} | ||
|
||
impl From<HashMap<String, XpcData>> for Dictionary { | ||
fn from(from: HashMap<String, XpcData>) -> Self { | ||
let mut result = Dictionary::new(); | ||
|
||
for (key, value) in from { | ||
result.set(key, value); | ||
} | ||
|
||
result | ||
} | ||
} | ||
|
||
impl TryFrom<Dictionary> for HashMap<String, XpcData> { | ||
type Error = (); | ||
|
||
fn try_from(value: Dictionary) -> Result<Self, Self::Error> { | ||
let result = Rc::new(RefCell::new(HashMap::new())); | ||
let result_weak = Rc::downgrade(&result); | ||
|
||
let block = ConcreteBlock::new(move |key: *const c_char, value: XpcObjectT| -> bool { | ||
let key = unsafe { CStr::from_ptr(key).to_string_lossy().to_string() }; | ||
|
||
let value = match unsafe { XpcData::from_xpc_value(value) } { | ||
Some(value) => value, | ||
None => return false, | ||
}; | ||
|
||
result_weak | ||
.upgrade() | ||
.unwrap() | ||
.borrow_mut() | ||
.insert(key, value); | ||
|
||
true | ||
}); | ||
let block = block.copy(); | ||
|
||
let ret = unsafe { xpc_dictionary_apply(value.xdict, &*block as *const _ as *mut _) }; | ||
if !ret { | ||
return Err(()); | ||
} | ||
|
||
Ok(Rc::try_unwrap(result).unwrap().into_inner()) | ||
} | ||
} |
Oops, something went wrong.