use std::{
ffi::CString,
fs::{File, OpenOptions},
io::{self, Read, Write},
mem::MaybeUninit,
os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd},
};
use super::{
TUN_DEVICE_PATH,
error::{Error, Result},
};
const TUNSETIFF: libc::c_ulong = 0x400454ca;
const TUNSETOWNER: libc::c_ulong = 0x400454cc;
const TUNSETGROUP: libc::c_ulong = 0x400454ce;
const TUNSETPERSIST: libc::c_ulong = 0x400454cb;
const TUNSETOFFLOAD: libc::c_ulong = 0x400454d0;
const TUNSETVNETHDRSZ: libc::c_ulong = 0x400454d8;
const IFF_TUN: libc::c_short = 0x0001;
const IFF_TAP: libc::c_short = 0x0002;
const IFF_NO_PI: libc::c_short = 0x1000;
const IFF_ONE_QUEUE: libc::c_short = 0x2000;
const IFF_VNET_HDR: libc::c_short = 0x4000;
const IFF_MULTI_QUEUE: libc::c_short = 0x0100;
#[allow(dead_code)]
const IFF_TUN_EXCL: libc::c_short = -0x8000;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Mode {
Tun,
Tap,
}
impl Mode {
fn flag(&self) -> libc::c_short {
match self {
Mode::Tun => IFF_TUN,
Mode::Tap => IFF_TAP,
}
}
pub fn name(&self) -> &'static str {
match self {
Mode::Tun => "tun",
Mode::Tap => "tap",
}
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct TunTapFlags {
pub no_pi: bool,
pub one_queue: bool,
pub vnet_hdr: bool,
pub multi_queue: bool,
}
impl TunTapFlags {
pub fn new() -> Self {
Self::default()
}
pub fn no_pi(mut self, value: bool) -> Self {
self.no_pi = value;
self
}
pub fn one_queue(mut self, value: bool) -> Self {
self.one_queue = value;
self
}
pub fn vnet_hdr(mut self, value: bool) -> Self {
self.vnet_hdr = value;
self
}
pub fn multi_queue(mut self, value: bool) -> Self {
self.multi_queue = value;
self
}
fn as_flags(&self) -> libc::c_short {
let mut flags: libc::c_short = 0;
if self.no_pi {
flags |= IFF_NO_PI;
}
if self.one_queue {
flags |= IFF_ONE_QUEUE;
}
if self.vnet_hdr {
flags |= IFF_VNET_HDR;
}
if self.multi_queue {
flags |= IFF_MULTI_QUEUE;
}
flags
}
}
#[derive(Debug, Clone)]
pub struct TunTapBuilder {
name: Option<String>,
mode: Option<Mode>,
owner: Option<u32>,
group: Option<u32>,
persistent: bool,
flags: TunTapFlags,
}
impl TunTapBuilder {
pub fn new() -> Self {
Self {
name: None,
mode: None,
owner: None,
group: None,
persistent: false,
flags: TunTapFlags {
no_pi: true, ..Default::default()
},
}
}
pub fn name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
pub fn mode(mut self, mode: Mode) -> Self {
self.mode = Some(mode);
self
}
pub fn owner(mut self, uid: u32) -> Self {
self.owner = Some(uid);
self
}
pub fn owner_name(mut self, name: &str) -> Result<Self> {
let uid = lookup_user(name)?;
self.owner = Some(uid);
Ok(self)
}
pub fn group(mut self, gid: u32) -> Self {
self.group = Some(gid);
self
}
pub fn group_name(mut self, name: &str) -> Result<Self> {
let gid = lookup_group(name)?;
self.group = Some(gid);
Ok(self)
}
pub fn persistent(mut self, persistent: bool) -> Self {
self.persistent = persistent;
self
}
pub fn no_pi(mut self, value: bool) -> Self {
self.flags.no_pi = value;
self
}
pub fn one_queue(mut self, value: bool) -> Self {
self.flags.one_queue = value;
self
}
pub fn vnet_hdr(mut self, value: bool) -> Self {
self.flags.vnet_hdr = value;
self
}
pub fn multi_queue(mut self, value: bool) -> Self {
self.flags.multi_queue = value;
self
}
pub fn flags(mut self, flags: TunTapFlags) -> Self {
self.flags = flags;
self
}
pub fn create(self) -> Result<TunTap> {
let mode = self.mode.ok_or(Error::NoModeSpecified)?;
if let Some(ref name) = self.name
&& name.len() > libc::IFNAMSIZ - 1
{
return Err(Error::NameTooLong {
name: name.clone(),
len: name.len(),
});
}
let file = OpenOptions::new()
.read(true)
.write(true)
.open(TUN_DEVICE_PATH)?;
let fd = file.as_raw_fd();
let mut ifr = {
let uninit = MaybeUninit::<libc::ifreq>::zeroed();
unsafe { uninit.assume_init() }
};
ifr.ifr_ifru.ifru_flags = mode.flag() | self.flags.as_flags();
if let Some(ref name) = self.name {
let name_bytes = name.as_bytes();
let name_slice =
unsafe { &mut *(&mut ifr.ifr_name as *mut [libc::c_char] as *mut [u8]) };
name_slice[..name_bytes.len()].copy_from_slice(name_bytes);
}
let ret = unsafe { libc::ioctl(fd, TUNSETIFF, &ifr) };
if ret < 0 {
return Err(Error::ioctl("TUNSETIFF", io::Error::last_os_error()));
}
if let Some(uid) = self.owner {
let ret = unsafe { libc::ioctl(fd, TUNSETOWNER, uid as libc::c_ulong) };
if ret < 0 {
return Err(Error::ioctl("TUNSETOWNER", io::Error::last_os_error()));
}
}
if let Some(gid) = self.group {
let ret = unsafe { libc::ioctl(fd, TUNSETGROUP, gid as libc::c_ulong) };
if ret < 0 {
return Err(Error::ioctl("TUNSETGROUP", io::Error::last_os_error()));
}
}
if self.persistent {
let ret = unsafe { libc::ioctl(fd, TUNSETPERSIST, 1 as libc::c_int) };
if ret < 0 {
return Err(Error::ioctl("TUNSETPERSIST", io::Error::last_os_error()));
}
}
let name = unsafe {
let name_slice = &*(&ifr.ifr_name as *const [libc::c_char] as *const [u8]);
let len = name_slice
.iter()
.position(|&c| c == 0)
.unwrap_or(name_slice.len());
String::from_utf8_lossy(&name_slice[..len]).to_string()
};
Ok(TunTap {
file,
name,
mode,
persistent: self.persistent,
})
}
pub fn create_persistent(self) -> Result<String> {
let device = self.persistent(true).create()?;
let name = device.name.clone();
Ok(name)
}
}
impl Default for TunTapBuilder {
fn default() -> Self {
Self::new()
}
}
pub struct TunTap {
file: File,
name: String,
mode: Mode,
persistent: bool,
}
impl TunTap {
pub fn builder() -> TunTapBuilder {
TunTapBuilder::new()
}
pub fn name(&self) -> &str {
&self.name
}
pub fn mode(&self) -> Mode {
self.mode
}
pub fn is_persistent(&self) -> bool {
self.persistent
}
pub fn as_raw_fd(&self) -> RawFd {
self.file.as_raw_fd()
}
pub fn set_vnet_hdr_size(&self, size: i32) -> Result<()> {
let ret = unsafe { libc::ioctl(self.file.as_raw_fd(), TUNSETVNETHDRSZ, &size) };
if ret < 0 {
return Err(Error::ioctl("TUNSETVNETHDRSZ", io::Error::last_os_error()));
}
Ok(())
}
pub fn set_offload(&self, flags: u32) -> Result<()> {
let ret =
unsafe { libc::ioctl(self.file.as_raw_fd(), TUNSETOFFLOAD, flags as libc::c_ulong) };
if ret < 0 {
return Err(Error::ioctl("TUNSETOFFLOAD", io::Error::last_os_error()));
}
Ok(())
}
pub fn set_persistent(&mut self, persistent: bool) -> Result<()> {
let value = if persistent { 1 } else { 0 };
let ret =
unsafe { libc::ioctl(self.file.as_raw_fd(), TUNSETPERSIST, value as libc::c_int) };
if ret < 0 {
return Err(Error::ioctl("TUNSETPERSIST", io::Error::last_os_error()));
}
self.persistent = persistent;
Ok(())
}
pub fn delete(self) -> Result<()> {
let ret = unsafe { libc::ioctl(self.file.as_raw_fd(), TUNSETPERSIST, 0 as libc::c_int) };
if ret < 0 {
return Err(Error::ioctl("TUNSETPERSIST", io::Error::last_os_error()));
}
Ok(())
}
pub fn delete_by_name(name: &str, mode: Mode) -> Result<()> {
let device = TunTapBuilder::new()
.name(name)
.mode(mode)
.no_pi(true)
.create()?;
device.delete()
}
pub fn read_packet(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.file.read(buf)
}
pub fn write_packet(&mut self, buf: &[u8]) -> io::Result<usize> {
self.file.write(buf)
}
pub fn into_file(self) -> File {
self.file
}
pub fn file(&self) -> &File {
&self.file
}
pub fn file_mut(&mut self) -> &mut File {
&mut self.file
}
}
impl Read for TunTap {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.file.read(buf)
}
}
impl Write for TunTap {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.file.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.file.flush()
}
}
impl AsRawFd for TunTap {
fn as_raw_fd(&self) -> RawFd {
self.file.as_raw_fd()
}
}
impl IntoRawFd for TunTap {
fn into_raw_fd(self) -> RawFd {
self.file.into_raw_fd()
}
}
impl FromRawFd for TunTap {
unsafe fn from_raw_fd(fd: RawFd) -> Self {
TunTap {
file: unsafe { File::from_raw_fd(fd) },
name: String::new(), mode: Mode::Tun, persistent: false,
}
}
}
fn lookup_user(name: &str) -> Result<u32> {
let name_cstr = CString::new(name).map_err(|_| Error::InvalidName(name.to_string()))?;
unsafe {
let pwd = libc::getpwnam(name_cstr.as_ptr());
if pwd.is_null() {
return Err(Error::UserNotFound(name.to_string()));
}
Ok((*pwd).pw_uid)
}
}
fn lookup_group(name: &str) -> Result<u32> {
let name_cstr = CString::new(name).map_err(|_| Error::InvalidName(name.to_string()))?;
unsafe {
let grp = libc::getgrnam(name_cstr.as_ptr());
if grp.is_null() {
return Err(Error::GroupNotFound(name.to_string()));
}
Ok((*grp).gr_gid)
}
}
#[allow(dead_code)]
pub fn list_devices() -> Result<Vec<TunTapInfo>> {
let mut devices = Vec::new();
let dir = match std::fs::read_dir("/sys/class/net") {
Ok(d) => d,
Err(e) if e.kind() == io::ErrorKind::NotFound => return Ok(devices),
Err(e) => return Err(e.into()),
};
for entry in dir {
let entry = entry?;
let name = entry.file_name().to_string_lossy().to_string();
let tun_flags_path = entry.path().join("tun_flags");
if tun_flags_path.exists() {
let flags_str = std::fs::read_to_string(&tun_flags_path)?;
let flags: u32 = flags_str.trim().parse().unwrap_or(0);
let mode = if flags & (IFF_TUN as u32) != 0 {
Mode::Tun
} else if flags & (IFF_TAP as u32) != 0 {
Mode::Tap
} else {
continue;
};
let owner = std::fs::read_to_string(entry.path().join("owner"))
.ok()
.and_then(|s| s.trim().parse().ok());
let group = std::fs::read_to_string(entry.path().join("group"))
.ok()
.and_then(|s| s.trim().parse().ok());
devices.push(TunTapInfo {
name,
mode,
owner,
group,
flags,
});
}
}
Ok(devices)
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct TunTapInfo {
pub name: String,
pub mode: Mode,
pub owner: Option<u32>,
pub group: Option<u32>,
pub flags: u32,
}
#[allow(dead_code)]
impl TunTapInfo {
pub fn no_pi(&self) -> bool {
self.flags & (IFF_NO_PI as u32) != 0
}
pub fn vnet_hdr(&self) -> bool {
self.flags & (IFF_VNET_HDR as u32) != 0
}
pub fn multi_queue(&self) -> bool {
self.flags & (IFF_MULTI_QUEUE as u32) != 0
}
}