use std::ffi::{CString, OsStr};
use std::mem;
#[cfg(unix)]
use std::os::fd::{AsFd, AsRawFd, BorrowedFd, RawFd};
#[cfg(unix)]
use std::os::unix::ffi::OsStrExt;
#[cfg(target_os = "freebsd")]
use std::slice;
#[cfg(any(doc, target_os = "freebsd"))]
use std::sync::atomic::{AtomicU32, Ordering};
use std::{array, cmp, io, ptr};
use crate::filter::{PacketFilter, PacketStatistics};
use crate::Interface;
const BPF_PATH: &[u8] = b"/dev/bpf\0";
const MAX_OPEN_BPF: u32 = 1024;
#[allow(non_camel_case_types)]
#[repr(C)]
pub(crate) struct bpf_version {
major: libc::c_ushort,
minor: libc::c_ushort,
}
#[allow(non_camel_case_types)]
#[repr(C)]
pub(crate) struct bpf_stat {
bs_recv: libc::c_uint,
bs_drop: libc::c_uint,
}
#[allow(non_camel_case_types)]
#[repr(C)]
pub(crate) struct bpf_xhdr {
bh_tstamp: bpf_ts,
bh_caplen: u32,
bh_datalen: u32,
bh_hdrlen: libc::c_ushort,
}
#[derive(Clone, Copy)]
#[allow(non_camel_case_types)]
#[repr(C)]
pub(crate) struct bpf_ts {
bt_sec: i64,
bt_frac: u64,
}
pub const BIOCSETF: libc::c_ulong = 0x80104267;
pub const BIOCSETFNR: libc::c_ulong = 0x8010427e;
const BIOCPROMISC: libc::c_ulong = 0x20004269;
const BIOCGDLT: libc::c_ulong = 0x4004426a;
const BIOCGBLEN: libc::c_ulong = 0x40044266;
const BIOCSBLEN: libc::c_ulong = 0xc0044266;
const BIOCVERSION: libc::c_ulong = 0x40044271;
const BIOCGETIF: libc::c_ulong = 0x4020426b;
const BIOCSETIF: libc::c_ulong = 0x8020426c;
const BIOCGSTATS: libc::c_ulong = 0x4008426f;
const BIOCIMMEDIATE: libc::c_ulong = 0x80044270;
const BIOCFEEDBACK: libc::c_ulong = 0x8004427c;
const BIOCLOCK: libc::c_ulong = 0x2000427a;
#[derive(Clone, Copy, Debug)]
pub enum BpfAccess {
ReadOnly,
ReadWrite,
WriteOnly,
}
#[derive(Clone, Copy, Debug)]
#[repr(C)]
pub struct BpfVersion {
major: u16,
minor: u16,
}
impl BpfVersion {
pub fn major(&self) -> u16 {
self.major
}
pub fn minor(&self) -> u16 {
self.minor
}
}
#[repr(u16)]
#[non_exhaustive]
pub enum LinkType {
Null = 0,
En10Mb = 1,
En3Mb = 2,
Ax25 = 3,
Pronet = 4,
Chaos = 5,
Ieee802 = 6,
Arcnet = 7,
Slip = 8,
Ppp = 9,
Fddi = 10,
#[cfg(target_os = "openbsd")]
Raw = 14,
#[cfg(any(doc, not(target_os = "openbsd")))]
Raw = 12,
#[cfg(any(doc, target_os = "freebsd", target_os = "netbsd"))]
SlipBsdos = 13,
#[cfg(any(doc, target_os = "freebsd", target_os = "netbsd"))]
PppBsdos = 14,
#[cfg(any(doc, target_os = "netbsd"))]
Hippi = 15,
#[cfg(any(doc, not(target_os = "freebsd")))]
Pfsync = 18,
AtmClip = 19,
RedbackSmartedge = 32,
PppSerial = 50,
PppEther = 51,
SymantecFirewall = 99,
Chdlc = 104,
Ieee80211 = 105,
Frelay = 107,
#[cfg(target_os = "openbsd")]
Loop = 12,
#[cfg(any(doc, not(target_os = "openbsd")))]
Loop = 108,
#[cfg(target_os = "openbsd")]
Enc = 13,
#[cfg(any(doc, not(target_os = "openbsd")))]
Enc = 109,
#[cfg(target_os = "netbsd")]
Hdlc = 16,
#[cfg(any(doc, not(target_os = "netbsd")))]
Hdlc = 112,
LinuxSll = 113,
Ltalk = 114,
Econet = 115,
Ipfilter = 116,
Pflog = 117,
CiscoIos = 118,
PrismHeader = 119,
AironetHeader = 120,
#[cfg(target_os = "freebsd")]
Pfsync = 121,
#[cfg(any(doc, not(target_os = "freebsd")))]
Hhdlc = 121,
IpOverFc = 122,
SunAtm = 123,
Rio = 124,
PciExp = 125,
Aurora = 126,
Ieee80211Radio = 127,
Tzsp = 128,
ArcnetLinux = 129,
}
#[cfg(any(doc, target_os = "freebsd"))]
#[repr(C)]
struct BpfHeader {
bzh_kernel_gen: AtomicU32,
bzh_kernel_len: AtomicU32,
bzh_user_gen: AtomicU32,
_bzh_pad: [libc::c_uint; 5],
}
#[cfg(any(doc, target_os = "freebsd"))]
#[derive(Clone, Copy)]
enum ReceiveIndex {
FirstBlock(usize),
SecondBlock(usize),
}
#[cfg(any(doc, target_os = "freebsd"))]
impl ReceiveIndex {
pub fn increment(&mut self, amount: usize) {
match self {
ReceiveIndex::FirstBlock(a) => *a += amount,
ReceiveIndex::SecondBlock(b) => *b += amount,
}
}
pub fn swap(&mut self) {
*self = match self {
ReceiveIndex::FirstBlock(_) => ReceiveIndex::SecondBlock(0),
ReceiveIndex::SecondBlock(_) => ReceiveIndex::FirstBlock(0),
}
}
}
pub struct Bpf {
#[cfg(unix)]
fd: RawFd,
}
impl Bpf {
pub fn new(access_mode: BpfAccess) -> io::Result<Self> {
let mode = match access_mode {
BpfAccess::ReadOnly => libc::O_RDONLY,
BpfAccess::ReadWrite => libc::O_RDWR,
BpfAccess::WriteOnly => libc::O_WRONLY,
};
let mut fd = unsafe { libc::open(BPF_PATH.as_ptr() as *const i8, mode) };
if fd >= 0 {
return Ok(Bpf { fd });
}
let error = io::Error::last_os_error();
if error.raw_os_error() != Some(libc::ENOENT) {
return Err(error);
}
for dev_idx in 1..MAX_OPEN_BPF {
let device = CString::new(format!("/dev/bpf{}", dev_idx).into_bytes()).unwrap();
fd = unsafe { libc::open(device.as_ptr(), mode) };
if fd >= 0 {
return Ok(Bpf { fd });
}
let error = io::Error::last_os_error();
if error.raw_os_error() != Some(libc::EBUSY) {
break; }
}
Err(io::Error::last_os_error())
}
pub fn bpf_version(&self) -> io::Result<BpfVersion> {
let mut version = bpf_version { major: 0, minor: 0 };
let res = unsafe {
libc::ioctl(
self.fd,
BIOCVERSION,
ptr::addr_of_mut!(version) as *mut libc::c_char,
)
};
match res {
0 => Ok(BpfVersion {
major: version.major,
minor: version.minor,
}),
_ => Err(io::Error::last_os_error()),
}
}
pub fn frame_len(&self) -> io::Result<u32> {
let mut frame_len: libc::c_uint = 0;
let res = unsafe { libc::ioctl(self.fd, BIOCGBLEN, ptr::addr_of_mut!(frame_len)) };
match res {
0.. => Ok(frame_len),
_ => Err(io::Error::last_os_error()),
}
}
pub fn set_frame_len(&self, mut frame_len: u32) -> io::Result<()> {
let res = unsafe { libc::ioctl(self.fd, BIOCSBLEN, ptr::addr_of_mut!(frame_len)) };
match res {
0.. => Ok(()),
_ => Err(io::Error::last_os_error()),
}
}
pub fn link_type(&self) -> io::Result<u16> {
let mut linktype: u16 = 0;
let res = unsafe { libc::ioctl(self.fd, BIOCGDLT, ptr::addr_of_mut!(linktype)) };
match res {
0.. => Ok(linktype),
_ => Err(io::Error::last_os_error()),
}
}
pub fn set_promiscuous(&self) -> io::Result<()> {
let res = unsafe { libc::ioctl(self.fd, BIOCPROMISC) };
match res {
0.. => Ok(()),
_ => Err(io::Error::last_os_error()),
}
}
pub fn flush(&self) -> io::Result<()> {
let mut filter = PacketFilter::reject_all();
unsafe {
let mut bpf_program = filter.as_bpf_program();
match libc::ioctl(
self.fd,
BIOCSETF,
ptr::addr_of_mut!(bpf_program) as *mut libc::c_void,
) {
0.. => Ok(()),
_ => Err(io::Error::last_os_error()),
}
}
}
#[inline]
pub fn set_filter(&self, filter: &mut PacketFilter) -> io::Result<()> {
let mut bpf_program = unsafe { filter.as_bpf_program() };
match unsafe {
libc::ioctl(
self.fd,
BIOCSETFNR,
ptr::addr_of_mut!(bpf_program) as *mut libc::c_void,
)
} {
0.. => Ok(()),
_ => Err(io::Error::last_os_error()),
}
}
pub fn bind(&self, iface: Interface) -> io::Result<()> {
let name = iface.name_raw();
let mut ifreq = libc::ifreq {
ifr_name: array::from_fn(|i| if i < name.len() { name[i] as i8 } else { 0i8 }),
ifr_ifru: libc::__c_anonymous_ifr_ifru {
ifru_addr: libc::sockaddr {
sa_family: 0,
sa_len: 0,
sa_data: [0i8; 14],
},
},
};
let res = unsafe { libc::ioctl(self.fd, BIOCSETIF, ptr::addr_of_mut!(ifreq)) };
match res {
0 => Ok(()),
_ => Err(io::Error::last_os_error()),
}
}
pub fn interface(&self) -> io::Result<Interface> {
let mut ifreq = libc::ifreq {
ifr_name: [0i8; 16],
ifr_ifru: libc::__c_anonymous_ifr_ifru {
ifru_addr: libc::sockaddr {
sa_family: 0,
sa_len: 0,
sa_data: [0i8; 14],
},
},
};
let res = unsafe { libc::ioctl(self.fd, BIOCGETIF, ptr::addr_of_mut!(ifreq)) };
match res {
0 => {
let name: [u8; 16] = array::from_fn(|i| ifreq.ifr_name[i] as u8);
let end = name.partition_point(|&x| x != 0);
Interface::new(OsStr::from_bytes(&name[..end]))
}
_ => Err(io::Error::last_os_error()),
}
}
pub fn stats(&self) -> io::Result<PacketStatistics> {
let mut stats = bpf_stat {
bs_recv: 0,
bs_drop: 0,
};
let res = unsafe { libc::ioctl(self.fd, BIOCGSTATS, ptr::addr_of_mut!(stats)) };
match res {
0.. => Ok(PacketStatistics {
received: stats.bs_recv,
dropped: stats.bs_drop,
}),
_ => Err(io::Error::last_os_error()),
}
}
pub fn set_immediate(&self, immediate: bool) -> io::Result<()> {
let mut immediate: libc::c_uint = if immediate { 1 } else { 0 };
let res = unsafe { libc::ioctl(self.fd, BIOCIMMEDIATE, ptr::addr_of_mut!(immediate)) };
match res {
0.. => Ok(()),
_ => Err(io::Error::last_os_error()),
}
}
pub fn nonblocking(&self) -> io::Result<bool> {
let flags = unsafe { libc::fcntl(self.fd, libc::F_GETFL) };
if flags < 0 {
return Err(io::Error::last_os_error());
}
Ok(flags & libc::O_NONBLOCK > 0)
}
pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> {
let flags = unsafe { libc::fcntl(self.fd, libc::F_GETFL) };
if flags < 0 {
return Err(io::Error::last_os_error());
}
let flags = match nonblocking {
true => flags | libc::O_NONBLOCK,
false => flags & !libc::O_NONBLOCK,
};
if unsafe { libc::fcntl(self.fd, libc::F_SETFL, flags) } < 0 {
return Err(io::Error::last_os_error());
} else {
Ok(())
}
}
pub fn set_feedback(&self, allow_feedback: bool) -> io::Result<()> {
let mut allow_feedback: libc::c_int = if allow_feedback { 1 } else { 0 };
let res = unsafe { libc::ioctl(self.fd, BIOCFEEDBACK, ptr::addr_of_mut!(allow_feedback)) };
match res {
0.. => Ok(()),
_ => Err(io::Error::last_os_error()),
}
}
#[inline]
pub fn lock(&self) -> io::Result<()> {
let res = unsafe { libc::ioctl(self.fd, BIOCLOCK) };
match res {
0.. => Ok(()),
_ => Err(io::Error::last_os_error()),
}
}
#[inline]
pub fn send(&self, buf: &[u8]) -> io::Result<usize> {
let res = unsafe { libc::write(self.fd, buf.as_ptr() as *const libc::c_void, buf.len()) };
match res {
0.. => Ok(res as usize),
_ => Err(io::Error::last_os_error()),
}
}
pub fn recv(&self, buf: &mut [u8]) -> io::Result<usize> {
let mut header = [0u8; mem::size_of::<bpf_xhdr>()];
let mut end_padding = [0u8; 0x40];
let mut iov = [
libc::iovec {
iov_base: header.as_mut_ptr() as *mut libc::c_void,
iov_len: header.len(),
},
libc::iovec {
iov_base: buf.as_mut_ptr() as *mut libc::c_void,
iov_len: buf.len(),
},
libc::iovec {
iov_base: end_padding.as_mut_ptr() as *mut libc::c_void,
iov_len: end_padding.len(),
},
];
match usize::try_from(unsafe {
libc::readv(self.fd, iov.as_mut_ptr(), iov.len() as libc::c_int)
}) {
Ok(len) => {
if len < mem::size_of::<bpf_xhdr>() {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"BPF recv() returned insufficient bytes for header",
));
}
let xhdr_bytes: [u8; mem::size_of::<bpf_xhdr>()] =
header[..mem::size_of::<bpf_xhdr>()].try_into().unwrap();
let xhdr: bpf_xhdr = unsafe { mem::transmute_copy(&xhdr_bytes) };
let caplen = xhdr.bh_caplen as usize;
let _datalen = xhdr.bh_datalen as usize;
let hdrlen = xhdr.bh_hdrlen as usize;
match hdrlen.cmp(&header.len()) {
cmp::Ordering::Equal => (),
cmp::Ordering::Less => {
let offset = header.len() - hdrlen;
let buf_len = buf.len();
unsafe {
ptr::copy(buf[offset..].as_ptr(), buf.as_mut_ptr(), buf_len - offset)
};
buf[..offset].copy_from_slice(&header[header.len() - offset..]);
}
cmp::Ordering::Greater => {
let offset = hdrlen - header.len();
let buf_len = buf.len();
assert!(
offset <= end_padding.len(),
"BPF recv() returned unusually long header padding"
);
unsafe {
ptr::copy(buf.as_ptr(), buf[offset..].as_mut_ptr(), buf.len() - offset)
};
buf[buf_len - offset..].copy_from_slice(&end_padding[..offset]);
}
}
Ok(cmp::min(caplen, buf.len()))
}
_ => Err(io::Error::last_os_error()),
}
}
#[cfg(any(doc, target_os = "freebsd"))]
pub fn packet_rx_ring(self, buffer_size: usize) -> io::Result<RxMappedBpf> {
let flags = libc::MAP_ANONYMOUS; let prot = libc::PROT_READ | libc::PROT_WRITE;
let ringbuf = unsafe { libc::mmap(ptr::null_mut(), buffer_size * 2, prot, flags, -1, 0) };
if ringbuf == libc::MAP_FAILED {
let errno = unsafe { *libc::__error() };
unsafe { libc::close(self.fd) };
return Err(io::Error::from_raw_os_error(errno));
}
let mut req = bpf_zbuf {
bz_bufa: ringbuf,
bz_bufb: unsafe { (ringbuf as *mut u8).add(buffer_size) as *mut libc::c_void },
bz_buflen: buffer_size,
};
let res = unsafe { libc::ioctl(self.fd, BIOCSETZBUF, ptr::addr_of_mut!(req)) };
match res {
0.. => Ok(RxMappedBpf {
l2: self,
raw: ringbuf,
buflen: buffer_size,
recv_idx: ReceiveIndex::FirstBlock(0),
}),
_ => {
unsafe { libc::close(self.fd) };
unsafe { libc::munmap(ringbuf, buffer_size * 2) };
Err(io::Error::from_raw_os_error(unsafe { *libc::__error() }))
}
}
}
}
impl Drop for Bpf {
fn drop(&mut self) {
unsafe { libc::close(self.fd) };
}
}
#[cfg(target_os = "freebsd")]
impl AsRawFd for Bpf {
#[inline]
fn as_raw_fd(&self) -> RawFd {
self.fd
}
}
#[cfg(any(doc, target_os = "freebsd"))]
pub struct RxMappedBpf {
l2: Bpf,
raw: *mut libc::c_void,
buflen: usize,
recv_idx: ReceiveIndex,
}
#[cfg(any(doc, target_os = "freebsd"))]
impl RxMappedBpf {
#[inline]
pub fn rx_ring(&mut self) -> RxRing<'_> {
RxRing {
block_1: self.raw,
block_2: unsafe { (self.raw as *mut u8).add(self.buflen) as *mut libc::c_void },
recv_idx: &mut self.recv_idx,
}
}
#[inline]
pub fn send(&self, buf: &[u8]) -> io::Result<usize> {
self.l2.send(buf)
}
#[inline]
pub fn recv(&self, buf: &mut [u8]) -> io::Result<usize> {
self.l2.recv(buf)
}
#[inline]
pub fn mapped_recv(&mut self) -> Option<RxFrame<'_>> {
let (raw, block_idx) = match self.recv_idx {
ReceiveIndex::FirstBlock(i) => (self.raw, i),
ReceiveIndex::SecondBlock(i) => unsafe {
(
(self.raw as *mut u8).add(self.buflen) as *mut libc::c_void,
i,
)
},
};
let data = unsafe { RxBlock::block_parts(raw)?.1 };
let mut iter = RxFrameIter {
rem: &mut data[block_idx..],
};
let prev_len = iter.rem.len();
let frame = iter.next();
if frame.is_some() {
self.recv_idx.increment(prev_len - iter.rem.len());
} else {
self.recv_idx.swap();
};
frame
}
}
#[cfg(any(doc, target_os = "freebsd"))]
impl Drop for RxMappedBpf {
fn drop(&mut self) {
unsafe { libc::munmap(self.raw, self.buflen * 2) };
}
}
#[cfg(unix)]
impl AsRawFd for Bpf {
#[inline]
fn as_raw_fd(&self) -> RawFd {
self.fd
}
}
#[cfg(not(target_os = "windows"))]
impl AsFd for Bpf {
fn as_fd(&self) -> BorrowedFd<'_> {
unsafe { BorrowedFd::borrow_raw(self.fd) }
}
}
#[cfg(any(doc, target_os = "freebsd"))]
pub struct RxRing<'a> {
block_1: *mut libc::c_void,
block_2: *mut libc::c_void,
recv_idx: &'a mut ReceiveIndex,
}
#[cfg(any(doc, target_os = "freebsd"))]
impl<'a> RxRing<'a> {
pub fn first_block(&'a mut self) -> Option<RxBlock<'a>> {
let (header, data) = unsafe { RxBlock::block_parts(self.block_1)? };
Some(RxBlock {
header,
data,
is_first_block: true,
recv_idx: &mut self.recv_idx,
})
}
pub fn second_block(&'a mut self) -> Option<RxBlock<'a>> {
let (header, data) = unsafe { RxBlock::block_parts(self.block_2)? };
Some(RxBlock {
header,
data,
is_first_block: false,
recv_idx: &mut self.recv_idx,
})
}
}
#[cfg(any(doc, target_os = "freebsd"))]
pub struct RxBlock<'a> {
header: &'a mut BpfHeader,
data: &'a mut [u8],
is_first_block: bool,
recv_idx: &'a mut ReceiveIndex,
}
#[cfg(any(doc, target_os = "freebsd"))]
impl<'a> RxBlock<'a> {
unsafe fn block_parts(buf: *mut libc::c_void) -> Option<(&'a mut BpfHeader, &'a mut [u8])> {
let header = &mut *(buf as *mut BpfHeader);
let buf_start = (buf as *mut u8).add(mem::size_of::<BpfHeader>());
let user_gen = header.bzh_user_gen.load(Ordering::Acquire);
let kernel_gen = header.bzh_kernel_gen.load(Ordering::Acquire);
if user_gen == kernel_gen {
None
} else {
let kernel_len = header.bzh_kernel_len.load(Ordering::Acquire) as usize;
Some((header, slice::from_raw_parts_mut(buf_start, kernel_len)))
}
}
#[inline]
pub fn frames(&'a mut self) -> RxFrameIter<'a> {
RxFrameIter { rem: self.data }
}
pub fn mark_read(self) {
if self.is_first_block {
if let ReceiveIndex::FirstBlock(_) = self.recv_idx {
*self.recv_idx = ReceiveIndex::SecondBlock(0);
}
} else {
if let ReceiveIndex::SecondBlock(_) = self.recv_idx {
*self.recv_idx = ReceiveIndex::FirstBlock(0);
}
}
let kernel_gen = self.header.bzh_kernel_gen.load(Ordering::Acquire);
self.header
.bzh_user_gen
.store(kernel_gen, Ordering::Release);
}
}
#[cfg(any(doc, target_os = "freebsd"))]
pub struct RxFrameIter<'a> {
rem: &'a mut [u8],
}
#[cfg(any(doc, target_os = "freebsd"))]
impl<'a> Iterator for RxFrameIter<'a> {
type Item = RxFrame<'a>;
fn next(&mut self) -> Option<Self::Item> {
if self.rem.len() < mem::size_of::<bpf_xhdr>() {
return None;
}
let hdr_bytes = &self.rem[..mem::size_of::<bpf_xhdr>()];
let hdr = unsafe { &*(hdr_bytes.as_ptr() as *const bpf_xhdr) };
let caplen = hdr.bh_caplen as usize;
let datalen = hdr.bh_datalen as usize;
let hdrlen = hdr.bh_hdrlen as usize;
let tstamp = hdr.bh_tstamp;
let offset_to_start = hdrlen.saturating_sub(mem::size_of::<bpf_xhdr>());
let unpadded_len = hdrlen + caplen;
let padded_len = BPF_WORDALIGN(unpadded_len);
let padding = padded_len - unpadded_len;
if self.rem.len() < padded_len {
return None;
}
let rem = mem::replace(&mut self.rem, &mut []);
let rem = &mut rem[offset_to_start..];
let (pkt, rem) = rem.split_at_mut(caplen);
self.rem = rem.get_mut(padding..).unwrap_or(&mut []);
Some(RxFrame {
data: pkt,
orig_len: datalen,
timestamp: tstamp,
})
}
}
#[cfg(any(doc, target_os = "freebsd"))]
pub struct RxFrame<'a> {
data: &'a mut [u8],
orig_len: usize,
timestamp: bpf_ts,
}
#[cfg(any(doc, target_os = "freebsd"))]
impl RxFrame<'_> {
#[inline]
pub fn data(&self) -> &[u8] {
self.data
}
#[inline]
pub fn data_mut(&mut self) -> &mut [u8] {
self.data
}
#[inline]
pub fn original_len(&self) -> usize {
self.orig_len
}
#[inline]
pub fn frame_len(&self) -> usize {
self.data.len()
}
#[inline]
pub fn truncated(&self) -> bool {
self.original_len() != self.frame_len()
}
pub fn timestamp(&self) -> bpf_ts {
self.timestamp
}
}