Compare commits

..

No commits in common. "8fefd54d9095e94c2af3fd245c0aeba5c93cf7ad" and "b37086e8f6ac09fc1811b6aa0dc82b6c1bf31581" have entirely different histories.

20 changed files with 467 additions and 805 deletions

View file

@ -7,7 +7,7 @@
"files.trimTrailingWhitespace": true,
"editor.suggest.preview": true,
"editor.acceptSuggestionOnEnter": "on",
"rust-analyzer.check.command": "clippy",
"rust-analyzer.checkOnSave.command": "clippy",
"rust-analyzer.restartServerOnConfigChange": true,
"rust-analyzer.cargo.features": "all",
"[rust]": {

611
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -3,8 +3,8 @@ name = "burrow"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
fehler = "1.0"
tokio = { version = "1.21", features = ["rt", "macros"] }
tun = { version = "0.1", path = "../tun" }
tun-async = { version = "0.1", path = "../tun-async" }

View file

@ -1,23 +1,14 @@
use std::{mem::MaybeUninit, net::Ipv4Addr};
use tokio::io::Result;
use tun::TunInterface;
use tun::TunQueue;
async fn try_main() -> Result<()> {
let iface = tun::create_interface();
iface.set_ipv4(Ipv4Addr::new(10, 0, 0, 2))?;
async fn lol() -> Result<()> {
let iface = TunInterface::new()?;
println!("{:?}", iface.name());
let queue = TunQueue::from(iface);
loop {
let mut buf = [MaybeUninit::<u8>::uninit(); 1500];
let len = queue.recv(&mut buf)?;
println!("Received {len} bytes");
}
Ok(())
}
#[tokio::main(flavor = "current_thread")]
async fn main() {
try_main().await.unwrap();
lol().await.unwrap();
}

View file

@ -3,11 +3,7 @@ name = "tun-async"
version = "0.1.0"
edition = "2021"
[features]
default = ["tokio"]
tokio = ["dep:tokio"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
fehler = "1.0"
tun = { path = "../tun" }
tokio = { version = "1.0", features = ["net"], optional = true }
tun = { version = "0.1", path = "../tun" }

View file

@ -1,5 +1,14 @@
#[cfg(feature = "tokio")]
#[path = "tokio/mod.rs"]
pub(crate) mod imp;
pub fn add(left: usize, right: usize) -> usize {
left + right
}
pub use imp::TunQueue;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}

View file

@ -1,29 +0,0 @@
use fehler::throws;
use std::{
io::{self, Error},
mem::MaybeUninit,
};
use tokio::io::unix::AsyncFd;
pub struct TunQueue {
io: AsyncFd<tun::TunQueue>,
}
impl TunQueue {
#[throws]
pub fn from_queue(queue: tun::TunQueue) -> Self {
Self {
io: AsyncFd::new(queue)?,
}
}
pub async fn try_recv(&self, buf: &mut [MaybeUninit<u8>]) -> io::Result<usize> {
loop {
let mut guard = self.io.readable().await?;
match guard.try_io(|inner| inner.get_ref().recv(buf)) {
Ok(result) => return result,
Err(..) => continue,
}
}
}
}

View file

@ -5,7 +5,6 @@ edition = "2021"
[dependencies]
libc = "0.2"
fehler = "1.0"
nix = { version = "0.25", features = ["ioctl"] }
socket2 = "0.4"
tokio = { version = "1.21", features = [] }
@ -21,5 +20,5 @@ hex-literal = "0.3"
platforms = "3.0"
reqwest = { version = "0.11", features = ["native-tls"] }
sha2 = "0.10"
tokio = { version = "1.0", features = ["rt"] }
tokio = { version = "1.21", features = ["rt"] }
zip = { version = "0.6", features = ["deflate"] }

View file

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

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

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

View file

@ -1,42 +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"))]
#[path = "unix/mod.rs"]
pub(crate) mod imp;
pub(crate) mod unix;
/**
* Standard platform-independent interface for a tunnel.
*/
pub trait TunInterface {
/**
* Sets the interface IP address. Accepts either IPv6 or IPv4
*/
fn set_ip(&self, ip: IpAddr) -> Result<(), std::io::Error>;
/**
* Sets the interface IP address to an IPv4 address.
*
* Used by [set_ip](TunInterface::set_ip)
*/
fn set_ipv4(&self, ip: Ipv4Addr) -> Result<(), std::io::Error>;
fn get_ip(&self) -> Result<IpAddr, std::io::Error>;
fn get_interface_name(&self) -> Result<String, std::io::Error>;
fn into_raw_socket(self) -> socket2::Socket;
}
#[cfg(target_os = "linux")]
pub fn create_interface() -> impl TunInterface {
PlatformTun::new().unwrap()
}
use std::{
net::{IpAddr, Ipv4Addr},
sync::Mutex,
};
pub use imp::{PlatformTun, TunQueue};
pub use imp::{TunInterface, TunQueue};

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

@ -0,0 +1,58 @@
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;

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

@ -0,0 +1,11 @@
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,98 +0,0 @@
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()
}
}

