201 lines
6.7 KiB
Rust
201 lines
6.7 KiB
Rust
use anyhow::{Context, Result, anyhow};
|
|
use clap::ValueEnum;
|
|
use log::info;
|
|
use std::net::Ipv4Addr;
|
|
use std::os::unix::io::{AsRawFd, RawFd};
|
|
use std::os::unix::net::UnixDatagram;
|
|
use std::str::FromStr;
|
|
use std::sync::mpsc::{SyncSender, sync_channel};
|
|
use vmnet::mode::Mode;
|
|
use vmnet::parameters::{Parameter, ParameterKind};
|
|
use vmnet::port_forwarding::{AddressFamily, Protocol};
|
|
use vmnet::{Batch, Events, Options};
|
|
|
|
#[derive(ValueEnum, Clone, Debug)]
|
|
pub enum NetType {
|
|
/// Shared network
|
|
///
|
|
/// Uses NAT-translation to give guests access to the global network
|
|
Nat,
|
|
/// Host network
|
|
///
|
|
/// Guests will be able to talk only to the host without access to global network
|
|
Host,
|
|
}
|
|
|
|
pub struct Host {
|
|
interface: vmnet::Interface,
|
|
new_packets_rx: UnixDatagram,
|
|
callback_can_continue_tx: SyncSender<()>,
|
|
pub gateway_ip: smoltcp::wire::Ipv4Address,
|
|
pub max_packet_size: u64,
|
|
pub read_max_packets: u64,
|
|
finalized: bool,
|
|
}
|
|
|
|
impl Host {
|
|
pub fn new(vm_net_type: NetType, enable_isolation: bool) -> Result<Host> {
|
|
// Initialize a vmnet.framework NAT or Host interface with isolation enabled
|
|
let mut interface = vmnet::Interface::new(
|
|
match vm_net_type {
|
|
NetType::Nat => Mode::Shared(Default::default()),
|
|
NetType::Host => Mode::Host(Default::default()),
|
|
},
|
|
Options {
|
|
enable_isolation: Some(enable_isolation),
|
|
..Default::default()
|
|
},
|
|
)
|
|
.context("failed to initialize vmnet interface")?;
|
|
|
|
// Retrieve first IP (gateway) used for this interface
|
|
let Some(Parameter::StartAddress(gateway_ip)) =
|
|
interface.parameters().get(ParameterKind::StartAddress)
|
|
else {
|
|
return Err(anyhow!(
|
|
"failed to retrieve vmnet's interface start address"
|
|
));
|
|
};
|
|
let gateway_ip = Ipv4Addr::from_str(&gateway_ip)
|
|
.context("failed to parse vmnet's interface start address")?;
|
|
|
|
// Retrieve max packet size for this interface
|
|
let Some(Parameter::MaxPacketSize(max_packet_size)) =
|
|
interface.parameters().get(ParameterKind::MaxPacketSize)
|
|
else {
|
|
return Err(anyhow!(
|
|
"failed to retrieve vmnet's interface max packet size"
|
|
));
|
|
};
|
|
|
|
// Retrieve read max packets for this interface
|
|
let Some(Parameter::ReadMaxPackets(read_max_packets)) =
|
|
interface.parameters().get(ParameterKind::ReadMaxPackets)
|
|
else {
|
|
return Err(anyhow!(
|
|
"failed to retrieve vmnet's interface read max packets"
|
|
));
|
|
};
|
|
|
|
// Set up a socketpair() to emulate polling of the vmnet interface
|
|
let (new_packets_tx, new_packets_rx) = UnixDatagram::pair()?;
|
|
new_packets_rx.set_nonblocking(true)?;
|
|
|
|
let (callback_can_continue_tx, callback_can_continue_rx) = sync_channel(0);
|
|
|
|
interface
|
|
.set_event_callback(Events::PACKETS_AVAILABLE, move |_mask, _params| {
|
|
// Send a dummy datagram to make the other end of socketpair() readable
|
|
// and ignore the error as this merely a signalling channel to wake up
|
|
// the poller
|
|
new_packets_tx.send(&[0; 1]).ok();
|
|
|
|
// Wait for the permission to continue to avoid
|
|
// wasting CPU cycles or in case of termination,
|
|
// to unblock this Block[1] and allow
|
|
// vmnet.framework to terminate
|
|
//
|
|
// [1]: https://en.wikipedia.org/wiki/Blocks_(C_language_extension)
|
|
callback_can_continue_rx.recv().unwrap();
|
|
})
|
|
.context("failed to set vmnet interface's event callback")?;
|
|
|
|
Ok(Host {
|
|
interface,
|
|
new_packets_rx,
|
|
callback_can_continue_tx,
|
|
gateway_ip,
|
|
max_packet_size,
|
|
read_max_packets,
|
|
finalized: false,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl Host {
|
|
pub fn port_forwarding_add_rule(
|
|
&mut self,
|
|
external_port: u16,
|
|
internal_addr: Ipv4Addr,
|
|
internal_port: u16,
|
|
) -> Result<()> {
|
|
let details = format!(
|
|
"external_port={external_port}, internal_addr={internal_addr}, internal_port={internal_port}"
|
|
);
|
|
|
|
self.interface
|
|
.port_forwarding_rule_add(
|
|
AddressFamily::Ipv4,
|
|
Protocol::Tcp,
|
|
external_port,
|
|
internal_addr.into(),
|
|
internal_port,
|
|
)
|
|
.map(|_| info!("added port forwarding rule {details}"))
|
|
.map_err(|err| anyhow!("failed to add port forwarding rule {details}: {err}"))
|
|
}
|
|
|
|
pub fn port_forwarding_remove_rule(&mut self, external_port: u16) -> Result<()> {
|
|
let details = format!("external_port={external_port}");
|
|
|
|
self.interface
|
|
.port_forwarding_rule_remove(AddressFamily::Ipv4, Protocol::Tcp, external_port)
|
|
.map(|_| info!("removed port forwarding rule {details}"))
|
|
.map_err(|err| anyhow!("failed to remove port forwarding rule {details}: {err}"))
|
|
}
|
|
|
|
pub fn read(&mut self, batch: &mut Batch, bufs: &mut [Vec<u8>]) -> vmnet::Result<usize> {
|
|
// Dequeue dummy datagram from the socket (if any)
|
|
// to free up buffer space and reduce false-positives
|
|
// when polling
|
|
let mut buf_to_be_discarded: [u8; 1] = [0; 1];
|
|
let _ = self.new_packets_rx.recv(&mut buf_to_be_discarded);
|
|
|
|
let result = self.interface.read_batch(batch, bufs);
|
|
|
|
if let Err(vmnet::Error::VmnetReadNothing) = result {
|
|
// We've emptied everything, unlock the callback
|
|
// so that it will be able to pick up new events
|
|
let _ = self.callback_can_continue_tx.send(());
|
|
}
|
|
|
|
result
|
|
}
|
|
|
|
pub fn write(&mut self, buf: &[u8]) -> vmnet::Result<usize> {
|
|
self.interface.write(buf)
|
|
}
|
|
|
|
pub fn finalize(&mut self) -> Result<()> {
|
|
// First make sure our callback won't be scheduled again after it finishes
|
|
self.interface
|
|
.clear_event_callback()
|
|
.context("failed to clear vmnet interface's event callback")?;
|
|
|
|
// Now let the callback finish
|
|
let _ = self.callback_can_continue_tx.send(());
|
|
|
|
self.interface
|
|
.finalize()
|
|
.context("failed to finalize vmnet's interface")?;
|
|
|
|
self.finalized = true;
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl Drop for Host {
|
|
fn drop(&mut self) {
|
|
if !self.finalized {
|
|
let _ = self.finalize();
|
|
}
|
|
}
|
|
}
|
|
|
|
impl AsRawFd for Host {
|
|
fn as_raw_fd(&self) -> RawFd {
|
|
self.new_packets_rx.as_raw_fd()
|
|
}
|
|
}
|