use std::ffi::CString;
use std::{io, mem, ptr};
use anyhow::{Result, anyhow};
use log::info;
use super::packet::{Sampler, parse_ethernet};
use super::{Capture, PacketInfo};
const ETH_P_ALL: u16 = 0x0003;
const PACKET_ADD_MEMBERSHIP: libc::c_int = 1;
const PACKET_RX_RING: libc::c_int = 5;
const PACKET_VERSION: libc::c_int = 10;
const TPACKET_V2: libc::c_int = 1;
const PACKET_MR_PROMISC: libc::c_ushort = 1;
const FRAME_SIZE: u32 = 2048;
const BLOCK_SIZE: u32 = 4096;
const BLOCK_NR: u32 = 256;
const FRAME_NR: u32 = (BLOCK_SIZE / FRAME_SIZE) * BLOCK_NR;
const TP_STATUS_KERNEL: u32 = 0;
const TP_STATUS_USER: u32 = 1;
#[repr(C)]
struct PacketMreq {
mr_ifindex: libc::c_int,
mr_type: libc::c_ushort,
mr_alen: libc::c_ushort,
mr_address: [libc::c_uchar; 8],
}
#[repr(C)]
struct TpacketReq {
tp_block_size: libc::c_uint,
tp_block_nr: libc::c_uint,
tp_frame_size: libc::c_uint,
tp_frame_nr: libc::c_uint,
}
#[repr(C)]
struct Tpacket2Hdr {
tp_status: u32,
tp_len: u32,
tp_snaplen: u32,
tp_mac: u16,
tp_net: u16,
tp_sec: u32,
tp_nsec: u32,
tp_vlan_tci: u16,
tp_vlan_tpid: u16,
tp_padding: [u8; 4],
}
pub struct AfPacket {
fd: libc::c_int,
ring: *mut u8,
ring_size: usize,
frame_idx: u32,
sampler: Sampler,
}
impl AfPacket {
pub fn new(interface: &str, promiscuous: bool, sampling_interval: u32) -> Result<Self> {
info!("Opening AF_PACKET capture on interface: {}", interface);
let fd = unsafe {
libc::socket(
libc::AF_PACKET,
libc::SOCK_RAW,
ETH_P_ALL.to_be() as libc::c_int,
)
};
if fd < 0 {
return Err(anyhow!(
"Failed to create socket: {}",
io::Error::last_os_error()
));
}
let version: libc::c_int = TPACKET_V2;
let ret = unsafe {
libc::setsockopt(
fd,
libc::SOL_PACKET,
PACKET_VERSION,
&version as *const _ as *const libc::c_void,
mem::size_of::<libc::c_int>() as libc::socklen_t,
)
};
if ret < 0 {
unsafe { libc::close(fd) };
return Err(anyhow!(
"Failed to set TPACKET_V2: {}",
io::Error::last_os_error()
));
}
let req = TpacketReq {
tp_block_size: BLOCK_SIZE,
tp_block_nr: BLOCK_NR,
tp_frame_size: FRAME_SIZE,
tp_frame_nr: FRAME_NR,
};
let ret = unsafe {
libc::setsockopt(
fd,
libc::SOL_PACKET,
PACKET_RX_RING,
&req as *const _ as *const libc::c_void,
mem::size_of::<TpacketReq>() as libc::socklen_t,
)
};
if ret < 0 {
unsafe { libc::close(fd) };
return Err(anyhow!(
"Failed to setup ring buffer: {}",
io::Error::last_os_error()
));
}
let ring_size = (BLOCK_SIZE * BLOCK_NR) as usize;
let ring = unsafe {
libc::mmap(
ptr::null_mut(),
ring_size,
libc::PROT_READ | libc::PROT_WRITE,
libc::MAP_SHARED,
fd,
0,
)
};
if ring == libc::MAP_FAILED {
unsafe { libc::close(fd) };
return Err(anyhow!(
"Failed to mmap ring buffer: {}",
io::Error::last_os_error()
));
}
let ifindex = get_interface_index(fd, interface)?;
let addr = libc::sockaddr_ll {
sll_family: libc::AF_PACKET as u16,
sll_protocol: ETH_P_ALL.to_be(),
sll_ifindex: ifindex,
sll_hatype: 0,
sll_pkttype: 0,
sll_halen: 0,
sll_addr: [0; 8],
};
let ret = unsafe {
libc::bind(
fd,
&addr as *const _ as *const libc::sockaddr,
mem::size_of::<libc::sockaddr_ll>() as libc::socklen_t,
)
};
if ret < 0 {
unsafe {
libc::munmap(ring, ring_size);
libc::close(fd);
}
return Err(anyhow!(
"Failed to bind to interface: {}",
io::Error::last_os_error()
));
}
if promiscuous {
let mreq = PacketMreq {
mr_ifindex: ifindex,
mr_type: PACKET_MR_PROMISC,
mr_alen: 0,
mr_address: [0; 8],
};
let ret = unsafe {
libc::setsockopt(
fd,
libc::SOL_PACKET,
PACKET_ADD_MEMBERSHIP,
&mreq as *const _ as *const libc::c_void,
mem::size_of::<PacketMreq>() as libc::socklen_t,
)
};
if ret < 0 {
unsafe {
libc::munmap(ring, ring_size);
libc::close(fd);
}
return Err(anyhow!(
"Failed to enable promiscuous mode on '{}': {}",
interface,
io::Error::last_os_error()
));
}
info!("Promiscuous mode enabled on {}", interface);
}
info!("AF_PACKET ring buffer ready: {} frames", FRAME_NR);
Ok(Self {
fd,
ring: ring as *mut u8,
ring_size,
frame_idx: 0,
sampler: Sampler::new(sampling_interval),
})
}
}
impl Capture for AfPacket {
fn next_packet(&mut self) -> Option<PacketInfo> {
let mut pfd = libc::pollfd {
fd: self.fd,
events: libc::POLLIN,
revents: 0,
};
let ret = unsafe { libc::poll(&mut pfd, 1, 1000) }; if ret <= 0 {
return None;
}
let frame_offset = (self.frame_idx * FRAME_SIZE) as usize;
let hdr = unsafe { &mut *(self.ring.add(frame_offset) as *mut Tpacket2Hdr) };
if (hdr.tp_status & TP_STATUS_USER) == 0 {
return None;
}
let result = if self.sampler.select() {
let packet_data = unsafe {
let data_ptr = self.ring.add(frame_offset + hdr.tp_mac as usize);
std::slice::from_raw_parts(data_ptr, hdr.tp_snaplen as usize)
};
parse_ethernet(packet_data)
} else {
None
};
hdr.tp_status = TP_STATUS_KERNEL;
self.frame_idx = (self.frame_idx + 1) % FRAME_NR;
result
}
}
impl Drop for AfPacket {
fn drop(&mut self) {
unsafe {
libc::munmap(self.ring as *mut libc::c_void, self.ring_size);
libc::close(self.fd);
}
}
}
fn get_interface_index(fd: libc::c_int, interface: &str) -> Result<libc::c_int> {
let ifname = CString::new(interface)?;
let mut ifr: libc::ifreq = unsafe { mem::zeroed() };
let name_bytes = ifname.as_bytes_with_nul();
let copy_len = name_bytes.len().min(libc::IFNAMSIZ);
unsafe {
ptr::copy_nonoverlapping(
name_bytes.as_ptr(),
ifr.ifr_name.as_mut_ptr() as *mut u8,
copy_len,
);
}
#[cfg(target_env = "musl")]
let request = libc::SIOCGIFINDEX as libc::c_int;
#[cfg(not(target_env = "musl"))]
let request = libc::SIOCGIFINDEX as libc::c_ulong;
let ret = unsafe { libc::ioctl(fd, request, &mut ifr) };
if ret < 0 {
return Err(anyhow!(
"Interface '{}' not found: {}",
interface,
io::Error::last_os_error()
));
}
Ok(unsafe { ifr.ifr_ifru.ifru_ifindex })
}