View file

@ -1,22 +0,0 @@
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;

View file

@ -1,93 +0,0 @@
use fehler::throws;
use socket2::{Domain, SockAddr, Socket, Type};
use std::fs::OpenOptions;
use std::io::Error;
use std::mem;
use std::net::{IpAddr, Ipv4Addr, SocketAddrV4};
use std::os::fd::RawFd;
use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd};
use std::ptr;
use std::sync::{Arc, Mutex};
use super::{ifname_to_string, string_to_ifname};
use crate::TunInterface;
mod sys;
#[derive(Debug)]
pub struct PlatformTun {
pub(crate) socket: socket2::Socket,
}
impl PlatformTun {
#[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.get_interface_name()?);
sys::if_get_index(socket.as_raw_fd(), &mut iff)?;
iff.ifr_ifru.ifru_ifindex
}
}
#[throws]
pub(crate) fn new() -> PlatformTun {
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()) };
PlatformTun { socket }
}
}
impl TunInterface for PlatformTun {
#[throws]
fn get_interface_name(&self) -> String {
unsafe {
let mut iff = mem::zeroed();
sys::tun_get_iff(self.socket.as_raw_fd(), &mut iff)?;
ifname_to_string(iff.ifr_name)
}
}
#[throws]
fn set_ip(&self, addr: IpAddr) {
match addr {
IpAddr::V4(addr) => self.set_ipv4(addr)?,
_ => (),
}
}
#[throws]
fn set_ipv4(&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.get_interface_name()?);
iff.ifr_ifru.ifru_addr = *addr.as_ptr();
sys::if_set_addr(socket.as_raw_fd(), &iff)?;
}
}
#[throws]
fn get_ip(&self) -> IpAddr {
let addr = self.socket.local_addr()?;
addr.as_socket().unwrap().ip()
}
fn into_raw_socket(self) -> socket2::Socket {
self.socket
}
}

View file

@ -1,19 +0,0 @@
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);

View file

@ -1,51 +0,0 @@
mod queue;
#[cfg(target_vendor = "apple")]
#[path = "apple/mod.rs"]
mod imp;
#[cfg(target_os = "linux")]
#[path = "linux/mod.rs"]
mod imp;
pub use crate::TunInterface;
pub use imp::PlatformTun;
use libc::IFNAMSIZ;
pub use queue::TunQueue;
use std::ffi::CString;
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;
}

View file

@ -1,61 +0,0 @@
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 TunQueue {
pub fn from<T: TunInterface>(interface: T) -> TunQueue {
TunQueue {
socket: interface.into_raw_socket(),
}
}
}
impl AsRawFd for TunQueue {
fn as_raw_fd(&self) -> RawFd {
self.socket.as_raw_fd()
}
}
//TODO: error[E0507]: cannot move out of `*self.socket` which is behind a mutable reference
//impl IntoRawFd for TunQueue {
// fn into_raw_fd(self) -> RawFd {
// self.socket.into_raw_fd()
// }
//}