Skip to content

Instantly share code, notes, and snippets.

@harrisonturton
Last active June 14, 2023 23:01
Show Gist options
  • Save harrisonturton/f7c8bc2c0396bae08999563840bdf964 to your computer and use it in GitHub Desktop.
Save harrisonturton/f7c8bc2c0396bae08999563840bdf964 to your computer and use it in GitHub Desktop.
Persistent TUN/TAP device in Rust
use nix::errno::Errno;
use nix::fcntl::{open, OFlag};
use nix::ioctl_write_int;
use nix::libc::{__c_anonymous_ifr_ifru, c_char, c_short, ifreq, IFNAMSIZ};
use nix::sys::stat::Mode;
use nix::unistd::close;
use std::ffi::{CString, NulError};
use std::os::fd::RawFd;
use std::ptr::copy_nonoverlapping;
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("name must be less than IFNAMSIZ characters long")]
NameTooLong(String),
#[error("failed to create CString with err {0}")]
FailedToCreateCString(NulError),
#[error("failed to open file with errno {0}")]
FailedToOpenFile(Errno),
#[error("failed to close file with errno {0}")]
FailedToCloseFile(Errno),
#[error("failed ioctl with errno {0}")]
FailedIoctl(Errno),
}
// Defined in <linux/if_tun.h>
// https://github.com/torvalds/linux/blob/master/include/uapi/linux/if_tun.h
ioctl_write_int!(ioctl_tun_set_iff, b'T', 202);
ioctl_write_int!(ioctl_tun_set_persist, b'T', 203);
ioctl_write_int!(ioctl_tun_set_owner, b'T', 204);
pub const IFF_TUN: c_short = 1;
pub const IFF_TAP: c_short = 2;
pub const IFF_NO_PI: c_short = 4096;
/// Create a new tun or tap device. Returns an error if the user does not have the
/// CAP_NET_ADMIN capability, `/dev/net/tun` is already opened by a different
/// process, or if the name is more than [IFNAMSIZ] characters long.
///
/// The device is created according to the value of `ifru_flags`. For example,
/// to create a tap device with no packet information, combine the [IFF_TAP] and
/// [IFF_NO_PI] flags:
///
/// ```rust
/// let tapfd = create("tap0", IFF_TAP | IFF_NO_PI)?;
/// ```
///
/// Returns a raw file descriptor to the device. The device will live until this
/// process dies; then it will be removed automatically. To make the device
/// outlive this process, see [persist].
///
/// The device can only be created by a user with the CAP_NET_ADMIN capability.
/// This is most easily achieved by executing this method as a process running
/// under the root user.
pub fn create(name: &str, ifru_flags: c_short) -> Result<RawFd, Error> {
let tunfd = open(
"/dev/net/tun",
OFlag::O_RDWR | OFlag::O_CLOEXEC,
Mode::empty(),
)
.map_err(Error::FailedToOpenFile)?;
let ifreq = ifreq {
ifr_name: to_ifr_name(name)?,
ifr_ifru: __c_anonymous_ifr_ifru { ifru_flags },
};
unsafe {
if let Err(err) = ioctl_tun_set_iff(tunfd, to_u64_ptr(&ifreq)) {
close(tunfd).map_err(Error::FailedToCloseFile)?;
return Err(Error::FailedIoctl(err));
}
}
Ok(tunfd)
}
/// Remove the tun/tap device.
pub fn remove(fd: RawFd) -> Result<(), Error> {
close(fd).map_err(Error::FailedToCloseFile)?;
Ok(())
}
/// Make the tun/tap device persistent between processes.
///
/// The following example creates a new tap device and makes it persistent:
///
/// ```rust
/// let tapfd = create("tap0", IFF_TAP);
/// persist(tapfd)?;
/// ```
pub fn persist(tunfd: RawFd) -> Result<(), Error> {
unsafe {
ioctl_tun_set_persist(tunfd, 1).map_err(Error::FailedIoctl)?;
Ok(())
}
}
fn to_ifr_name(name: &str) -> Result<[c_char; IFNAMSIZ], Error> {
if name.len() > IFNAMSIZ {
return Err(Error::NameTooLong(name.to_owned()));
}
let ffi_name = CString::new(name).map_err(Error::FailedToCreateCString)?;
let mut ifr_name: [c_char; IFNAMSIZ] = [0; IFNAMSIZ];
unsafe {
// Safe because we've checked that the length of name is no bigger than the
// ifr_name slice with length IFNAMSIZ
copy_nonoverlapping(ffi_name.as_ptr(), ifr_name.as_mut_ptr(), IFNAMSIZ);
}
Ok(ifr_name)
}
fn to_u64_ptr<T>(val: &T) -> u64 {
val as *const T as u64
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment