7 Commits
v0.0.4 ... xdp

Author SHA1 Message Date
97436ee92a add libxdp submodule
Signed-off-by: varun-r-mallya <varunrmallya@gmail.com>
2025-09-28 10:57:19 +05:30
6bea1bc8e5 bump version
Signed-off-by: varun-r-mallya <varunrmallya@gmail.com>
2025-09-26 22:54:13 +05:30
64109fac3b bump version
Signed-off-by: varun-r-mallya <varunrmallya@gmail.com>
2025-09-26 22:50:36 +05:30
e5a946a767 add map support 2025-09-21 23:55:10 +05:30
0a27d5a520 add destroy function 2025-09-21 19:24:01 +05:30
aa3bd82194 fix format errors 2025-09-21 18:12:19 +05:30
ec003a2c0a addd example and support for load and attach 2025-09-21 18:02:49 +05:30
14 changed files with 444 additions and 43 deletions

1
.gitignore vendored
View File

@ -9,3 +9,4 @@ _generate/
build/ build/
*venv/ *venv/
.idea/ .idea/
sudo-python.sh

3
.gitmodules vendored
View File

@ -5,3 +5,6 @@
[submodule "libbpf"] [submodule "libbpf"]
path = libbpf path = libbpf
url = https://github.com/libbpf/libbpf.git url = https://github.com/libbpf/libbpf.git
[submodule "xdp-tools"]
path = xdp-tools
url = https://github.com/xdp-project/xdp-tools/

View File

@ -4,8 +4,14 @@ project(pylibbpf)
# pybind11 # pybind11
include_directories(${CMAKE_SOURCE_DIR}/src) include_directories(${CMAKE_SOURCE_DIR}/src)
add_subdirectory(pybind11) add_subdirectory(pybind11)
pybind11_add_module(pylibbpf src/core/bpf_program.h src/core/bpf_exception.h pybind11_add_module(
src/bindings/main.cpp src/core/bpf_program.cpp) pylibbpf
src/core/bpf_program.h
src/core/bpf_exception.h
src/core/bpf_map.h
src/bindings/main.cpp
src/core/bpf_program.cpp
src/core/bpf_map.cpp)
# --- libbpf build rules --- # --- libbpf build rules ---
set(LIBBPF_SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/libbpf/src) set(LIBBPF_SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/libbpf/src)

View File

@ -1,10 +1,13 @@
# Py-libbpf # Py-libbpf
<p align="center">
<a href="https://www.python.org/downloads/release/python-3080/"><img src="https://img.shields.io/badge/python-3.8-blue.svg"></a>
<a href="https://pypi.org/project/pylibbpf"><img src="https://badge.fury.io/py/pylibbpf.svg"></a>
</p>
This library provides Python bindings for libbpf on Linux to make loading of eBPF object files easier. This is meant to This library provides Python bindings for libbpf on Linux to make loading of eBPF object files easier. This is meant to
be used along with `pythonbpf`, the eBPF Python DSL compiler. This library makes it possible to attach these programs to be used along with `pythonbpf`, the eBPF Python DSL compiler. This library makes it possible to attach these programs to
events in the kernel right from inside Python. events in the kernel right from inside Python.
# Warning # IN DEVELOPMENT. DO NOT USE.
IN DEVELOPMENT. DO NOT USE.
## Prerequisites ## Prerequisites
@ -19,6 +22,7 @@ Just clone this repository and pip install. Note the `--recursive` option which
needed for the pybind11 submodule: needed for the pybind11 submodule:
```bash ```bash
sudo apt install libelf-dev
git clone --recursive https://github.com/varun-r-mallya/pylibbpf.git git clone --recursive https://github.com/varun-r-mallya/pylibbpf.git
pip install . pip install .
``` ```
@ -26,5 +30,11 @@ pip install .
With the `setup.py` file included in this example, the `pip install` command will With the `setup.py` file included in this example, the `pip install` command will
invoke CMake and build the pybind11 module as specified in `CMakeLists.txt`. invoke CMake and build the pybind11 module as specified in `CMakeLists.txt`.
## Development
Do this before running to make sure Python can manipulate bpf programs without sudo
```bash
sudo setcap cap_bpf,cap_sys_admin+ep /usr/bin/python3.12
```
## Building the documentation ## Building the documentation
The documentation here is still boilerplate. The documentation here is still boilerplate.

