Skip to content

Commit

Permalink
Improvements to BPF iterator
Browse files Browse the repository at this point in the history
  • Loading branch information
Stringy committed Dec 30, 2024
1 parent 76a3b5d commit 56a5f9a
Show file tree
Hide file tree
Showing 10 changed files with 330 additions and 8 deletions.
3 changes: 3 additions & 0 deletions collector/lib/rust/scraper/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
15 changes: 15 additions & 0 deletions collector/lib/rust/scraper/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down Expand Up @@ -50,5 +63,7 @@ fn main() -> io::Result<()> {
}
}

make_bindings();

Ok(())
}
70 changes: 70 additions & 0 deletions collector/lib/rust/scraper/src/bpf.rs
Original file line number Diff line number Diff line change
@@ -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<libbpf_rs::Link>,
debug: bool,
reader: Option<io::BufReader<Iter>>,
}

#[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<Self::Item>;

fn into_iter(self) -> Self::IntoIter {
let mut buffer = vec![0u8; size_of::<common::bpf::bpf_prog_result>()];
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()
}
}
67 changes: 67 additions & 0 deletions collector/lib/rust/scraper/src/bpf/bpf.bpf.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2022 Red Hat, Inc. */
#include <vmlinux.h>

#include <bpf/bpf_core_read.h>
#include <bpf/bpf_helpers.h>

#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;
}
6 changes: 6 additions & 0 deletions collector/lib/rust/scraper/src/bpf/common/bindings.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#ifndef __SCRAPER_BINDINGS_H
#define __SCRAPER_BINDINGS_H

#include "bpf_iterator.h"

#endif
12 changes: 12 additions & 0 deletions collector/lib/rust/scraper/src/bpf/common/bpf_iterator.h
Original file line number Diff line number Diff line change
@@ -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
25 changes: 25 additions & 0 deletions collector/lib/rust/scraper/src/common.rs
Original file line number Diff line number Diff line change
@@ -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()
}
}
}
}
18 changes: 10 additions & 8 deletions collector/lib/rust/scraper/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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<common::bpf::bpf_prog_result> {
vec![]
}
16 changes: 16 additions & 0 deletions collector/lib/rust/scraper/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -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);
Expand Down Expand Up @@ -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),
}
}
106 changes: 106 additions & 0 deletions collector/lib/rust/scraper/src/network.rs
Original file line number Diff line number Diff line change
@@ -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<libbpf_rs::Link>,
debug: bool,
reader: Option<io::BufReader<Iter>>,
}

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<Connection> {
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<Connection> {
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)
}
}

0 comments on commit 56a5f9a

Please sign in to comment.