From 1378eb7eb389ef8c7c3ebc14ed274f20f49ebf99 Mon Sep 17 00:00:00 2001 From: Conrad Kramer Date: Sat, 22 Apr 2023 14:12:57 -0400 Subject: [PATCH] Implement IPv4 address configuration on Linux This involved refactoring the crate structure to share code between macOS and Linux. The new methods have not yet been implemented on macOS, but they have todo!() placeholders. --- Cargo.lock | 21 ++++++ tun/Cargo.toml | 1 + tun/src/apple/mod.rs | 90 ------------------------ tun/src/apple/queue.rs | 17 ----- tun/src/lib.rs | 11 +-- tun/src/linux.rs | 58 --------------- tun/src/unix.rs | 11 --- tun/src/{ => unix}/apple/kern_control.rs | 36 ++++------ tun/src/unix/apple/mod.rs | 76 ++++++++++++++++++++ tun/src/unix/apple/sys.rs | 35 +++++++++ tun/src/unix/linux/mod.rs | 84 ++++++++++++++++++++++ tun/src/unix/linux/sys.rs | 19 +++++ tun/src/unix/mod.rs | 29 ++++++++ tun/src/unix/queue.rs | 59 ++++++++++++++++ tun/tests/configure.rs | 22 ++++++ 15 files changed, 361 insertions(+), 208 deletions(-) delete mode 100644 tun/src/apple/mod.rs delete mode 100644 tun/src/apple/queue.rs delete mode 100644 tun/src/linux.rs delete mode 100644 tun/src/unix.rs rename tun/src/{ => unix}/apple/kern_control.rs (53%) create mode 100644 tun/src/unix/apple/mod.rs create mode 100644 tun/src/unix/apple/sys.rs create mode 100644 tun/src/unix/linux/mod.rs create mode 100644 tun/src/unix/linux/sys.rs create mode 100644 tun/src/unix/mod.rs create mode 100644 tun/src/unix/queue.rs create mode 100644 tun/tests/configure.rs 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