use std::{
collections::HashMap,
fs::{create_dir_all, remove_dir_all},
path::{Path, PathBuf},
thread::sleep,
time::Duration,
};
use anyhow::anyhow;
use aya::{
Btf, Ebpf, EbpfLoader,
programs::{
Extension, FEntry, FExit, KProbe, LinkOrder as AyaLinkOrder, ProbeKind, SchedClassifier,
TcAttachType, TracePoint, UProbe,
fentry::FEntryLink,
fexit::FExitLink,
kprobe::KProbeLink,
links::FdLink,
loaded_programs,
tc::{SchedClassifierLink, TcAttachOptions},
trace_point::TracePointLink,
uprobe::UProbeLink,
},
};
use log::{debug, error, info, warn};
use multiprog::{TcDispatcher, XdpDispatcher};
use sled::{Config as SledConfig, Db};
use types::{AttachInfo, AttachOrder, Link, TcxLink};
use utils::{initialize_bpfman, tc_dispatcher_id, xdp_dispatcher_id};
use crate::{
config::Config,
directories::*,
errors::BpfmanError,
multiprog::{Dispatcher, DispatcherId, DispatcherInfo},
oci_utils::image_manager::ImageManager,
types::{
BpfProgType, BytecodeImage, Direction, LINKS_LINK_PREFIX, ListFilter, PROGRAM_PREFIX,
Program, ProgramData,
},
utils::{
bytes_to_string, bytes_to_u32, enter_netns, get_error_msg_from_stderr, open_config_file,
set_dir_permissions, should_map_be_pinned, sled_insert,
},
};
pub mod config;
mod dispatcher_config;
pub mod errors;
mod multiprog;
mod netlink;
mod oci_utils;
mod static_program;
pub mod types;
pub mod utils;
const MAPS_MODE: u32 = 0o0660;
const MAP_PREFIX: &str = "map_";
const MAPS_USED_BY_PREFIX: &str = "map_used_by_";
pub mod directories {
pub(crate) const CFGDIR_MODE: u32 = 0o6750;
pub(crate) const CFGDIR: &str = "/etc/bpfman";
pub(crate) const CFGDIR_STATIC_PROGRAMS: &str = "/etc/bpfman/programs.d";
pub(crate) const CFGPATH_BPFMAN_CONFIG: &str = "/etc/bpfman/bpfman.toml";
pub(crate) const RTDIR_MODE: u32 = 0o6770;
pub(crate) const RTDIR: &str = "/run/bpfman";
pub(crate) const RTDIR_FS: &str = "/run/bpfman/fs";
pub(crate) const RTDIR_FS_TC_INGRESS: &str = "/run/bpfman/fs/tc-ingress";
pub(crate) const RTDIR_FS_TC_EGRESS: &str = "/run/bpfman/fs/tc-egress";
pub(crate) const RTDIR_FS_XDP: &str = "/run/bpfman/fs/xdp";
pub(crate) const RTDIR_FS_DISPATCHER_TEST: &str = "/run/bpfman/fs/dispatcher-test";
pub(crate) const RTDIR_FS_TEST_TC_DISPATCHER: &str = "/run/bpfman/fs/dispatcher-test/tc";
pub(crate) const RTDIR_FS_TEST_XDP_DISPATCHER: &str = "/run/bpfman/fs/dispatcher-test/xdp";
pub(crate) const RTDIR_FS_LINKS: &str = "/run/bpfman/fs/links";
pub const RTDIR_FS_MAPS: &str = "/run/bpfman/fs/maps";
pub(crate) const RTDIR_TUF: &str = "/run/bpfman/tuf";
pub(crate) const RTDIR_DB: &str = "/run/bpfman/db";
}
#[cfg(not(test))]
pub fn get_db_config() -> SledConfig {
SledConfig::default().path(RTDIR_DB)
}
#[cfg(test)]
pub fn get_db_config() -> SledConfig {
SledConfig::default().temporary(true)
}
pub fn add_programs(
_config: &Config,
root_db: &Db,
programs: Vec<Program>,
) -> Result<Vec<Program>, BpfmanError> {
info!("Request to load {} programs", programs.len());
let result = add_programs_internal(root_db, programs);
match result {
Ok(ref p) => {
for prog in p.iter() {
let kind = prog
.get_data()
.get_kind()
.unwrap_or(Some(BpfProgType::Unspec));
let kind = kind.unwrap_or(BpfProgType::Unspec);
let name = prog.get_data().get_name().unwrap_or("not set".to_string());
info!("Success: loaded {kind} program named \"{name}\"");
}
}
Err(ref e) => {
error!("Error: failed to load programs: {e}");
}
};
result
}
fn add_programs_internal(
root_db: &Db,
mut programs: Vec<Program>,
) -> Result<Vec<Program>, BpfmanError> {
for program in programs.iter_mut() {
program.get_data_mut().load(root_db)?;
}
let map_owner_id = programs[0].get_data().get_map_owner_id()?;
if let Some(map_owner_id) = map_owner_id {
for program in programs.iter_mut() {
let map_pin_path = is_map_owner_id_valid(root_db, map_owner_id)?;
program.get_data_mut().set_map_pin_path(&map_pin_path)?;
}
}
let mut image_manager = init_image_manager()?;
for program in programs.iter_mut() {
program
.get_data_mut()
.set_program_bytes(root_db, &mut image_manager)?;
}
let mut loader = EbpfLoader::new();
loader.allow_unsupported_maps();
let global_data = programs[0].get_data().get_global_data()?;
for (key, value) in global_data.iter() {
loader.set_global(key, value.as_slice(), true);
}
let extensions: Vec<String> = programs
.iter()
.filter(|p| {
p.kind() == BpfProgType::Xdp
|| (p.kind() == BpfProgType::Tc && !p.get_data().get_is_tcx())
})
.map(|p| p.get_data().get_name().unwrap())
.collect();
for extension in extensions.iter() {
loader.extension(extension);
}
debug!("creating ebpf loader for bytecode");
let mut ebpf = loader.load(&programs[0].get_data().get_program_bytes()?)?;
let mut results = vec![];
for program in programs.iter_mut() {
let res = match program {
Program::Unsupported(_) => panic!("Cannot add unsupported program"),
_ => load_program(root_db, &mut ebpf, program.clone()),
};
results.push(res);
}
let program_ids = results
.iter()
.filter_map(|r| r.as_ref().ok())
.copied()
.collect::<Vec<u32>>();
let err_results: Vec<BpfmanError> = results.into_iter().filter_map(|r| r.err()).collect();
if !err_results.is_empty() {
for program in programs.into_iter() {
if let Ok(Some(pin_path)) = program.get_data().get_map_pin_path() {
let _ = cleanup_map_pin_path(&pin_path, map_owner_id);
}
let _ = program.delete(root_db);
}
return Err(BpfmanError::ProgramsLoadFailure(err_results));
}
for (program, id) in programs.iter_mut().zip(program_ids) {
save_map(root_db, program, id, map_owner_id)?;
program.get_data_mut().finalize(root_db, id)?;
}
Ok(programs)
}
pub fn remove_program(config: &Config, root_db: &Db, id: u32) -> Result<(), BpfmanError> {
let prog = match get(root_db, &id) {
Some(p) => p,
None => {
error!(
"Error: Request to unload program with id {id} but id does not exist or was not created by bpfman"
);
return Err(BpfmanError::Error(format!(
"Program {0} does not exist or was not created by bpfman",
id,
)));
}
};
let kind = prog
.get_data()
.get_kind()
.unwrap_or(Some(BpfProgType::Unspec))
.unwrap_or(BpfProgType::Unspec);
let name = prog.get_data().get_name().unwrap_or("not set".to_string());
info!("Request to unload {kind} program named \"{name}\" with id {id}");
let result = remove_program_internal(id, config, root_db, prog);
match result {
Ok(_) => info!("Success: unloaded {kind} program named \"{name}\" with id {id}"),
Err(ref e) => error!("Error: failed to unload {kind} program named \"{name}\": {e}"),
};
result
}
fn remove_program_internal(
id: u32,
config: &Config,
root_db: &Db,
prog: Program,
) -> Result<(), BpfmanError> {
let map_owner_id = prog.get_data().get_map_owner_id()?;
for link in prog.get_data().get_links(root_db)?.into_iter() {
detach_program_internal(config, root_db, prog.clone(), link)?;
}
prog.delete(root_db)
.map_err(BpfmanError::BpfmanProgramDeleteError)?;
delete_map(root_db, id, map_owner_id)?;
Ok(())
}
pub fn detach(config: &Config, root_db: &Db, id: u32) -> Result<(), BpfmanError> {
let tree = root_db
.open_tree(format!("{LINKS_LINK_PREFIX}{id}"))
.expect("Unable to open program database tree");
let link = Link::new_from_db(tree)?;
let program = link.get_program(root_db)?;
detach_program_internal(config, root_db, program, link)
}
pub fn attach_program(
config: &Config,
root_db: &Db,
id: u32,
attach_info: AttachInfo,
) -> Result<Link, BpfmanError> {
let mut prog = match get(root_db, &id) {
Some(p) => p,
None => {
error!(
"Error: Request to attach program with id {id} but id does not exist or was not created by bpfman"
);
return Err(BpfmanError::Error(format!(
"Program {0} does not exist or was not created by bpfman",
id,
)));
}
};
let kind = prog
.get_data()
.get_kind()
.unwrap_or(Some(BpfProgType::Unspec))
.unwrap_or(BpfProgType::Unspec);
let name = prog.get_data().get_name().unwrap_or("not set".to_string());
info!("Request to attach {kind} program named \"{name}\" with id {id}");
let mut link = prog.add_link()?;
let result = match link.attach(attach_info) {
Ok(_) => attach_program_internal(config, root_db, &prog, link.clone()),
Err(e) => Err(e),
};
match result {
Ok(ref link) => info!(
"Success: attached {kind} program named \"{name}\" with program id {id} with link {}",
link.get_id().unwrap_or_default(),
),
Err(ref e) => {
error!("Error: failed to attach {kind} program named \"{name}\": {e}");
if let Err(e) = prog.remove_link(root_db, link.clone()) {
warn!("Error: failed to remove link: {e}");
}
link.delete(root_db)?;
}
};
result
}
fn attach_program_internal(
config: &Config,
root_db: &Db,
program: &Program,
mut link: Link,
) -> Result<Link, BpfmanError> {
let program_type = program.kind();
if let Err(e) = match program {
Program::Xdp(_) | Program::Tc(_) => {
attach_multi_attach_program(root_db, program_type, &mut link, config)
}
Program::Tcx(_)
| Program::Tracepoint(_)
| Program::Kprobe(_)
| Program::Uprobe(_)
| Program::Fentry(_)
| Program::Fexit(_)
| Program::Unsupported(_) => attach_single_attach_program(root_db, &mut link),
} {
link.delete(root_db)?;
return Err(e);
}
link.finalize(root_db)?;
Ok(link)
}
fn detach_program_internal(
config: &Config,
root_db: &Db,
mut program: Program,
link: Link,
) -> Result<(), BpfmanError> {
match program {
Program::Xdp(_) | Program::Tc(_) => {
let did = link
.dispatcher_id()?
.ok_or(BpfmanError::DispatcherNotRequired)?;
let program_type = program.kind();
let if_index = link.ifindex()?;
let if_name = link.if_name().unwrap();
let direction = link.direction()?;
let nsid = link.nsid()?;
let netns = link.netns()?;
program.get_data().remove_link(root_db, link)?;
detach_multi_attach_program(
root_db,
config,
did,
program_type,
if_index,
if_name,
direction,
nsid,
netns,
)?
}
Program::Tcx(_) => {
let if_index = link
.ifindex()?
.ok_or_else(|| BpfmanError::InvalidInterface)?;
let direction = link
.direction()?
.ok_or_else(|| BpfmanError::InvalidDirection)?;
let nsid = link.nsid()?;
detach_single_attach_program(root_db, &mut program, link)?;
set_tcx_program_positions(root_db, if_index, direction, nsid)?;
}
Program::Tracepoint(_)
| Program::Kprobe(_)
| Program::Uprobe(_)
| Program::Fentry(_)
| Program::Fexit(_)
| Program::Unsupported(_) => {
detach_single_attach_program(root_db, &mut program, link)?;
}
};
Ok(())
}
pub fn list_programs(root_db: &Db, filter: ListFilter) -> Result<Vec<Program>, BpfmanError> {
debug!("BpfManager::list_programs()");
let mut bpfman_progs: HashMap<u32, Program> = get_programs_iter(root_db).collect();
Ok(loaded_programs()
.filter_map(|p| p.ok())
.map(|prog| {
let prog_id: u32 = prog.id();
match bpfman_progs.remove(&prog_id) {
Some(p) => p.to_owned(),
None => {
let db_tree = root_db
.open_tree(prog_id.to_string())
.expect("Unable to open program database tree for listing programs");
let mut data = ProgramData::new_empty(db_tree);
if let Err(e) = data.set_kernel_info(&prog) {
warn!("Unable to set kernal info for prog {prog_id}, error: {e}");
};
Program::Unsupported(data)
}
}
})
.filter(|p| filter.matches(p))
.collect())
}
pub fn get_program(root_db: &Db, id: u32) -> Result<Program, BpfmanError> {
debug!("Getting program with id: {id}");
match get(root_db, &id) {
Some(p) => Ok(p.to_owned()),
None => loaded_programs()
.find_map(|p| {
let prog = p.ok()?;
let prog_id: u32 = prog.id();
if prog_id == id {
let db_tree = root_db
.open_tree(prog_id.to_string())
.expect("Unable to open program database tree for listing programs");
let mut data = ProgramData::new_empty(db_tree);
data.set_kernel_info(&prog)
.expect("unable to set kernel info");
Some(Program::Unsupported(data))
} else {
None
}
})
.ok_or(BpfmanError::Error(format!(
"Program {0} does not exist",
id
))),
}
}
pub fn get_link(root_db: &Db, id: u32) -> Result<Link, BpfmanError> {
let tree = root_db
.open_tree(format!("{LINKS_LINK_PREFIX}{id}"))
.expect("Unable to open link database tree");
let link = Link::new_from_db(tree)?;
Ok(link)
}
pub fn pull_bytecode(root_db: &Db, image: BytecodeImage) -> anyhow::Result<()> {
let image_manager = &mut init_image_manager().map_err(|e| anyhow!(format!("{e}")))?;
image_manager.get_image(
root_db,
&image.image_url,
image.image_pull_policy.clone(),
image.username.clone(),
image.password.clone(),
)?;
Ok(())
}
pub fn init_database(sled_config: SledConfig) -> Result<Db, BpfmanError> {
let database_config = open_config_file().database().to_owned();
for _ in 0..=database_config.max_retries {
if let Ok(db) = sled_config.open() {
debug!("Successfully opened database");
return Ok(db);
} else {
info!(
"Database lock is already held, retrying after {} milliseconds",
database_config.millisec_delay
);
sleep(Duration::from_millis(database_config.millisec_delay));
}
}
Err(BpfmanError::DatabaseLockError)
}
pub(crate) fn init_image_manager() -> Result<ImageManager, BpfmanError> {
let signing_config = open_config_file().signing().to_owned();
match ImageManager::new(signing_config.verify_enabled, signing_config.allow_unsigned) {
Ok(im) => Ok(im),
Err(e) => {
error!("Unable to initialize ImageManager: {e}");
Err(BpfmanError::Error(format!(
"Unable to initialize ImageManager: {e}"
)))
}
}
}
fn get_dispatcher(id: &DispatcherId, root_db: &Db) -> Result<Option<Dispatcher>, BpfmanError> {
debug!("Getting dispatcher with id: {:?}", id);
let dispatcher_id = match id {
DispatcherId::Xdp(DispatcherInfo(nsid, if_index, _)) => {
xdp_dispatcher_id(*nsid, *if_index)?
}
DispatcherId::Tc(DispatcherInfo(nsid, if_index, Some(direction))) => {
tc_dispatcher_id(*nsid, *if_index, *direction)?
}
_ => {
return Ok(None);
}
};
let tree_name_prefix = format!("{dispatcher_id}_");
Ok(root_db
.tree_names()
.into_iter()
.find(|p| bytes_to_string(p).contains(&tree_name_prefix))
.map(|p| {
let tree = root_db.open_tree(p).expect("unable to open database tree");
Dispatcher::new_from_db(tree)
}))
}
fn num_attached_programs(did: &DispatcherId, root_db: &Db) -> Result<usize, BpfmanError> {
if let Some(d) = get_dispatcher(did, root_db)? {
Ok(d.num_extensions())
} else {
Ok(0)
}
}
fn get(root_db: &Db, id: &u32) -> Option<Program> {
let prog_tree: sled::IVec = (PROGRAM_PREFIX.to_string() + &id.to_string())
.as_bytes()
.into();
if root_db.tree_names().contains(&prog_tree) {
let tree = root_db
.open_tree(prog_tree)
.expect("unable to open database tree");
Some(Program::new_from_db(*id, tree).expect("Failed to build program from database"))
} else {
None
}
}
fn get_multi_attach_links(
root_db: &'_ Db,
program_type: BpfProgType,
if_index: Option<u32>,
direction: Option<Direction>,
nsid: u64,
) -> Result<Vec<Link>, BpfmanError> {
let mut links = Vec::new();
debug!(
"if_index: {:?}, direction: {:?}, nsid: {:?}",
if_index, direction, nsid
);
for p in root_db.tree_names() {
if !bytes_to_string(&p).contains(LINKS_LINK_PREFIX) {
continue;
}
debug!("Checking tree: {}", bytes_to_string(&p));
let tree = match root_db.open_tree(p) {
Ok(tree) => tree,
Err(e) => {
return Err(BpfmanError::DatabaseError(
"Unable to open database tree".to_string(),
e.to_string(),
));
}
};
let link = match Link::new_from_db(tree) {
Ok(link) => link,
Err(_) => {
debug!("Failed to create link from db tree");
continue;
}
};
if !(match program_type {
BpfProgType::Xdp => {
matches!(link, Link::Xdp(_))
}
BpfProgType::Tc => {
matches!(link, Link::Tc(_))
}
_ => false,
}) {
continue;
}
debug!(
"link {}: program_id: {} ifindex {} direction {:?} nsid {}",
link.get_id()?,
link.get_program_id()?,
link.ifindex()?.unwrap(),
link.direction()?,
link.nsid()?
);
if link.ifindex().unwrap() == if_index
&& link.direction().unwrap() == direction
&& link.nsid()? == nsid
{
links.push(link);
}
}
Ok(links)
}
fn get_tcx_links(
root_db: &Db,
if_index: u32,
direction: Direction,
nsid: u64,
) -> Result<Vec<TcxLink>, BpfmanError> {
let mut tcx_links = Vec::new();
for p in root_db.tree_names() {
if bytes_to_string(&p).contains(LINKS_LINK_PREFIX) {
let tree = root_db.open_tree(p).map_err(|e| {
BpfmanError::DatabaseError(
"Unable to open database tree".to_string(),
e.to_string(),
)
})?;
if let Ok(Link::Tcx(tcx_p)) = Link::new_from_db(tree) {
if let Ok(Some(tcx_p_if_index)) = tcx_p.get_ifindex() {
if let Ok(tcx_p_direction) = tcx_p.get_direction() {
if let Ok(tcx_p_nsid) = tcx_p.get_nsid() {
if tcx_p_if_index == if_index
&& tcx_p_direction == direction
&& tcx_p_nsid == nsid
{
tcx_links.push(tcx_p);
}
}
}
}
}
}
}
Ok(tcx_links)
}
fn sort_tcx_links(tcx_links: &mut [TcxLink]) {
tcx_links.sort_by_key(|l| {
let priority = l.get_priority().unwrap_or(1000); let position = l.get_current_position().unwrap_or(None); (priority, position)
});
}
fn add_and_set_tcx_link_positions(
root_db: &Db,
new_link: &mut TcxLink,
) -> Result<AttachOrder, BpfmanError> {
let if_index = new_link
.get_ifindex()?
.ok_or_else(|| BpfmanError::InvalidInterface)?;
let direction = new_link.get_direction()?;
let nsid = new_link.get_nsid()?;
let mut tcx_links = get_tcx_links(root_db, if_index, direction, nsid)?;
if tcx_links.is_empty() {
new_link.set_current_position(0)?;
return Ok(AttachOrder::First);
}
new_link.set_current_position(usize::MAX)?;
tcx_links.push(new_link.clone());
sort_tcx_links(&mut tcx_links);
for (i, p) in tcx_links.iter_mut().enumerate() {
p.set_current_position(i)?;
}
let new_program_position = new_link
.get_current_position()?
.ok_or_else(|| BpfmanError::InternalError("could not get current position".to_string()))?;
let order = if new_program_position == tcx_links.len() - 1 {
AttachOrder::After(tcx_links[new_program_position - 1].0.get_program_id()?)
} else {
AttachOrder::Before(tcx_links[new_program_position + 1].0.get_program_id()?)
};
Ok(order)
}
fn set_tcx_program_positions(
root_db: &Db,
if_index: u32,
direction: Direction,
netns: u64,
) -> Result<(), BpfmanError> {
let mut tcx_links = get_tcx_links(root_db, if_index, direction, netns)?;
sort_tcx_links(&mut tcx_links);
for (i, p) in tcx_links.iter_mut().enumerate() {
p.set_current_position(i)?;
}
Ok(())
}
fn add_and_set_link_positions(
root_db: &Db,
program_type: BpfProgType,
link: Link,
) -> Result<(), BpfmanError> {
debug!("BpfManager::add_and_set_link_positions()");
let if_index = link.ifindex().unwrap();
let direction = link.direction().unwrap();
let nsid = link.nsid().unwrap();
let mut extensions = get_multi_attach_links(root_db, program_type, if_index, direction, nsid)?;
extensions.push(link);
debug!("Found {} extensions", extensions.len());
extensions.sort_by_key(|b| {
(
b.priority().unwrap(),
b.get_attached().unwrap(),
b.get_program_name().unwrap().to_owned(),
)
});
for (i, v) in extensions.iter_mut().enumerate() {
v.set_current_position(i)
.expect("unable to set program position");
}
Ok(())
}
fn set_program_positions(
root_db: &Db,
program_type: BpfProgType,
if_index: u32,
direction: Option<Direction>,
nsid: u64,
) -> Result<(), BpfmanError> {
let mut extensions =
get_multi_attach_links(root_db, program_type, Some(if_index), direction, nsid)?;
extensions.sort_by_key(|b| {
(
b.priority().unwrap(),
b.get_attached().unwrap(),
b.get_program_name().unwrap().to_owned(),
)
});
for (i, v) in extensions.iter_mut().enumerate() {
v.set_current_position(i)
.expect("unable to set program position");
}
Ok(())
}
fn get_programs_iter(root_db: &Db) -> impl Iterator<Item = (u32, Program)> + '_ {
root_db
.tree_names()
.into_iter()
.filter(|p| bytes_to_string(p).contains(PROGRAM_PREFIX))
.filter_map(|p| {
let id = bytes_to_string(&p)
.split('_')
.next_back()
.unwrap()
.parse::<u32>()
.unwrap();
let tree = root_db.open_tree(p).expect("unable to open database tree");
match Program::new_from_db(id, tree) {
Ok(prog) => Some((id, prog)),
Err(_) => None, }
})
}
pub fn setup() -> Result<(Config, Db), BpfmanError> {
initialize_bpfman()?;
debug!("BpfManager::setup()");
Ok((open_config_file(), init_database(get_db_config())?))
}
pub(crate) fn load_program(
root_db: &Db,
loader: &mut Ebpf,
mut p: Program,
) -> Result<u32, BpfmanError> {
debug!("BpfManager::load_program()");
let name = &p.get_data().get_name()?;
let raw_program = loader
.program_mut(name)
.ok_or(BpfmanError::BpfFunctionNameNotValid(name.to_owned()))?;
let res = match p {
Program::Tc(ref mut program) => {
let ext: &mut Extension = raw_program.try_into()?;
let dispatcher = TcDispatcher::get_test()?;
let fd = dispatcher.fd()?.try_clone()?;
ext.load(fd, "compat_test")?;
program.get_data_mut().set_kernel_info(&ext.info()?)?;
let id = program.data.get_id()?;
ext.pin(format!("{RTDIR_FS}/prog_{id}"))
.map_err(BpfmanError::UnableToPinProgram)?;
Ok(id)
}
Program::Xdp(ref mut program) => {
let ext: &mut Extension = raw_program.try_into()?;
let dispatcher = XdpDispatcher::get_test()?;
let fd = dispatcher.fd()?.try_clone()?;
ext.load(fd, "compat_test")?;
program.get_data_mut().set_kernel_info(&ext.info()?)?;
let id = program.data.get_id()?;
ext.pin(format!("{RTDIR_FS}/prog_{id}"))
.map_err(BpfmanError::UnableToPinProgram)?;
Ok(id)
}
Program::Tracepoint(ref mut program) => {
let tracepoint: &mut TracePoint = raw_program.try_into()?;
tracepoint.load()?;
program
.get_data_mut()
.set_kernel_info(&tracepoint.info()?)?;
let id = program.data.get_id()?;
tracepoint
.pin(format!("{RTDIR_FS}/prog_{id}"))
.map_err(BpfmanError::UnableToPinProgram)?;
Ok(id)
}
Program::Kprobe(ref mut program) => {
let kprobe: &mut KProbe = raw_program.try_into()?;
kprobe.load()?;
match kprobe.kind() {
ProbeKind::KRetProbe => program.set_retprobe(true),
_ => Ok(()),
}?;
program.get_data_mut().set_kernel_info(&kprobe.info()?)?;
let id = program.data.get_id()?;
kprobe
.pin(format!("{RTDIR_FS}/prog_{id}"))
.map_err(BpfmanError::UnableToPinProgram)?;
Ok(id)
}
Program::Uprobe(ref mut program) => {
let uprobe: &mut UProbe = raw_program.try_into()?;
uprobe.load()?;
match uprobe.kind() {
ProbeKind::URetProbe => program.set_retprobe(true),
_ => Ok(()),
}?;
program.get_data_mut().set_kernel_info(&uprobe.info()?)?;
let id = program.data.get_id()?;
let program_pin_path = format!("{RTDIR_FS}/prog_{id}");
uprobe
.pin(program_pin_path.clone())
.map_err(BpfmanError::UnableToPinProgram)?;
Ok(id)
}
Program::Fentry(ref mut program) => {
let fn_name = program.get_fn_name()?;
let btf = Btf::from_sys_fs()?;
let fentry: &mut FEntry = raw_program.try_into()?;
fentry
.load(&fn_name, &btf)
.map_err(BpfmanError::BpfProgramError)?;
program.get_data_mut().set_kernel_info(&fentry.info()?)?;
let id = program.data.get_id()?;
fentry
.pin(format!("{RTDIR_FS}/prog_{id}"))
.map_err(BpfmanError::UnableToPinProgram)?;
Ok(id)
}
Program::Fexit(ref mut program) => {
let fn_name = program.get_fn_name()?;
let btf = Btf::from_sys_fs()?;
let fexit: &mut FExit = raw_program.try_into()?;
fexit
.load(&fn_name, &btf)
.map_err(BpfmanError::BpfProgramError)?;
program.get_data_mut().set_kernel_info(&fexit.info()?)?;
let id = program.data.get_id()?;
fexit
.pin(format!("{RTDIR_FS}/prog_{id}"))
.map_err(BpfmanError::UnableToPinProgram)?;
Ok(id)
}
Program::Tcx(ref mut program) => {
debug!("Loading TCX program");
let tcx: &mut SchedClassifier = raw_program.try_into()?;
debug!("Calling load on TCX program");
tcx.load()?;
program.get_data_mut().set_kernel_info(&tcx.info()?)?;
let id = program.data.get_id()?;
debug!("Pinning TCX program");
tcx.pin(format!("{RTDIR_FS}/prog_{id}"))
.map_err(BpfmanError::UnableToPinProgram)?;
Ok(id)
}
_ => panic!("not a supported single attach program"),
};
match res {
Ok(id) => {
if p.get_data().get_map_pin_path()?.is_none() {
let map_pin_path = calc_map_pin_path(id);
p.get_data_mut().set_map_pin_path(&map_pin_path)?;
create_map_pin_path(&map_pin_path)?;
for (name, map) in loader.maps_mut() {
if !should_map_be_pinned(name) {
continue;
}
debug!(
"Pinning map: {name} to path: {}",
map_pin_path.join(name).display()
);
map.pin(map_pin_path.join(name))
.map_err(BpfmanError::UnableToPinMap)?;
}
}
}
Err(_) => {
if p.get_data().get_id().is_ok() {
p.delete(root_db)
.map_err(BpfmanError::BpfmanProgramDeleteError)?;
};
}
};
res
}
pub(crate) fn attach_single_attach_program(root_db: &Db, l: &mut Link) -> Result<(), BpfmanError> {
debug!("BpfManager::attach_single_attach_program()");
let prog_id = l.get_program_id()?;
let id = l.get_id()?;
match l {
Link::Tracepoint(link) => {
if let Program::Tracepoint(_) = get_program(root_db, prog_id)? {
Ok(())
} else {
Err(BpfmanError::InvalidAttach(
"program is not a tracepoint program".to_string(),
))
}?;
let tracepoint = link.get_tracepoint()?;
let parts: Vec<&str> = tracepoint.split('/').collect();
if parts.len() != 2 {
return Err(BpfmanError::InvalidAttach(
link.get_tracepoint()?.to_string(),
));
}
let category = parts[0].to_owned();
let name = parts[1].to_owned();
let mut tracepoint: TracePoint =
TracePoint::from_pin(format!("{RTDIR_FS}/prog_{prog_id}"))?;
let link_id = tracepoint.attach(&category, &name)?;
let owned_link: TracePointLink = tracepoint.take_link(link_id)?;
let fd_link: FdLink = owned_link
.try_into()
.expect("unable to get owned tracepoint attach link");
fd_link
.pin(format!("{RTDIR_FS_LINKS}/{id}"))
.map_err(BpfmanError::UnableToPinLink)?;
Ok(())
}
Link::Kprobe(link) => {
let retprobe = if let Program::Kprobe(prog) = get_program(root_db, prog_id)? {
Ok(prog.get_retprobe()?)
} else {
Err(BpfmanError::InvalidAttach(
"program is not a kprobe program".to_string(),
))
}?;
let kind = match retprobe {
true => ProbeKind::KRetProbe,
false => ProbeKind::KProbe,
};
let mut kprobe: KProbe = KProbe::from_pin(format!("{RTDIR_FS}/prog_{prog_id}"), kind)?;
let link_id = kprobe.attach(link.get_fn_name()?, link.get_offset()?)?;
let owned_link: KProbeLink = kprobe.take_link(link_id)?;
let fd_link: FdLink = owned_link
.try_into()
.expect("unable to get owned kprobe attach link");
fd_link
.pin(format!("{RTDIR_FS_LINKS}/{id}"))
.map_err(BpfmanError::UnableToPinLink)?;
Ok(())
}
Link::Uprobe(link) => {
let retprobe = if let Program::Uprobe(prog) = get_program(root_db, prog_id)? {
Ok(prog.get_retprobe()?)
} else {
Err(BpfmanError::InvalidAttach(
"program is not a uprobe program".to_string(),
))
}?;
let kind = match retprobe {
true => ProbeKind::URetProbe,
false => ProbeKind::UProbe,
};
let program_pin_path = format!("{RTDIR_FS}/prog_{prog_id}");
let mut uprobe: UProbe = UProbe::from_pin(&program_pin_path, kind)?;
let fn_name = link.get_fn_name()?;
match link.get_container_pid()? {
None => {
let link_id = uprobe.attach(
fn_name.as_deref(),
link.get_offset()?,
link.get_target()?,
None,
)?;
let owned_link: UProbeLink = uprobe.take_link(link_id)?;
let fd_link: FdLink = owned_link
.try_into()
.expect("unable to get owned uprobe attach link");
fd_link
.pin(format!("{RTDIR_FS_LINKS}/{id}"))
.map_err(BpfmanError::UnableToPinLink)?;
Ok(())
}
Some(p) => {
let offset = link.get_offset()?.to_string();
let container_pid = p.to_string();
let mut prog_args = vec![
"uprobe".to_string(),
"--program-pin-path".to_string(),
program_pin_path,
"--link-pin-path".to_string(),
format!("{RTDIR_FS_LINKS}/{id}"),
"--offset".to_string(),
offset,
"--target".to_string(),
link.get_target()?.to_string(),
"--container-pid".to_string(),
container_pid,
];
if let Some(fn_name) = &link.get_fn_name()? {
prog_args.extend(["--fn-name".to_string(), fn_name.to_string()])
}
if let ProbeKind::URetProbe = kind {
prog_args.push("--retprobe".to_string());
}
if let Some(pid) = link.get_pid()? {
prog_args.extend(["--pid".to_string(), pid.to_string()])
}
debug!("calling bpfman-ns to attach uprobe in pid: {:?}", p);
let bpfman_ns_path = if Path::new("./target/debug/bpfman-ns").exists() {
"./target/debug/bpfman-ns"
} else if Path::new("./bpfman-ns").exists() {
"./bpfman-ns"
} else {
"bpfman-ns"
};
let output = std::process::Command::new(bpfman_ns_path)
.args(prog_args)
.output();
match output {
Ok(o) => {
if !o.status.success() {
info!(
"Error from bpfman-ns: {:?}",
get_error_msg_from_stderr(&o.stderr)
);
return Err(BpfmanError::ContainerAttachError {
program_type: "uprobe".to_string(),
container_pid: link.get_container_pid()?.unwrap(),
});
};
Ok(())
}
Err(e) => {
info!("bpfman-ns returned error: {:?}", e);
if let std::io::ErrorKind::NotFound = e.kind() {
info!("bpfman-ns binary was not found. Please check your PATH.");
}
Err(BpfmanError::ContainerAttachError {
program_type: "uprobe".to_string(),
container_pid: link.get_container_pid()?.unwrap(),
})
}
}
}
}
}
Link::Fentry(_link) => {
if let Program::Fentry(_) = get_program(root_db, prog_id)? {
Ok(())
} else {
Err(BpfmanError::InvalidAttach(
"program is not a fentry program".to_string(),
))
}?;
let mut fentry: FEntry = FEntry::from_pin(format!("{RTDIR_FS}/prog_{prog_id}"))?;
let link_id = fentry.attach()?;
let owned_link: FEntryLink = fentry.take_link(link_id)?;
let fd_link: FdLink = owned_link.into();
fd_link
.pin(format!("{RTDIR_FS_LINKS}/{id}"))
.map_err(BpfmanError::UnableToPinLink)?;
Ok(())
}
Link::Fexit(_link) => {
if let Program::Fexit(_) = get_program(root_db, prog_id)? {
Ok(())
} else {
Err(BpfmanError::InvalidAttach(
"program is not a fexit program".to_string(),
))
}?;
let mut fexit: FExit = FExit::from_pin(format!("{RTDIR_FS}/prog_{prog_id}"))?;
let link_id = fexit.attach()?;
let owned_link: FExitLink = fexit.take_link(link_id)?;
let fd_link: FdLink = owned_link.into();
fd_link
.pin(format!("{RTDIR_FS_LINKS}/{id}"))
.map_err(BpfmanError::UnableToPinLink)?;
Ok(())
}
Link::Tcx(link) => {
if let Program::Tcx(_) = get_program(root_db, prog_id)? {
Ok(())
} else {
Err(BpfmanError::InvalidAttach(
"program is not a tcx program".to_string(),
))
}?;
let mut tcx: SchedClassifier =
SchedClassifier::from_pin(format!("{RTDIR_FS}/prog_{prog_id}"))?;
let iface_string: String = link.get_iface()?;
let iface = iface_string.as_str();
let aya_direction = match link.get_direction()? {
Direction::Ingress => TcAttachType::Ingress,
Direction::Egress => TcAttachType::Egress,
};
let order = add_and_set_tcx_link_positions(root_db, link)?;
let link_order: AyaLinkOrder = order.into();
info!(
"Attaching tcx program to iface: {} direction: {:?} link_order: {:?}",
iface, aya_direction, link_order
);
let options = TcAttachOptions::TcxOrder(link_order);
let link_id = if let Some(netns) = link.get_netns()? {
let _netns_guard = enter_netns(netns)?;
tcx.attach_with_options(iface, aya_direction, options)?
} else {
tcx.attach_with_options(iface, aya_direction, options)?
};
let owned_link: SchedClassifierLink = tcx.take_link(link_id)?;
let fd_link: FdLink = owned_link.try_into()?;
fd_link
.pin(format!("{RTDIR_FS_LINKS}/{id}"))
.map_err(BpfmanError::UnableToPinLink)?;
Ok(())
}
_ => panic!("not a supported single attach program"),
}
}
pub(crate) fn detach_single_attach_program(
root_db: &Db,
p: &mut Program,
link: Link,
) -> Result<(), BpfmanError> {
p.remove_link(root_db, link)?;
Ok(())
}
pub(crate) fn attach_multi_attach_program(
root_db: &Db,
program_type: BpfProgType,
l: &mut Link,
config: &Config,
) -> Result<(), BpfmanError> {
debug!("BpfManager::attach_multi_attach_program()");
match program_type {
BpfProgType::Xdp => {
if let Program::Xdp(_) = get_program(root_db, l.get_program_id()?)? {
Ok(())
} else {
Err(BpfmanError::InvalidAttach(
"program is not a xdp program".to_string(),
))
}
}
BpfProgType::Tc => {
if let Program::Tc(_) = get_program(root_db, l.get_program_id()?)? {
Ok(())
} else {
Err(BpfmanError::InvalidAttach(
"program is not a tc program".to_string(),
))
}
}
_ => Err(BpfmanError::InvalidAttach(
"invalid program type".to_string(),
)),
}?;
let did = l
.dispatcher_id()?
.ok_or(BpfmanError::DispatcherNotRequired)?;
let next_available_id = num_attached_programs(&did, root_db)?;
debug!("next_available_id={next_available_id}");
if next_available_id >= 10 {
return Err(BpfmanError::TooManyPrograms);
}
let if_index = l.ifindex()?;
let if_name = l.if_name().unwrap().to_string();
let direction = l.direction()?;
let nsid = l.nsid()?;
add_and_set_link_positions(root_db, program_type, l.clone())?;
let mut programs = get_multi_attach_links(root_db, program_type, if_index, direction, nsid)?;
programs.push(l.clone());
debug!("programs={programs:?}");
let old_dispatcher = get_dispatcher(&did, root_db)?;
let if_config = if let Some(i) = config.interfaces() {
i.get(&if_name)
} else {
None
};
let next_revision = if let Some(ref old) = old_dispatcher {
old.next_revision()
} else {
1
};
let _ = Dispatcher::new(
root_db,
if_config,
&mut programs,
next_revision,
old_dispatcher,
)?;
l.set_attached()?;
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn detach_multi_attach_program(
root_db: &Db,
config: &Config,
did: DispatcherId,
program_type: BpfProgType,
if_index: Option<u32>,
if_name: String,
direction: Option<Direction>,
nsid: u64,
netns: Option<PathBuf>,
) -> Result<(), BpfmanError> {
debug!("BpfManager::detach_multi_attach_program()");
let netns_deleted = netns.is_some_and(|n| !n.exists());
let num_remaining_programs = num_attached_programs(&did, root_db)?.saturating_sub(1);
let mut old_dispatcher = get_dispatcher(&did, root_db)?;
debug!(
"netns_deleted: {}, num_remaining_programs: {}, old_dispatcher exists: {}",
netns_deleted,
num_remaining_programs,
old_dispatcher.is_some()
);
if num_remaining_programs == 0 || netns_deleted {
if let Some(ref mut old) = old_dispatcher {
return old.delete(root_db, true);
}
return Ok(());
}
set_program_positions(root_db, program_type, if_index.unwrap(), direction, nsid)?;
let mut programs = get_multi_attach_links(root_db, program_type, if_index, direction, nsid)?;
let if_config = if let Some(i) = config.interfaces() {
i.get(&if_name)
} else {
None
};
let next_revision = if let Some(ref old) = old_dispatcher {
old.next_revision()
} else {
1
};
debug!("next_revision = {next_revision}");
Dispatcher::new(
root_db,
if_config,
&mut programs,
next_revision,
old_dispatcher,
)?;
Ok(())
}
fn is_map_owner_id_valid(root_db: &Db, map_owner_id: u32) -> Result<PathBuf, BpfmanError> {
let map_pin_path = calc_map_pin_path(map_owner_id);
let name: &sled::IVec = &format!("{}{}", MAP_PREFIX, map_owner_id).as_bytes().into();
if root_db.tree_names().contains(name) {
return Ok(map_pin_path);
}
Err(BpfmanError::Error(
"map_owner_id does not exists".to_string(),
))
}
fn cleanup_map_pin_path(map_pin_path: &Path, map_owner_id: Option<u32>) -> Result<(), BpfmanError> {
if map_owner_id.is_none() && map_pin_path.exists() {
let _ = remove_dir_all(map_pin_path)
.map_err(|e| BpfmanError::Error(format!("can't delete map dir: {e}")));
Ok(())
} else {
Ok(())
}
}
fn save_map(
root_db: &Db,
program: &mut Program,
id: u32,
map_owner_id: Option<u32>,
) -> Result<(), BpfmanError> {
let data = program.get_data_mut();
match map_owner_id {
Some(m) => {
if let Some(map) = get_map(m, root_db) {
push_maps_used_by(map.clone(), id)?;
let used_by = get_maps_used_by(map)?;
data.set_maps_used_by(used_by.clone())?;
for used_by_id in used_by.iter() {
if let Some(mut program) = get(root_db, used_by_id) {
program.get_data_mut().set_maps_used_by(used_by.clone())?;
}
}
} else {
return Err(BpfmanError::Error(
"map_owner_id does not exist".to_string(),
));
}
}
None => {
let db_tree = root_db
.open_tree(format!("{}{}", MAP_PREFIX, id))
.expect("Unable to open map db tree");
set_maps_used_by(db_tree, vec![id])?;
data.set_maps_used_by(vec![id])?;
if let Some(map_pin_path) = data.get_map_pin_path()? {
if let Some(path) = map_pin_path.to_str() {
debug!("bpf set dir permissions for {}", path);
set_dir_permissions(path, MAPS_MODE);
} else {
return Err(BpfmanError::Error(format!(
"invalid map_pin_path {} for {}",
map_pin_path.display(),
id
)));
}
} else {
return Err(BpfmanError::Error(format!(
"map_pin_path should be set for {}",
id
)));
}
}
}
Ok(())
}
fn delete_map(root_db: &Db, id: u32, map_owner_id: Option<u32>) -> Result<(), BpfmanError> {
let index = match map_owner_id {
Some(i) => i,
None => id,
};
if let Some(map) = get_map(index, root_db) {
let mut used_by = get_maps_used_by(map.clone())?;
if let Some(index) = used_by.iter().position(|value| *value == id) {
used_by.swap_remove(index);
}
clear_maps_used_by(map.clone());
set_maps_used_by(map.clone(), used_by.clone())?;
if used_by.is_empty() {
let path: PathBuf = calc_map_pin_path(index);
root_db
.drop_tree(MAP_PREFIX.to_string() + &index.to_string())
.expect("unable to drop maps tree");
remove_dir_all(path)
.map_err(|e| BpfmanError::Error(format!("can't delete map dir: {e}")))?;
} else {
for id in used_by.iter() {
if let Some(mut program) = get(root_db, id) {
program.get_data_mut().set_maps_used_by(used_by.clone())?;
}
}
}
} else {
return Err(BpfmanError::Error(
"map_pin_path does not exists".to_string(),
));
}
Ok(())
}
pub(crate) fn calc_map_pin_path(id: u32) -> PathBuf {
PathBuf::from(format!("{RTDIR_FS_MAPS}/{}", id))
}
pub(crate) fn create_map_pin_path(p: &Path) -> Result<(), BpfmanError> {
create_dir_all(p).map_err(|e| BpfmanError::Error(format!("can't create map dir: {e}")))
}
pub(crate) fn set_maps_used_by(db_tree: sled::Tree, ids: Vec<u32>) -> Result<(), BpfmanError> {
ids.iter().enumerate().try_for_each(|(i, v)| {
sled_insert(
&db_tree,
format!("{MAPS_USED_BY_PREFIX}{i}").as_str(),
&v.to_ne_bytes(),
)
})
}
fn push_maps_used_by(db_tree: sled::Tree, id: u32) -> Result<(), BpfmanError> {
let existing_maps_used_by = get_maps_used_by(db_tree.clone())?;
sled_insert(
&db_tree,
format!("{MAPS_USED_BY_PREFIX}{}", existing_maps_used_by.len() + 1).as_str(),
&id.to_ne_bytes(),
)
}
fn get_maps_used_by(db_tree: sled::Tree) -> Result<Vec<u32>, BpfmanError> {
db_tree
.scan_prefix(MAPS_USED_BY_PREFIX)
.map(|n| n.map(|(_, v)| bytes_to_u32(v.to_vec())))
.map(|n| {
n.map_err(|e| {
BpfmanError::DatabaseError("Failed to get maps used by".to_string(), e.to_string())
})
})
.collect()
}
pub(crate) fn clear_maps_used_by(db_tree: sled::Tree) {
db_tree.scan_prefix(MAPS_USED_BY_PREFIX).for_each(|n| {
db_tree
.remove(n.unwrap().0)
.expect("unable to clear maps used by");
});
}
fn get_map(id: u32, root_db: &Db) -> Option<sled::Tree> {
root_db
.tree_names()
.into_iter()
.find(|n| bytes_to_string(n) == format!("{}{}", MAP_PREFIX, id))
.map(|n| root_db.open_tree(n).expect("unable to open map tree"))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_assert_rtdir_db() {
assert!(RTDIR_DB.starts_with("/run/"));
}
}