54
examples/execve.py Normal file
View File

@ -0,0 +1,54 @@
import time
from ctypes import c_int32, c_int64, c_uint64, c_void_p
from pylibbpf import BpfMap
from pythonbpf import BPF, bpf, bpfglobal, map, section
from pythonbpf.maps import HashMap
@bpf
@map
def last() -> HashMap:
return HashMap(key=c_uint64, value=c_uint64, max_entries=3)
@bpf
@section("tracepoint/syscalls/sys_enter_execve")
def hello(ctx: c_void_p) -> c_int32:
print("entered")
print("multi constant support")
return c_int32(0)
@bpf
@section("tracepoint/syscalls/sys_exit_execve")
def hello_again(ctx: c_void_p) -> c_int64:
print("exited")
key = 0
tsp = last().lookup(key)
print(tsp)
return c_int64(0)
@bpf
@bpfglobal
def LICENSE() -> str:
return "GPL"
b = BPF()
b.load_and_attach()
mp = BpfMap(b, last)
mp[42] = 100 # Update entry
value = mp[42] # Lookup entry
del mp[42] # Delete entry
mp[69] = 420
mp[31] = 42
mp[21] = 34
print(mp.items())
# Iterate through map
for key in mp.keys():
print(mp[key])
while True:
print("running")
time.sleep(1)

View File

