Skip to content

Commit e59938d

Browse files
committed
feat(aya): Add task storage map type (in the user-space)
Task storage is a type of map which uses `task_struct` kernel type as a key. When the task (process) stops, the corresponding entry is automatically removed. This change add support only in the user-space and tests the functionality with a C program.
1 parent 9e52d2c commit e59938d

File tree

11 files changed

+368
-2
lines changed

11 files changed

+368
-2
lines changed

aya/src/bpf.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -728,6 +728,7 @@ fn parse_map(
728728
BPF_MAP_TYPE_DEVMAP => Map::DevMap(map),
729729
BPF_MAP_TYPE_DEVMAP_HASH => Map::DevMapHash(map),
730730
BPF_MAP_TYPE_XSKMAP => Map::XskMap(map),
731+
BPF_MAP_TYPE_TASK_STORAGE => Map::TaskStorage(map),
731732
m_type => {
732733
if allow_unsupported_maps {
733734
Map::Unsupported(map)

aya/src/maps/mod.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ pub mod ring_buf;
8585
pub mod sock;
8686
pub mod stack;
8787
pub mod stack_trace;
88+
pub mod task_storage;
8889
pub mod xdp;
8990

9091
pub use array::{Array, PerCpuArray, ProgramArray};
@@ -101,6 +102,7 @@ pub use ring_buf::RingBuf;
101102
pub use sock::{SockHash, SockMap};
102103
pub use stack::Stack;
103104
pub use stack_trace::StackTraceMap;
105+
pub use task_storage::TaskStorage;
104106
pub use xdp::{CpuMap, DevMap, DevMapHash, XskMap};
105107

106108
#[derive(Error, Debug)]
@@ -312,6 +314,8 @@ pub enum Map {
312314
Stack(MapData),
313315
/// A [`StackTraceMap`] map.
314316
StackTraceMap(MapData),
317+
/// A [`TaskStorage`] map.
318+
TaskStorage(MapData),
315319
/// An unsupported map type.
316320
Unsupported(MapData),
317321
/// A [`XskMap`] map.
@@ -341,6 +345,7 @@ impl Map {
341345
Self::SockMap(map) => map.obj.map_type(),
342346
Self::Stack(map) => map.obj.map_type(),
343347
Self::StackTraceMap(map) => map.obj.map_type(),
348+
Self::TaskStorage(map) => map.obj.map_type(),
344349
Self::Unsupported(map) => map.obj.map_type(),
345350
Self::XskMap(map) => map.obj.map_type(),
346351
}
@@ -371,6 +376,7 @@ impl Map {
371376
Self::SockMap(map) => map.pin(path),
372377
Self::Stack(map) => map.pin(path),
373378
Self::StackTraceMap(map) => map.pin(path),
379+
Self::TaskStorage(map) => map.pin(path),
374380
Self::Unsupported(map) => map.pin(path),
375381
Self::XskMap(map) => map.pin(path),
376382
}
@@ -420,6 +426,7 @@ impl_map_pin!((V) {
420426
BloomFilter,
421427
Queue,
422428
Stack,
429+
TaskStorage,
423430
});
424431

425432
impl_map_pin!((K, V) {
@@ -502,6 +509,7 @@ impl_try_from_map!((V) {
502509
Queue,
503510
SockHash,
504511
Stack,
512+
TaskStorage,
505513
});
506514

507515
impl_try_from_map!((K, V) {

aya/src/maps/task_storage.rs

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
//! Task storage.
2+
use std::{
3+
borrow::Borrow,
4+
marker::PhantomData,
5+
os::fd::{AsFd, AsRawFd},
6+
};
7+
8+
use crate::{
9+
maps::{check_kv_size, MapData, MapError},
10+
sys::{bpf_map_lookup_elem, PidFd, SyscallError},
11+
Pod,
12+
};
13+
14+
/// Task storage is a type of map which uses `task_struct` kernel type as a
15+
/// key. When the task (process) stops, the corresponding entry is
16+
/// automatically removed.
17+
///
18+
/// # Minimum kernel version
19+
///
20+
/// The minimum kernel version required to use this feature is 5.12.
21+
///
22+
/// # Examples
23+
///
24+
/// ```no_run
25+
/// # let mut ebpf = aya::Ebpf::load(&[])?;
26+
/// use aya::maps::TaskStorage;
27+
///
28+
/// let mut task_storage: TaskStorage<_, u32> = TaskStorage::try_from(ebpf.map_mut("TASK_STORAGE").unwrap())?;
29+
///
30+
/// let pid = 0;
31+
/// let value = task_storage.get(&pid, 0)?;
32+
/// # Ok::<(), aya::EbpfError>(())
33+
/// ```
34+
#[doc(alias = "BPF_MAP_TYPE_TASK_STORAGE")]
35+
#[derive(Debug)]
36+
pub struct TaskStorage<T, V> {
37+
pub(crate) inner: T,
38+
_v: PhantomData<V>,
39+
}
40+
41+
impl<T: Borrow<MapData>, V: Pod> TaskStorage<T, V> {
42+
pub(crate) fn new(map: T) -> Result<Self, MapError> {
43+
let data = map.borrow();
44+
check_kv_size::<u32, V>(data)?;
45+
Ok(Self {
46+
inner: map,
47+
_v: PhantomData,
48+
})
49+
}
50+
51+
/// Returns the value stored for the given `pid`.
52+
pub fn get(&self, pid: &u32, flags: u64) -> Result<V, MapError> {
53+
let pidfd = PidFd::open(*pid, 0).map_err(|(_, io_error)| SyscallError {
54+
call: "pidfd_open",
55+
io_error,
56+
})?;
57+
let map_fd = self.inner.borrow().fd().as_fd();
58+
let value =
59+
bpf_map_lookup_elem(map_fd, &pidfd.as_raw_fd(), flags).map_err(|(_, io_error)| {
60+
SyscallError {
61+
call: "bpf_map_lookup_elem",
62+
io_error,
63+
}
64+
})?;
65+
value.ok_or(MapError::KeyNotFound)
66+
}
67+
}
68+
69+
#[cfg(test)]
70+
mod tests {
71+
use std::io;
72+
73+
use assert_matches::assert_matches;
74+
use aya_obj::generated::bpf_map_type::BPF_MAP_TYPE_TASK_STORAGE;
75+
use libc::EFAULT;
76+
77+
use super::*;
78+
use crate::{
79+
maps::{
80+
test_utils::{self, new_map},
81+
Map,
82+
},
83+
sys::{override_syscall, SysResult, Syscall},
84+
};
85+
86+
fn new_obj_map() -> aya_obj::Map {
87+
test_utils::new_obj_map::<u32>(BPF_MAP_TYPE_TASK_STORAGE)
88+
}
89+
90+
fn sys_error(value: i32) -> SysResult<i64> {
91+
Err((-1, io::Error::from_raw_os_error(value)))
92+
}
93+
94+
#[test]
95+
fn test_wrong_value_size() {
96+
let map = new_map(new_obj_map());
97+
let map = Map::TaskStorage(map);
98+
assert_matches!(
99+
TaskStorage::<_, u16>::try_from(&map),
100+
Err(MapError::InvalidValueSize {
101+
size: 2,
102+
expected: 4
103+
})
104+
);
105+
}
106+
107+
#[test]
108+
fn test_try_from_wrong_map() {
109+
let map = new_map(new_obj_map());
110+
let map = Map::Array(map);
111+
assert_matches!(
112+
TaskStorage::<_, u32>::try_from(&map),
113+
Err(MapError::InvalidMapType { .. })
114+
);
115+
}
116+
117+
#[test]
118+
fn test_new_ok() {
119+
let map = new_map(new_obj_map());
120+
assert!(TaskStorage::<_, u32>::new(&map).is_ok());
121+
}
122+
123+
#[test]
124+
fn test_try_from_ok() {
125+
let map = new_map(new_obj_map());
126+
let map = Map::TaskStorage(map);
127+
assert!(TaskStorage::<_, u32>::try_from(&map).is_ok());
128+
}
129+
130+
#[test]
131+
fn test_get_pidfd_syscall_error() {
132+
let mut map = new_map(new_obj_map());
133+
let map = TaskStorage::<_, u32>::new(&mut map).unwrap();
134+
135+
override_syscall(|call| match call {
136+
Syscall::Ebpf { .. } => Ok(1),
137+
Syscall::PidfdOpen { .. } => sys_error(EFAULT),
138+
_ => sys_error(EFAULT),
139+
});
140+
141+
assert_matches!(
142+
map.get(&1, 0), Err(MapError::SyscallError(
143+
SyscallError {
144+
call: "pidfd_open",
145+
io_error
146+
}
147+
))
148+
if io_error.raw_os_error() == Some(EFAULT)
149+
);
150+
}
151+
}

aya/src/sys/mod.rs

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,22 @@ mod fake;
1010
use std::{
1111
ffi::{c_int, c_long, c_void},
1212
io, mem,
13-
os::fd::{AsRawFd as _, BorrowedFd, OwnedFd},
13+
os::fd::{AsRawFd, BorrowedFd, FromRawFd, OwnedFd, RawFd},
1414
};
1515

1616
use aya_obj::generated::{bpf_attr, bpf_cmd, perf_event_attr};
1717
pub(crate) use bpf::*;
1818
#[cfg(test)]
1919
pub(crate) use fake::*;
20-
use libc::{pid_t, SYS_bpf, SYS_ioctl, SYS_perf_event_open};
20+
use libc::{pid_t, SYS_bpf, SYS_ioctl, SYS_perf_event_open, SYS_pidfd_open};
2121
#[doc(hidden)]
2222
pub use netlink::netlink_set_link_up;
2323
pub(crate) use netlink::*;
2424
pub(crate) use perf_event::*;
2525
use thiserror::Error;
2626

27+
use crate::MockableFd;
28+
2729
pub(crate) type SysResult<T> = Result<T, (c_long, io::Error)>;
2830

2931
pub(crate) enum Syscall<'a> {
@@ -43,6 +45,10 @@ pub(crate) enum Syscall<'a> {
4345
request: u32,
4446
arg: c_int,
4547
},
48+
PidfdOpen {
49+
pid: pid_t,
50+
flags: u32,
51+
},
4652
}
4753

4854
/// A system call error.
@@ -84,6 +90,11 @@ impl std::fmt::Debug for Syscall<'_> {
8490
.field("request", request)
8591
.field("arg", arg)
8692
.finish(),
93+
Self::PidfdOpen { pid, flags } => f
94+
.debug_struct("Syscall::PidfdOpen")
95+
.field("pid", pid)
96+
.field("flags", flags)
97+
.finish(),
8798
}
8899
}
89100
}
@@ -109,6 +120,7 @@ fn syscall(call: Syscall<'_>) -> SysResult<c_long> {
109120
Syscall::PerfEventIoctl { fd, request, arg } => {
110121
libc::syscall(SYS_ioctl, fd.as_raw_fd(), request, arg)
111122
}
123+
Syscall::PidfdOpen { pid, flags } => libc::syscall(SYS_pidfd_open, pid, flags),
112124
}
113125
};
114126

@@ -185,3 +197,35 @@ impl From<Stats> for aya_obj::generated::bpf_stats_type {
185197
pub fn enable_stats(stats_type: Stats) -> Result<OwnedFd, SyscallError> {
186198
bpf_enable_stats(stats_type.into()).map(|fd| fd.into_inner())
187199
}
200+
201+
/// A file descriptor of a process.
202+
///
203+
/// A similar type is provided by the Rust standard library as
204+
/// [`std::os::linux::process`] as a nigtly-only experimental API. We are
205+
/// planning to migrate to it once it stabilizes.
206+
pub(crate) struct PidFd(MockableFd);
207+
208+
impl PidFd {
209+
pub(crate) fn open(pid: u32, flags: u32) -> SysResult<Self> {
210+
let pid_fd = pidfd_open(pid, flags)? as RawFd;
211+
let pid_fd = unsafe { MockableFd::from_raw_fd(pid_fd) };
212+
Ok(Self(pid_fd))
213+
}
214+
}
215+
216+
impl AsRawFd for PidFd {
217+
fn as_raw_fd(&self) -> RawFd {
218+
self.0.as_raw_fd()
219+
}
220+
}
221+
222+
fn pidfd_open(pid: u32, flags: u32) -> SysResult<i64> {
223+
let call = Syscall::PidfdOpen {
224+
pid: pid as pid_t,
225+
flags,
226+
};
227+
#[cfg(not(test))]
228+
return crate::sys::syscall(call);
229+
#[cfg(test)]
230+
return crate::sys::TEST_SYSCALL.with(|test_impl| unsafe { test_impl.borrow()(call) });
231+
}

test/integration-test/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ netns-rs = { workspace = true }
2424
object = { workspace = true, features = ["elf", "read_core", "std"] }
2525
rand = { workspace = true, features = ["thread_rng"] }
2626
rbpf = { workspace = true }
27+
tempfile = { workspace = true }
2728
test-case = { workspace = true }
2829
test-log = { workspace = true, features = ["log"] }
2930
tokio = { workspace = true, features = ["macros", "rt-multi-thread", "time"] }
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// clang-format off
2+
#include <vmlinux.h>
3+
#include <bpf/bpf_helpers.h>
4+
#include <bpf/bpf_core_read.h>
5+
#include <bpf/bpf_tracing.h>
6+
// clang-format on
7+
8+
char _license[] SEC("license") = "GPL";
9+
10+
struct {
11+
__uint(type, BPF_MAP_TYPE_TASK_STORAGE);
12+
__uint(map_flags, BPF_F_NO_PREALLOC);
13+
__type(key, int);
14+
__type(value, __u32);
15+
} task_storage SEC(".maps");
16+
17+
SEC("tp_btf/sys_enter")
18+
int BPF_PROG(sys_enter, struct pt_regs *regs, long id) {
19+
__u32 value = 1;
20+
struct task_struct *task = bpf_get_current_task_btf();
21+
bpf_task_storage_get(&task_storage, task, &value,
22+
BPF_LOCAL_STORAGE_GET_F_CREATE);
23+
24+
return 0;
25+
}

test/integration-test/build.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ fn main() -> Result<()> {
6767
("main.bpf.c", false),
6868
("multimap-btf.bpf.c", false),
6969
("reloc.bpf.c", true),
70+
("task_storage.bpf.c", true),
7071
("text_64_64_reloc.c", false),
7172
("variables_reloc.bpf.c", false),
7273
];

test/integration-test/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ pub const MULTIMAP_BTF: &[u8] =
88
pub const RELOC_BPF: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/reloc.bpf.o"));
99
pub const RELOC_BTF: &[u8] =
1010
include_bytes_aligned!(concat!(env!("OUT_DIR"), "/reloc.bpf.target.o"));
11+
pub const TASK_STORAGE: &[u8] =
12+
include_bytes_aligned!(concat!(env!("OUT_DIR"), "/task_storage.bpf.o"));
1113
pub const TEXT_64_64_RELOC: &[u8] =
1214
include_bytes_aligned!(concat!(env!("OUT_DIR"), "/text_64_64_reloc.o"));
1315
pub const VARIABLES_RELOC: &[u8] =

test/integration-test/src/tests.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ mod relocations;
1111
mod ring_buf;
1212
mod smoke;
1313
mod strncmp;
14+
mod task_storage;
1415
mod tcx;
1516
mod uprobe_cookie;
1617
mod xdp;

0 commit comments

Comments
 (0)