use std::io;
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd};
use super::insn::{Insn, encode};
use super::netlink;
use super::sys::{self, LinkCreateAttr, ProgLoadAttr, bpf_cmd, ctx_err};
use crate::Result;
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Action(pub u32);
impl Action {
pub const ABORTED: Action = Action(0);
pub const DROP: Action = Action(1);
pub const PASS: Action = Action(2);
pub const TX: Action = Action(3);
pub const REDIRECT: Action = Action(4);
}
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Mode(pub u32);
impl Mode {
pub const AUTO: Mode = Mode(0);
pub const GENERIC: Mode = Mode(1 << 1);
pub const DRIVER: Mode = Mode(1 << 2);
pub const HARDWARE: Mode = Mode(1 << 3);
#[inline]
pub fn supports_zerocopy(self) -> bool {
self == Mode::DRIVER || self == Mode::HARDWARE
}
fn candidates(self) -> &'static [Mode] {
match self {
Mode::AUTO => &[Mode::DRIVER, Mode::GENERIC],
Mode::DRIVER => &[Mode::DRIVER],
Mode::GENERIC => &[Mode::GENERIC],
Mode::HARDWARE => &[Mode::HARDWARE],
_ => &[Mode::GENERIC],
}
}
fn name(self) -> &'static str {
match self {
Mode::GENERIC => "generic",
Mode::DRIVER => "driver",
Mode::HARDWARE => "hardware",
_ => "auto",
}
}
}
const XDP_FLAGS_UPDATE_IF_NOEXIST: u32 = 1 << 0;
#[derive(Debug)]
pub struct Program {
fd: OwnedFd,
}
impl Program {
pub fn load(insns: &[Insn], name: &str) -> Result<Program> {
let bytes = encode(insns);
let mut prog_name = [0u8; 16];
let n = name.len().min(15);
prog_name[..n].copy_from_slice(&name.as_bytes()[..n]);
let load = |log: Option<&mut Vec<u8>>| -> Result<i32> {
let (log_buf, log_size, log_level) = match log {
Some(b) => (b.as_mut_ptr() as u64, b.len() as u32, 1),
None => (0, 0, 0),
};
let mut attr = ProgLoadAttr {
prog_type: sys::BPF_PROG_TYPE_XDP,
insn_cnt: insns.len() as u32,
insns: bytes.as_ptr() as u64,
license: c"GPL".as_ptr() as u64,
log_level,
log_size,
log_buf,
kern_version: 0,
prog_flags: 0,
prog_name,
prog_ifindex: 0,
expected_attach_type: 0,
};
unsafe { bpf_cmd(sys::BPF_PROG_LOAD, &mut attr) }
};
match load(None) {
Ok(fd) => Ok(Program {
fd: unsafe { OwnedFd::from_raw_fd(fd) },
}),
Err(first) => {
let mut log = vec![0u8; 65536];
match load(Some(&mut log)) {
Ok(fd) => Ok(Program {
fd: unsafe { OwnedFd::from_raw_fd(fd) },
}),
Err(e) => {
let end = log.iter().position(|&c| c == 0).unwrap_or(log.len());
let text = String::from_utf8_lossy(&log[..end]);
let text = text.trim();
if text.is_empty() {
Err(ctx_err("prog load", first))
} else {
Err(io::Error::new(
e.kind(),
format!("xdp: prog load: {e}\nverifier log:\n{text}"),
))
}
}
}
}
}
}
pub fn attach(&self, ifindex: u32, mode: Mode) -> Result<Link> {
let mut last: Option<io::Error> = None;
for &m in mode.candidates() {
match self.attach_exact(ifindex, m) {
Ok(link) => return Ok(link),
Err(e) => last = Some(e),
}
}
Err(last.unwrap_or_else(|| {
io::Error::new(io::ErrorKind::InvalidInput, "xdp: no attach mode to try")
}))
}
fn attach_exact(&self, ifindex: u32, mode: Mode) -> Result<Link> {
let mut attr = LinkCreateAttr {
prog_fd: self.fd.as_raw_fd() as u32,
target_ifindex: ifindex,
attach_type: sys::BPF_ATTACH_TYPE_XDP,
flags: mode.0,
};
let link = unsafe { bpf_cmd(sys::BPF_LINK_CREATE, &mut attr) };
if let Ok(fd) = link {
return Ok(Link {
kind: LinkKind::Bpf {
_link: unsafe { OwnedFd::from_raw_fd(fd) },
},
ifindex,
mode,
});
}
netlink::set_xdp(
ifindex,
self.fd.as_raw_fd(),
mode.0 | XDP_FLAGS_UPDATE_IF_NOEXIST,
)
.map_err(|e| ctx_err(&format!("attach {} mode", mode.name()), e))?;
Ok(Link {
kind: LinkKind::Netlink,
ifindex,
mode,
})
}
}
impl Program {
#[inline]
pub fn into_fd(self) -> OwnedFd {
self.fd
}
}
impl AsRawFd for Program {
#[inline]
fn as_raw_fd(&self) -> RawFd {
self.fd.as_raw_fd()
}
}
#[derive(Debug)]
enum LinkKind {
Bpf { _link: OwnedFd },
Netlink,
}
#[derive(Debug)]
pub struct Link {
kind: LinkKind,
ifindex: u32,
mode: Mode,
}
impl Link {
#[inline]
pub fn mode(&self) -> Mode {
self.mode
}
#[inline]
pub fn ifindex(&self) -> u32 {
self.ifindex
}
}
impl Drop for Link {
fn drop(&mut self) {
if matches!(self.kind, LinkKind::Netlink) {
let _ = netlink::set_xdp(self.ifindex, -1, self.mode.0);
}
}
}
pub fn detach(ifindex: u32) -> Result<()> {
netlink::set_xdp(ifindex, -1, 0)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn auto_tries_driver_before_generic() {
assert_eq!(Mode::AUTO.candidates(), &[Mode::DRIVER, Mode::GENERIC]);
}
#[test]
fn explicit_mode_does_not_fall_back() {
assert_eq!(Mode::DRIVER.candidates(), &[Mode::DRIVER]);
assert_eq!(Mode::GENERIC.candidates(), &[Mode::GENERIC]);
}
#[test]
fn only_native_modes_can_zerocopy() {
assert!(Mode::DRIVER.supports_zerocopy());
assert!(Mode::HARDWARE.supports_zerocopy());
assert!(!Mode::GENERIC.supports_zerocopy());
assert!(!Mode::AUTO.supports_zerocopy());
}
#[test]
fn mode_flag_values_match_uapi() {
assert_eq!(Mode::GENERIC.0, 2); assert_eq!(Mode::DRIVER.0, 4); assert_eq!(Mode::HARDWARE.0, 8); }
#[test]
fn action_values_match_uapi() {
assert_eq!(Action::ABORTED.0, 0);
assert_eq!(Action::DROP.0, 1);
assert_eq!(Action::PASS.0, 2);
assert_eq!(Action::TX.0, 3);
assert_eq!(Action::REDIRECT.0, 4);
}
}