Initial commit

This commit is contained in:
Conrad Kramer 2023-04-10 16:49:23 -04:00
commit c1e7415871
56 changed files with 3225 additions and 0 deletions

24
tun/Cargo.toml Normal file
View file

@ -0,0 +1,24 @@
[package]
name = "tun"
version = "0.1.0"
edition = "2021"
[dependencies]
libc = "0.2"
nix = { version = "0.25", features = ["ioctl"] }
socket2 = "0.4"
tokio = { version = "1.21", features = [] }
[target.'cfg(windows)'.dependencies]
libloading = "0.7"
widestring = "1.0"
[target.'cfg(windows)'.build-dependencies]
anyhow = "1.0"
bindgen = "0.61"
hex-literal = "0.3"
platforms = "3.0"
reqwest = { version = "0.11", features = ["native-tls"] }
sha2 = "0.10"
tokio = { version = "1.21", features = ["rt"] }
zip = { version = "0.6", features = ["deflate"] }

79
tun/build.rs Normal file
View file

@ -0,0 +1,79 @@
#[cfg(windows)]
#[tokio::main(flavor = "current_thread")]
async fn main() -> anyhow::Result<()> {
use std::io::{Cursor, Read};
let buf = reqwest::get("https://www.wintun.net/builds/wintun-0.14.1.zip")
.await?
.bytes()
.await?;
assert_content_hash(
&buf,
hex_literal::hex!("07c256185d6ee3652e09fa55c0b673e2624b565e02c4b9091c79ca7d2f24ef51"),
);
let mut archive = zip::ZipArchive::new(Cursor::new(buf))?;
let out_dir = std::path::PathBuf::from(std::env::var("OUT_DIR")?);
let mut header = String::new();
archive
.by_name("wintun/include/wintun.h")?
.read_to_string(&mut header)?;
header.push_str(
"WINTUN_CLOSE_ADAPTER_FUNC WintunCloseAdapter;
WINTUN_OPEN_ADAPTER_FUNC WintunOpenAdapter;
WINTUN_GET_ADAPTER_LUID_FUNC WintunGetAdapterLUID;
WINTUN_GET_RUNNING_DRIVER_VERSION_FUNC WintunGetRunningDriverVersion;
WINTUN_DELETE_DRIVER_FUNC WintunDeleteDriver;
WINTUN_SET_LOGGER_FUNC WintunSetLogger;
WINTUN_START_SESSION_FUNC WintunStartSession;
WINTUN_END_SESSION_FUNC WintunEndSession;
WINTUN_CREATE_ADAPTER_FUNC WintunCreateAdapter;
WINTUN_GET_READ_WAIT_EVENT_FUNC WintunGetReadWaitEvent;
WINTUN_RECEIVE_PACKET_FUNC WintunReceivePacket;
WINTUN_RELEASE_RECEIVE_PACKET_FUNC WintunReleaseReceivePacket;
WINTUN_ALLOCATE_SEND_PACKET_FUNC WintunAllocateSendPacket;
WINTUN_SEND_PACKET_FUNC WintunSendPacket;",
);
let bindings = bindgen::Builder::default()
.header_contents("wintun.h", &header)
.allowlist_function("Wintun.*")
.allowlist_type("WINTUN_.*")
.dynamic_library_name("wintun")
.dynamic_link_require_all(true)
.generate()
.unwrap();
bindings.write_to_file(out_dir.join("wintun.rs"))?;
let mut library = Vec::new();
let platform = platforms::Platform::find(&env::var("TARGET")?).unwrap();
let arch = match platform.target_arch {
platforms::target::Arch::Arm => "arm",
platforms::Arch::AArch64 => "arm64",
platforms::Arch::X86 => "x86",
platforms::Arch::X86_64 => "amd64",
arch => panic!("{} is not a supported architecture", arch),
};
archive
.by_name(&format!("wintun/bin/{}/wintun.dll", arch))?
.read_to_end(&mut library)?;
std::fs::write(out_dir.join("wintun.dll"), library)?;
println!("cargo:rerun-if-changed=build.rs");
Ok(())
}
#[cfg(not(windows))]
fn main() {
println!("cargo:rerun-if-changed=build.rs");
}
#[cfg(windows)]
fn assert_content_hash(content: &[u8], hash: [u8; 32]) {
use sha2::digest::Update;
use sha2::Digest;
let computed = sha2::Sha256::new().chain(content).finalize();
assert_eq!(computed.as_slice(), &hash[..]);
}

View file

