前言
eBPF是一項革命性的技術,可以在Linux核心中執行沙盒程式,而無需重新編譯核心或載入核心模組。它能夠在許多核心 hook 點安全地執行位元組碼,主要應用在雲原生網路、安全、跟蹤監控等方面。
eBPF 基金會 (https://ebpf.io) 是一個為 eBPF 技術而建立的非盈利性組織,隸屬於 Linux 基金會,其意在推動 eBPF 更好地發展,使其得到更加廣泛的運用。
下面我將介紹如何在Rust中開發基於eBPF技術的應用示例。(該示例教程主要面向具備Rust開發基礎的同學)
(一)環境準備
一臺VM或Linux系統主機
推薦系統:Ubuntu20.04
記憶體:4G以上
Rust開發工具鏈:v1.56~
(二)安裝llvm13(編譯bpf位元組碼需要)
apt-get update
apt-get -y install wget build-essential software-properties-common lsb-release libelf-dev linux-headers-generic pkg-config
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
./llvm.sh 13
rm -f ./llvm.sh
//檢驗是否安裝成功,輸出版本號表示安裝成功
llvm-config-13 --version | grep 13
(三)安裝 cargo-bpf腳手架
cargo install cargo-bpf --no-default-features --features=llvm13
(四)應用示例
#1、建立使用者空間程式碼目錄
cargo new bpfdemo
cd bpfdemo
#2、建立bpf程式碼目錄
cargo bpf new probes
ls
>>Cargo.lock Cargo.toml probes src
#3、編寫一個openmonitor bpf程式 輸出系統開啟的檔案
cd probes
cargo bpf add openmonitor
cd src
ls
>>lib.rs openmonitor
cd openmonitor
nano main.rs
完整openmonitor/main.rs程式碼如下:
#![no_std]
#![no_main]
use probes::openmonitor::*;
use redbpf_probes::kprobe::prelude::*;
program!(0xFFFFFFFE, "GPL");
#[map]
static mut OPEN_PATHS: PerfMap<OpenPath> = PerfMap::with_max_entries(1024);
#[kprobe]
fn do_sys_open(regs: Registers) {
let mut path = OpenPath::default();
unsafe {
let filename = regs.parm2() as *const u8;
if bpf_probe_read_user_str(
path.filename.as_mut_ptr() as *mut _,
path.filename.len() as u32,
filename as *const _,
) <= 0
{
bpf_trace_printk(b"error on bpf_probe_read_user_str\0");
return;
}
OPEN_PATHS.insert(regs.ctx, &path);
}
}
完整openmonitor/mod.rs程式碼如下
pub const PATHLEN: usize = 256;
#[repr(C)]
#[derive(Debug, Clone)]
pub struct OpenPath {
pub filename: [u8; PATHLEN],
}
impl Default for OpenPath {
fn default() -> OpenPath {
OpenPath {
filename: [0; PATHLEN],
}
}
}
#4.在probes目錄下編譯bpf程式,生成openmonitor.elf檔案
cargo bpf build --target-dir=../target
ls ../target/bpf/programs/openmonitor/openmonitor.elf
#5.在使用者空間程式碼中使用bpf程式,獲取系統開啟檔案
cd ../src
nano main.rs
完整bpfdemo/src/main.rs程式碼如下
use futures::stream::StreamExt;
use std::{ffi::CStr, ptr};
use tracing::Level;
use tracing_subscriber::FmtSubscriber;
use redbpf::load::Loader;
use probes::openmonitor::OpenPath;
fn probe_code() -> &'static [u8] {
include_bytes!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/target/bpf/programs/openmonitor/openmonitor.elf"
))
}
#[tokio::main(flavor = "current_thread")]
async fn main() {
let subscriber = FmtSubscriber::builder()
.with_max_level(Level::WARN)
.finish();
tracing::subscriber::set_global_default(subscriber).unwrap();
let mut loaded = Loader::load(probe_code()).expect("error on Loader::load");
let probe = loaded
.kprobe_mut("do_sys_open")
.expect("error on Loaded::kprobe_mut");
probe
.attach_kprobe("do_sys_open", 0)
.expect("error on KProbe::attach_kprobe");
probe
.attach_kprobe("do_sys_openat2", 0)
.expect("error on KProbe::attach_kprobe");
while let Some((map_name, events)) = loaded.events.next().await {
if map_name == "OPEN_PATHS" {
for event in events {
let open_path = unsafe { ptr::read(event.as_ptr() as *const OpenPath) };
unsafe {
let cfilename = CStr::from_ptr(open_path.filename.as_ptr() as *const _);
println!("{}", cfilename.to_string_lossy());
};
}
}
}
}
#6.在bpfdemo目錄下編譯執行
cargo build
cargo run
#將會輸出系統實時開啟的檔案
>>
/proc/driver/nvidia/params
/dev/nvidia0
/proc/driver/nvidia/params
/dev/nvidia0
/proc/driver/nvidia/params
/dev/nvidia0
/etc/localtime
/lib/x86_64-linux-gnu/libcuda.so.1
/lib/x86_64-linux-gnu/libm.so.6
/etc/netconfig
/sys/fs/cgroup/unified/system.slice/systemd-udevd.service/cgroup.procs
/sys/fs/cgroup/unified/system.slice/systemd-udevd.service/cgroup.threads
/proc/3084/cmdline
/proc/3729/cmdline
/proc/3994/cmdline
/proc/8823/cmdline
/proc/2231364/cmdline
/proc/2431788/cmdline
/proc/2560949/cmdline
/sys/class/hwmon
/sys/class/hwmon/hwmon6
/sys/class/hwmon/hwmon4
/sys/class/hwmon/hwmon2
/sys/class/hwmon/hwmon0
/sys/class/hwmon/hwmon7
/sys/class/hwmon/hwmon5
完畢!