use anyhow::Result; #[cfg(feature = "cgroupsv2_devices")] mod bpf { use std::os::unix::io::AsRawFd; use std::path::Path; use anyhow::{bail, Result}; use clap::{Arg, Command}; use libcgroups::v2::devices::{bpf, emulator, program}; use nix::fcntl::OFlag; use nix::sys::stat::Mode; use oci_spec::runtime::LinuxDeviceCgroup; const LICENSE: &str = "Apache"; fn cli() -> Command { clap::Command::new("bpf") .version("0.1") .about("tools to test BPF program for cgroups v2 devices") .arg(Arg::new("cgroup_dir").short('c').value_name("CGROUP_DIR")) .subcommand( Command::new("query").about("query list of BPF programs attached to cgroup dir"), ) .subcommand( Command::new("detach") .about("detach BPF program by id") .arg( Arg::new("id") .value_name("PROG_ID") .required(true) .help("ID of BPF program returned by query command"), ), ) .subcommand( Command::new("attach") .about("compile rules to BPF and attach to cgroup dir") .arg( Arg::new("input_file") .value_name("INPUT_FILE") .required(true) .help("File contains Vec in json format"), ), ) } fn parse_cgroupv1_device_rules>(path: P) -> Result> { let content = std::fs::read_to_string(path)?; let devices = serde_json::from_str(&content)?; Ok(devices) } pub fn run() -> Result<()> { let matches = cli().get_matches(); let cgroup_dir = matches.get_one::("cgroup_dir").unwrap(); let cgroup_fd = nix::dir::Dir::open( cgroup_dir.as_str(), OFlag::O_RDONLY | OFlag::O_DIRECTORY, Mode::from_bits(0o600).unwrap(), )?; match matches.subcommand() { Some(("query", _)) => { let progs = bpf::prog::query(cgroup_fd.as_raw_fd())?; for prog in &progs { println!("prog: id={}, fd={}", prog.id, prog.fd); } } Some(("detach", submatch)) => { let prog_id = submatch.get_one::("id").unwrap().parse::()?; let progs = bpf::prog::query(cgroup_fd.as_raw_fd())?; let prog = progs.iter().find(|v| v.id == prog_id); if prog.is_none() { bail!("can't get prog fd by prog id"); } bpf::prog::detach2(prog.unwrap().fd, cgroup_fd.as_raw_fd())?; println!("detach ok"); } Some(("attach", submatch)) => { let input_file = submatch.get_one::("input_file").unwrap(); let rules = parse_cgroupv1_device_rules(input_file)?; let mut emulator = emulator::Emulator::with_default_allow(false); emulator.add_rules(&rules); let prog = program::Program::from_rules(&emulator.rules, emulator.default_allow)?; let prog_fd = bpf::prog::load(LICENSE, prog.bytecodes())?; bpf::prog::attach(prog_fd, cgroup_fd.as_raw_fd())?; println!("attach ok"); } _ => unreachable!(), }; Ok(()) } } #[cfg(not(feature = "cgroupsv2_devices"))] mod bpf { use anyhow::{bail, Result}; pub fn run() -> Result<()> { if !cfg!(feature = "cgroupsv2_devices") { bail!("cgroupsv2_devices feature is not enabled"); } unreachable!() } } fn main() -> Result<()> { env_logger::init(); bpf::run()?; Ok(()) }