@ -9,7 +9,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "pylibbpf" name = "pylibbpf"
version = "0.0.4" version = "0.0.5"
description = "Python Bindings for Libbpf" description = "Python Bindings for Libbpf"
authors = [ authors = [
{ name = "r41k0u", email = "pragyanshchaturvedi18@gmail.com" }, { name = "r41k0u", email = "pragyanshchaturvedi18@gmail.com" },

View File

@ -3,10 +3,12 @@
#define MACRO_STRINGIFY(x) STRINGIFY(x) #define MACRO_STRINGIFY(x) STRINGIFY(x)
extern "C" { extern "C" {
#include "libbpf.h" #include <libbpf.h>
} }
#include "core/bpf_program.h" #include "core/bpf_program.h"
#include "core/bpf_exception.h" #include "core/bpf_exception.h"
#include "core/bpf_map.h"
namespace py = pybind11; namespace py = pybind11;
@ -21,6 +23,7 @@ PYBIND11_MODULE(pylibbpf, m) {
:toctree: _generate :toctree: _generate
BpfProgram BpfProgram
BpfMap
BpfException BpfException
)pbdoc"; )pbdoc";
@ -32,10 +35,30 @@ PYBIND11_MODULE(pylibbpf, m) {
.def(py::init<const std::string &, const std::string &>()) .def(py::init<const std::string &, const std::string &>())
.def("load", &BpfProgram::load) .def("load", &BpfProgram::load)
.def("attach", &BpfProgram::attach) .def("attach", &BpfProgram::attach)
// .def("detach", &BpfProgram::detach) .def("destroy", &BpfProgram::destroy)
.def("load_and_attach", &BpfProgram::load_and_attach)
.def("is_loaded", &BpfProgram::is_loaded) .def("is_loaded", &BpfProgram::is_loaded)
.def("is_attached", &BpfProgram::is_attached); .def("is_attached", &BpfProgram::is_attached);
py::class_<BpfMap>(m, "BpfMap")
.def(py::init<BpfProgram *, py::object &>())
.def("lookup", &BpfMap::lookup)
.def("update", &BpfMap::update)
.def("delete", &BpfMap::delete_elem)
.def("get_next_key", &BpfMap::get_next_key, py::arg("key") = py::none())
.def("items", &BpfMap::items)
.def("keys", &BpfMap::keys)
.def("values", &BpfMap::values)
.def("get_name", &BpfMap::get_name)
.def("get_type", &BpfMap::get_type)
.def("get_key_size", &BpfMap::get_key_size)
.def("get_value_size", &BpfMap::get_value_size)
.def("get_max_entries", &BpfMap::get_max_entries)
.def("__getitem__", &BpfMap::lookup)
.def("__setitem__", &BpfMap::update)
.def("__delitem__", &BpfMap::delete_elem);
#ifdef VERSION_INFO #ifdef VERSION_INFO
m.attr("__version__") = MACRO_STRINGIFY(VERSION_INFO); m.attr("__version__") = MACRO_STRINGIFY(VERSION_INFO);
#else #else

View File

@ -7,10 +7,12 @@
class BpfException final : public std::runtime_error { class BpfException final : public std::runtime_error {
public: public:
explicit BpfException(const std::string &message) explicit BpfException(const std::string &message)
: std::runtime_error(message) {} : std::runtime_error(message) {
}
explicit BpfException(const char *message) explicit BpfException(const char *message)
: std::runtime_error(message) {} : std::runtime_error(message) {
}
}; };
#endif // PYLIBBPF_BPF_EXCEPTION_H #endif // PYLIBBPF_BPF_EXCEPTION_H

211
src/core/bpf_map.cpp Normal file
View File

@ -0,0 +1,211 @@
#include "bpf_map.h"
#include "bpf_exception.h"
BpfMap::BpfMap(BpfProgram *program_, const py::object &map_from_python) {
if (py::isinstance<py::function>(map_from_python)) {
const auto name = map_from_python.attr("__name__").cast<std::string>();
bpf_program = program_;
map_ = bpf_object__find_map_by_name(bpf_program->get_obj(), name.c_str());
if (!map_) {
throw BpfException("Failed to find map by name");
}
map_fd = bpf_map__fd(map_);
if (map_fd == -1) {
throw BpfException("Failed to open map File Descriptor");
}
} else {
throw BpfException("Invalid map object passed to function.");
}
}
std::vector<uint8_t> BpfMap::python_to_bytes(const py::object &obj, size_t size) {
std::vector<uint8_t> result(size, 0);
if (py::isinstance<py::int_>(obj)) {
const auto value = obj.cast<uint64_t>();
std::memcpy(result.data(), &value, std::min(size, sizeof(uint64_t)));
} else if (py::isinstance<py::bytes>(obj)) {
const auto bytes_str = obj.cast<std::string>();
std::memcpy(result.data(), bytes_str.data(), std::min(size, bytes_str.size()));
} else if (py::isinstance<py::str>(obj)) {
const auto str_val = obj.cast<std::string>();
std::memcpy(result.data(), str_val.data(), std::min(size, str_val.size()));
}
return result;
}
py::object BpfMap::bytes_to_python(const std::vector<uint8_t> &data) {
// Try to interpret as integer if it's a common integer size
if (data.size() == 4) {
uint32_t value;
std::memcpy(&value, data.data(), 4);
return py::cast(value);
} else if (data.size() == 8) {
uint64_t value;
std::memcpy(&value, data.data(), 8);
return py::cast(value);
} else {
// Return as bytes
return py::bytes(reinterpret_cast<const char *>(data.data()), data.size());
}
}
void BpfMap::update(const py::object &key, const py::object &value) const {
const size_t key_size = bpf_map__key_size(map_);
const size_t value_size = bpf_map__value_size(map_);
const auto key_bytes = python_to_bytes(key, key_size);
const auto value_bytes = python_to_bytes(value, value_size);
const int ret = bpf_map__update_elem(
map_,
key_bytes.data(),
key_size,
value_bytes.data(),
value_size,
BPF_ANY);
if (ret != 0) {
throw BpfException("Failed to update map element");
}
}
void BpfMap::delete_elem(const py::object &key) const {
const size_t key_size = bpf_map__key_size(map_);
std::vector<uint8_t> key_bytes;
key_bytes = python_to_bytes(key, key_size);
if (const int ret = bpf_map__delete_elem(map_, key_bytes.data(), key_size, BPF_ANY); ret != 0) {
throw BpfException("Failed to delete map element");
}
}
py::list BpfMap::get_next_key(const py::object &key) const {
const size_t key_size = bpf_map__key_size(map_);
std::vector<uint8_t> next_key(key_size);
int ret;
if (key.is_none()) {
ret = bpf_map__get_next_key(map_, nullptr, next_key.data(), key_size);
} else {
const auto key_bytes = python_to_bytes(key, key_size);
ret = bpf_map__get_next_key(map_, key_bytes.data(), next_key.data(), key_size);
}
py::list result;
if (ret == 0) {
result.append(bytes_to_python(next_key));
}
return result;
}
py::list BpfMap::keys() const {
py::list result;
const size_t key_size = bpf_map__key_size(map_);
std::vector<uint8_t> key(key_size);
std::vector<uint8_t> next_key(key_size);
int ret = bpf_map__get_next_key(map_, nullptr, key.data(), key_size);
while (ret == 0) {
result.append(bytes_to_python(key));
ret = bpf_map__get_next_key(map_, key.data(), next_key.data(), key_size);
key = next_key;
}
return result;
}
py::list BpfMap::values() const {
py::list result;
const size_t key_size = bpf_map__key_size(map_);
const size_t value_size = bpf_map__value_size(map_);
std::vector<uint8_t> key(key_size);
std::vector<uint8_t> next_key(key_size);
std::vector<uint8_t> value(value_size);
int ret = bpf_map__get_next_key(map_, nullptr, key.data(), key_size);
while (ret == 0) {
if (bpf_map__lookup_elem(map_, key.data(), key_size, value.data(), value_size, BPF_ANY) == 0) {
result.append(bytes_to_python(value));
}
ret = bpf_map__get_next_key(map_, key.data(), next_key.data(), key_size);
key = next_key;
}
return result;
}
std::string BpfMap::get_name() const {
const char *name = bpf_map__name(map_);
return name ? std::string(name) : "";
}
int BpfMap::get_type() const {
return bpf_map__type(map_);
}
int BpfMap::get_key_size() const {
return bpf_map__key_size(map_);
}
int BpfMap::get_value_size() const {
return bpf_map__value_size(map_);
}
int BpfMap::get_max_entries() const {
return bpf_map__max_entries(map_);
}
py::dict BpfMap::items() const {
py::dict result;
const size_t key_size = bpf_map__key_size(map_);
const size_t value_size = bpf_map__value_size(map_);
std::vector<uint8_t> key(key_size);
std::vector<uint8_t> next_key(key_size);
std::vector<uint8_t> value(value_size);
// Get first key
int ret = bpf_map__get_next_key(map_, nullptr, key.data(), key_size);
while (ret == 0) {
// Lookup value for current key
if (bpf_map__lookup_elem(map_, key.data(), key_size, value.data(), value_size, BPF_ANY) == 0) {
result[bytes_to_python(key)] = bytes_to_python(value);
}
// Get next key
ret = bpf_map__get_next_key(map_, key.data(), next_key.data(), key_size);
key = next_key;
}
return result;
}
py::object BpfMap::lookup(const py::object &key) const {
const __u32 key_size = bpf_map__key_size(map_);
const __u32 value_size = bpf_map__value_size(map_);
const auto key_bytes = python_to_bytes(key, key_size);
std::vector<uint8_t> value_bytes(value_size);
// The flags field here matters only when spin locks are used which is close to fucking never, so fuck no,
// im not adding it
const int ret = bpf_map__lookup_elem(
map_,
key_bytes.data(),
key_size,
value_bytes.data(),
value_size,
BPF_ANY);
if (ret != 0) {
return py::none();
}
return bytes_to_python(value_bytes);
}

55
src/core/bpf_map.h Normal file
View File

@ -0,0 +1,55 @@
#ifndef PYLIBBPF_MAPS_H
#define PYLIBBPF_MAPS_H
#include <libbpf.h>
#include <pybind11/pybind11.h>
#include <vector>
#include <string>
#include "bpf_program.h"
namespace py = pybind11;
class BpfMap {
private:
struct bpf_map *map_;
int map_fd = -1;
//TODO: turn below into a shared pointer and ref count it so that there is no resource leakage
BpfProgram *bpf_program;
public:
BpfMap(BpfProgram *program_, const py::object &map_from_python);
~BpfMap() = default;
[[nodiscard]] py::object lookup(const py::object &key) const;
void update(const py::object &key, const py::object &value) const;
void delete_elem(const py::object &key) const;
py::list get_next_key(const py::object &key = py::none()) const;
py::dict items() const;
py::list keys() const;
py::list values() const;
[[nodiscard]] std::string get_name() const;
int get_type() const;
int get_key_size() const;
int get_value_size() const;
int get_max_entries() const;
private:
static std::vector<uint8_t> python_to_bytes(const py::object &obj, size_t size);
static py::object bytes_to_python(const std::vector<uint8_t> &data);
};
#endif //PYLIBBPF_MAPS_H

View File

@ -1,19 +1,24 @@
#include "bpf_program.h" #include "bpf_program.h"
#include "bpf_exception.h" #include "bpf_exception.h"
#include <filesystem> #include <filesystem>
#include <utility>
BpfProgram::BpfProgram(const std::string& object_path, const std::string& program_name) BpfProgram::BpfProgram(std::string object_path, std::string program_name)
: obj_(nullptr), prog_(nullptr), link_(nullptr), : obj_(nullptr), prog_(nullptr), link_(nullptr),
object_path_(object_path), program_name_(program_name) { object_path_(std::move(object_path)), program_name_(std::move(program_name)) {
} }
BpfProgram::~BpfProgram() { BpfProgram::~BpfProgram() {
//TODO: detach here as well destroy();
if (obj_) { if (obj_) {
bpf_object__close(obj_); bpf_object__close(obj_);
} }
} }
struct bpf_object * BpfProgram::get_obj() const {
return obj_;
}
bool BpfProgram::load() { bool BpfProgram::load() {
// Open the eBPF object file // Open the eBPF object file
obj_ = bpf_object__open_file(object_path_.c_str(), nullptr); obj_ = bpf_object__open_file(object_path_.c_str(), nullptr);
@ -28,9 +33,12 @@ bool BpfProgram::load() {
throw BpfException("Program '" + program_name_ + "' not found in object"); throw BpfException("Program '" + program_name_ + "' not found in object");
} }
} else { } else {
// Use the first program if no name specified while ((prog_ = bpf_object__next_program(obj_, prog_)) != nullptr) {
prog_ = bpf_object__next_program(obj_, nullptr); programs.emplace_back(prog_, nullptr);
if (!prog_) { }
// throw if no programs found
if (programs.empty()) {
throw BpfException("No programs found in object file"); throw BpfException("No programs found in object file");
} }
} }
@ -44,15 +52,33 @@ bool BpfProgram::load() {
} }
bool BpfProgram::attach() { bool BpfProgram::attach() {
if (!prog_) { for (auto [prog, link]: programs) {
if (!prog) {
throw BpfException("Program not loaded"); throw BpfException("Program not loaded");
} }
link_ = bpf_program__attach(prog_); link = bpf_program__attach(prog);
if (libbpf_get_error(link_)) { if (libbpf_get_error(link)) {
link_ = nullptr; link = nullptr;
throw BpfException("Failed to attach BPF program"); throw BpfException("Failed to attach BPF program");
} }
}
return true; return true;
} }
bool BpfProgram::destroy() {
bool success = true;
for (auto [prog, link]: programs) {
if (!prog) {
throw BpfException("Program not loaded");
}
success = success & bpf_link__destroy(link);
}
return success;
}
void BpfProgram::load_and_attach() {
load();
attach();
}

