#![allow(unsafe_code)]
use std::io;
use std::net::SocketAddr;
use std::os::fd::{AsRawFd, BorrowedFd, FromRawFd, OwnedFd, RawFd};
use std::time::Duration;
use crate::RxMeta;
pub const CTRL_LEN: usize = 128;
pub fn enable_rx_timestamps(fd: RawFd) -> io::Result<()> {
let flags = libc::SOF_TIMESTAMPING_RX_SOFTWARE
| libc::SOF_TIMESTAMPING_SOFTWARE
| libc::SOF_TIMESTAMPING_RX_HARDWARE
| libc::SOF_TIMESTAMPING_RAW_HARDWARE;
setsockopt_int(fd, libc::SOL_SOCKET, libc::SO_TIMESTAMPING, to_int(flags)?)
}
pub fn set_busy_poll(fd: RawFd, usec: u32) -> io::Result<()> {
setsockopt_int(fd, libc::SOL_SOCKET, libc::SO_BUSY_POLL, to_int(usec)?)
}
const SO_PREFER_BUSY_POLL: libc::c_int = 69;
const SO_BUSY_POLL_BUDGET: libc::c_int = 70;
pub fn set_prefer_busy_poll(fd: RawFd, on: bool) -> io::Result<()> {
setsockopt_int(fd, libc::SOL_SOCKET, SO_PREFER_BUSY_POLL, i32::from(on))
}
pub fn set_busy_poll_budget(fd: RawFd, packets: u16) -> io::Result<()> {
setsockopt_int(
fd,
libc::SOL_SOCKET,
SO_BUSY_POLL_BUDGET,
i32::from(packets),
)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BusyPoll {
pub usec: u32,
pub prefer: bool,
}
pub fn busy_poll_settings(fd: RawFd) -> io::Result<BusyPoll> {
let get = |opt| getsockopt_int(fd, libc::SOL_SOCKET, opt);
Ok(BusyPoll {
usec: u32::try_from(get(libc::SO_BUSY_POLL)?)
.map_err(|_| io::Error::from(io::ErrorKind::InvalidData))?,
prefer: get(SO_PREFER_BUSY_POLL)? != 0,
})
}
pub fn tcp_nodelay_and_cork(fd: RawFd) -> io::Result<Option<bool>> {
let sock = unsafe { BorrowedFd::borrow_raw(fd) };
let r = socket2::SockRef::from(&sock);
if r.protocol()? != Some(socket2::Protocol::TCP) {
return Ok(None);
}
r.set_tcp_nodelay(true)?;
Ok(Some(r.tcp_cork()?))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TcpKeepalive {
pub enabled: bool,
pub idle_s: u32,
pub interval_s: u32,
pub count: u32,
pub user_timeout_ms: u32,
}
pub fn set_tcp_keepalive(fd: RawFd, idle_s: u32, interval_s: u32, count: u32) -> io::Result<()> {
setsockopt_int(fd, libc::IPPROTO_TCP, libc::TCP_KEEPIDLE, to_int(idle_s)?)?;
setsockopt_int(
fd,
libc::IPPROTO_TCP,
libc::TCP_KEEPINTVL,
to_int(interval_s)?,
)?;
setsockopt_int(fd, libc::IPPROTO_TCP, libc::TCP_KEEPCNT, to_int(count)?)?;
setsockopt_int(fd, libc::SOL_SOCKET, libc::SO_KEEPALIVE, 1)
}
pub fn set_tcp_user_timeout(fd: RawFd, ms: u32) -> io::Result<()> {
setsockopt_int(fd, libc::IPPROTO_TCP, libc::TCP_USER_TIMEOUT, to_int(ms)?)
}
pub fn tcp_keepalive(fd: RawFd) -> io::Result<TcpKeepalive> {
let get = |level, opt| {
getsockopt_int(fd, level, opt)
.and_then(|v| u32::try_from(v).map_err(|_| io::Error::from(io::ErrorKind::InvalidData)))
};
Ok(TcpKeepalive {
enabled: get(libc::SOL_SOCKET, libc::SO_KEEPALIVE)? != 0,
idle_s: get(libc::IPPROTO_TCP, libc::TCP_KEEPIDLE)?,
interval_s: get(libc::IPPROTO_TCP, libc::TCP_KEEPINTVL)?,
count: get(libc::IPPROTO_TCP, libc::TCP_KEEPCNT)?,
user_timeout_ms: get(libc::IPPROTO_TCP, libc::TCP_USER_TIMEOUT)?,
})
}
pub fn granted_rcvbuf(bytes: u32) -> io::Result<u32> {
let s = socket2::Socket::new(socket2::Domain::IPV4, socket2::Type::STREAM, None)?;
s.set_recv_buffer_size(usize::try_from(bytes).map_err(io::Error::other)?)?;
u32::try_from(s.recv_buffer_size()?).map_err(io::Error::other)
}
pub fn set_rcvbuf(fd: RawFd, bytes: u32) -> io::Result<()> {
setsockopt_int(fd, libc::SOL_SOCKET, libc::SO_RCVBUF, to_int(bytes)?)
}
pub fn incoming_napi_id(fd: RawFd) -> io::Result<u32> {
const SO_INCOMING_NAPI_ID: libc::c_int = 56;
getsockopt_int(fd, libc::SOL_SOCKET, SO_INCOMING_NAPI_ID).map(i32::cast_unsigned)
}
pub fn incoming_cpu(fd: RawFd) -> io::Result<i32> {
getsockopt_int(fd, libc::SOL_SOCKET, libc::SO_INCOMING_CPU)
}
#[must_use]
pub fn current_cpu() -> Option<usize> {
usize::try_from(unsafe { libc::sched_getcpu() }).ok()
}
#[must_use]
pub fn parse_cpu_list(s: &str) -> Vec<usize> {
let mut out = Vec::new();
for part in s.trim().split(',').filter(|p| !p.is_empty()) {
match part.split_once('-') {
Some((a, b)) => {
if let (Ok(a), Ok(b)) = (a.trim().parse::<usize>(), b.trim().parse::<usize>()) {
out.extend(a..=b);
}
}
None => {
if let Ok(c) = part.trim().parse() {
out.push(c);
}
}
}
}
out
}
pub fn set_affinity(cpus: &[usize]) -> io::Result<()> {
if cpus.is_empty() {
return Ok(());
}
let rc = unsafe {
let mut set: libc::cpu_set_t = std::mem::zeroed();
for &c in cpus.iter().filter(|&&c| c < libc::CPU_SETSIZE as usize) {
libc::CPU_SET(c, &mut set);
}
libc::sched_setaffinity(0, std::mem::size_of::<libc::cpu_set_t>(), &raw const set)
};
if rc == 0 {
Ok(())
} else {
Err(io::Error::last_os_error())
}
}
pub fn spawn_pinned(
name: &str,
cpus: &[usize],
f: impl FnOnce() + Send + 'static,
) -> io::Result<()> {
type Job = Box<dyn FnOnce() + Send>;
extern "C" fn start(arg: *mut libc::c_void) -> *mut libc::c_void {
let job = unsafe { Box::from_raw(arg.cast::<Job>()) };
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(job));
std::ptr::null_mut()
}
let check = |rc: libc::c_int| {
if rc == 0 {
Ok(())
} else {
Err(io::Error::from_raw_os_error(rc))
}
};
let cname = std::ffi::CString::new(
name.as_bytes()
.iter()
.take(15)
.copied()
.collect::<Vec<u8>>(),
)
.map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "thread name contains NUL"))?;
let job: Job = Box::new(move || {
unsafe { libc::prctl(libc::PR_SET_NAME, cname.as_ptr()) };
f();
});
unsafe {
let mut attr: libc::pthread_attr_t = std::mem::zeroed();
check(libc::pthread_attr_init(&raw mut attr))?;
let result = (|| {
if !cpus.is_empty() {
let mut set: libc::cpu_set_t = std::mem::zeroed();
for &c in cpus.iter().filter(|&&c| c < libc::CPU_SETSIZE as usize) {
libc::CPU_SET(c, &mut set);
}
check(libc::pthread_attr_setaffinity_np(
&raw mut attr,
std::mem::size_of::<libc::cpu_set_t>(),
&raw const set,
))?;
}
check(libc::pthread_attr_setdetachstate(
&raw mut attr,
libc::PTHREAD_CREATE_DETACHED,
))?;
let arg = Box::into_raw(Box::new(job)).cast::<libc::c_void>();
let mut tid: libc::pthread_t = 0;
let rc = libc::pthread_create(&raw mut tid, &raw const attr, start, arg);
if rc != 0 {
drop(Box::from_raw(arg.cast::<Job>()));
}
check(rc)
})();
libc::pthread_attr_destroy(&raw mut attr);
result
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct SocketFlow {
pub local: SocketAddr,
pub peer: SocketAddr,
pub oif: u32,
pub mark: u32,
pub tos: u8,
}
pub(crate) fn socket_flow(fd: RawFd) -> io::Result<SocketFlow> {
let sock = unsafe { BorrowedFd::borrow_raw(fd) };
let r = socket2::SockRef::from(&sock);
let ip = |a: socket2::SockAddr| {
a.as_socket()
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "not an IP socket"))
};
let local = ip(r.local_addr()?)?;
let peer = ip(r.peer_addr()?)?;
let opt = |name| getsockopt_int(fd, libc::SOL_SOCKET, name).map_or(0, i32::cast_unsigned);
let v4 = match peer {
SocketAddr::V4(_) => true,
SocketAddr::V6(a) => a.ip().to_ipv4_mapped().is_some(),
};
let (level, name) = if v4 {
(libc::IPPROTO_IP, libc::IP_TOS)
} else {
(libc::IPPROTO_IPV6, libc::IPV6_TCLASS)
};
let tos = getsockopt_int(fd, level, name)
.ok()
.and_then(|t| u8::try_from(t).ok())
.unwrap_or(0);
Ok(SocketFlow {
local,
peer,
oif: opt(libc::SO_BINDTOIFINDEX),
mark: opt(libc::SO_MARK),
tos,
})
}
#[doc(hidden)]
pub fn rtnetlink_exchange(req: &[u8], reply: &mut [u8], timeout: Duration) -> io::Result<usize> {
let timed_out = |e: io::Error| {
if e.kind() == io::ErrorKind::WouldBlock {
io::Error::from(io::ErrorKind::TimedOut)
} else {
e
}
};
let raw = unsafe {
libc::socket(
libc::AF_NETLINK,
libc::SOCK_RAW | libc::SOCK_CLOEXEC,
libc::NETLINK_ROUTE,
)
};
if raw < 0 {
return Err(io::Error::last_os_error());
}
let sock = unsafe { OwnedFd::from_raw_fd(raw) };
let fd = sock.as_raw_fd();
let timeout = timeout.max(Duration::from_micros(1));
let tv = libc::timeval {
tv_sec: libc::time_t::try_from(timeout.as_secs()).unwrap_or(libc::time_t::MAX),
tv_usec: libc::suseconds_t::from(timeout.subsec_micros().min(999_999)),
};
for opt in [libc::SO_RCVTIMEO, libc::SO_SNDTIMEO] {
let rc = unsafe {
libc::setsockopt(
fd,
libc::SOL_SOCKET,
opt,
(&raw const tv).cast(),
socklen_of::<libc::timeval>(),
)
};
if rc != 0 {
return Err(io::Error::last_os_error());
}
}
let mut kernel: libc::sockaddr_nl = unsafe { std::mem::zeroed() };
kernel.nl_family = libc::sa_family_t::try_from(libc::AF_NETLINK).unwrap_or_default();
let sent = unsafe {
libc::sendto(
fd,
req.as_ptr().cast(),
req.len(),
0,
(&raw const kernel).cast(),
socklen_of::<libc::sockaddr_nl>(),
)
};
if usize::try_from(sent).ok() != Some(req.len()) {
return Err(if sent < 0 {
timed_out(io::Error::last_os_error())
} else {
io::Error::new(io::ErrorKind::WriteZero, "netlink request cut short")
});
}
for _ in 0..3 {
let mut from: libc::sockaddr_nl = unsafe { std::mem::zeroed() };
let mut from_len = socklen_of::<libc::sockaddr_nl>();
let n = unsafe {
libc::recvfrom(
fd,
reply.as_mut_ptr().cast(),
reply.len(),
libc::MSG_TRUNC,
(&raw mut from).cast(),
&raw mut from_len,
)
};
let Ok(n) = usize::try_from(n) else {
let e = io::Error::last_os_error();
if e.kind() == io::ErrorKind::Interrupted {
continue;
}
return Err(timed_out(e));
};
if from.nl_pid != 0 {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"netlink reply not from the kernel",
));
}
if n > reply.len() {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"netlink reply larger than the buffer",
));
}
return Ok(n);
}
Err(io::Error::from(io::ErrorKind::Interrupted))
}
#[must_use]
pub(crate) fn if_name(index: u32) -> Option<String> {
let mut buf = [0u8; libc::IF_NAMESIZE];
let p = unsafe { libc::if_indextoname(index, buf.as_mut_ptr().cast()) };
if p.is_null() {
return None;
}
std::ffi::CStr::from_bytes_until_nul(&buf)
.ok()?
.to_str()
.ok()
.map(str::to_owned)
}
#[allow(clippy::cast_possible_truncation)] const fn socklen_of<T>() -> libc::socklen_t {
std::mem::size_of::<T>() as libc::socklen_t
}
fn to_int<T: TryInto<libc::c_int>>(v: T) -> io::Result<libc::c_int> {
v.try_into()
.map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "socket option out of range"))
}
#[allow(clippy::cast_possible_truncation)] const INT_LEN: libc::socklen_t = std::mem::size_of::<libc::c_int>() as libc::socklen_t;
fn getsockopt_int(fd: RawFd, level: libc::c_int, name: libc::c_int) -> io::Result<i32> {
let mut v: libc::c_int = 0;
let mut len = INT_LEN;
let rc = unsafe { libc::getsockopt(fd, level, name, (&raw mut v).cast(), &raw mut len) };
if rc == 0 {
Ok(v)
} else {
Err(io::Error::last_os_error())
}
}
fn setsockopt_int(
fd: RawFd,
level: libc::c_int,
name: libc::c_int,
v: libc::c_int,
) -> io::Result<()> {
let rc = unsafe { libc::setsockopt(fd, level, name, (&raw const v).cast(), INT_LEN) };
if rc == 0 {
Ok(())
} else {
Err(io::Error::last_os_error())
}
}
#[repr(C)]
pub struct MsgSlot {
pub hdr: libc::msghdr,
iov: libc::iovec,
ctrl: [u8; CTRL_LEN],
}
impl MsgSlot {
#[must_use]
pub fn new() -> Box<Self> {
let mut s: Box<Self> = Box::new(unsafe { std::mem::zeroed() });
s.hdr.msg_iovlen = 1;
s
}
pub const fn arm(&mut self, buf: &mut [u8]) {
self.iov.iov_base = buf.as_mut_ptr().cast();
self.iov.iov_len = buf.len();
self.hdr.msg_iov = &raw mut self.iov;
self.hdr.msg_iovlen = 1;
self.hdr.msg_control = self.ctrl.as_mut_ptr().cast();
#[allow(clippy::cast_possible_truncation)]
{
self.hdr.msg_controllen = CTRL_LEN as _;
}
self.hdr.msg_flags = 0;
}
#[must_use]
pub fn timestamps(&self) -> (i64, i64) {
let mut sw = 0i64;
let mut hw = 0i64;
unsafe {
let mut c = libc::CMSG_FIRSTHDR(&raw const self.hdr);
while !c.is_null() {
if (*c).cmsg_level == libc::SOL_SOCKET && (*c).cmsg_type == libc::SO_TIMESTAMPING {
let ts = libc::CMSG_DATA(c)
.cast::<[libc::timespec; 3]>()
.read_unaligned();
sw = ts_ns(&ts[0]);
hw = ts_ns(&ts[2]);
}
c = libc::CMSG_NXTHDR(&raw const self.hdr, c);
}
}
(sw, hw)
}
#[must_use]
pub fn meta(&self, read_seq: u64) -> RxMeta {
let (rx_sw_ns, rx_hw_ns) = self.timestamps();
RxMeta {
rx_sw_ns,
rx_hw_ns,
read_seq,
}
}
}
const fn ts_ns(t: &libc::timespec) -> i64 {
t.tv_sec * 1_000_000_000 + t.tv_nsec
}
pub fn recvmsg(fd: RawFd, slot: &mut MsgSlot) -> io::Result<Option<usize>> {
let n = unsafe { libc::recvmsg(fd, &raw mut slot.hdr, libc::MSG_DONTWAIT) };
if let Ok(n) = usize::try_from(n) {
return Ok(Some(n));
}
let e = io::Error::last_os_error();
if e.raw_os_error() == Some(libc::EAGAIN) {
Ok(None)
} else {
Err(e)
}
}
pub fn send(fd: RawFd, buf: &[u8]) -> io::Result<usize> {
let n = unsafe {
libc::send(
fd,
buf.as_ptr().cast(),
buf.len(),
libc::MSG_DONTWAIT | libc::MSG_NOSIGNAL,
)
};
if let Ok(n) = usize::try_from(n) {
return Ok(n);
}
let e = io::Error::last_os_error();
if e.raw_os_error() == Some(libc::EAGAIN) {
Ok(0)
} else {
Err(e)
}
}