use std::{
fs::File,
io::{self, Read, Write},
os::fd::AsFd,
};
#[cfg(target_os = "linux")]
use std::{fs::OpenOptions, io::Error, os::unix::io::AsRawFd};
#[cfg(target_os = "linux")]
use libc::{IFF_NO_PI, IFF_TAP, ifreq};
use nix::poll::{PollFd, PollFlags, PollTimeout, poll};
#[cfg(target_os = "linux")]
use nix::{ifaddrs::getifaddrs, ioctl_write_int};
use crate::net::{NetworkInterface, NetworkInterfaceRX, NetworkInterfaceTX};
pub(crate) const TUN_PATH: &str = "/dev/net/tun";
pub struct Tap {
fd: File,
mac: [u8; 6],
name: String,
}
impl Tap {
#[cfg(target_os = "linux")]
pub fn new(iface_name: &str) -> io::Result<Self> {
if iface_name.len() > 15 {
return Err(Error::other("Interface name must not exceed 16 bytes"));
}
let mut ifr_name: [i8; 16] = [0; 16];
iface_name
.as_bytes()
.iter()
.take(15)
.map(|b| *b as i8)
.enumerate()
.for_each(|(i, b)| ifr_name[i] = b);
let config_str = ifreq {
ifr_name,
ifr_ifru: libc::__c_anonymous_ifr_ifru {
ifru_flags: IFF_TAP as i16 | IFF_NO_PI as i16, },
};
let fd = OpenOptions::new().read(true).write(true).open(TUN_PATH)?;
ioctl_write_int!(tun_set_iff, b'T', 202);
let res =
unsafe { tun_set_iff(fd.as_raw_fd(), &config_str as *const ifreq as u64).unwrap() };
if res < 0 {
error!("Can't open TAP device {iface_name} - Error {res}");
return Err(Error::other("Can't open TAP device"));
}
let mut mac_addr = None;
for ifaddr in getifaddrs().unwrap() {
if let Some(address) = ifaddr.address
&& ifaddr.interface_name == iface_name
&& let Some(link_addr) = address.as_link_addr()
{
mac_addr = Some(link_addr.addr().unwrap());
}
}
Ok(Self {
fd,
name: iface_name.to_string(),
mac: mac_addr.expect("TAP device without MAC address?"),
})
}
}
impl NetworkInterface for Tap {
type RX = TapRX;
type TX = TapTX;
fn mac_address_as_bytes(&self) -> [u8; 6] {
self.mac
}
fn split(self) -> (Self::RX, Self::TX) {
(
Self::RX {
fd: self.fd.try_clone().unwrap(),
name: self.name.clone(),
},
Self::TX {
fd: self.fd.try_clone().unwrap(),
name: self.name,
},
)
}
}
pub struct TapTX {
fd: File,
name: String,
}
impl NetworkInterfaceTX for TapTX {
fn send(&mut self, buf: &[u8]) -> io::Result<usize> {
trace!("sending {} bytes on {}", buf.len(), self.name);
self.fd.write(buf)
}
}
pub(crate) fn read_file_with_timeout<F: AsFd + Read>(
file: &mut F,
target: &mut [u8],
timeout: u16,
) -> io::Result<usize> {
let mut pollfds = [PollFd::new(file.as_fd(), PollFlags::POLLIN)];
let nready = poll::<PollTimeout>(&mut pollfds, timeout.into())?;
if nready == 0 {
Ok(0)
} else {
file.read(target)
}
}
pub struct TapRX {
fd: File,
name: String,
}
impl NetworkInterfaceRX for TapRX {
fn recv(&mut self, buf: &mut [u8], timeout: u16) -> io::Result<usize> {
match read_file_with_timeout(&mut self.fd, buf, timeout) {
Ok(0) => Ok(0), Ok(i) => {
trace!("receiving {i:?} bytes on {}", self.name);
Ok(i)
}
Err(e) => Err(e),
}
}
}