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.
This commit is contained in:
Conrad Kramer 2023-04-22 14:12:57 -04:00
parent 45499da9c2
commit 1378eb7eb3
15 changed files with 361 additions and 208 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,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<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],
@ -22,32 +26,18 @@ impl SysControlSocket for socket2::Socket {
let (_, addr) = unsafe {
socket2::SockAddr::init(|addr_storage, len| {
*len = size_of::<sockaddr_ctl>() as u32;
*len = size_of::<sys::sockaddr_ctl>() 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);
}

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

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

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

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

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

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

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

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

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