@ -0,0 +1,53 @@
use libc::{sockaddr_ctl, AF_SYSTEM, AF_SYS_CONTROL};
use std::io::Result;
use std::mem::size_of;
use std::os::unix::io::AsRawFd;
/// Trait to connect to kernel extensions on Apple platforms
///
/// Pulled from XNU source: https://github.com/apple/darwin-xnu/blob/main/bsd/sys/kern_control.h
pub trait SysControlSocket {
fn resolve(&self, name: &str, index: u32) -> Result<socket2::SockAddr>;
}
impl SysControlSocket for socket2::Socket {
fn resolve(&self, name: &str, index: u32) -> Result<socket2::SockAddr> {
let mut info = sys::ctl_info {
ctl_id: 0,
ctl_name: [0; 96],
};
info.ctl_name[..name.len()].copy_from_slice(name.as_bytes());
unsafe { sys::resolve_ctl_info(self.as_raw_fd(), &mut info as *mut sys::ctl_info)? };
let (_, addr) = unsafe {
socket2::SockAddr::init(|addr_storage, len| {
*len = size_of::<sockaddr_ctl>() as u32;
let mut addr: &mut sockaddr_ctl = &mut *addr_storage.cast();
addr.sc_len = *len as u8;
addr.sc_family = AF_SYSTEM as u8;
addr.ss_sysaddr = AF_SYS_CONTROL as u16;
addr.sc_id = info.ctl_id;
addr.sc_unit = index;
Ok(())
})
}?;
Ok(addr)
}
}
mod sys {
use nix::ioctl_readwrite;
const MAX_KCTL_NAME: usize = 96;
#[repr(C)]
pub struct ctl_info {
pub ctl_id: u32,
pub ctl_name: [u8; MAX_KCTL_NAME],
}
ioctl_readwrite!(resolve_ctl_info, b'N', 3, ctl_info);
}

90
tun/src/apple/mod.rs Normal file
View file

@ -0,0 +1,90 @@
use socket2::SockAddr;
use std::io::Result;
use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd};
mod kern_control;
mod queue;
pub use queue::TunQueue;
use crate::syscall;
use crate::unix::copy_if_name;
use kern_control::SysControlSocket;
pub struct TunInterface {
socket: socket2::Socket,
}
impl TunInterface {
pub fn new() -> Result<TunInterface> {
TunInterface::connect(None)
}
fn connect(addr: Option<SockAddr>) -> Result<TunInterface> {
use socket2::{Domain, Protocol, Socket, Type};
let socket = Socket::new(
Domain::from(libc::AF_SYSTEM),
Type::DGRAM,
Some(Protocol::from(libc::SYSPROTO_CONTROL)),
)?;
let addr = match addr {
Some(addr) => addr,
None => socket.resolve(sys::UTUN_CONTROL_NAME, 0)?,
};
socket.connect(&addr)?;
Ok(TunInterface { socket })
}
pub fn name(&self) -> Result<String> {
let mut buf = [0u8; libc::IFNAMSIZ];
let mut len = buf.len() as libc::socklen_t;
syscall!(getsockopt(
self.as_raw_fd(),
libc::SYSPROTO_CONTROL,
sys::UTUN_OPT_IFNAME,
buf.as_mut_ptr() as *mut libc::c_void,
&mut len,
))?;
let name = copy_if_name(buf);
Ok(name)
}
pub fn queue(&self) -> Result<TunQueue> {
todo!()
}
}
impl AsRawFd for TunInterface {
fn as_raw_fd(&self) -> RawFd {
self.socket.as_raw_fd()
}
}
impl IntoRawFd for TunInterface {
fn into_raw_fd(self) -> RawFd {
self.socket.into_raw_fd()
}
}
mod sys {
pub const UTUN_CONTROL_NAME: &str = "com.apple.net.utun_control";
pub const UTUN_OPT_IFNAME: libc::c_int = 2;
/// Copied from https://github.com/rust-lang/socket2/blob/61314a231f73964b3db969ef72c0e9479df320f3/src/sys/unix.rs#L168-L178
/// getsockopt is not exposed by socket2
#[macro_export]
macro_rules! syscall {
($fn: ident ( $($arg: expr),* $(,)* ) ) => {{
#[allow(unused_unsafe)]
let res = unsafe { libc::$fn($($arg, )*) };
if res == -1 {
Err(std::io::Error::last_os_error())
} else {
Ok(res)
}
}};
}
}

17
tun/src/apple/queue.rs Normal file
View file

@ -0,0 +1,17 @@
use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd};
pub struct TunQueue {
socket: socket2::Socket,
}
impl AsRawFd for TunQueue {
fn as_raw_fd(&self) -> RawFd {
self.socket.as_raw_fd()
}
}
impl IntoRawFd for TunQueue {
fn into_raw_fd(self) -> RawFd {
self.socket.into_raw_fd()
}
}

