use std::io;
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd};
use crate::Result;
const NETLINK_ROUTE: i32 = 0;
const RTM_SETLINK: u16 = 19;
const NLM_F_REQUEST: u16 = 0x01;
const NLM_F_ACK: u16 = 0x04;
const NLA_F_NESTED: u16 = 0x8000;
const IFLA_XDP: u16 = 43;
const IFLA_XDP_FD: u16 = 1;
const IFLA_XDP_FLAGS: u16 = 3;
fn nl_attr(typ: u16, data: &[u8]) -> Vec<u8> {
let l = 4 + data.len();
let padded = (l + 3) & !3;
let mut buf = vec![0u8; padded];
buf[0..2].copy_from_slice(&(l as u16).to_ne_bytes());
buf[2..4].copy_from_slice(&typ.to_ne_bytes());
buf[4..4 + data.len()].copy_from_slice(data);
buf
}
fn build_setlink_xdp(ifindex: u32, prog_fd: i32, flags: u32, seq: u32) -> Vec<u8> {
let fd_attr = nl_attr(IFLA_XDP_FD, &prog_fd.to_ne_bytes());
let flags_attr = nl_attr(IFLA_XDP_FLAGS, &flags.to_ne_bytes());
let mut nested_data = fd_attr;
nested_data.extend_from_slice(&flags_attr);
let nested = nl_attr(IFLA_XDP | NLA_F_NESTED, &nested_data);
let mut ifinfo = [0u8; 16];
ifinfo[0] = libc::AF_UNSPEC as u8;
ifinfo[4..8].copy_from_slice(&ifindex.to_ne_bytes());
let mut payload = Vec::with_capacity(16 + nested.len());
payload.extend_from_slice(&ifinfo);
payload.extend_from_slice(&nested);
let msg_len = 16 + payload.len();
let mut msg = vec![0u8; msg_len];
msg[0..4].copy_from_slice(&(msg_len as u32).to_ne_bytes());
msg[4..6].copy_from_slice(&RTM_SETLINK.to_ne_bytes());
msg[6..8].copy_from_slice(&(NLM_F_REQUEST | NLM_F_ACK).to_ne_bytes());
msg[8..12].copy_from_slice(&seq.to_ne_bytes());
msg[16..].copy_from_slice(&payload);
msg
}
pub fn set_xdp(ifindex: u32, prog_fd: i32, flags: u32) -> Result<()> {
let sock = unsafe { libc::socket(libc::AF_NETLINK, libc::SOCK_RAW, NETLINK_ROUTE) };
if sock < 0 {
return Err(io::Error::last_os_error());
}
let sock = unsafe { OwnedFd::from_raw_fd(sock) };
let raw = sock.as_raw_fd();
let msg = build_setlink_xdp(ifindex, prog_fd, flags, 1);
let mut sa = [0u8; 12];
sa[0..2].copy_from_slice(&(libc::AF_NETLINK as u16).to_ne_bytes());
let sent = unsafe {
libc::sendto(
raw,
msg.as_ptr() as *const libc::c_void,
msg.len(),
0,
sa.as_ptr() as *const libc::sockaddr,
sa.len() as libc::socklen_t,
)
};
if sent < 0 {
return Err(io::Error::last_os_error());
}
let mut buf = [0u8; 4096];
let n = unsafe { libc::recv(raw, buf.as_mut_ptr() as *mut libc::c_void, buf.len(), 0) };
if n < 0 {
return Err(io::Error::last_os_error());
}
if n as usize >= 20 {
let err_code = i32::from_ne_bytes([buf[16], buf[17], buf[18], buf[19]]);
if err_code != 0 {
return Err(io::Error::from_raw_os_error(-err_code));
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn setlink_message_layout() {
let msg = build_setlink_xdp(7, 42, 1 << 2, 1);
assert_eq!(
u32::from_ne_bytes(msg[0..4].try_into().unwrap()),
msg.len() as u32
);
assert_eq!(
u16::from_ne_bytes(msg[4..6].try_into().unwrap()),
RTM_SETLINK
);
assert_eq!(
u16::from_ne_bytes(msg[6..8].try_into().unwrap()),
NLM_F_REQUEST | NLM_F_ACK
);
assert_eq!(u32::from_ne_bytes(msg[20..24].try_into().unwrap()), 7);
assert_eq!(
u16::from_ne_bytes(msg[34..36].try_into().unwrap()),
IFLA_XDP | NLA_F_NESTED
);
assert_eq!(
u16::from_ne_bytes(msg[38..40].try_into().unwrap()),
IFLA_XDP_FD
);
assert_eq!(i32::from_ne_bytes(msg[40..44].try_into().unwrap()), 42);
assert_eq!(
u16::from_ne_bytes(msg[46..48].try_into().unwrap()),
IFLA_XDP_FLAGS
);
assert_eq!(u32::from_ne_bytes(msg[48..52].try_into().unwrap()), 1 << 2);
}
#[test]
fn detach_encodes_negative_fd() {
let msg = build_setlink_xdp(3, -1, 0, 1);
assert_eq!(i32::from_ne_bytes(msg[40..44].try_into().unwrap()), -1);
}
#[test]
fn attributes_are_padded_to_four_bytes() {
let a = nl_attr(1, &[1, 2, 3, 4, 5]);
assert_eq!(a.len(), 12);
assert_eq!(u16::from_ne_bytes(a[0..2].try_into().unwrap()), 9);
}
}