#![cfg(target_os = "linux")]
use std::os::unix::io::RawFd;
use crate::backend::{NetBackend, NetOffloadFlags};
use crate::header::NetPacket;
const TUNSETOFFLOAD: libc::c_ulong = 0x400454d0;
const TUNSETVNETHDRSZ: libc::c_ulong = 0x400454d8;
const TUN_F_CSUM: u32 = 0x01;
const TUN_F_TSO4: u32 = 0x02;
const TUN_F_TSO6: u32 = 0x04;
const TUN_F_TSO_ECN: u32 = 0x08;
const TUN_F_UFO: u32 = 0x10;
pub struct TapBackend {
fd: RawFd,
name: String,
nonblocking: bool,
}
impl TapBackend {
pub fn new(name: Option<&str>) -> std::io::Result<Self> {
let fd: RawFd = unsafe {
libc::open(
b"/dev/net/tun\0".as_ptr() as *const libc::c_char,
libc::O_RDWR | libc::O_CLOEXEC,
)
};
if fd < 0 {
return Err(std::io::Error::last_os_error());
}
#[repr(C)]
struct Ifreq {
ifr_name: [libc::c_char; libc::IFNAMSIZ],
ifr_flags: libc::c_short,
_padding: [u8; 22], }
let mut ifr = Ifreq {
ifr_name: [0; libc::IFNAMSIZ],
ifr_flags: (libc::IFF_TAP | libc::IFF_NO_PI) as libc::c_short,
_padding: [0; 22],
};
if let Some(dev_name) = name {
let name_bytes = dev_name.as_bytes();
let len = name_bytes.len().min(libc::IFNAMSIZ - 1);
for (i, &b) in name_bytes[..len].iter().enumerate() {
ifr.ifr_name[i] = b as libc::c_char;
}
}
const TUNSETIFF: libc::c_ulong = 0x400454ca;
let ret = unsafe { libc::ioctl(fd, TUNSETIFF, &ifr) };
if ret < 0 {
unsafe { libc::close(fd) };
return Err(std::io::Error::last_os_error());
}
let name = {
let len = ifr
.ifr_name
.iter()
.position(|&c| c == 0)
.unwrap_or(libc::IFNAMSIZ);
let bytes: Vec<u8> = ifr.ifr_name[..len].iter().map(|&c| c as u8).collect();
String::from_utf8_lossy(&bytes).into_owned()
};
tracing::info!("Created TAP device: {}", name);
Ok(Self {
fd,
name,
nonblocking: false,
})
}
pub fn set_nonblocking(&mut self, nonblocking: bool) -> std::io::Result<()> {
let flags = unsafe { libc::fcntl(self.fd, libc::F_GETFL) };
if flags < 0 {
return Err(std::io::Error::last_os_error());
}
let new_flags = if nonblocking {
flags | libc::O_NONBLOCK
} else {
flags & !libc::O_NONBLOCK
};
let ret = unsafe { libc::fcntl(self.fd, libc::F_SETFL, new_flags) };
if ret < 0 {
return Err(std::io::Error::last_os_error());
}
self.nonblocking = nonblocking;
Ok(())
}
#[must_use]
pub fn name(&self) -> &str {
&self.name
}
pub fn bring_up(&self) -> std::io::Result<()> {
use std::process::Command;
let status = Command::new("ip")
.args(["link", "set", &self.name, "up"])
.status()?;
if status.success() {
Ok(())
} else {
Err(std::io::Error::new(
std::io::ErrorKind::Other,
"Failed to bring interface up",
))
}
}
pub fn set_ip(&self, ip: &str, prefix_len: u8) -> std::io::Result<()> {
use std::process::Command;
let addr = format!("{}/{}", ip, prefix_len);
let status = Command::new("ip")
.args(["addr", "add", &addr, "dev", &self.name])
.status()?;
if status.success() {
Ok(())
} else {
Err(std::io::Error::new(
std::io::ErrorKind::Other,
"Failed to set IP address",
))
}
}
}
impl Drop for TapBackend {
fn drop(&mut self) {
if self.fd >= 0 {
unsafe { libc::close(self.fd) };
}
}
}
impl NetBackend for TapBackend {
fn send(&mut self, packet: &NetPacket) -> std::io::Result<usize> {
let ret = unsafe {
libc::write(
self.fd,
packet.data.as_ptr() as *const libc::c_void,
packet.data.len(),
)
};
if ret < 0 {
Err(std::io::Error::last_os_error())
} else {
Ok(ret as usize)
}
}
fn recv(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
let ret = unsafe { libc::read(self.fd, buf.as_mut_ptr() as *mut libc::c_void, buf.len()) };
if ret < 0 {
let err = std::io::Error::last_os_error();
if err.kind() == std::io::ErrorKind::WouldBlock {
Ok(0)
} else {
Err(err)
}
} else {
Ok(ret as usize)
}
}
fn has_data(&self) -> bool {
let mut pollfd = libc::pollfd {
fd: self.fd,
events: libc::POLLIN,
revents: 0,
};
let ret = unsafe { libc::poll(&mut pollfd, 1, 0) };
ret > 0 && (pollfd.revents & libc::POLLIN) != 0
}
fn configure_offload(&mut self, flags: NetOffloadFlags) -> std::io::Result<()> {
let mut tun_flags: u32 = 0;
if flags.csum {
tun_flags |= TUN_F_CSUM;
}
if flags.tso4 {
tun_flags |= TUN_F_TSO4;
}
if flags.tso6 {
tun_flags |= TUN_F_TSO6;
}
if flags.tso_ecn {
tun_flags |= TUN_F_TSO_ECN;
}
if flags.ufo {
tun_flags |= TUN_F_UFO;
}
let ret = unsafe { libc::ioctl(self.fd, TUNSETOFFLOAD, tun_flags) };
if ret < 0 {
return Err(std::io::Error::last_os_error());
}
tracing::debug!(
"TAP {}: configured offload flags=0x{:x} (csum={} tso4={} tso6={} tso_ecn={} ufo={})",
self.name,
tun_flags,
flags.csum,
flags.tso4,
flags.tso6,
flags.tso_ecn,
flags.ufo,
);
Ok(())
}
fn set_vnet_hdr_sz(&mut self, size: u32) -> std::io::Result<()> {
let sz: libc::c_int = size as libc::c_int;
let ret = unsafe { libc::ioctl(self.fd, TUNSETVNETHDRSZ, &sz) };
if ret < 0 {
return Err(std::io::Error::last_os_error());
}
tracing::debug!("TAP {}: set vnet_hdr_sz = {}", self.name, size);
Ok(())
}
}