use crate::{
ethernet::{ETHERNET_HEADER_LEN, EthernetAddress},
fmt,
std::unix::{ifreq, ifreq_for},
};
use async_io::IoSafe;
use std::{
io, mem,
os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd},
};
#[cfg(not(target_os = "netbsd"))]
use libc::bpf_hdr;
#[cfg(target_os = "netbsd")]
#[allow(dead_code, non_camel_case_types)]
struct bpf_hdr {
pub bh_tstamp: libc::timeval,
pub bh_caplen: u32,
pub bh_datalen: u32,
pub bh_hdrlen: libc::c_ushort,
}
#[cfg(any(
target_os = "macos",
target_os = "ios",
target_os = "netbsd",
target_os = "openbsd",
target_os = "freebsd"
))]
const BIOCSETIF: libc::c_ulong = 0x8020426c;
#[cfg(any(
target_os = "macos",
target_os = "ios",
target_os = "netbsd",
target_os = "openbsd",
target_os = "freebsd"
))]
const BIOCGBLEN: libc::c_ulong = 0x40044266;
#[cfg(any(
target_os = "macos",
target_os = "ios",
target_os = "netbsd",
target_os = "openbsd",
target_os = "freebsd"
))]
const BIOCIMMEDIATE: libc::c_ulong = 0x80044270;
#[cfg(any(target_os = "macos", target_os = "ios", target_os = "netbsd"))]
const SIZEOF_BPF_HDR: usize = 18;
#[cfg(any(target_os = "openbsd", target_os = "freebsd"))]
const SIZEOF_BPF_HDR: usize = 24;
#[cfg(any(
target_os = "macos",
target_os = "ios",
target_os = "netbsd",
target_os = "openbsd",
target_os = "freebsd"
))]
const BPF_HDRLEN: usize = (((SIZEOF_BPF_HDR + ETHERNET_HEADER_LEN) + mem::align_of::<u32>() - 1)
& !(mem::align_of::<u32>() - 1))
- ETHERNET_HEADER_LEN;
#[cfg_attr(not(unix), allow(unused_macros))]
macro_rules! try_ioctl {
($fd:expr,$cmd:expr,$req:expr) => {
unsafe {
if libc::ioctl($fd, $cmd, $req) == -1 {
return Err(io::Error::last_os_error());
}
}
};
}
#[derive(Debug)]
pub struct BpfDevice {
fd: libc::c_int,
ifreq: ifreq,
name: String,
buf: Vec<u8>,
}
impl AsRawFd for BpfDevice {
fn as_raw_fd(&self) -> RawFd {
self.fd
}
}
impl AsFd for BpfDevice {
fn as_fd(&self) -> BorrowedFd<'_> {
unsafe { BorrowedFd::borrow_raw(self.fd) }
}
}
fn open_device() -> io::Result<libc::c_int> {
unsafe {
for i in 0..256 {
let dev = format!("/dev/bpf{}\0", i);
match libc::open(
dev.as_ptr() as *const libc::c_char,
libc::O_RDWR | libc::O_NONBLOCK,
) {
-1 => continue,
fd => return Ok(fd),
};
}
}
Err(io::Error::last_os_error())
}
impl BpfDevice {
pub fn new(name: &str) -> io::Result<Self> {
let mut self_ = BpfDevice {
fd: open_device()?,
ifreq: ifreq_for(name),
name: name.to_string(),
buf: Vec::with_capacity(4096),
};
self_.bind_interface()?;
Ok(self_)
}
#[allow(trivial_casts)]
pub fn bind_interface(&mut self) -> io::Result<()> {
let mut bufsize: libc::c_int = 1;
try_ioctl!(self.fd, BIOCIMMEDIATE, &mut bufsize as *mut libc::c_int);
try_ioctl!(self.fd, BIOCSETIF, &mut self.ifreq);
Ok(())
}
#[allow(trivial_casts)]
pub fn interface_mtu(&mut self) -> io::Result<usize> {
let mut bufsize: libc::c_int = 1;
try_ioctl!(self.fd, BIOCIMMEDIATE, &mut bufsize as *mut libc::c_int);
try_ioctl!(self.fd, BIOCGBLEN, &mut bufsize as *mut libc::c_int);
Ok(bufsize as usize)
}
pub fn mac(&self) -> io::Result<Option<EthernetAddress>> {
Ok(nix::ifaddrs::getifaddrs()?
.find(|iface| iface.interface_name == self.name)
.and_then(|iface| iface.address)
.and_then(|addr| addr.as_link_addr()?.addr())
.map(EthernetAddress))
}
}
impl Drop for BpfDevice {
fn drop(&mut self) {
unsafe {
libc::close(self.fd);
}
}
}
unsafe impl IoSafe for BpfDevice {}
impl io::Read for BpfDevice {
fn read(&mut self, buffer: &mut [u8]) -> io::Result<usize> {
let len = if !self.buf.is_empty() {
let len = self.buf.len().min(buffer.len());
debug_assert!(
len >= BPF_HDRLEN,
"not enough previous buffer {} B to hold BPF header {} B",
len,
BPF_HDRLEN
);
fmt::trace!("{} bytes left from previous read", len);
let (cached_chunk, rest) = self.buf.split_at(len);
buffer[0..len].copy_from_slice(cached_chunk);
self.buf = rest.to_vec();
len
} else {
let len = unsafe {
libc::read(
self.fd,
buffer.as_mut_ptr() as *mut libc::c_void,
buffer.len(),
)
};
if len == -1 || len < BPF_HDRLEN as isize {
return Err(io::Error::last_os_error());
}
len as usize
};
let frame_len = {
let bpf_header = &buffer[0..BPF_HDRLEN];
let bpf_header = unsafe {
core::ptr::NonNull::new(bpf_header.as_ptr() as *mut bpf_hdr)
.ok_or(io::Error::other("no BPF header"))?
.as_ref()
};
debug_assert_eq!(
bpf_header.bh_caplen, bpf_header.bh_datalen,
"not all frame data was read"
);
bpf_header.bh_datalen as usize
};
let remaining = len as u32 - BPF_HDRLEN as u32 - frame_len as u32;
let remaining = remaining.next_multiple_of(core::mem::align_of::<bpf_hdr>() as u32);
if remaining > 0 {
let start = (BPF_HDRLEN + frame_len).next_multiple_of(core::mem::align_of::<bpf_hdr>());
self.buf = buffer[start..len].to_vec();
}
#[allow(trivial_casts)]
unsafe {
libc::memmove(
buffer.as_mut_ptr() as *mut libc::c_void,
&buffer[BPF_HDRLEN] as *const u8 as *const libc::c_void,
frame_len,
)
};
Ok(frame_len)
}
}
impl io::Write for BpfDevice {
fn write(&mut self, buffer: &[u8]) -> io::Result<usize> {
unsafe {
let len = libc::write(
self.fd,
buffer.as_ptr() as *const libc::c_void,
buffer.len(),
);
if len == -1 {
return Err(io::Error::last_os_error());
}
Ok(len as usize)
}
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}