16
tun/src/lib.rs Normal file
View file

@ -0,0 +1,16 @@
#[cfg(target_vendor = "apple")]
#[path = "apple/mod.rs"]
mod imp;
#[cfg(target_os = "linux")]
#[path = "linux.rs"]
mod imp;
#[cfg(target_os = "windows")]
#[path = "windows/mod.rs"]
mod imp;
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
pub(crate) mod unix;
pub use imp::{TunInterface, TunQueue};

56
tun/src/linux.rs Normal file
View file

@ -0,0 +1,56 @@
use std::fs::OpenOptions;
use std::io::Result;
use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd};
use crate::unix::copy_if_name;
pub struct TunInterface {
inner: socket2::Socket,
}
impl TunInterface {
pub fn new() -> Result<TunInterface> {
let file = OpenOptions::new()
.read(true)
.write(true)
.open("/dev/net/tun")?;
let mut iff = libc::ifreq {
ifr_name: [0; libc::IFNAMSIZ],
ifr_ifru: libc::__c_anonymous_ifr_ifru {
ifru_flags: (libc::IFF_TUN | libc::IFF_TUN_EXCL | libc::IFF_NO_PI) as i16,
},
};
unsafe { sys::tun_set_iff(file.as_raw_fd(), &mut iff)? };
let inner = unsafe { socket2::Socket::from_raw_fd(file.into_raw_fd()) };
Ok(TunInterface { inner })
}
pub fn name(&self) -> Result<String> {
let mut iff = libc::ifreq {
ifr_name: [0; libc::IFNAMSIZ],
ifr_ifru: libc::__c_anonymous_ifr_ifru { ifru_flags: 0 },
};
unsafe { sys::tun_get_iff(self.inner.as_raw_fd(), &mut iff)? };
let name = copy_if_name(iff.ifr_name);
Ok(name)
}
}
mod sys {
use nix::{ioctl_read_bad, ioctl_write_ptr_bad, request_code_read, request_code_write};
use std::mem::size_of;
ioctl_write_ptr_bad!(
tun_set_iff,
request_code_write!(b'T', 202, size_of::<libc::c_int>()),
libc::ifreq
);
ioctl_read_bad!(
tun_get_iff,
request_code_read!(b'T', 210, size_of::<libc::c_int>()),
libc::ifreq
);
}

1
tun/src/queue.rs Normal file
View file

@ -0,0 +1 @@

11
tun/src/unix.rs Normal file
View file

@ -0,0 +1,11 @@
use std::ffi::CStr;
pub fn copy_if_name(buf: [u8; libc::IFNAMSIZ]) -> String {
// TODO: Switch to `CStr::from_bytes_until_nul` when stabilized
unsafe {
CStr::from_ptr(buf.as_ptr() as *const _)
.to_str()
.unwrap()
.to_string()
}
}

44
tun/src/windows/mod.rs Normal file
View file

@ -0,0 +1,44 @@
use std::io::Result;
use std::ptr;
use widestring::{u16cstr, U16CString};
pub struct TunInterface {
wintun: sys::wintun,
handle: sys::WINTUN_ADAPTER_HANDLE,
name: String,
}
impl TunInterface {
pub fn new() -> Result<TunInterface> {
let name = U16CString::from(u16cstr!("ConradNet"));
let wintun = sys::wintun::default();
let handle =
unsafe { wintun.WintunCreateAdapter(name.as_ptr(), name.as_ptr(), ptr::null()) };
Ok(TunInterface {
wintun,
handle,
name: String::from("ConradNet"),
})
}
pub fn name(&self) -> String {
self.name.clone()
}
}
impl Drop for TunInterface {
fn drop(&mut self) {
unsafe { self.wintun.WintunCloseAdapter(self.handle) }
}
}
pub(crate) mod sys {
#![allow(dead_code, non_camel_case_types, non_snake_case)]
include!(concat!(env!("OUT_DIR"), "/wintun.rs"));
impl Default for wintun {
fn default() -> Self {
unsafe { wintun::new(format!("{}/wintun.dll", env!("OUT_DIR"))).unwrap() }
}
}
}

19
tun/src/windows/queue.rs Normal file
View file

@ -0,0 +1,19 @@
use crate::TunInterface;
use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd};
pub struct TunQueue {
inner: crate::windows::sys::
inner: socket2::Socket,
}
impl AsRawFd for TunQueue {
fn as_raw_fd(&self) -> RawFd {
self.socket.as_raw_fd()
}
}
impl IntoRawFd for TunQueue {
fn into_raw_fd(self) -> RawFd {
self.socket.into_raw_fd()
}
}