bpf_loader_lib/skeleton/
mod.rs

1//!  SPDX-License-Identifier: MIT
2//!
3//! Copyright (c) 2023, eunomia-bpf
4//! All rights reserved.
5//!
6
7//! # BpfSkeleton
8//!
9//! This is the main module of the bpf-loader
10//!
11//! It provides interfaces to load a bpf-skeleton from JSON, parse it and verify it, and receive data (ringbuf, perfevent, or map values) from it
12//!
13//! ## The main structure
14//!
15//! Three objects are provided to the user, see below.
16//!
17//! ### `BpfSkeletonBuilder`
18//!
19//! The builder of PreLoadBpfSkeleton, also the start point that the user should use
20//!
21//! It accepts the JSON skeleton, verify the definitions along with the BTF info in the program, and open the bpf_object
22//!
23//! It will build a `PreLoadBpfSkeleton`
24//!
25//! ### `PreLoadBpfSkeleton`
26//!
27//! A verified and opened bpf_object
28//!
29//! It has a method called `load_and_attach`, which will try to load the bpf_object, and attach the bpf programs in it to the corresponding attach points. On success, it will return a BpfSkeleton
30//!
31//! ### `BpfSkeleton`
32//!
33//! This is the most important object that the user will use.
34//!
35//! It provide abilities to polling data from the bpf program (through ringbuf, perfevent, or maps) in a unified interface. See `wait_and_poll_to_handler` for details.
36//!
37//! Besides, it provide ability to control the polling progress in another thread. You can get a handle using `create_poll_handle`, then pause/resume/terminate the polling function in another thread.
38use std::{any::Any, sync::Arc};
39
40use libbpf_rs::{Map, MapType, Object};
41use log::{debug, warn};
42
43use self::{handle::PollingHandle, poller::Poller, preload::attach::AttachLink};
44use crate::{
45    btf_container::BtfContainer,
46    export_event::{
47        type_descriptor::TypeDescriptor, EventExporter, EventExporterBuilder, EventHandler,
48        ExportFormatType,
49    },
50    meta::{EunomiaObjectMeta, MapExportConfig, MapMeta, MapSampleMeta, RunnerConfig},
51    program_poll_loop,
52};
53use anyhow::{anyhow, bail, Context, Result};
54
55const VMLINUX_BTF_PATH: &str = "/sys/kernel/btf/vmlinux";
56const BTF_PATH_ENV_NAME: &str = "BTF_FILE_PATH";
57
58/// The builder of the skeleton
59pub mod builder;
60/// controlling handles
61pub mod handle;
62pub(crate) mod poller;
63/// The preloaded skeleton
64pub mod preload;
65
66#[cfg(test)]
67#[cfg(not(feature = "no-load-bpf-tests"))]
68mod tests;
69/// Represents a polling-ready bpf skeleton. With you can control the ebpf program and poll from it.
70pub struct BpfSkeleton {
71    pub(crate) handle: PollingHandle,
72    ///   data storage
73    /// meta data control the behavior of ebpf program:
74    /// eg. types of the eBPF maps and prog, export data types
75    pub(crate) meta: EunomiaObjectMeta,
76    /// config of eunomia itself,
77    /// for how we creating, loading and interacting with the eBPF program
78    /// eg. poll maps timeout in ms
79    /// Note: this field seems to be never used
80    #[allow(unused)]
81    pub(crate) config_data: RunnerConfig,
82
83    // exporter: EventExporter,
84    /// the btf info of the loaded program
85    pub(crate) btf: Arc<BtfContainer>,
86    /// the links
87    #[allow(unused)]
88    pub(crate) links: Vec<AttachLink>,
89    pub(crate) prog: Object,
90}
91
92impl BpfSkeleton {
93    /// Create a poll handle to control the poll progress
94    /// You can create multiple ones. All handles have the same ability
95    pub fn create_poll_handle(&self) -> PollingHandle {
96        self.handle.clone()
97    }
98    /// Get the name of the loaded program
99    pub fn get_program_name(&self) -> &str {
100        &self.meta.bpf_skel.obj_name
101    }
102    /// Get the fd of the provided map
103    /// returns None if not found
104    pub fn get_map_fd(&self, name: impl AsRef<str>) -> Option<i32> {
105        self.prog.map(name).map(|m| m.fd())
106    }
107    /// Get the fd of the provided program
108    /// returns None if not found
109    pub fn get_prog_fd(&self, name: impl AsRef<str>) -> Option<i32> {
110        self.prog.prog(name).map(|p| p.fd())
111    }
112
113    fn build_poller_from_exporter<'a>(
114        &self,
115        exporter: Arc<EventExporter>,
116        export_type: ExportMapType<'a>,
117        bpf_map: &'a Map,
118    ) -> Result<Poller<'a>> {
119        let ret = match export_type {
120            ExportMapType::RingBuffer => Poller::RingBuf(
121                self.build_ringbuf_poller(bpf_map, exporter)
122                    .with_context(|| anyhow!("Failed to build ringbuf poller"))?,
123            ),
124            ExportMapType::PerfEventArray => Poller::PerfEvent(
125                self.build_perfevent_poller(bpf_map, exporter)
126                    .with_context(|| anyhow!("Failed to builf perfevent poller"))?,
127            ),
128            ExportMapType::Sample(sp) => Poller::SampleMap(
129                self.build_sample_map_poller(bpf_map, exporter, sp)
130                    .with_context(|| anyhow!("Failed to build sample map poller"))?,
131            ),
132        };
133        Ok(ret)
134    }
135
136    fn wait_and_poll_with_old_single_export(
137        &self,
138        export_format_type: ExportFormatType,
139        export_event_handler: Option<Arc<dyn EventHandler>>,
140        user_context: Option<Arc<dyn Any>>,
141    ) -> Result<()> {
142        let mut export_map: Option<(&MapMeta, ExportMapType)> = None;
143        for map_meta in self.meta.bpf_skel.maps.iter() {
144            let bpf_map = self
145                .prog
146                .map(&map_meta.name)
147                .ok_or_else(|| anyhow!("Map `{}` not found in bpf program", map_meta.name))?;
148            if let Some(sample_meta) = &map_meta.sample {
149                set_and_warn_existsing_map(
150                    &mut export_map,
151                    map_meta,
152                    ExportMapType::Sample(sample_meta),
153                );
154            } else if let MapType::RingBuf = bpf_map.map_type() {
155                set_and_warn_existsing_map(&mut export_map, map_meta, ExportMapType::RingBuffer);
156            } else if let MapType::PerfEventArray = bpf_map.map_type() {
157                set_and_warn_existsing_map(
158                    &mut export_map,
159                    map_meta,
160                    ExportMapType::PerfEventArray,
161                );
162            }
163        }
164        if let Some((map_meta, export_type)) = export_map {
165            let bpf_map = self
166                .prog
167                .map(&map_meta.name)
168                .ok_or_else(|| anyhow!("Invalid map name: {}", map_meta.name))?;
169            let exporter_builder =
170                create_exporter_builder(export_format_type, export_event_handler, user_context);
171            if self.meta.export_types.is_empty() {
172                bail!(
173                    "Export map named `{}` found, but no export type is provided",
174                    map_meta.name
175                );
176            }
177            let exporter = match export_type {
178                ExportMapType::RingBuffer => exporter_builder.build_for_single_value(
179                    &self.meta.export_types[0],
180                    self.btf.clone(),
181                    &map_meta.intepreter,
182                )?,
183                ExportMapType::PerfEventArray => exporter_builder.build_for_single_value(
184                    &self.meta.export_types[0],
185                    self.btf.clone(),
186                    &map_meta.intepreter,
187                )?,
188                ExportMapType::Sample(sp) => {
189                    let map_info = bpf_map.info().with_context(|| {
190                        anyhow!("Failed to get map info for `{}`", bpf_map.name())
191                    })?;
192                    exporter_builder.build_for_key_value(
193                        map_info.info.btf_key_type_id,
194                        map_info.info.btf_value_type_id,
195                        sp,
196                        &self.meta.export_types[0],
197                        self.btf.clone(),
198                    )?
199                }
200            };
201            let poller = self.build_poller_from_exporter(exporter, export_type, bpf_map)?;
202            self.handle.reset();
203            program_poll_loop!(&self.handle, {
204                poller.poll()?;
205            });
206        } else {
207            self.wait_for_no_export_program()
208                .with_context(|| anyhow!("Failed to wait for program"))?;
209        }
210        Ok(())
211    }
212    /// Start poll with each map corresponding to a different exporter
213    /// The function `exporter_provider` should return the ExportFormatType, EventHandler, and UserContext(if applies) for the given map name (If you want to set the exporter)
214    pub fn wait_and_poll_to_handler_with_multiple_exporter(
215        &self,
216        exporter_provider: impl Fn(
217            &str,
218        ) -> Option<(
219            ExportFormatType,
220            Arc<dyn EventHandler>,
221            Option<Arc<dyn Any>>,
222        )>,
223    ) -> Result<()> {
224        if !self.meta.enable_multiple_export_types {
225            bail!("This function only supports multiple export types");
226        }
227        let mut export_maps: Vec<(&MapMeta, ExportMapType)> = vec![];
228        for map_meta in self
229            .meta
230            .bpf_skel
231            .maps
232            .iter()
233            .filter(|v| !matches!(v.export_config, MapExportConfig::NoExport))
234        {
235            let bpf_map = self
236                .prog
237                .map(&map_meta.name)
238                .ok_or_else(|| anyhow!("Map `{}` not found in bpf program", map_meta.name))?;
239            if let Some(sample_meta) = &map_meta.sample {
240                export_maps.push((map_meta, ExportMapType::Sample(sample_meta)))
241            } else {
242                match bpf_map.map_type() {
243                    MapType::RingBuf => export_maps.push((map_meta, ExportMapType::RingBuffer)),
244                    MapType::PerfEventArray => {
245                        export_maps.push((map_meta, ExportMapType::PerfEventArray))
246                    }
247                    _ => {
248                        debug!(
249                            "Ignore map named {}, it's neither ringbuf nor perf event",
250                            map_meta.name
251                        )
252                    }
253                }
254            }
255        }
256        debug!("Export maps: {:#?}", export_maps);
257
258        // Before polling, we should reset the control flags
259        self.handle.reset();
260        if export_maps.is_empty() {
261            self.wait_for_no_export_program()
262                .with_context(|| anyhow!("Failed to wait for a non-export program"))?;
263        } else {
264            let mut pollers = vec![];
265            for (map_meta, export_map_type) in export_maps.into_iter() {
266                let bpf_map = self
267                    .prog
268                    .map(&map_meta.name)
269                    .ok_or_else(|| anyhow!("Invalid map name: {}", map_meta.name))?;
270                let map_info = bpf_map
271                    .info()
272                    .with_context(|| anyhow!("Failed to get map info for `{}`", bpf_map.name()))?;
273                let is_sample_map = matches!(export_map_type, ExportMapType::Sample(_));
274                // Fetch the export type, at here.
275                let type_desc = match &map_meta.export_config {
276                    MapExportConfig::ExportUseBtf(ty_id) => {
277                        TypeDescriptor::BtfType { type_id: *ty_id }
278                    }
279                    MapExportConfig::ExportUseCustomMembers(mems) => {
280                        TypeDescriptor::ManuallyOverride(mems.clone())
281                    }
282                    MapExportConfig::Default => {
283                        if is_sample_map {
284                            TypeDescriptor::BtfType {
285                                type_id: map_info.info.btf_value_type_id,
286                            }
287                        } else {
288                            bail!("MapExportConfig::Default only applies to sample map");
289                        }
290                    }
291                    MapExportConfig::NoExport => unreachable!("How could you reach here?"),
292                };
293                let builder = EventExporterBuilder::new();
294                let builder = if let Some((ty, handler, ctx)) = exporter_provider(&map_meta.name) {
295                    builder
296                        .set_export_format(ty)
297                        .set_export_event_handler(handler)
298                        .set_user_context(ctx)
299                } else {
300                    builder
301                };
302                match export_map_type {
303                    ExportMapType::RingBuffer => {
304                        let exporter = builder
305                            .build_for_single_value_with_type_descriptor(
306                                type_desc,
307                                self.btf.clone(),
308                                &map_meta.intepreter,
309                            )
310                            .with_context(|| anyhow!("Failed to build ringbuf exporter"))?;
311                        pollers.push(Poller::RingBuf(
312                            self.build_ringbuf_poller(bpf_map, exporter)?,
313                        ));
314                    }
315                    ExportMapType::PerfEventArray => {
316                        let exporter = builder
317                            .build_for_single_value_with_type_descriptor(
318                                type_desc,
319                                self.btf.clone(),
320                                &map_meta.intepreter,
321                            )
322                            .with_context(|| anyhow!("Failed to build perf event exporter"))?;
323                        pollers.push(Poller::PerfEvent(
324                            self.build_perfevent_poller(bpf_map, exporter)?,
325                        ));
326                    }
327                    ExportMapType::Sample(cfg) => {
328                        let exporter = builder
329                            .build_for_key_value_with_type_desc(
330                                TypeDescriptor::BtfType {
331                                    type_id: map_info.info.btf_key_type_id,
332                                },
333                                type_desc,
334                                cfg,
335                                self.btf.clone(),
336                            )
337                            .with_context(|| {
338                                anyhow!(
339                                    "Failed to build sampling exporter for `{}`",
340                                    bpf_map.name()
341                                )
342                            })?;
343                        pollers.push(Poller::SampleMap(
344                            self.build_sample_map_poller(bpf_map, exporter, cfg)?,
345                        ));
346                    }
347                }
348            }
349            program_poll_loop!(&self.handle, {
350                for poller in pollers.iter() {
351                    poller.poll()?;
352                }
353            });
354        }
355        Ok(())
356    }
357    /// @brief auto polling and export the data to user space handler
358    /// @details The key of the value is the field name in the export json.
359    /// This function will block the current thread and poll
360    /// If you want to control the poller, just create a handle using `create_poll_handle` before calling this.
361    /// Note: this function will set paused and terminating to false before polling.
362    pub fn wait_and_poll_to_handler(
363        &self,
364        export_format_type: ExportFormatType,
365        export_event_handler: Option<Arc<dyn EventHandler>>,
366        user_context: Option<Arc<dyn Any>>,
367    ) -> Result<()> {
368        if !self.meta.enable_multiple_export_types {
369            return self.wait_and_poll_with_old_single_export(
370                export_format_type,
371                export_event_handler,
372                user_context,
373            );
374        }
375        self.wait_and_poll_to_handler_with_multiple_exporter(|_| {
376            export_event_handler
377                .clone()
378                .map(|v| (export_format_type, v, user_context.clone()))
379        })
380    }
381}
382
383fn set_and_warn_existsing_map<'a>(
384    export_map: &mut Option<(&'a MapMeta, ExportMapType<'a>)>,
385    curr_map: &'a MapMeta,
386    ty: ExportMapType<'a>,
387) {
388    if let Some((meta, _)) = export_map {
389        warn!(
390            "Multiple export maps found, one is `{}`, another is `{}`",
391            meta.name, curr_map.name
392        );
393    }
394    export_map.replace((curr_map, ty));
395}
396#[derive(Debug)]
397enum ExportMapType<'a> {
398    RingBuffer,
399    PerfEventArray,
400    Sample(&'a MapSampleMeta),
401}
402
403fn create_exporter_builder(
404    export_format: ExportFormatType,
405    event_handler: Option<Arc<dyn EventHandler>>,
406    ctx: Option<Arc<dyn Any>>,
407) -> EventExporterBuilder {
408    let exporter_builder = EventExporterBuilder::new().set_export_format(export_format);
409    let exporter_builder = if let Some(hdl) = event_handler.clone() {
410        exporter_builder.set_export_event_handler(hdl)
411    } else {
412        exporter_builder
413    };
414
415    if let Some(user_ctx) = ctx.clone() {
416        exporter_builder.set_user_context(user_ctx)
417    } else {
418        exporter_builder
419    }
420}