View File

@ -1,7 +1,7 @@
#ifndef PYLIBBPF_BPF_PROGRAM_H #ifndef PYLIBBPF_BPF_PROGRAM_H
#define PYLIBBPF_BPF_PROGRAM_H #define PYLIBBPF_BPF_PROGRAM_H
#include "libbpf.h" #include <libbpf.h>
#include <pybind11/stl.h> #include <pybind11/stl.h>
#include <string> #include <string>
@ -14,16 +14,25 @@ private:
struct bpf_link *link_; struct bpf_link *link_;
std::string object_path_; std::string object_path_;
std::string program_name_; std::string program_name_;
std::vector<std::pair<bpf_program *, bpf_link *> > programs;
public: public:
explicit BpfProgram(const std::string& object_path, const std::string& program_name = ""); explicit BpfProgram(std::string object_path, std::string program_name = "");
~BpfProgram(); ~BpfProgram();
struct bpf_object *get_obj() const;
bool load(); bool load();
bool attach(); bool attach();
bool is_loaded() const { return obj_ != nullptr; } bool destroy();
bool is_attached() const { return link_ != nullptr; }
void load_and_attach();
[[nodiscard]] bool is_loaded() const { return obj_ != nullptr; }
[[nodiscard]] bool is_attached() const { return link_ != nullptr; }
}; };
#endif //PYLIBBPF_BPF_PROGRAM_H #endif //PYLIBBPF_BPF_PROGRAM_H

View File

@ -2,6 +2,6 @@ import pylibbpf as m
def test_main(): def test_main():
assert m.__version__ == "0.0.4" assert m.__version__ == "0.0.5"
prog = m.BpfProgram("tests/execve2.o") prog = m.BpfProgram("tests/execve2.o")
print(prog) print(prog)

1
xdp-tools Submodule

Submodule xdp-tools added at fa3bcd03cc