1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
use std::collections::HashMap;
use crossbeam_channel::Sender;
use slog::info;
use crate::{
program_version::PerfBufferSize, set_memlock_limit, ArrayMap, BpfHashMap, DebugfsMountOpts,
OxidebpfError, PerfChannelMessage, ProgramBlueprint, ProgramVersion, SchedulingPolicy, LOGGER,
};
/// A group of eBPF [`ProgramVersion`](struct@ProgramVersion)s that a user
/// wishes to load from a blueprint. The loader will attempt each `ProgramVersion`
/// in order until one successfully loads, or none do.
pub struct ProgramGroup<'a> {
loaded_version: Option<ProgramVersion<'a>>,
mem_limit: Option<usize>,
loaded: bool,
debugfs_mount: DebugfsMountOpts,
polling_thread_policy: Option<SchedulingPolicy>,
}
impl<'a> Default for ProgramGroup<'a> {
fn default() -> Self {
Self::new()
}
}
impl<'a> ProgramGroup<'a> {
/// Create a program group that will manage multiple
/// [`ProgramVersion`](struct@ProgramVersion)s.
///
/// Together with [`load()`](fn.load.html), this is the primary
/// public interface of the oxidebpf library. You feed your
/// `ProgramGroup` a collection of `ProgramVersion`s, each with
/// their own set of `Program`s. Note that you must provide your
/// `ProgramGroup` with a
/// [`ProgramBlueprint`](struct@ProgramBlueprint). The blueprint
/// contains the parsed object file with all the eBPF programs and
/// maps you may load.
///
/// # Example
///
/// ```
/// use oxidebpf::ProgramBlueprint;
/// use oxidebpf::{ProgramGroup, Program, ProgramVersion, ProgramType};
/// use std::path::PathBuf;
///
/// let program = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
/// .join("test")
/// .join(format!("test_program_{}", std::env::consts::ARCH));
/// let program_blueprint =
/// ProgramBlueprint::new(&std::fs::read(program).expect("Could not open file"), None)
/// .expect("Could not open test object file");
///
/// ProgramGroup::new();
/// ```
pub fn new() -> ProgramGroup<'a> {
ProgramGroup {
loaded_version: None,
mem_limit: None,
debugfs_mount: DebugfsMountOpts::MountDisabled,
loaded: false,
polling_thread_policy: None,
}
}
/// Manually set the memlock ulimit for this `ProgramGroup`. The limit will be applied
/// when calling `load()`.
pub fn mem_limit(mut self, limit: usize) -> Self {
self.mem_limit = Some(limit);
self
}
/// Controls whether `debugfs` is mounted before attaching {k,u}probes. This operation only
/// occurs if `perf_event_open` is not supported and debugfs is not mounted. The
/// [DebugfsMountOpts](enum@DebugfsMountOpts) enum determines where `debugfs` gets mounted to.
pub fn auto_mount_debugfs(mut self, mount_options: DebugfsMountOpts) -> Self {
self.debugfs_mount = mount_options;
self
}
/// Sets the thread priority for the thread that polls perfmaps for events coming from eBPF
/// to userspace. The priority number specified should be valid for the scheduling policy you
/// provide (documented in the enum). This may be useful if you find you're missing messages
/// that you expect to be present, or are dropping more messages than seems reasonable.
pub fn polling_thread_priority(mut self, scheduling_policy: SchedulingPolicy) -> Self {
self.polling_thread_policy = Some(scheduling_policy);
self
}
/// Attempt to load [`ProgramVersion`](struct@ProgramVersion)s until one
/// successfully loads.
///
/// This function attempts to load each `ProgramVersion` in the order given until
/// one successfully loads. When one loads, if that version had a perfmap channel,
/// a [`PerfChannelMessage`](struct@PerfChannelMessage) receiver crossbeam channel
/// is available after loading by calling `get_receiver()` on the `ProgramGroup`.
/// If none load, a `NoProgramVersionLoaded` error is returned, along with all the
/// internal errors generated during attempted loading.
///
/// If the program version contain any perfmaps,
/// perfmap_opts_fn(), will be called on each one until a version
/// suceeds to load. perfmap_opts_fn() returns a channel from
/// which to send perf messages, and a [`PerfBufferSize`] to
/// specify how big to make the perf buffer(s). When the perfmap
/// is created the buffer will take at most the specified number
/// of bytes but it will shrink to fit a page size that is a power
/// of two.
///
/// NOTE: Once you call `load()`, it cannot be called again without re-creating
/// the `ProgramGroup`.
///
/// # Example
///
/// ```
/// use oxidebpf::ProgramBlueprint;
/// use oxidebpf::{ProgramGroup, Program, ProgramVersion, ProgramType, PerfBufferSize};
/// use std::path::PathBuf;
///
/// let program = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
/// .join("test")
/// .join(format!("test_program_{}", std::env::consts::ARCH));
/// let program_blueprint =
/// ProgramBlueprint::new(&std::fs::read(program).expect("Could not open file"), None)
/// .expect("Could not open test object file");
/// let mut program_group = ProgramGroup::new();
///
/// let (tx, rx) = crossbeam_channel::bounded(1024);
///
/// program_group.load(
/// program_blueprint,
/// vec![ProgramVersion::new(vec![Program::new(
/// "test_program",
/// &["do_mount"],
/// ).syscall(true)])],
/// || (tx.clone(), PerfBufferSize::PerCpu(1024 * 8)),
/// ).expect("Could not load programs");
/// ```
pub fn load(
&mut self,
program_blueprint: ProgramBlueprint,
program_versions: Vec<ProgramVersion<'a>>,
mut perfmap_opts_fn: impl FnMut() -> (Sender<PerfChannelMessage>, PerfBufferSize),
) -> Result<(), OxidebpfError> {
if self.loaded {
info!(
LOGGER.0,
"ProgramGroup::load(); error: attempting to load a program group that was already loaded"
);
return Err(OxidebpfError::ProgramGroupAlreadyLoaded);
}
if let Some(limit) = self.mem_limit {
set_memlock_limit(limit)?;
}
let mut errors = vec![];
for mut program_version in program_versions {
program_version.set_debugfs_mount_point(self.debugfs_mount.clone());
program_version.set_polling_policy(self.polling_thread_policy);
match program_version
.load_program_version(program_blueprint.clone(), &mut perfmap_opts_fn)
{
Ok(()) => {
self.loaded_version = Some(program_version);
break;
}
Err(e) => {
errors.push(e);
}
};
}
match &self.loaded_version {
None => {
info!(
LOGGER.0,
"ProgramGroup::load(); error: no program version was able to load for {:?}, errors: {:?}",
match std::env::current_exe() {
Ok(p) => p,
Err(_) => std::path::PathBuf::from("unknown"),
},
errors
);
Err(OxidebpfError::NoProgramVersionLoaded(errors))
}
Some(_) => {
self.loaded = true;
Ok(())
}
}
}
/// Get a reference to the array maps in the [`Program`](struct@ProgramGroup)s.
pub fn get_array_maps(&self) -> Option<&HashMap<String, ArrayMap>> {
self.loaded_version.as_ref().map(|ver| &ver.array_maps)
}
/// Get a reference to the hash maps in the ['Program'](struct@ProgramGroup)s.
pub fn get_hash_maps(&self) -> Option<&HashMap<String, BpfHashMap>> {
self.loaded_version.as_ref().map(|ver| &ver.hash_maps)
}
}