diff --git a/src/lib.rs b/src/lib.rs index 69d97c9..20cef0c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,6 +17,10 @@ mod http; pub mod setup; mod socks; mod tun2proxy; +#[cfg(any(target_os = "linux", target_os = "android"))] +mod tuntapinterface; +#[cfg(any(target_os = "linux", target_os = "android"))] +mod tuntapinterfacedesc; mod virtdevice; mod virtdns; diff --git a/src/tun2proxy.rs b/src/tun2proxy.rs index a2629c1..9905124 100644 --- a/src/tun2proxy.rs +++ b/src/tun2proxy.rs @@ -1,5 +1,7 @@ #![allow(dead_code)] +#[cfg(any(target_os = "linux", target_os = "android"))] +use crate::tuntapinterface::TunTapInterface; use crate::{dns, error::Error, error::Result, virtdevice::VirtualTunDevice, NetworkInterface, Options}; #[cfg(target_family = "unix")] use mio::unix::SourceFd; @@ -8,8 +10,8 @@ use mio::{event::Event, net::TcpStream, net::UdpSocket, Events, Interest, Poll, use smoltcp::phy::DeviceCapabilities; #[cfg(any(target_os = "macos", target_os = "ios"))] use smoltcp::phy::RawSocket; -#[cfg(any(target_os = "linux", target_os = "android"))] -use smoltcp::phy::TunTapInterface; +// #[cfg(any(target_os = "linux", target_os = "android"))] +// use smoltcp::phy::TunTapInterface; #[cfg(target_family = "unix")] use smoltcp::phy::{Device, Medium, RxToken, TxToken}; use smoltcp::{ diff --git a/src/tuntapinterface.rs b/src/tuntapinterface.rs new file mode 100644 index 0000000..10710f9 --- /dev/null +++ b/src/tuntapinterface.rs @@ -0,0 +1,129 @@ +use crate::tuntapinterfacedesc::TunTapInterfaceDesc; +use smoltcp::{ + phy::{self, Device, DeviceCapabilities, Medium}, + time::Instant, +}; +use std::{ + cell::RefCell, + io, + os::unix::io::{AsRawFd, RawFd}, + rc::Rc, + vec::Vec, +}; + +/// A virtual TUN (IP) or TAP (Ethernet) interface. +#[derive(Debug)] +pub struct TunTapInterface { + lower: Rc>, + mtu: usize, + medium: Medium, +} + +impl AsRawFd for TunTapInterface { + fn as_raw_fd(&self) -> RawFd { + self.lower.borrow().as_raw_fd() + } +} + +impl TunTapInterface { + /// Attaches to a TUN/TAP interface called `name`, or creates it if it does not exist. + /// + /// If `name` is a persistent interface configured with UID of the current user, + /// no special privileges are needed. Otherwise, this requires superuser privileges + /// or a corresponding capability set on the executable. + pub fn new(name: &str, medium: Medium) -> io::Result { + let lower = TunTapInterfaceDesc::new(name, medium)?; + let mtu = lower.interface_mtu()?; + Ok(TunTapInterface { + lower: Rc::new(RefCell::new(lower)), + mtu, + medium, + }) + } + + /// Attaches to a TUN/TAP interface specified by file descriptor `fd`. + /// + /// On platforms like Android, a file descriptor to a tun interface is exposed. + /// On these platforms, a TunTapInterface cannot be instantiated with a name. + pub fn from_fd(fd: RawFd, medium: Medium, mtu: usize) -> io::Result { + let lower = TunTapInterfaceDesc::from_fd(fd, mtu)?; + Ok(TunTapInterface { + lower: Rc::new(RefCell::new(lower)), + mtu, + medium, + }) + } +} + +impl Device for TunTapInterface { + type RxToken<'a> = RxToken; + type TxToken<'a> = TxToken; + + fn capabilities(&self) -> DeviceCapabilities { + let mut v = DeviceCapabilities::default(); + v.max_transmission_unit = self.mtu; + v.medium = self.medium; + v + } + + fn receive(&mut self, _timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { + let mut lower = self.lower.borrow_mut(); + let mut buffer = vec![0; self.mtu]; + match lower.recv(&mut buffer[..]) { + Ok(size) => { + buffer.resize(size, 0); + let rx = RxToken { buffer }; + let tx = TxToken { + lower: self.lower.clone(), + }; + Some((rx, tx)) + } + Err(err) if err.kind() == io::ErrorKind::WouldBlock => None, + Err(err) => panic!("{}", err), + } + } + + fn transmit(&mut self, _timestamp: Instant) -> Option> { + Some(TxToken { + lower: self.lower.clone(), + }) + } +} + +#[doc(hidden)] +pub struct RxToken { + buffer: Vec, +} + +impl phy::RxToken for RxToken { + fn consume(mut self, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + f(&mut self.buffer[..]) + } +} + +#[doc(hidden)] +pub struct TxToken { + lower: Rc>, +} + +impl phy::TxToken for TxToken { + fn consume(self, len: usize, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + let mut lower = self.lower.borrow_mut(); + let mut buffer = vec![0; len]; + let result = f(&mut buffer); + match lower.send(&buffer[..]) { + Ok(_) => {} + Err(err) if err.kind() == io::ErrorKind::WouldBlock => { + log::error!("phy: tx failed due to WouldBlock") + } + Err(err) => panic!("{}", err), + } + result + } +} diff --git a/src/tuntapinterfacedesc.rs b/src/tuntapinterfacedesc.rs new file mode 100644 index 0000000..75ea2ce --- /dev/null +++ b/src/tuntapinterfacedesc.rs @@ -0,0 +1,154 @@ +use smoltcp::{phy::Medium, wire::EthernetFrame}; +use std::{ + io, + os::unix::io::{AsRawFd, RawFd}, +}; + +#[derive(Debug)] +pub struct TunTapInterfaceDesc { + lower: libc::c_int, + mtu: usize, +} + +impl AsRawFd for TunTapInterfaceDesc { + fn as_raw_fd(&self) -> RawFd { + self.lower + } +} + +impl TunTapInterfaceDesc { + pub fn new(name: &str, medium: Medium) -> io::Result { + let lower = unsafe { + let lower = libc::open( + "/dev/net/tun\0".as_ptr() as *const libc::c_char, + libc::O_RDWR | libc::O_NONBLOCK, + ); + if lower == -1 { + return Err(io::Error::last_os_error()); + } + lower + }; + + let mut ifreq = ifreq_for(name); + Self::attach_interface_ifreq(lower, medium, &mut ifreq)?; + let mtu = Self::mtu_ifreq(medium, &mut ifreq)?; + + Ok(TunTapInterfaceDesc { lower, mtu }) + } + + pub fn from_fd(fd: RawFd, mtu: usize) -> io::Result { + Ok(TunTapInterfaceDesc { lower: fd, mtu }) + } + + fn attach_interface_ifreq(lower: libc::c_int, medium: Medium, ifr: &mut Ifreq) -> io::Result<()> { + let mode = match medium { + Medium::Ip => imp::IFF_TUN, + Medium::Ethernet => imp::IFF_TAP, + Medium::Ieee802154 => todo!(), + }; + ifr.ifr_data = mode | imp::IFF_NO_PI; + ifreq_ioctl(lower, ifr, imp::TUNSETIFF).map(|_| ()) + } + + fn mtu_ifreq(medium: Medium, ifr: &mut Ifreq) -> io::Result { + let lower = unsafe { + let lower = libc::socket(libc::AF_INET, libc::SOCK_DGRAM, libc::IPPROTO_IP); + if lower == -1 { + return Err(io::Error::last_os_error()); + } + lower + }; + + let ip_mtu = ifreq_ioctl(lower, ifr, imp::SIOCGIFMTU).map(|mtu| mtu as usize); + + unsafe { + libc::close(lower); + } + + // Propagate error after close, to ensure we always close. + let ip_mtu = ip_mtu?; + + // SIOCGIFMTU returns the IP MTU (typically 1500 bytes.) + // smoltcp counts the entire Ethernet packet in the MTU, so add the Ethernet header size to it. + let mtu = match medium { + Medium::Ip => ip_mtu, + Medium::Ethernet => ip_mtu + EthernetFrame::<&[u8]>::header_len(), + Medium::Ieee802154 => todo!(), + }; + + Ok(mtu) + } + + pub fn interface_mtu(&self) -> io::Result { + Ok(self.mtu) + } + + pub fn recv(&mut self, buffer: &mut [u8]) -> io::Result { + unsafe { + let len = libc::read(self.lower, buffer.as_mut_ptr() as *mut libc::c_void, buffer.len()); + if len == -1 { + return Err(io::Error::last_os_error()); + } + Ok(len as usize) + } + } + + pub fn send(&mut self, buffer: &[u8]) -> io::Result { + unsafe { + let len = libc::write(self.lower, buffer.as_ptr() as *const libc::c_void, buffer.len()); + if len == -1 { + return Err(io::Error::last_os_error()); + } + Ok(len as usize) + } + } +} + +impl Drop for TunTapInterfaceDesc { + fn drop(&mut self) { + unsafe { + libc::close(self.lower); + } + } +} + +#[repr(C)] +#[derive(Debug)] +struct Ifreq { + ifr_name: [libc::c_char; libc::IF_NAMESIZE], + ifr_data: libc::c_int, /* ifr_ifindex or ifr_mtu */ +} + +fn ifreq_for(name: &str) -> Ifreq { + let mut ifreq = Ifreq { + ifr_name: [0; libc::IF_NAMESIZE], + ifr_data: 0, + }; + for (i, byte) in name.as_bytes().iter().enumerate() { + ifreq.ifr_name[i] = *byte as libc::c_char + } + ifreq +} + +fn ifreq_ioctl(lower: libc::c_int, ifreq: &mut Ifreq, cmd: libc::c_ulong) -> io::Result { + unsafe { + let res = libc::ioctl(lower, cmd as _, ifreq as *mut Ifreq); + if res == -1 { + return Err(io::Error::last_os_error()); + } + } + + Ok(ifreq.ifr_data) +} + +mod imp { + pub const SIOCGIFMTU: libc::c_ulong = 0x8921; + // pub const SIOCGIFINDEX: libc::c_ulong = 0x8933; + // pub const ETH_P_ALL: libc::c_short = 0x0003; + // pub const ETH_P_IEEE802154: libc::c_short = 0x00F6; + + pub const TUNSETIFF: libc::c_ulong = 0x400454CA; + pub const IFF_TUN: libc::c_int = 0x0001; + pub const IFF_TAP: libc::c_int = 0x0002; + pub const IFF_NO_PI: libc::c_int = 0x1000; +}