use std::{
mem,
os::fd::{AsRawFd, BorrowedFd, FromRawFd, OwnedFd},
ptr,
};
use dope_sys::{
io::socket::{
Socket as _,
route::{
Attach, Plan,
count::{Count, Index},
install::{Error, Operation},
},
},
kernel,
};
use crate::Uring;
mod program;
const MAP_CREATE: libc::c_uint = 0;
const MAP_UPDATE_ELEM: libc::c_uint = 2;
const PROG_LOAD: libc::c_uint = 5;
const MAP_TYPE: u32 = 20;
const PROGRAM_TYPE: u32 = 21;
const ATTACH_TYPE: u32 = 39;
#[repr(C)]
#[derive(Default)]
struct MapCreate {
map_type: u32,
key_size: u32,
value_size: u32,
max_entries: u32,
map_flags: u32,
inner_map_fd: u32,
numa_node: u32,
map_name: [u8; 16],
map_ifindex: u32,
btf_fd: u32,
btf_key_type_id: u32,
btf_value_type_id: u32,
btf_vmlinux_value_type_id: u32,
map_extra: u64,
}
#[repr(C)]
struct MapUpdate {
map_fd: u32,
padding: u32,
key: u64,
value: u64,
flags: u64,
}
#[repr(C)]
struct ProgLoad {
prog_type: u32,
insn_cnt: u32,
insns: u64,
license: u64,
log_level: u32,
log_size: u32,
log_buf: u64,
kern_version: u32,
prog_flags: u32,
prog_name: [u8; 16],
prog_ifindex: u32,
expected_attach_type: u32,
}
pub struct Installer {
program: Loaded,
map: Map,
}
#[repr(transparent)]
struct Map(OwnedFd);
#[repr(transparent)]
struct Loaded(OwnedFd);
impl Installer {
pub(crate) fn try_new(plan: Plan) -> Result<Self, Error> {
let count = plan.count();
let map = Map::create(count)?;
let program = program::load(&map, plan)?;
Ok(Self { program, map })
}
}
impl Attach for Installer {
fn try_attach(&self, socket: BorrowedFd<'_>) -> Result<(), Error> {
let value = AsRawFd::as_raw_fd(&self.program.0);
Uring::set_option::<dope_sys::Errno>(
socket,
libc::SOL_SOCKET,
libc::SO_ATTACH_REUSEPORT_EBPF,
&value,
)
.map_err(|errno| Error {
operation: Operation::ProgramAttach,
errno,
})
}
fn try_insert(self, key: Index, socket: BorrowedFd<'_>) -> Result<Self, Error> {
let key = key.into_raw() as u32;
let value = AsRawFd::as_raw_fd(&socket) as u32;
let attr = MapUpdate {
map_fd: AsRawFd::as_raw_fd(&self.map.0) as u32,
padding: 0,
key: address(&key),
value: address(&value),
flags: 0,
};
bpf(Operation::MapInsert, MAP_UPDATE_ELEM, &attr)?;
Ok(self)
}
}
impl Map {
fn create(count: Count) -> Result<Self, Error> {
let mut attr = MapCreate {
map_type: MAP_TYPE,
key_size: mem::size_of::<u32>() as u32,
value_size: mem::size_of::<u32>() as u32,
max_entries: u32::from(count.encode()),
..MapCreate::default()
};
attr.map_name[..14].copy_from_slice(b"dope_route_map");
let map = bpf_fd(Operation::MapCreate, MAP_CREATE, &attr)?;
Ok(Self(map))
}
fn encode(&self) -> program::Instruction {
program::instruction(0x18, 2, 1, 0, AsRawFd::as_raw_fd(&self.0))
}
}
impl Loaded {
fn load(code: &[program::Instruction]) -> Result<Self, Error> {
let license = b"GPL\0";
let mut name = [0_u8; 16];
name[..10].copy_from_slice(b"dope_route");
let attr = ProgLoad {
prog_type: PROGRAM_TYPE,
insn_cnt: code.len() as u32,
insns: address_slice(code),
license: address_slice(license),
log_level: 0,
log_size: 0,
log_buf: 0,
kern_version: 0,
prog_flags: 0,
prog_name: name,
prog_ifindex: 0,
expected_attach_type: ATTACH_TYPE,
};
Ok(Self(bpf_fd(Operation::ProgramLoad, PROG_LOAD, &attr)?))
}
}
fn address<T>(value: &T) -> u64 {
ptr::from_ref(value) as u64
}
fn address_slice<T>(values: &[T]) -> u64 {
values.as_ptr() as u64
}
fn bpf_fd<T>(op: Operation, command: libc::c_uint, attr: &T) -> Result<OwnedFd, Error> {
let value = bpf(op, command, attr)?;
Ok(unsafe { OwnedFd::from_raw_fd(value.get()) })
}
fn bpf<T>(op: Operation, command: libc::c_uint, attr: &T) -> Result<kernel::Value, Error> {
let rc = unsafe {
libc::syscall(
libc::SYS_bpf,
command,
ptr::from_ref(attr).cast::<libc::c_void>(),
mem::size_of::<T>(),
)
};
unsafe { Uring::status(rc).try_value::<Uring, dope_sys::Errno>() }.map_err(|errno| Error {
operation: op,
errno,
})
}
const _: () = {
assert!(mem::size_of::<MapCreate>() == 72);
assert!(mem::size_of::<MapUpdate>() == 32);
assert!(mem::size_of::<ProgLoad>() == 72);
assert!(mem::size_of::<Installer>() == 2 * mem::size_of::<OwnedFd>());
assert!(mem::align_of::<Installer>() == mem::align_of::<OwnedFd>());
assert!(mem::align_of::<Map>() == mem::align_of::<OwnedFd>());
assert!(mem::align_of::<Loaded>() == mem::align_of::<OwnedFd>());
assert!(mem::size_of::<program::Instruction>() == 8);
};