#[cfg(all(not(feature = "simple"), not(feature = "namespaced")))]
compile_error!("Either \"simple\" or \"namespaced\" networking feature flags must be enabled");
#[cfg(feature = "namespaced")]
use std::{future::Future, net::IpAddr};
use std::{path::PathBuf, process::ExitStatus, sync::Arc};
use cidr::IpInet;
use futures_util::TryStreamExt;
#[cfg(feature = "namespaced")]
use netns::NamespacedData;
#[cfg(feature = "namespaced")]
use netns_rs::NetNs;
use tokio::process::Command;
#[cfg(feature = "namespaced")]
mod netns;
#[cfg(feature = "simple")]
mod simple;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct FirecrackerNetwork {
pub iptables_path: PathBuf,
pub iface_name: String,
pub tap_name: String,
pub tap_ip: IpInet,
pub network_type: FirecrackerNetworkType,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum FirecrackerNetworkType {
#[cfg(feature = "simple")]
Simple,
#[cfg(feature = "namespaced")]
Namespaced {
netns_name: String,
veth1_name: String,
veth2_name: String,
veth1_ip: IpInet,
veth2_ip: IpInet,
guest_ip: IpAddr,
forwarded_guest_ip: Option<IpAddr>,
},
}
#[derive(Debug, thiserror::Error)]
pub enum FirecrackerNetworkError {
#[error("An rtnetlink operation failed: `{0}`")]
NetlinkOperationError(rtnetlink::Error),
#[error("Creating or deleting a tap device failed: `{0}`")]
TapDeviceError(tokio_tun::Error),
#[cfg(feature = "namespaced")]
#[error("Interacting with a network namespace failed: `{0}`")]
NetnsError(netns_rs::Error),
#[error("A generic I/O error occurred: `{0}`")]
IoError(std::io::Error),
#[cfg(feature = "namespaced")]
#[error("Receiving from a supporting oneshot channel failed: `{0}`")]
ChannelRecvError(tokio::sync::oneshot::error::RecvError),
#[error("Invoking a process failed due to its non-zero exit status: `{0}`")]
FailedInvocation(ExitStatus),
#[error("An expected IP route was not found on the host")]
RouteNotFound,
#[error("An expected IP link was not found on the host")]
LinkNotFound,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum FirecrackerNetworkOperation {
Add,
Check,
Delete,
}
impl FirecrackerNetwork {
pub async fn run(self: Arc<Self>, operation: FirecrackerNetworkOperation) -> Result<(), FirecrackerNetworkError> {
let (connection, netlink_handle, _) = rtnetlink::new_connection().map_err(FirecrackerNetworkError::IoError)?;
tokio::task::spawn(connection);
match &self.network_type {
#[cfg(feature = "simple")]
FirecrackerNetworkType::Simple => simple::run(self, netlink_handle, operation).await,
#[cfg(feature = "namespaced")]
FirecrackerNetworkType::Namespaced {
netns_name: _,
veth1_name: _,
veth2_name: _,
veth1_ip: _,
veth2_ip: _,
guest_ip: _,
forwarded_guest_ip: _,
} => netns::run(operation, self, netlink_handle).await,
}
}
pub fn guest_ip_boot_arg(&self, guest_ip: &IpInet, guest_iface_name: impl AsRef<str>) -> String {
format!(
"ip={}::{}:{}::{}:off",
guest_ip.address().to_string(),
self.tap_ip.address().to_string(),
guest_ip.mask().to_string(),
guest_iface_name.as_ref()
)
}
async fn run_iptables(&self, iptables_cmd: String) -> Result<(), FirecrackerNetworkError> {
let mut command = Command::new(&self.iptables_path);
for iptables_arg in iptables_cmd.split(' ') {
command.arg(iptables_arg);
}
let status = command.status().await.map_err(FirecrackerNetworkError::IoError)?;
if !status.success() {
return Err(FirecrackerNetworkError::FailedInvocation(status));
}
Ok(())
}
}
async fn get_link_index(link: String, netlink_handle: &rtnetlink::Handle) -> Result<u32, FirecrackerNetworkError> {
Ok(netlink_handle
.link()
.get()
.match_name(link)
.execute()
.try_next()
.await
.map_err(FirecrackerNetworkError::NetlinkOperationError)?
.ok_or(FirecrackerNetworkError::LinkNotFound)?
.header
.index)
}
#[cfg(feature = "namespaced")]
async fn use_netns_in_thread<
F: 'static + Send + FnOnce(Arc<FirecrackerNetwork>, Arc<NamespacedData>) -> Fut,
Fut: Send + Future<Output = Result<(), FirecrackerNetworkError>>,
>(
netns_name: String,
network: Arc<FirecrackerNetwork>,
namespaced_data: Arc<NamespacedData>,
function: F,
) -> Result<(), FirecrackerNetworkError> {
let netns = NetNs::get(netns_name).map_err(FirecrackerNetworkError::NetnsError)?;
let (sender, receiver) = tokio::sync::oneshot::channel();
std::thread::spawn(move || {
let result = {
match tokio::runtime::Builder::new_current_thread().enable_all().build() {
Ok(runtime) => runtime.block_on(async move {
netns.enter().map_err(FirecrackerNetworkError::NetnsError)?;
function(network, namespaced_data).await
}),
Err(err) => Err(FirecrackerNetworkError::IoError(err)),
}
};
let _ = sender.send(result);
});
match receiver.await {
Ok(result) => result,
Err(err) => Err(FirecrackerNetworkError::ChannelRecvError(err)),
}
}