use std::{
fs::{File, OpenOptions},
mem::size_of,
os::fd::AsRawFd,
};
use ioctl_gen::{ioc, iow};
use libc::{
__c_anonymous_ifr_ifru, ifreq, ioctl, IFF_MULTI_QUEUE, IFF_NO_PI, IFF_TUN, IF_NAMESIZE,
};
mod arch {
#[cfg(all(target_os = "linux", target_env = "gnu"))]
pub type IoctlRequestType = libc::c_ulong;
#[cfg(all(target_os = "linux", target_env = "musl"))]
pub type IoctlRequestType = libc::c_int;
#[cfg(not(any(target_env = "gnu", target_env = "musl")))]
compile_error!(
"Unsupported target environment. Only gnu and musl targets are currently supported."
);
}
pub struct Tun {
fds: Vec<File>,
name: String,
}
impl Tun {
#[allow(clippy::cast_possible_truncation)]
#[allow(clippy::cast_lossless)]
pub fn new(dev: &str, queues: usize) -> Result<Self, std::io::Error> {
log::debug!(
"Creating new TUN device with requested name: {} ({} queues)",
dev,
queues
);
log::trace!("Opening /dev/net/tun");
let mut fds = Vec::with_capacity(queues);
for _ in 0..queues {
let fd = OpenOptions::new()
.read(true)
.write(true)
.open("/dev/net/tun")?;
fds.push(fd);
}
let mut dev_cstr: [libc::c_char; IF_NAMESIZE] = [0; IF_NAMESIZE];
let dev_bytes: Vec<libc::c_char> = dev.chars().map(|c| c as libc::c_char).collect();
let dev_len = dev_bytes.len().min(IF_NAMESIZE);
log::trace!("Device name length after truncation: {}", dev_len);
dev_cstr[..dev_len].copy_from_slice(&dev_bytes[..dev_len]);
let mut ifr = ifreq {
ifr_name: dev_cstr,
ifr_ifru: __c_anonymous_ifr_ifru {
ifru_flags: (IFF_TUN | IFF_NO_PI | IFF_MULTI_QUEUE) as i16,
},
};
for fd in &mut fds {
log::trace!("Calling ioctl to create TUN device");
let err = unsafe {
ioctl(
fd.as_raw_fd(),
iow!('T', 202, size_of::<libc::c_int>()) as arch::IoctlRequestType,
&mut ifr,
)
};
log::trace!("ioctl returned: {}", err);
if err < 0 {
log::error!("ioctl failed: {}", err);
return Err(std::io::Error::last_os_error());
}
}
let name = unsafe { std::ffi::CStr::from_ptr(ifr.ifr_name.as_ptr()) }
.to_str()
.unwrap()
.to_string();
log::debug!("Created TUN device: {}", name);
Ok(Self { fds, name })
}
#[must_use]
pub fn name(&self) -> &str {
&self.name
}
#[must_use]
pub fn fd(&self, queue_id: usize) -> Option<&File> {
self.fds.get(queue_id)
}
#[must_use]
pub fn fd_mut(&mut self, queue_id: usize) -> Option<&mut File> {
self.fds.get_mut(queue_id).map(|fd| &mut *fd)
}
}