111 lines
3.0 KiB
Rust
111 lines
3.0 KiB
Rust
use std::{
|
|
fs::{File, OpenOptions},
|
|
io::{Read, Write},
|
|
mem::size_of,
|
|
os::fd::{AsRawFd, IntoRawFd, RawFd},
|
|
};
|
|
|
|
use ioctl_gen::{ioc, iow};
|
|
use libc::{__c_anonymous_ifr_ifru, ifreq, ioctl, IFF_NO_PI, IFF_TUN, IF_NAMESIZE};
|
|
|
|
/// A TUN device
|
|
pub struct Tun {
|
|
/// Internal file descriptor for the TUN device
|
|
fd: File,
|
|
/// Device name
|
|
name: String,
|
|
}
|
|
|
|
impl Tun {
|
|
/// Creates a new Tun device with the given name.
|
|
///
|
|
/// The `name` argument must be less than the system's `IFNAMSIZ` constant,
|
|
/// and may contain a `%d` format specifier to allow for multiple devices with the same name.
|
|
#[allow(clippy::cast_possible_truncation)]
|
|
#[allow(clippy::cast_lossless)]
|
|
pub fn new(dev: &str) -> Result<Self, std::io::Error> {
|
|
// Get a file descriptor for `/dev/net/tun`
|
|
log::trace!("Opening /dev/net/tun");
|
|
let fd = OpenOptions::new()
|
|
.read(true)
|
|
.write(true)
|
|
.open("/dev/net/tun")?;
|
|
|
|
// Copy the device name into a C string with padding
|
|
// NOTE: No zero padding is needed because we pre-init the array to all 0s
|
|
let mut dev_cstr = [0i8; IF_NAMESIZE];
|
|
let dev_bytes: Vec<i8> = dev.chars().map(|c| c as i8).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]);
|
|
|
|
// Build an `ifreq` struct to send to the kernel
|
|
let mut ifr = ifreq {
|
|
ifr_name: dev_cstr,
|
|
ifr_ifru: __c_anonymous_ifr_ifru {
|
|
ifru_flags: (IFF_TUN | IFF_NO_PI) as i16,
|
|
},
|
|
};
|
|
|
|
// Make an ioctl call to create the TUN device
|
|
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 u64,
|
|
&mut ifr,
|
|
)
|
|
};
|
|
log::trace!("ioctl returned: {}", err);
|
|
|
|
// Check for errors
|
|
if err < 0 {
|
|
log::error!("ioctl failed: {}", err);
|
|
return Err(std::io::Error::last_os_error());
|
|
}
|
|
|
|
// Get the name of the device
|
|
let name = unsafe { std::ffi::CStr::from_ptr(ifr.ifr_name.as_ptr()) }
|
|
.to_str()
|
|
.unwrap()
|
|
.to_string();
|
|
|
|
// Build the TUN struct
|
|
Ok(Self { fd, name })
|
|
}
|
|
|
|
/// Get the name of the TUN device
|
|
#[must_use]
|
|
pub fn name(&self) -> &str {
|
|
&self.name
|
|
}
|
|
}
|
|
|
|
impl AsRawFd for Tun {
|
|
fn as_raw_fd(&self) -> RawFd {
|
|
self.fd.as_raw_fd()
|
|
}
|
|
}
|
|
|
|
impl IntoRawFd for Tun {
|
|
fn into_raw_fd(self) -> RawFd {
|
|
self.fd.into_raw_fd()
|
|
}
|
|
}
|
|
|
|
impl Read for Tun {
|
|
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
|
self.fd.read(buf)
|
|
}
|
|
}
|
|
|
|
impl Write for Tun {
|
|
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
|
self.fd.write(buf)
|
|
}
|
|
|
|
fn flush(&mut self) -> std::io::Result<()> {
|
|
self.fd.flush()
|
|
}
|
|
}
|