diff --git a/Cargo.lock b/Cargo.lock index 7fa5b6c..0817761 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -266,6 +266,26 @@ dependencies = [ "instant", ] +[[package]] +name = "fehler" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5729fe49ba028cd550747b6e62cd3d841beccab5390aa398538c31a2d983635" +dependencies = [ + "fehler-macros", +] + +[[package]] +name = "fehler-macros" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccb5acb1045ebbfa222e2c50679e392a71dd77030b78fb0189f2d9c5974400f9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "flate2" version = "1.0.24" @@ -1163,6 +1183,7 @@ version = "0.1.0" dependencies = [ "anyhow", "bindgen", + "fehler", "hex-literal", "libc", "libloading", diff --git a/tun/Cargo.toml b/tun/Cargo.toml index 9a6f8fd..f203870 100644 --- a/tun/Cargo.toml +++ b/tun/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" [dependencies] libc = "0.2" +fehler = "1.0" nix = { version = "0.25", features = ["ioctl"] } socket2 = "0.4" tokio = { version = "1.21", features = [] } diff --git a/tun/src/apple/mod.rs b/tun/src/apple/mod.rs deleted file mode 100644 index cce4ea1..0000000 --- a/tun/src/apple/mod.rs +++ /dev/null @@ -1,90 +0,0 @@ -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::connect(None) - } - - fn connect(addr: Option) -> Result { - 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 { - let mut buf = [0i8; 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 { - 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) - } - }}; - } -} diff --git a/tun/src/apple/queue.rs b/tun/src/apple/queue.rs deleted file mode 100644 index fdc23b6..0000000 --- a/tun/src/apple/queue.rs +++ /dev/null @@ -1,17 +0,0 @@ -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() - } -} diff --git a/tun/src/lib.rs b/tun/src/lib.rs index 7082e2d..7e86059 100644 --- a/tun/src/lib.rs +++ b/tun/src/lib.rs @@ -1,16 +1,9 @@ -#[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; +#[path = "unix/mod.rs"] +pub(crate) mod imp; pub use imp::{TunInterface, TunQueue}; diff --git a/tun/src/linux.rs b/tun/src/linux.rs deleted file mode 100644 index b1ef0ae..0000000 --- a/tun/src/linux.rs +++ /dev/null @@ -1,58 +0,0 @@ -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 { - let file = OpenOptions::new() - .read(true) - .write(true) - .open("/dev/net/tun")?; - - let 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(), &iff)? }; - - let inner = unsafe { socket2::Socket::from_raw_fd(file.into_raw_fd()) }; - Ok(TunInterface { inner }) - } - - pub fn name(&self) -> Result { - 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::ifreq - ); - ioctl_read_bad!( - tun_get_iff, - request_code_read!(b'T', 210, size_of::()), - libc::ifreq - ); -} - -pub struct TunQueue; diff --git a/tun/src/unix.rs b/tun/src/unix.rs deleted file mode 100644 index 17c425c..0000000 --- a/tun/src/unix.rs +++ /dev/null @@ -1,11 +0,0 @@ -use std::ffi::{c_char, CStr}; - -pub fn copy_if_name(buf: [c_char; 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() - } -} diff --git a/tun/src/apple/kern_control.rs b/tun/src/unix/apple/kern_control.rs similarity index 53% rename from tun/src/apple/kern_control.rs rename to tun/src/unix/apple/kern_control.rs index f913fb6..abc1e04 100644 --- a/tun/src/apple/kern_control.rs +++ b/tun/src/unix/apple/kern_control.rs @@ -1,17 +1,21 @@ -use libc::{sockaddr_ctl, AF_SYSTEM, AF_SYS_CONTROL}; -use std::io::Result; +use fehler::throws; +use std::io::Error; use std::mem::size_of; use std::os::unix::io::AsRawFd; +use super::sys; + /// 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; + #[throws] + fn resolve(&self, name: &str, index: u32) -> socket2::SockAddr; } impl SysControlSocket for socket2::Socket { - fn resolve(&self, name: &str, index: u32) -> Result { + #[throws] + fn resolve(&self, name: &str, index: u32) -> socket2::SockAddr { let mut info = sys::ctl_info { ctl_id: 0, ctl_name: [0; 96], @@ -22,32 +26,18 @@ impl SysControlSocket for socket2::Socket { let (_, addr) = unsafe { socket2::SockAddr::init(|addr_storage, len| { - *len = size_of::() as u32; + *len = size_of::() as u32; - let mut addr: &mut sockaddr_ctl = &mut *addr_storage.cast(); + let mut addr: &mut sys::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_family = sys::AF_SYSTEM as u8; + addr.ss_sysaddr = sys::AF_SYS_CONTROL as u16; addr.sc_id = info.ctl_id; addr.sc_unit = index; Ok(()) }) }?; - Ok(addr) + 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); -} diff --git a/tun/src/unix/apple/mod.rs b/tun/src/unix/apple/mod.rs new file mode 100644 index 0000000..178ec6a --- /dev/null +++ b/tun/src/unix/apple/mod.rs @@ -0,0 +1,76 @@ +use fehler::throws; +use libc::c_char; +use std::net::Ipv4Addr; +use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd}; +use std::io::Error; + +mod sys; +mod kern_control; + +pub use super::queue::TunQueue; + +use super::ifname_to_string; +use kern_control::SysControlSocket; + +#[derive(Debug)] +pub struct TunInterface { + pub(crate) socket: socket2::Socket, +} + +impl TunInterface { + #[throws] + pub fn new() -> TunInterface { + TunInterface::connect(0)? + } + + #[throws] + fn connect(index: u32) -> TunInterface { + use socket2::{Domain, Protocol, Socket, Type}; + + let socket = Socket::new( + Domain::from(sys::AF_SYSTEM), + Type::DGRAM, + Some(Protocol::from(sys::SYSPROTO_CONTROL)), + )?; + let addr = socket.resolve(sys::UTUN_CONTROL_NAME, index)?; + socket.connect(&addr)?; + + TunInterface { socket } + } + + #[throws] + pub fn name(&self) -> String { + let mut buf = [0 as c_char; sys::IFNAMSIZ]; + let mut len = buf.len() as sys::socklen_t; + sys::syscall!(getsockopt( + self.as_raw_fd(), + sys::SYSPROTO_CONTROL, + sys::UTUN_OPT_IFNAME, + buf.as_mut_ptr() as *mut sys::c_void, + &mut len, + ))?; + ifname_to_string(buf) + } + + #[throws] + pub fn set_ipv4_addr(&self, _addr: Ipv4Addr) { + todo!() + } + + #[throws] + pub fn ipv4_addr(&self) -> Ipv4Addr { + 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() + } +} diff --git a/tun/src/unix/apple/sys.rs b/tun/src/unix/apple/sys.rs new file mode 100644 index 0000000..ff909ed --- /dev/null +++ b/tun/src/unix/apple/sys.rs @@ -0,0 +1,35 @@ +pub use libc::{c_void, socklen_t, SYSPROTO_CONTROL, IFNAMSIZ, sockaddr_ctl, AF_SYSTEM, AF_SYS_CONTROL}; +use nix::ioctl_readwrite; + +pub const UTUN_CONTROL_NAME: &str = "com.apple.net.utun_control"; +pub const UTUN_OPT_IFNAME: libc::c_int = 2; + +pub const MAX_KCTL_NAME: usize = 96; + + #[repr(C)] +pub struct ctl_info { + pub ctl_id: u32, + pub ctl_name: [u8; MAX_KCTL_NAME], +} + +#[repr(C)] +pub struct ifreq {} + +ioctl_readwrite!(resolve_ctl_info, b'N', 3, ctl_info); + +/// 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) + } + }}; +} + +pub use syscall; diff --git a/tun/src/unix/linux/mod.rs b/tun/src/unix/linux/mod.rs new file mode 100644 index 0000000..cc80f97 --- /dev/null +++ b/tun/src/unix/linux/mod.rs @@ -0,0 +1,84 @@ +use fehler::throws; + +use socket2::{Domain, SockAddr, Socket, Type}; +use std::fs::OpenOptions; +use std::io::Error; +use std::mem; +use std::net::{Ipv4Addr, SocketAddrV4}; +use std::os::fd::RawFd; +use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd}; + +use super::{ifname_to_string, string_to_ifname}; + +mod sys; + +#[derive(Debug)] +pub struct TunInterface { + pub(crate) socket: socket2::Socket, +} + +impl TunInterface { + #[throws] + pub fn new() -> TunInterface { + let file = OpenOptions::new() + .read(true) + .write(true) + .open("/dev/net/tun")?; + + let 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(), &iff)? }; + + let socket = unsafe { socket2::Socket::from_raw_fd(file.into_raw_fd()) }; + TunInterface { socket } + } + + #[throws] + pub fn name(&self) -> String { + let mut iff = unsafe { mem::zeroed() }; + unsafe { sys::tun_get_iff(self.socket.as_raw_fd(), &mut iff)? }; + ifname_to_string(iff.ifr_name) + } + + #[throws] + fn ifreq(&self) -> sys::ifreq { + let mut iff: sys::ifreq = unsafe { mem::zeroed() }; + iff.ifr_name = string_to_ifname(&self.name()?); + iff + } + + #[throws] + pub fn index(&self) -> i32 { + let mut iff = self.ifreq()?; + self.perform(|fd| unsafe { sys::if_get_index(fd, &mut iff) })?; + unsafe { iff.ifr_ifru.ifru_ifindex } + } + + #[throws] + pub fn set_ipv4_addr(&self, addr: Ipv4Addr) { + let addr = SockAddr::from(SocketAddrV4::new(addr, 0)); + + let mut iff = self.ifreq()?; + iff.ifr_ifru.ifru_addr = unsafe { *addr.as_ptr() }; + + self.perform(|fd| unsafe { sys::if_set_addr(fd, &iff) })?; + } + + #[throws] + pub fn ipv4_addr(&self) -> Ipv4Addr { + let mut iff = self.ifreq()?; + self.perform(|fd| unsafe { sys::if_get_addr(fd, &mut iff) })?; + let addr = unsafe { *(&iff.ifr_ifru.ifru_addr as *const _ as *const sys::sockaddr_in) }; + Ipv4Addr::from(u32::from_be(addr.sin_addr.s_addr)) + } + + #[throws] + fn perform(&self, perform: impl FnOnce(RawFd) -> Result) -> R { + let socket = Socket::new(Domain::IPV4, Type::DGRAM, None)?; + perform(socket.as_raw_fd())? + } +} diff --git a/tun/src/unix/linux/sys.rs b/tun/src/unix/linux/sys.rs new file mode 100644 index 0000000..5df22d9 --- /dev/null +++ b/tun/src/unix/linux/sys.rs @@ -0,0 +1,19 @@ +use nix::{ioctl_read_bad, ioctl_write_ptr_bad, request_code_read, request_code_write}; +use std::mem::size_of; + +pub use libc::ifreq; +pub use libc::sockaddr_in; + +ioctl_write_ptr_bad!( + tun_set_iff, + request_code_write!(b'T', 202, size_of::()), + libc::ifreq +); +ioctl_read_bad!( + tun_get_iff, + request_code_read!(b'T', 210, size_of::()), + libc::ifreq +); +ioctl_read_bad!(if_get_index, libc::SIOCGIFINDEX, libc::ifreq); +ioctl_read_bad!(if_get_addr, libc::SIOCGIFADDR, libc::ifreq); +ioctl_write_ptr_bad!(if_set_addr, libc::SIOCSIFADDR, libc::ifreq); diff --git a/tun/src/unix/mod.rs b/tun/src/unix/mod.rs new file mode 100644 index 0000000..615e647 --- /dev/null +++ b/tun/src/unix/mod.rs @@ -0,0 +1,29 @@ +mod queue; + +#[cfg(target_vendor = "apple")] +#[path = "apple/mod.rs"] +mod imp; + +#[cfg(target_os = "linux")] +#[path = "linux/mod.rs"] +mod imp; + +pub use imp::TunInterface; +pub use queue::TunQueue; + +pub fn ifname_to_string(buf: [libc::c_char; libc::IFNAMSIZ]) -> String { + // TODO: Switch to `CStr::from_bytes_until_nul` when stabilized + unsafe { + std::ffi::CStr::from_ptr(buf.as_ptr() as *const _) + .to_str() + .unwrap() + .to_string() + } +} + +pub fn string_to_ifname(name: &str) -> [libc::c_char; libc::IFNAMSIZ] { + let mut buf = [0 as libc::c_char; libc::IFNAMSIZ]; + let len = name.len().min(buf.len()); + buf[..len].copy_from_slice(unsafe { &*(name.as_bytes() as *const _ as *const [libc::c_char]) }); + buf +} diff --git a/tun/src/unix/queue.rs b/tun/src/unix/queue.rs new file mode 100644 index 0000000..1288a3b --- /dev/null +++ b/tun/src/unix/queue.rs @@ -0,0 +1,59 @@ +use fehler::throws; + +use std::{ + io::{Error, Read, Write}, + mem::MaybeUninit, + os::unix::io::{AsRawFd, IntoRawFd, RawFd}, +}; + +use crate::TunInterface; + +pub struct TunQueue { + socket: socket2::Socket, +} + +impl TunQueue { + #[throws] + pub fn recv(&self, buf: &mut [MaybeUninit]) -> usize { + self.socket.recv(buf)? + } +} + +impl Read for TunQueue { + #[throws] + fn read(&mut self, buf: &mut [u8]) -> usize { + self.socket.read(buf)? + } +} + +impl Write for TunQueue { + #[throws] + fn write(&mut self, buf: &[u8]) -> usize { + self.socket.write(buf)? + } + + #[throws] + fn flush(&mut self) { + self.socket.flush()? + } +} + +impl From for TunQueue { + fn from(interface: TunInterface) -> TunQueue { + TunQueue { + socket: interface.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() + } +} diff --git a/tun/tests/configure.rs b/tun/tests/configure.rs new file mode 100644 index 0000000..29674b6 --- /dev/null +++ b/tun/tests/configure.rs @@ -0,0 +1,22 @@ +use fehler::throws; +use tun::TunInterface; +use std::io::Error; +use std::net::Ipv4Addr; + +#[test] +#[throws] +fn test_create() { + TunInterface::new()?; +} + +#[test] +#[throws] +fn test_set_get_ipv4() { + let tun = TunInterface::new()?; + + let addr = Ipv4Addr::new(10, 0, 0, 1); + tun.set_ipv4_addr(addr)?; + let result = tun.ipv4_addr()?; + + assert_eq!(addr, result); +} \ No newline at end of file