use std::ffi::c_int;
use std::io;
use std::mem;
use std::os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, OwnedFd, RawFd};
use crate::error::{Error, Result};
use crate::interface::IfIndex;
use crate::sys::Stats;
pub const ETH_P_ALL: u16 = 0x0003;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SocketKind {
Raw,
Dgram,
}
impl SocketKind {
const fn as_raw(self) -> c_int {
match self {
SocketKind::Raw => libc::SOCK_RAW,
SocketKind::Dgram => libc::SOCK_DGRAM,
}
}
}
#[derive(Debug)]
pub struct PacketSocket {
fd: OwnedFd,
ifindex: IfIndex,
}
impl PacketSocket {
pub fn open(ifindex: IfIndex, protocol: u16, kind: SocketKind) -> Result<Self> {
let proto_be = i32::from(protocol.to_be());
let raw = unsafe {
libc::socket(
libc::AF_PACKET,
kind.as_raw() | libc::SOCK_NONBLOCK | libc::SOCK_CLOEXEC,
proto_be,
)
};
if raw < 0 {
return Err(Error::Socket(io::Error::last_os_error()));
}
let fd = unsafe { OwnedFd::from_raw_fd(raw) };
let sock = PacketSocket { fd, ifindex };
sock.bind(protocol)?;
Ok(sock)
}
fn bind(&self, protocol: u16) -> Result<()> {
let mut addr: libc::sockaddr_ll = unsafe { mem::zeroed() };
addr.sll_family = libc::AF_PACKET as u16;
addr.sll_protocol = protocol.to_be();
addr.sll_ifindex = self.ifindex.0 as c_int;
let rc = unsafe {
libc::bind(
self.fd.as_raw_fd(),
(&raw const addr).cast::<libc::sockaddr>(),
mem::size_of::<libc::sockaddr_ll>() as libc::socklen_t,
)
};
if rc < 0 {
return Err(Error::Bind(io::Error::last_os_error()));
}
Ok(())
}
pub fn send(&self, frame: &[u8]) -> io::Result<usize> {
let n = unsafe {
libc::send(
self.fd.as_raw_fd(),
frame.as_ptr().cast(),
frame.len(),
libc::MSG_DONTWAIT,
)
};
if n < 0 {
return Err(io::Error::last_os_error());
}
Ok(n as usize)
}
pub fn recv_into(&self, buf: &mut [u8]) -> io::Result<(usize, u8)> {
let mut addr: libc::sockaddr_ll = unsafe { mem::zeroed() };
let mut addr_len = mem::size_of::<libc::sockaddr_ll>() as libc::socklen_t;
let n = unsafe {
libc::recvfrom(
self.fd.as_raw_fd(),
buf.as_mut_ptr().cast(),
buf.len(),
libc::MSG_DONTWAIT,
(&raw mut addr).cast::<libc::sockaddr>(),
&raw mut addr_len,
)
};
if n < 0 {
return Err(io::Error::last_os_error());
}
Ok((n as usize, addr.sll_pkttype))
}
pub fn set_promiscuous(&self, on: bool) -> Result<()> {
let mut mreq: libc::packet_mreq = unsafe { mem::zeroed() };
mreq.mr_ifindex = self.ifindex.0 as c_int;
mreq.mr_type = libc::PACKET_MR_PROMISC as u16;
let opt = if on { libc::PACKET_ADD_MEMBERSHIP } else { libc::PACKET_DROP_MEMBERSHIP };
let rc = unsafe {
libc::setsockopt(
self.fd.as_raw_fd(),
libc::SOL_PACKET,
opt,
(&raw const mreq).cast(),
mem::size_of::<libc::packet_mreq>() as libc::socklen_t,
)
};
if rc < 0 {
return Err(Error::SetSockOpt {
option: "PACKET_MR_PROMISC",
source: io::Error::last_os_error(),
});
}
Ok(())
}
pub fn set_fanout(&self, group_id: u16, mode: u16) -> Result<()> {
self.set_packet_opt_int(libc::PACKET_FANOUT, "PACKET_FANOUT", fanout_arg(group_id, mode))
}
pub fn set_packet_opt_int(&self, opt: c_int, name: &'static str, val: c_int) -> Result<()> {
let rc = unsafe {
libc::setsockopt(
self.fd.as_raw_fd(),
libc::SOL_PACKET,
opt,
(&raw const val).cast(),
mem::size_of::<c_int>() as libc::socklen_t,
)
};
if rc < 0 {
return Err(Error::SetSockOpt { option: name, source: io::Error::last_os_error() });
}
Ok(())
}
pub unsafe fn set_packet_opt<T>(&self, opt: c_int, name: &'static str, req: &T) -> Result<()> {
let rc = unsafe {
libc::setsockopt(
self.fd.as_raw_fd(),
libc::SOL_PACKET,
opt,
(req as *const T).cast(),
mem::size_of::<T>() as libc::socklen_t,
)
};
if rc < 0 {
return Err(Error::SetSockOpt { option: name, source: io::Error::last_os_error() });
}
Ok(())
}
pub fn statistics(&self) -> io::Result<Stats> {
let mut stats: libc::tpacket_stats_v3 = unsafe { mem::zeroed() };
let mut len = mem::size_of::<libc::tpacket_stats_v3>() as libc::socklen_t;
let rc = unsafe {
libc::getsockopt(
self.fd.as_raw_fd(),
libc::SOL_PACKET,
libc::PACKET_STATISTICS,
(&raw mut stats).cast(),
&raw mut len,
)
};
if rc < 0 {
return Err(io::Error::last_os_error());
}
Ok(Stats {
received: u64::from(stats.tp_packets),
dropped: u64::from(stats.tp_drops),
freezes: u64::from(stats.tp_freeze_q_cnt),
})
}
}
pub fn fanout_arg(group_id: u16, mode: u16) -> c_int {
c_int::from(group_id) | (c_int::from(mode) << 16)
}
pub fn poll_readable(fd: BorrowedFd<'_>, timeout_ms: c_int) -> io::Result<bool> {
let mut pfd = libc::pollfd { fd: fd.as_raw_fd(), events: libc::POLLIN, revents: 0 };
let rc = unsafe { libc::poll(&raw mut pfd, 1, timeout_ms) };
if rc < 0 {
return Err(io::Error::last_os_error());
}
Ok(rc > 0 && pfd.revents & libc::POLLIN != 0)
}
impl AsFd for PacketSocket {
fn as_fd(&self) -> BorrowedFd<'_> {
self.fd.as_fd()
}
}
impl AsRawFd for PacketSocket {
fn as_raw_fd(&self) -> RawFd {
self.fd.as_raw_fd()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fanout_arg_encoding() {
assert_eq!(fanout_arg(0x1234, libc::PACKET_FANOUT_LB as u16), (1 << 16) | 0x1234);
assert_eq!(fanout_arg(0, libc::PACKET_FANOUT_HASH as u16), 0);
assert_eq!(fanout_arg(0xffff, libc::PACKET_FANOUT_CPU as u16), (2 << 16) | 0xffff);
}
#[test]
#[cfg_attr(miri, ignore = "opens an AF_PACKET socket; not interpretable by Miri")]
fn open_without_privileges_reports_socket_error() {
match PacketSocket::open(IfIndex(1), ETH_P_ALL, SocketKind::Raw) {
Ok(sock) => {
assert!(sock.as_raw_fd() >= 0);
}
Err(Error::Socket(_)) | Err(Error::Bind(_)) => {}
Err(other) => panic!("unexpected error opening packet socket: {other}"),
}
}
}