This commit is contained in:
Conrad Kramer 2023-04-17 11:58:31 -04:00
parent b37086e8f6
commit 1a13b77295
20 changed files with 767 additions and 468 deletions

View file

@ -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> {
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 = [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<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)
}
}};
}
}

View file

@ -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()
}
}

View file

@ -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};

View file

@ -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<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 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_uint>()),
libc::ifreq
);
}
pub struct TunQueue;

View file

@ -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()
}
}

View file

@ -1,5 +1,6 @@
use fehler::throws;
use libc::{sockaddr_ctl, AF_SYSTEM, AF_SYS_CONTROL};
use std::io::Result;
use std::io::Error;
use std::mem::size_of;
use std::os::unix::io::AsRawFd;
@ -7,11 +8,13 @@ use std::os::unix::io::AsRawFd;
///
/// 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>;
#[throws]
fn resolve(&self, name: &str, index: u32) -> socket2::SockAddr;
}
impl SysControlSocket for socket2::Socket {
fn resolve(&self, name: &str, index: u32) -> Result<socket2::SockAddr> {
#[throws]
fn resolve(&self, name: &str, index: u32) -> socket2::SockAddr {
let mut info = sys::ctl_info {
ctl_id: 0,
ctl_name: [0; 96],
@ -34,7 +37,7 @@ impl SysControlSocket for socket2::Socket {
})
}?;
Ok(addr)
addr
}
}

98
tun/src/unix/apple/mod.rs Normal file
View file

@ -0,0 +1,98 @@
use fehler::throws;
use libc::c_char;
use socket2::{Domain, SockAddr};
use std::mem;
use std::net::SocketAddrV4;
use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd};
mod kern_control;
mod sys;
pub use super::queue::TunQueue;
use super::{ifname_to_string, string_to_ifname};
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(libc::AF_SYSTEM),
Type::DGRAM,
Some(Protocol::from(libc::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; libc::IFNAMSIZ];
let mut len = buf.len() as libc::socklen_t;
sys::syscall!(getsockopt(
self.as_raw_fd(),
libc::SYSPROTO_CONTROL,
sys::UTUN_OPT_IFNAME,
buf.as_mut_ptr() as *mut libc::c_void,
&mut len,
))?;
ifname_to_string(buf)
}
#[throws]
pub fn index(&self) -> i32 {
let socket = Socket::new(Domain::IPV4, Type::DGRAM, None)?;
unsafe {
let mut iff: libc::ifreq = mem::zeroed();
iff.ifr_name = string_to_ifname(self.name()?);
sys::if_get_index(socket.as_raw_fd(), &mut iff)?;
iff.ifr_ifru.ifru_ifindex
}
}
#[throws]
pub fn set_addr(&self, addr: IpAddr) {
match addr {
IpAddr::V4(addr) => self.set_ipv4_addr(addr)?,
_ => (),
}
}
#[throws]
pub fn set_ipv4_addr(&self, addr: Ipv4Addr) {
let socket = Socket::new(Domain::IPV4, Type::DGRAM, None)?;
let addr = SockAddr::from(SocketAddrV4::new(addr, 0));
unsafe {
let mut iff: libc::ifreq = mem::zeroed();
iff.ifr_name = string_to_ifname(self.name()?);
iff.ifr_ifru.ifru_addr = *addr.as_ptr();
sys::if_set_addr(socket.as_raw_fd(), &iff)?;
}
}
}
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()
}
}

22
tun/src/unix/apple/sys.rs Normal file
View file

@ -0,0 +1,22 @@
pub const UTUN_CONTROL_NAME: &str = "com.apple.net.utun_control";
pub const UTUN_OPT_IFNAME: libc::c_int = 2;
#[repr(C)]
pub struct ifreq {}
/// 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;

87
tun/src/unix/linux/mod.rs Normal file
View file

@ -0,0 +1,87 @@
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(addr.sin_addr.s_addr)
}
#[throws]
fn perform<R>(&self, perform: impl FnOnce(RawFd) -> Result<R, nix::Error>) -> R {
let socket = Socket::new(Domain::IPV4, Type::DGRAM, None)?;
perform(socket.as_raw_fd())?
}
}

19
tun/src/unix/linux/sys.rs Normal file
View file

@ -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::c_int>()),
libc::ifreq
);
ioctl_read_bad!(
tun_get_iff,
request_code_read!(b'T', 210, size_of::<libc::c_uint>()),
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);

51
tun/src/unix/mod.rs Normal file
View file

@ -0,0 +1,51 @@
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
}
mod sys {
/// 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! call {
($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 call;
}

59
tun/src/unix/queue.rs Normal file
View file

@ -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<u8>]) -> 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<TunInterface> 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()
}
}