Skip to content

Commit e5a946a

Browse files
add map support
1 parent 0a27d5a commit e5a946a

File tree

9 files changed

+349
-31
lines changed

9 files changed

+349
-31
lines changed

CMakeLists.txt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,14 @@ project(pylibbpf)
44
# pybind11
55
include_directories(${CMAKE_SOURCE_DIR}/src)
66
add_subdirectory(pybind11)
7-
pybind11_add_module(pylibbpf src/core/bpf_program.h src/core/bpf_exception.h
8-
src/bindings/main.cpp src/core/bpf_program.cpp)
7+
pybind11_add_module(
8+
pylibbpf
9+
src/core/bpf_program.h
10+
src/core/bpf_exception.h
11+
src/core/bpf_map.h
12+
src/bindings/main.cpp
13+
src/core/bpf_program.cpp
14+
src/core/bpf_map.cpp)
915

1016
# --- libbpf build rules ---
1117
set(LIBBPF_SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/libbpf/src)

examples/execve.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import time
22
from ctypes import c_int32, c_int64, c_uint64, c_void_p
33

4+
from pylibbpf import BpfMap
45
from pythonbpf import BPF, bpf, bpfglobal, map, section
56
from pythonbpf.maps import HashMap
67

78

89
@bpf
910
@map
1011
def last() -> HashMap:
11-
return HashMap(key=c_uint64, value=c_uint64, max_entries=1)
12+
return HashMap(key=c_uint64, value=c_uint64, max_entries=3)
1213

1314

1415
@bpf
@@ -37,6 +38,17 @@ def LICENSE() -> str:
3738

3839
b = BPF()
3940
b.load_and_attach()
41+
mp = BpfMap(b, last)
42+
mp[42] = 100 # Update entry
43+
value = mp[42] # Lookup entry
44+
del mp[42] # Delete entry
45+
mp[69] = 420
46+
mp[31] = 42
47+
mp[21] = 34
48+
print(mp.items())
49+
# Iterate through map
50+
for key in mp.keys():
51+
print(mp[key])
4052
while True:
4153
print("running")
4254
time.sleep(1)

pyproject.toml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,9 @@ line-length = 88
6969

7070
[tool.ruff.lint]
7171
extend-select = [
72-
"B", # flake8-bugbear
73-
"I", # isort
74-
"PGH", # pygrep-hooks
75-
"RUF", # Ruff-specific
76-
"UP", # pyupgrade
72+
"B", # flake8-bugbear
73+
"I", # isort
74+
"PGH", # pygrep-hooks
75+
"RUF", # Ruff-specific
76+
"UP", # pyupgrade
7777
]

src/bindings/main.cpp

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
#define MACRO_STRINGIFY(x) STRINGIFY(x)
44

55
extern "C" {
6-
#include "libbpf.h"
6+
#include <libbpf.h>
77
}
8+
89
#include "core/bpf_program.h"
910
#include "core/bpf_exception.h"
11+
#include "core/bpf_map.h"
1012

1113
namespace py = pybind11;
1214

@@ -21,21 +23,41 @@ PYBIND11_MODULE(pylibbpf, m) {
2123
:toctree: _generate
2224
2325
BpfProgram
26+
BpfMap
2427
BpfException
2528
)pbdoc";
2629

2730
// Register the custom exception
2831
py::register_exception<BpfException>(m, "BpfException");
2932

3033
py::class_<BpfProgram>(m, "BpfProgram")
31-
.def(py::init<const std::string&>())
32-
.def(py::init<const std::string&, const std::string&>())
33-
.def("load", &BpfProgram::load)
34-
.def("attach", &BpfProgram::attach)
35-
// .def("detach", &BpfProgram::detach)
36-
.def("load_and_attach", &BpfProgram::load_and_attach)
37-
.def("is_loaded", &BpfProgram::is_loaded)
38-
.def("is_attached", &BpfProgram::is_attached);
34+
.def(py::init<const std::string &>())
35+
.def(py::init<const std::string &, const std::string &>())
36+
.def("load", &BpfProgram::load)
37+
.def("attach", &BpfProgram::attach)
38+
.def("destroy", &BpfProgram::destroy)
39+
.def("load_and_attach", &BpfProgram::load_and_attach)
40+
.def("is_loaded", &BpfProgram::is_loaded)
41+
.def("is_attached", &BpfProgram::is_attached);
42+
43+
py::class_<BpfMap>(m, "BpfMap")
44+
.def(py::init<BpfProgram *, py::object &>())
45+
.def("lookup", &BpfMap::lookup)
46+
.def("update", &BpfMap::update)
47+
.def("delete", &BpfMap::delete_elem)
48+
.def("get_next_key", &BpfMap::get_next_key, py::arg("key") = py::none())
49+
.def("items", &BpfMap::items)
50+
.def("keys", &BpfMap::keys)
51+
.def("values", &BpfMap::values)
52+
.def("get_name", &BpfMap::get_name)
53+
.def("get_type", &BpfMap::get_type)
54+
.def("get_key_size", &BpfMap::get_key_size)
55+
.def("get_value_size", &BpfMap::get_value_size)
56+
.def("get_max_entries", &BpfMap::get_max_entries)
57+
.def("__getitem__", &BpfMap::lookup)
58+
.def("__setitem__", &BpfMap::update)
59+
.def("__delitem__", &BpfMap::delete_elem);
60+
3961

