From 56a5f9a7024a78d57222f6b609e845b1852f5a41 Mon Sep 17 00:00:00 2001 From: Giles Hutton Date: Mon, 30 Dec 2024 14:36:21 +0000 Subject: [PATCH] Improvements to BPF iterator --- collector/lib/rust/scraper/Cargo.toml | 3 + collector/lib/rust/scraper/build.rs | 15 +++ collector/lib/rust/scraper/src/bpf.rs | 70 ++++++++++++ collector/lib/rust/scraper/src/bpf/bpf.bpf.c | 67 +++++++++++ .../rust/scraper/src/bpf/common/bindings.h | 6 + .../scraper/src/bpf/common/bpf_iterator.h | 12 ++ collector/lib/rust/scraper/src/common.rs | 25 +++++ collector/lib/rust/scraper/src/lib.rs | 18 +-- collector/lib/rust/scraper/src/main.rs | 16 +++ collector/lib/rust/scraper/src/network.rs | 106 ++++++++++++++++++ 10 files changed, 330 insertions(+), 8 deletions(-) create mode 100644 collector/lib/rust/scraper/src/bpf.rs create mode 100644 collector/lib/rust/scraper/src/bpf/bpf.bpf.c create mode 100644 collector/lib/rust/scraper/src/bpf/common/bindings.h create mode 100644 collector/lib/rust/scraper/src/bpf/common/bpf_iterator.h create mode 100644 collector/lib/rust/scraper/src/common.rs create mode 100644 collector/lib/rust/scraper/src/network.rs diff --git a/collector/lib/rust/scraper/Cargo.toml b/collector/lib/rust/scraper/Cargo.toml index baa3e76b26..8d83f8e9a5 100644 --- a/collector/lib/rust/scraper/Cargo.toml +++ b/collector/lib/rust/scraper/Cargo.toml @@ -12,8 +12,11 @@ anyhow = "1.0.95" arrayvec = "0.7.6" clap = { version = "4.5.23", features = ["derive"] } clap_derive = "4.5.18" +cxx = "1.0.136" libbpf-rs = "=0.25.0-beta.1" [build-dependencies] +bindgen = "0.71.1" +cxx-build = "1.0.136" libbpf-cargo = "=0.25.0-beta.1" vmlinux = { version = "0.0", git = "https://github.com/libbpf/vmlinux.h.git", rev = "83a228cf37fc65f2d14e4896a04922b5ee531a94" } diff --git a/collector/lib/rust/scraper/build.rs b/collector/lib/rust/scraper/build.rs index 7401d814aa..574687813a 100644 --- a/collector/lib/rust/scraper/build.rs +++ b/collector/lib/rust/scraper/build.rs @@ -3,6 +3,19 @@ use std::io; use std::path::PathBuf; use std::{env, fs}; +fn make_bindings() { + let bindings = bindgen::Builder::default() + .header("src/bpf/common/bindings.h") + .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) + .generate() + .expect("failed to generate bindings"); + + let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); + bindings + .write_to_file(out_path.join("bindings.rs")) + .expect("failed to write bindings"); +} + fn make_skeleton(src: PathBuf) { let src_file = src.file_name().unwrap(); @@ -50,5 +63,7 @@ fn main() -> io::Result<()> { } } + make_bindings(); + Ok(()) } diff --git a/collector/lib/rust/scraper/src/bpf.rs b/collector/lib/rust/scraper/src/bpf.rs new file mode 100644 index 0000000000..6ecc5873d3 --- /dev/null +++ b/collector/lib/rust/scraper/src/bpf.rs @@ -0,0 +1,70 @@ +use anyhow::Result; + +use std::io::{self, Read}; +use std::mem::MaybeUninit; +mod bpf_kernel { + include!(concat!(env!("OUT_DIR"), "/bpf.bpf.skel.rs")); +} + +use bpf_kernel::*; +use libbpf_rs::skel::OpenSkel; +use libbpf_rs::skel::SkelBuilder; +use libbpf_rs::Iter; + +use crate::common; + +#[allow(dead_code)] +pub struct BPFScraper { + link: Option, + debug: bool, + reader: Option>, +} + +#[allow(dead_code)] +impl BPFScraper { + pub fn new(debug: bool) -> Self { + Self { + debug, + link: None, + reader: None, + } + } + + pub fn start(&mut self) -> Result<()> { + let mut builder = BpfSkelBuilder::default(); + builder.obj_builder.debug(self.debug); + + let mut open_object = MaybeUninit::uninit(); + let open = builder.open(&mut open_object)?; + + let skel = open.load()?; + + self.link = Some(skel.progs.dump_bpf_prog.attach()?); + + Ok(()) + } + + pub fn stop(&self) -> Result<()> { + Ok(()) + } +} + +impl IntoIterator for BPFScraper { + type Item = common::bpf::bpf_prog_result; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + let mut buffer = vec![0u8; size_of::()]; + let iter = Iter::new(&self.link.unwrap()).unwrap(); + let mut r = io::BufReader::new(iter); + let mut v = Vec::new(); + + while r.read_exact(&mut buffer).is_ok() { + let item: common::bpf::bpf_prog_result = + unsafe { std::ptr::read(buffer.as_ptr() as *const _) }; + v.push(item); + } + + v.into_iter() + } +} diff --git a/collector/lib/rust/scraper/src/bpf/bpf.bpf.c b/collector/lib/rust/scraper/src/bpf/bpf.bpf.c new file mode 100644 index 0000000000..2221db5909 --- /dev/null +++ b/collector/lib/rust/scraper/src/bpf/bpf.bpf.c @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2022 Red Hat, Inc. */ +#include + +#include +#include + +#include "common/bpf_iterator.h" + +char _license[] SEC("license") = "GPL"; + +static const char* get_name(struct btf* btf, long btf_id, const char* fallback) { + struct btf_type **types, *t; + unsigned int name_off; + const char* str; + + if (!btf) { + return fallback; + } + str = btf->strings; + types = btf->types; + bpf_probe_read_kernel(&t, sizeof(t), types + btf_id); + name_off = BPF_CORE_READ(t, name_off); + if (name_off >= btf->hdr.str_len) { + return fallback; + } + return str + name_off; +} + +SEC("iter/bpf_link") +int dump_bpf_link(struct bpf_iter__bpf_link* ctx) { + struct seq_file* seq = ctx->meta->seq; + struct bpf_link* link = ctx->link; + int link_id; + + if (!link) { + return 0; + } + + link_id = link->id; + BPF_SEQ_PRINTF(seq, "%d\n", link_id); + return 0; +} + +SEC("iter/bpf_prog") +int dump_bpf_prog(struct bpf_iter__bpf_prog* ctx) { + struct seq_file* seq = ctx->meta->seq; + __u64 seq_num = ctx->meta->seq_num; + struct bpf_prog* prog = ctx->prog; + struct bpf_prog_aux* aux; + + if (!prog) { + return 0; + } + + struct bpf_prog_result result = {0}; + + aux = prog->aux; + + result.id = aux->id; + bpf_core_read_str(result.name, BPF_STR_MAX, get_name(aux->btf, aux->func_info[0].type_id, aux->name)); + bpf_core_read_str(result.attached, BPF_STR_MAX, aux->attach_func_name); + + bpf_seq_write(seq, &result, sizeof(result)); + + return 0; +} diff --git a/collector/lib/rust/scraper/src/bpf/common/bindings.h b/collector/lib/rust/scraper/src/bpf/common/bindings.h new file mode 100644 index 0000000000..087a3ccf0d --- /dev/null +++ b/collector/lib/rust/scraper/src/bpf/common/bindings.h @@ -0,0 +1,6 @@ +#ifndef __SCRAPER_BINDINGS_H +#define __SCRAPER_BINDINGS_H + +#include "bpf_iterator.h" + +#endif diff --git a/collector/lib/rust/scraper/src/bpf/common/bpf_iterator.h b/collector/lib/rust/scraper/src/bpf/common/bpf_iterator.h new file mode 100644 index 0000000000..2438b02e2e --- /dev/null +++ b/collector/lib/rust/scraper/src/bpf/common/bpf_iterator.h @@ -0,0 +1,12 @@ +#ifndef __SCRAPER_BPF_ITER_H +#define __SCRAPER_BPF_ITER_H + +#define BPF_STR_MAX 32 + +struct bpf_prog_result { + unsigned int id; + char name[BPF_STR_MAX]; + char attached[BPF_STR_MAX]; +}; + +#endif diff --git a/collector/lib/rust/scraper/src/common.rs b/collector/lib/rust/scraper/src/common.rs new file mode 100644 index 0000000000..3b2a855068 --- /dev/null +++ b/collector/lib/rust/scraper/src/common.rs @@ -0,0 +1,25 @@ +pub mod bpf { + include!(concat!(env!("OUT_DIR"), "/bindings.rs")); + + use std::ffi::CStr; + + impl bpf_prog_result { + pub fn name(&self) -> String { + unsafe { + CStr::from_ptr(self.name.as_ptr()) + .to_str() + .unwrap_or("") + .to_string() + } + } + + pub fn attached(&self) -> String { + unsafe { + CStr::from_ptr(self.attached.as_ptr()) + .to_str() + .unwrap_or("") + .to_string() + } + } + } +} diff --git a/collector/lib/rust/scraper/src/lib.rs b/collector/lib/rust/scraper/src/lib.rs index 1bc56670dc..0d20c16c3f 100644 --- a/collector/lib/rust/scraper/src/lib.rs +++ b/collector/lib/rust/scraper/src/lib.rs @@ -1,12 +1,14 @@ +pub mod bpf; +pub mod common; pub mod network; -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); +#[cxx::bridge(namespace = "collector::rust")] +mod ffi { + extern "Rust" { + fn get_bpf_programs() -> Vec<::common::bpf::bpf_prog_result>; } } + +pub fn get_bpf_programs() -> Vec { + vec![] +} diff --git a/collector/lib/rust/scraper/src/main.rs b/collector/lib/rust/scraper/src/main.rs index b1d057b897..7a93b98809 100644 --- a/collector/lib/rust/scraper/src/main.rs +++ b/collector/lib/rust/scraper/src/main.rs @@ -12,13 +12,17 @@ mod tasks { include!(concat!(env!("OUT_DIR"), "/tasks.bpf.skel.rs")); } +mod bpf; +mod common; mod network; + use tasks::*; #[derive(Debug, ValueEnum, Clone)] enum Probe { Process, Network, + Bpf, } #[derive(Parser, Debug)] @@ -41,6 +45,17 @@ fn do_network(debug: bool) -> Result<()> { Ok(()) } +fn do_bpf(debug: bool) -> Result<()> { + let mut bpf = bpf::BPFScraper::new(debug); + bpf.start().unwrap(); + + for line in bpf.into_iter() { + println!("{} ({})", line.name(), line.attached()); + } + + Ok(()) +} + fn do_process(debug: bool) -> Result<()> { let mut builder = TasksSkelBuilder::default(); builder.obj_builder.debug(debug); @@ -70,5 +85,6 @@ fn main() -> Result<()> { match args.probe { Probe::Process => do_process(debug), Probe::Network => do_network(debug), + Probe::Bpf => do_bpf(debug), } } diff --git a/collector/lib/rust/scraper/src/network.rs b/collector/lib/rust/scraper/src/network.rs new file mode 100644 index 0000000000..0d647fac24 --- /dev/null +++ b/collector/lib/rust/scraper/src/network.rs @@ -0,0 +1,106 @@ +use std::io::{self, BufRead}; +use std::net::{Ipv4Addr, Ipv6Addr}; +use std::{mem::MaybeUninit, net::IpAddr}; + +use libbpf_rs::skel::OpenSkel; +use libbpf_rs::skel::SkelBuilder; + +use anyhow::Result; +use libbpf_rs::Iter; +use network_kernel::NetworkSkelBuilder; + +mod network_kernel { + include!(concat!(env!("OUT_DIR"), "/network.bpf.skel.rs")); +} + +#[derive(Debug)] +#[allow(dead_code)] +pub struct Connection { + local: IpAddr, + remote: IpAddr, + state: u8, +} + +#[allow(dead_code)] +pub struct NetworkScraper { + link: Option, + debug: bool, + reader: Option>, +} + +impl NetworkScraper { + pub fn new(debug: bool) -> Self { + Self { + link: None, + reader: None, + debug, + } + } + + pub fn start(&mut self) -> Result<()> { + let mut builder = NetworkSkelBuilder::default(); + builder.obj_builder.debug(self.debug); + + let mut open_object = MaybeUninit::uninit(); + let open = builder.open(&mut open_object)?; + + let skel = open.load()?; + + self.link = Some(skel.progs.dump_tcp4.attach()?); + + println!("started"); + Ok(()) + } + + #[allow(dead_code)] + pub fn stop(&self) -> Result<()> { + Ok(()) + } + + fn parse_connection(&self, line: &str) -> Option { + let parts: Vec<&str> = line.split_whitespace().collect(); + + let local_raw = parts[1]; + let remote_raw = parts[2]; + let state = parts[3]; + + Some(Connection { + local: self.parse_addr(local_raw), + remote: self.parse_addr(remote_raw), + state: u8::from_str_radix(state, 16).unwrap_or(0), + }) + } + + fn parse_addr(&self, raw: &str) -> IpAddr { + let sections: Vec<&str> = raw.split(':').collect(); + + if sections[0].len() == 8 { + let ip = u32::from_str_radix(sections[0], 16).unwrap(); + Ipv4Addr::from_bits(ip).into() + } else { + let ip = u128::from_str_radix(sections[0], 16).unwrap(); + Ipv6Addr::from_bits(ip).into() + } + } +} + +impl Iterator for NetworkScraper { + type Item = Connection; + + fn next(&mut self) -> Option { + let Some(l) = &self.link else { + println!("no link"); + return None; + }; + let iter = Iter::new(l).unwrap(); + let mut r = io::BufReader::new(iter); + + let mut buf = String::new(); + let Ok(_) = r.read_line(&mut buf) else { + println!("line read failed"); + return None; + }; + + self.parse_connection(&buf) + } +}