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
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
//! BTF-compatible BPF hash map variants.
#![deny(missing_docs)]
use core::{borrow::Borrow, ptr::NonNull};
use aya_ebpf_bindings::bindings::{BPF_F_NO_COMMON_LRU, BPF_F_NO_PREALLOC};
use crate::{btf_maps::btf_map_def, insert, lookup, remove};
macro_rules! define_btf_hash_map {
// Enforces kernel constraints from kernel/bpf/hashtab.c
// htab_map_alloc_check. The 8-byte value-alignment ceiling applies to
// all four variants: shared hashtab slots sit at
// `htab_elem.key[__aligned(8)] + round_up(key_size, 8)`, and per-CPU
// slots come from `bpf_map_alloc_percpu(..., 8, ...)`. Values with
// stricter alignment would be under-aligned for `&V` returned by
// `get`.
(
$(#[$top_attr:meta])*
$name:ident,
map_type: $map_type:ident,
rejected_flag: ($rejected_flag:expr, $rejected_flag_msg:literal $(,)?),
get_doc: { $(#[$get_attr:meta])+ } $(,)?
) => {
btf_map_def!(
$(#[$top_attr])*
pub struct $name<K, V; const MAX_ENTRIES: usize, const FLAGS: usize = 0>,
map_type: $map_type,
max_entries: MAX_ENTRIES,
map_flags: FLAGS,
key_type: K,
value_type: V,
);
impl<K, V, const MAX_ENTRIES: usize, const FLAGS: usize> $name<K, V, MAX_ENTRIES, FLAGS> {
const _CHECK: () = {
assert!(
size_of::<K>() > 0,
concat!(stringify!($name), " key must be non-zero sized."),
);
assert!(
size_of::<V>() > 0,
concat!(stringify!($name), " value must be non-zero sized."),
);
assert!(
MAX_ENTRIES > 0,
concat!(stringify!($name), " max_entries must be greater than zero."),
);
assert!(FLAGS & $rejected_flag as usize == 0, $rejected_flag_msg);
assert!(
align_of::<V>() <= 8,
concat!(
stringify!($name),
" value alignment must be at most 8 bytes.",
),
);
};
$(#[$get_attr])+
#[inline(always)]
pub unsafe fn get(&self, key: impl Borrow<K>) -> Option<&V> {
let () = Self::_CHECK;
// SAFETY: The caller upholds the aliasing invariants documented above.
unsafe { self.lookup(key.borrow()).map(|p| p.as_ref()) }
}
/// Returns a `*const V` for the value associated with `key`.
///
/// The same aliasing caveat as [`Self::get`] applies; it is the
/// caller's responsibility to decide whether dereferencing the
/// pointer is safe.
#[inline(always)]
pub fn get_ptr(&self, key: impl Borrow<K>) -> Option<*const V> {
let () = Self::_CHECK;
unsafe { self.lookup(key.borrow()).map(|p| p.as_ptr().cast_const()) }
}
/// Returns a `*mut V` for the value associated with `key`.
///
/// The same aliasing caveat as [`Self::get`] applies, and the
/// caller must additionally avoid concurrent writes.
#[inline(always)]
pub fn get_ptr_mut(&self, key: impl Borrow<K>) -> Option<*mut V> {
let () = Self::_CHECK;
unsafe { self.lookup(key.borrow()).map(NonNull::as_ptr) }
}
#[inline(always)]
unsafe fn lookup(&self, key: &K) -> Option<NonNull<V>> {
lookup(self.as_ptr(), key)
}
/// Inserts `value` under `key`.
///
/// `flags` is forwarded to `bpf_map_update_elem`; common choices
/// are `BPF_ANY`, `BPF_NOEXIST`, and `BPF_EXIST`.
#[inline(always)]
pub fn insert(
&self,
key: impl Borrow<K>,
value: impl Borrow<V>,
flags: u64,
) -> Result<(), i32> {
let () = Self::_CHECK;
insert(self.as_ptr(), key.borrow(), value.borrow(), flags)
}
/// Removes the entry for `key`.
#[inline(always)]
pub fn remove(&self, key: impl Borrow<K>) -> Result<(), i32> {
let () = Self::_CHECK;
remove(self.as_ptr(), key.borrow())
}
}
};
}
define_btf_hash_map!(
/// A BTF-compatible BPF hash map.
///
/// Stores values of type `V` keyed by `K` with kernel-managed collision
/// chaining. Entries persist until explicitly removed or the map is
/// dropped.
///
/// # Minimum kernel version
///
/// The minimum kernel version required to use this feature is 4.19.
///
/// # Flag and size restrictions
///
/// `BPF_F_NO_COMMON_LRU` is rejected by the kernel on non-LRU hash maps
/// and returns `EINVAL`. The key and value must both be non-zero sized
/// and `max_entries` must be at least 1.
///
/// # Example
///
/// ```rust
/// use aya_ebpf::{btf_maps::HashMap, macros::btf_map};
///
/// #[btf_map]
/// static COUNTS: HashMap<u32, u64, 1024> = HashMap::new();
/// ```
HashMap,
map_type: BPF_MAP_TYPE_HASH,
rejected_flag: (
BPF_F_NO_COMMON_LRU,
"BPF_F_NO_COMMON_LRU is only valid on LRU hash variants.",
),
get_doc: {
/// Returns a reference to the value associated with `key`.
///
/// # Safety
///
/// The returned reference is only valid until the next `insert` or
/// `remove` on this map. Unless the map was created with
/// `BPF_F_NO_PREALLOC`, the kernel keeps a preallocated freelist
/// and recycles a slot the moment its entry is removed; a
/// subsequent `insert` into that slot reuses the bytes the
/// reference points to, causing garbage to be read or corruption
/// on write.
},
);
define_btf_hash_map!(
/// A BTF-compatible BPF LRU hash map.
///
/// Stores values of type `V` keyed by `K`. When the map is full, the
/// kernel evicts the least-recently-used entry on insert. Lookups from
/// BPF context bump the LRU position; passing `BPF_F_NO_COMMON_LRU`
/// switches bookkeeping to per-CPU LRU lists at the cost of rounding
/// `max_entries` up to a multiple of `num_possible_cpus()`.
///
/// # Minimum kernel version
///
/// The minimum kernel version required to use this feature is 4.19.
/// `BPF_MAP_TYPE_LRU_HASH` itself dates back to 4.10, but BTF map
/// definitions require the BTF pretty-print path for hashtab maps,
/// which landed in 4.19.
///
/// # Flag and size restrictions
///
/// `BPF_F_NO_PREALLOC` is rejected by the kernel on LRU hash maps and
/// returns `ENOTSUPP`. The key and value must both be non-zero sized
/// and `max_entries` must be at least 1.
///
/// # Example
///
/// ```rust
/// use aya_ebpf::{btf_maps::LruHashMap, macros::btf_map};
///
/// #[btf_map]
/// static RECENT: LruHashMap<u32, u64, 1024> = LruHashMap::new();
/// ```
LruHashMap,
map_type: BPF_MAP_TYPE_LRU_HASH,
rejected_flag: (
BPF_F_NO_PREALLOC,
"BPF_F_NO_PREALLOC is rejected by LRU hash maps.",
),
get_doc: {
/// Returns a reference to the value associated with `key`, bumping
/// its LRU position.
///
/// # Safety
///
/// The returned reference is only valid until the next `insert` or
/// `remove` on this map. When the map is full, an `insert` evicts
/// the least-recently-used entry and recycles its slot; if that
/// slot held the borrowed entry, the reference now aliases the new
/// value. A `remove` of the borrowed entry has the same effect.
},
);
define_btf_hash_map!(
/// A BTF-compatible BPF per-CPU hash map.
///
/// Stores a distinct value of type `V` per CPU, keyed by `K`. Reads and
/// writes from BPF context reference the slot belonging to the CPU that
/// executes the program; other CPUs' slots are accessible only from
/// user space via `bpf_map_lookup_elem` with a `num_possible_cpus()`-sized
/// value buffer.
///
/// # Minimum kernel version
///
/// The minimum kernel version required to use this feature is 4.19.
/// `BPF_MAP_TYPE_PERCPU_HASH` itself dates back to 4.6, but BTF map
/// definitions require the BTF pretty-print path for hashtab maps,
/// which landed in 4.19.
///
/// # Flag and size restrictions
///
/// `BPF_F_NO_COMMON_LRU` is rejected by the kernel on non-LRU hash maps
/// and returns `EINVAL`. Each per-CPU value must also satisfy
/// `round_up(size_of::<V>(), 8) <= PCPU_MIN_UNIT_SIZE` (32 KiB on most
/// kernel builds); violations fail with `E2BIG` at load time. The key
/// and value must both be non-zero sized and `max_entries` must be at
/// least 1.
///
/// # Example
///
/// ```rust
/// use aya_ebpf::{btf_maps::PerCpuHashMap, macros::btf_map};
///
/// #[btf_map]
/// static COUNTS: PerCpuHashMap<u32, u64, 1024> = PerCpuHashMap::new();
/// ```
PerCpuHashMap,
map_type: BPF_MAP_TYPE_PERCPU_HASH,
rejected_flag: (
BPF_F_NO_COMMON_LRU,
"BPF_F_NO_COMMON_LRU is only valid on LRU hash variants.",
),
get_doc: {
/// Returns a reference to the current CPU's value for `key`.
///
/// # Safety
///
/// The returned reference is only valid until the next `insert` or
/// `remove` on this map. Unless the map was created with
/// `BPF_F_NO_PREALLOC`, the kernel keeps a preallocated freelist
/// and recycles a slot the moment its entry is removed; a
/// subsequent `insert` into that slot reuses the bytes the
/// reference points to, causing garbage to be read or corruption
/// on write.
},
);
define_btf_hash_map!(
/// A BTF-compatible BPF LRU per-CPU hash map.
///
/// Stores a distinct value of type `V` per CPU, keyed by `K`, with LRU
/// eviction when the map fills. Reads and writes from BPF context
/// reference the slot belonging to the CPU that executes the program;
/// LRU bookkeeping defaults to a single shared list across CPUs and may
/// be switched to per-CPU LRU lists with `BPF_F_NO_COMMON_LRU`, at the
/// cost of rounding `max_entries` up to a multiple of
/// `num_possible_cpus()`.
///
/// # Minimum kernel version
///
/// The minimum kernel version required to use this feature is 4.19.
/// `BPF_MAP_TYPE_LRU_PERCPU_HASH` itself dates back to 4.10, but BTF
/// map definitions require the BTF pretty-print path for hashtab maps,
/// which landed in 4.19.
///
/// # Flag and size restrictions
///
/// `BPF_F_NO_PREALLOC` is rejected by the kernel on LRU hash maps and
/// returns `ENOTSUPP`. Each per-CPU value must also satisfy
/// `round_up(size_of::<V>(), 8) <= PCPU_MIN_UNIT_SIZE` (32 KiB on most
/// kernel builds); violations fail with `E2BIG` at load time. The key
/// and value must both be non-zero sized and `max_entries` must be at
/// least 1.
///
/// # Example
///
/// ```rust
/// use aya_ebpf::{btf_maps::LruPerCpuHashMap, macros::btf_map};
///
/// #[btf_map]
/// static RECENT: LruPerCpuHashMap<u32, u64, 1024> = LruPerCpuHashMap::new();
/// ```
LruPerCpuHashMap,
map_type: BPF_MAP_TYPE_LRU_PERCPU_HASH,
rejected_flag: (
BPF_F_NO_PREALLOC,
"BPF_F_NO_PREALLOC is rejected by LRU hash maps.",
),
get_doc: {
/// Returns a reference to the current CPU's value for `key`,
/// bumping its LRU position.
///
/// # Safety
///
/// The returned reference is only valid until the next `insert` or
/// `remove` on this map. When the map is full, an `insert` evicts
/// the least-recently-used entry and recycles its slot; if that
/// slot held the borrowed entry, the reference now aliases the new
/// value. A `remove` of the borrowed entry has the same effect.
},
);