Last active
June 14, 2023 23:01
-
-
Save harrisonturton/f7c8bc2c0396bae08999563840bdf964 to your computer and use it in GitHub Desktop.
Persistent TUN/TAP device in Rust
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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