4062
#ifdef VERSION_INFO
4163
m.attr("__version__") = MACRO_STRINGIFY(VERSION_INFO);

src/core/bpf_exception.h

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@
66

77
class BpfException final : public std::runtime_error {
88
public:
9-
explicit BpfException(const std::string& message)
10-
: std::runtime_error(message) {}
9+
explicit BpfException(const std::string &message)
10+
: std::runtime_error(message) {
11+
}
1112

12-
explicit BpfException(const char* message)
13-
: std::runtime_error(message) {}
13+
explicit BpfException(const char *message)
14+
: std::runtime_error(message) {
15+
}
1416
};
1517

1618
#endif // PYLIBBPF_BPF_EXCEPTION_H

src/core/bpf_map.cpp

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
#include "bpf_map.h"
2+
3+
#include "bpf_exception.h"
4+
5+
BpfMap::BpfMap(BpfProgram *program_, const py::object &map_from_python) {
6+
if (py::isinstance<py::function>(map_from_python)) {
7+
const auto name = map_from_python.attr("__name__").cast<std::string>();
8+
bpf_program = program_;
9+
map_ = bpf_object__find_map_by_name(bpf_program->get_obj(), name.c_str());
10+
if (!map_) {
11+
throw BpfException("Failed to find map by name");
12+
}
13+
map_fd = bpf_map__fd(map_);
14+
if (map_fd == -1) {
15+
throw BpfException("Failed to open map File Descriptor");
16+
}
17+
} else {
18+
throw BpfException("Invalid map object passed to function.");
19+
}
20+
}
21+
22+
std::vector<uint8_t> BpfMap::python_to_bytes(const py::object &obj, size_t size) {
23+
std::vector<uint8_t> result(size, 0);
24+
25+
if (py::isinstance<py::int_>(obj)) {
26+
const auto value = obj.cast<uint64_t>();
27+
std::memcpy(result.data(), &value, std::min(size, sizeof(uint64_t)));
28+
} else if (py::isinstance<py::bytes>(obj)) {
29+
const auto bytes_str = obj.cast<std::string>();
30+
std::memcpy(result.data(), bytes_str.data(), std::min(size, bytes_str.size()));
31+
} else if (py::isinstance<py::str>(obj)) {
32+
const auto str_val = obj.cast<std::string>();
33+
std::memcpy(result.data(), str_val.data(), std::min(size, str_val.size()));
34+
}
35+
36+
return result;
37+
}
38+
39+
py::object BpfMap::bytes_to_python(const std::vector<uint8_t> &data) {
40+
// Try to interpret as integer if it's a common integer size
41+
if (data.size() == 4) {
42+
uint32_t value;
43+
std::memcpy(&value, data.data(), 4);
44+
return py::cast(value);
45+
} else if (data.size() == 8) {
46+
uint64_t value;
47+
std::memcpy(&value, data.data(), 8);
48+
return py::cast(value);
49+
} else {
50+
// Return as bytes
51+
return py::bytes(reinterpret_cast<const char *>(data.data()), data.size());
52+
}
53+
}
54+
55+
void BpfMap::update(const py::object &key, const py::object &value) const {
56+
const size_t key_size = bpf_map__key_size(map_);
57+
const size_t value_size = bpf_map__value_size(map_);
58+
59+
const auto key_bytes = python_to_bytes(key, key_size);
60+
const auto value_bytes = python_to_bytes(value, value_size);
61+
62+
const int ret = bpf_map__update_elem(
63+
map_,
64+
key_bytes.data(),
65+
key_size,
66+
value_bytes.data(),
67+
value_size,
68+
BPF_ANY);
69+
if (ret != 0) {
70+
throw BpfException("Failed to update map element");
71+
}
72+
}
73+
74+
void BpfMap::delete_elem(const py::object &key) const {
75+
const size_t key_size = bpf_map__key_size(map_);
76+
std::vector<uint8_t> key_bytes;
77+
key_bytes = python_to_bytes(key, key_size);
78+
79+
if (const int ret = bpf_map__delete_elem(map_, key_bytes.data(), key_size, BPF_ANY); ret != 0) {
80+
throw BpfException("Failed to delete map element");
81+
}
82+
}
83+
84+
py::list BpfMap::get_next_key(const py::object &key) const {
85+
const size_t key_size = bpf_map__key_size(map_);
86+
std::vector<uint8_t> next_key(key_size);
87+
88+
int ret;
89+
if (key.is_none()) {
90+
ret = bpf_map__get_next_key(map_, nullptr, next_key.data(), key_size);
91+
} else {
92+
const auto key_bytes = python_to_bytes(key, key_size);
93+
ret = bpf_map__get_next_key(map_, key_bytes.data(), next_key.data(), key_size);
94+
}
95+
96+
py::list result;
97+
if (ret == 0) {
98+
result.append(bytes_to_python(next_key));
99+
}
100+
return result;
101+
}
102+
103+
py::list BpfMap::keys() const {
104+
py::list result;
105+
const size_t key_size = bpf_map__key_size(map_);
106+
107+
std::vector<uint8_t> key(key_size);
108+
std::vector<uint8_t> next_key(key_size);
109+
110+
int ret = bpf_map__get_next_key(map_, nullptr, key.data(), key_size);
111+
112+
while (ret == 0) {
113+
result.append(bytes_to_python(key));
114+
ret = bpf_map__get_next_key(map_, key.data(), next_key.data(), key_size);
115+
key = next_key;
116+
}
117+
118+
return result;
119+
}
120+
121+
py::list BpfMap::values() const {
122+
py::list result;
123+
const size_t key_size = bpf_map__key_size(map_);
124+
const size_t value_size = bpf_map__value_size(map_);
125+
126+
std::vector<uint8_t> key(key_size);
127+
std::vector<uint8_t> next_key(key_size);
128+
std::vector<uint8_t> value(value_size);
129+
130+
int ret = bpf_map__get_next_key(map_, nullptr, key.data(), key_size);
131+
132+
while (ret == 0) {
133+
if (bpf_map__lookup_elem(map_, key.data(), key_size, value.data(), value_size, BPF_ANY) == 0) {
134+
result.append(bytes_to_python(value));
135+
}
136+
ret = bpf_map__get_next_key(map_, key.data(), next_key.data(), key_size);
137+
key = next_key;
138+
}
139+
140+
return result;
141+
}
142+
143+
std::string BpfMap::get_name() const {
144+
const char *name = bpf_map__name(map_);
145+
return name ? std::string(name) : "";
146+
}
147+
148+
int BpfMap::get_type() const {
149+
return bpf_map__type(map_);
150+
}
151+
152+
int BpfMap::get_key_size() const {
153+
return bpf_map__key_size(map_);
154+
}
155+
156+
int BpfMap::get_value_size() const {
157+
return bpf_map__value_size(map_);
158+
}
159+
160+
int BpfMap::get_max_entries() const {
161+
return bpf_map__max_entries(map_);
162+
}
163+
164+
py::dict BpfMap::items() const {
165+
py::dict result;
166+
const size_t key_size = bpf_map__key_size(map_);
167+
const size_t value_size = bpf_map__value_size(map_);
168+
169+
std::vector<uint8_t> key(key_size);
170+
std::vector<uint8_t> next_key(key_size);
171+
std::vector<uint8_t> value(value_size);
172+
173+
// Get first key
174+
int ret = bpf_map__get_next_key(map_, nullptr, key.data(), key_size);
175+
176+
while (ret == 0) {
177+
// Lookup value for current key
178+
if (bpf_map__lookup_elem(map_, key.data(), key_size, value.data(), value_size, BPF_ANY) == 0) {
179+
result[bytes_to_python(key)] = bytes_to_python(value);
180+
}
181+
182+
// Get next key
183+
ret = bpf_map__get_next_key(map_, key.data(), next_key.data(), key_size);
184+
key = next_key;
185+
}
186+
187+
return result;
188+
}
189+
190+
py::object BpfMap::lookup(const py::object &key) const {
191+
const __u32 key_size = bpf_map__key_size(map_);
192+
const __u32 value_size = bpf_map__value_size(map_);
193+
194+
const auto key_bytes = python_to_bytes(key, key_size);
195+
std::vector<uint8_t> value_bytes(value_size);
196+
197+
// The flags field here matters only when spin locks are used which is close to fucking never, so fuck no,
198+
// im not adding it
199+
const int ret = bpf_map__lookup_elem(
200+
map_,
201+
key_bytes.data(),
202+
key_size,
203+
value_bytes.data(),
204+
value_size,
205+
BPF_ANY);
206+
if (ret != 0) {
207+
return py::none();
208+
}
209+
210+
return bytes_to_python(value_bytes);
211+
}

0 commit comments

Comments
 (0)