use std::ffi::CString;
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd};
use crate::error::{Error, Result};
use crate::packet::{LinkType, Packet};
const BIOCSETIF: libc::c_ulong = 0x8020426c;
const BIOCSETF: libc::c_ulong = 0x80104267;
const BIOCIMMEDIATE: libc::c_ulong = 0x80044270;
const BIOCPROMISC: libc::c_ulong = 0x20004269;
const BIOCGBLEN: libc::c_ulong = 0x40044266;
const BIOCGDLT: libc::c_ulong = 0x4004426a;
pub fn query_link_type(iface: &str) -> Result<LinkType> {
let mut ifap: *mut libc::ifaddrs = std::ptr::null_mut();
let rc = unsafe { libc::getifaddrs(&mut ifap) };
if rc < 0 {
return Err(super::io_err());
}
let mut result = LinkType::Ethernet;
let mut cur = ifap;
while !cur.is_null() {
let ifa = unsafe { &*cur };
let name = unsafe { std::ffi::CStr::from_ptr(ifa.ifa_name) }.to_string_lossy();
if name == iface && !ifa.ifa_addr.is_null() {
let sa_family = unsafe { (*ifa.ifa_addr).sa_family } as libc::c_int;
if sa_family == libc::AF_LINK {
let sdl = unsafe { &*(ifa.ifa_addr as *const libc::sockaddr_dl) };
result = if sdl.sdl_type == 0x06 {
LinkType::Ethernet
} else {
LinkType::Ethernet
};
break;
}
}
cur = ifa.ifa_next;
}
unsafe { libc::freeifaddrs(ifap) };
Ok(result)
}
#[repr(C)]
struct BpfProgram {
bf_len: u32,
bf_insns: *const pktbaffle::bpf::Insn,
}
#[repr(C)]
struct BpfHdr {
bh_tstamp_sec: i64,
bh_tstamp_usec: i64,
bh_caplen: u32,
bh_datalen: u32,
bh_hdrlen: u16,
}
pub struct MacosLive {
fd: OwnedFd,
buf: Vec<u8>,
buf_filled: usize,
buf_pos: usize,
snaplen: usize,
link_type: LinkType,
}
impl MacosLive {
pub fn open(
iface: &str,
filter: Option<&pktbaffle::bpf::Program>,
snaplen: u32,
promiscuous: bool,
) -> Result<Self> {
let fd = open_bpf_device()?;
let one: libc::c_uint = 1;
let rc = unsafe { libc::ioctl(fd.as_raw_fd(), BIOCIMMEDIATE, &one) };
if rc < 0 {
return Err(super::io_err());
}
let iface_c =
CString::new(iface).map_err(|_| Error::Platform("invalid interface name".into()))?;
let mut ifreq: libc::ifreq = unsafe { std::mem::zeroed() };
let bytes = iface_c.as_bytes_with_nul();
for (i, &b) in bytes.iter().enumerate().take(libc::IFNAMSIZ) {
ifreq.ifr_name[i] = b as libc::c_char;
}
let rc = unsafe { libc::ioctl(fd.as_raw_fd(), BIOCSETIF, &ifreq) };
if rc < 0 {
return Err(super::io_err());
}
let mut dlt: libc::c_uint = 0;
let dlt_rc = unsafe { libc::ioctl(fd.as_raw_fd(), BIOCGDLT, &mut dlt) };
let link_type = if dlt_rc >= 0 {
super::dlt_to_link_type(dlt)
} else {
LinkType::Ethernet
};
if promiscuous {
let rc = unsafe { libc::ioctl(fd.as_raw_fd(), BIOCPROMISC) };
if rc < 0 {
return Err(super::io_err());
}
}
if let Some(prog) = filter {
let insns = prog.instructions();
let bpf_prog = BpfProgram {
bf_len: insns.len() as u32,
bf_insns: insns.as_ptr(),
};
let rc = unsafe { libc::ioctl(fd.as_raw_fd(), BIOCSETF, &bpf_prog) };
if rc < 0 {
return Err(super::io_err());
}
}
let mut kbuf_len: libc::c_uint = 0;
unsafe { libc::ioctl(fd.as_raw_fd(), BIOCGBLEN, &mut kbuf_len) };
let buf_size = (kbuf_len as usize).max(snaplen as usize).max(65535);
Ok(Self {
fd,
buf: vec![0u8; buf_size],
buf_filled: 0,
buf_pos: 0,
snaplen: snaplen as usize,
link_type,
})
}
pub fn link_type(&self) -> LinkType {
self.link_type
}
pub fn next_packet(&mut self) -> Result<Packet> {
loop {
if self.buf_pos < self.buf_filled {
if let Some(pkt) = self.parse_next_frame() {
return Ok(pkt);
}
}
let n = unsafe {
libc::read(
self.fd.as_raw_fd(),
self.buf.as_mut_ptr() as *mut libc::c_void,
self.buf.len(),
)
};
if n < 0 {
let e = std::io::Error::last_os_error();
if e.kind() == std::io::ErrorKind::Interrupted {
continue;
}
return Err(e.into());
}
self.buf_filled = n as usize;
self.buf_pos = 0;
}
}
fn parse_next_frame(&mut self) -> Option<Packet> {
let hdr_size = std::mem::size_of::<BpfHdr>();
if self.buf_pos + hdr_size > self.buf_filled {
return None;
}
let hdr = unsafe {
std::ptr::read_unaligned(self.buf.as_ptr().add(self.buf_pos) as *const BpfHdr)
};
let data_start = self.buf_pos + hdr.bh_hdrlen as usize;
let cap = (hdr.bh_caplen as usize).min(self.snaplen);
let data_end = data_start + cap;
if data_end > self.buf_filled {
return None;
}
let data = self.buf[data_start..data_end].to_vec();
let frame_len = hdr.bh_hdrlen as usize + hdr.bh_caplen as usize;
self.buf_pos += word_align(frame_len);
Some(Packet::new(
data,
hdr.bh_tstamp_sec as u64,
hdr.bh_tstamp_usec as u32 * 1000,
hdr.bh_datalen,
self.link_type,
))
}
}
fn open_bpf_device() -> Result<OwnedFd> {
for n in 0..256 {
let path = CString::new(format!("/dev/bpf{n}")).unwrap();
let fd = unsafe { libc::open(path.as_ptr(), libc::O_RDWR) };
if fd >= 0 {
return Ok(unsafe { OwnedFd::from_raw_fd(fd) });
}
let errno = unsafe { *libc::__error() };
if errno == libc::EBUSY {
continue;
}
break;
}
Err(Error::Platform("no available /dev/bpf device found".into()))
}
#[inline]
fn word_align(n: usize) -> usize {
(n + 3) & !3
}
pub fn list_interfaces() -> Result<Vec<String>> {
let mut ifap: *mut libc::ifaddrs = std::ptr::null_mut();
let rc = unsafe { libc::getifaddrs(&mut ifap) };
if rc < 0 {
return Err(super::io_err());
}
let mut names = Vec::new();
let mut cur = ifap;
while !cur.is_null() {
let ifa = unsafe { &*cur };
let name = unsafe { std::ffi::CStr::from_ptr(ifa.ifa_name) }
.to_string_lossy()
.into_owned();
if !names.contains(&name) {
names.push(name);
}
cur = ifa.ifa_next;
}
unsafe { libc::freeifaddrs(ifap) };
Ok(names)
}
pub fn default_interface() -> Result<String> {
list_interfaces()?
.into_iter()
.find(|name| !name.starts_with("lo"))
.ok_or_else(|| Error::Platform("no non-loopback interface found".into()))
}