mirror of
https://github.com/varun-r-mallya/Python-BPF.git
synced 2026-02-07 13:40:59 +00:00
414 lines
8.0 KiB
Markdown
414 lines
8.0 KiB
Markdown
# BPF Structs
|
|
|
|
Structs allow you to define custom data types for use in BPF programs. They provide a way to group related fields together and can be used as map values, event payloads, or local variables.
|
|
|
|
## Defining Structs
|
|
|
|
Use the `@bpf` and `@struct` decorators to define a BPF struct:
|
|
|
|
```python
|
|
from pythonbpf import bpf, struct
|
|
from ctypes import c_uint64, c_uint32
|
|
|
|
@bpf
|
|
@struct
|
|
class Event:
|
|
timestamp: c_uint64
|
|
pid: c_uint32
|
|
cpu: c_uint32
|
|
```
|
|
|
|
## Field Types
|
|
|
|
Structs support various field types from Python's `ctypes` module.
|
|
|
|
### Integer Types
|
|
|
|
```python
|
|
from ctypes import (
|
|
c_int8, c_int16, c_int32, c_int64,
|
|
c_uint8, c_uint16, c_uint32, c_uint64
|
|
)
|
|
|
|
@bpf
|
|
@struct
|
|
class Numbers:
|
|
small_int: c_int8 # -128 to 127
|
|
short_int: c_int16 # -32768 to 32767
|
|
int_val: c_int32 # -2^31 to 2^31-1
|
|
long_int: c_int64 # -2^63 to 2^63-1
|
|
|
|
byte: c_uint8 # 0 to 255
|
|
word: c_uint16 # 0 to 65535
|
|
dword: c_uint32 # 0 to 2^32-1
|
|
qword: c_uint64 # 0 to 2^64-1
|
|
```
|
|
|
|
### String Types
|
|
|
|
Fixed-length strings are defined using `str(N)` where N is the size:
|
|
|
|
```python
|
|
@bpf
|
|
@struct
|
|
class ProcessInfo:
|
|
name: str(16) # 16-byte string
|
|
path: str(256) # 256-byte string
|
|
```
|
|
|
|
```{note}
|
|
Strings in BPF are fixed-length and null-terminated. The size includes the null terminator.
|
|
```
|
|
|
|
### Pointer Types
|
|
|
|
```python
|
|
from ctypes import c_void_p, c_char_p
|
|
|
|
@bpf
|
|
@struct
|
|
class Pointers:
|
|
ptr: c_void_p # Generic pointer
|
|
str_ptr: c_char_p # Character pointer
|
|
```
|
|
|
|
### Nested Structs
|
|
|
|
Structs can contain other structs as fields:
|
|
|
|
```python
|
|
@bpf
|
|
@struct
|
|
class Address:
|
|
street: str(64)
|
|
city: str(32)
|
|
zip_code: c_uint32
|
|
|
|
@bpf
|
|
@struct
|
|
class Person:
|
|
name: str(32)
|
|
age: c_uint32
|
|
address: Address # Nested struct
|
|
```
|
|
|
|
## Using Structs
|
|
|
|
### As Local Variables
|
|
|
|
Create and use struct instances within BPF functions:
|
|
|
|
```python
|
|
from pythonbpf import bpf, struct, section
|
|
from pythonbpf.helper import pid, ktime, comm
|
|
from ctypes import c_void_p, c_int64, c_uint64, c_uint32
|
|
|
|
@bpf
|
|
@struct
|
|
class Event:
|
|
timestamp: c_uint64
|
|
pid: c_uint32
|
|
comm: str(16)
|
|
|
|
@bpf
|
|
@section("tracepoint/syscalls/sys_enter_execve")
|
|
def capture_event(ctx: c_void_p) -> c_int64:
|
|
# Create an instance
|
|
event = Event()
|
|
|
|
# Set fields
|
|
event.timestamp = ktime()
|
|
event.pid = pid()
|
|
comm(event.comm) # Fills event.comm with process name
|
|
|
|
# Use the struct
|
|
print(f"Process with PID {event.pid}")
|
|
|
|
return 0
|
|
```
|
|
|
|
### As Map Keys and Values
|
|
|
|
Use structs as keys and values in maps for complex state storage:
|
|
|
|
```python
|
|
from pythonbpf import bpf, struct, map, section
|
|
from pythonbpf.maps import HashMap
|
|
from ctypes import c_uint32, c_uint64
|
|
|
|
@bpf
|
|
@struct
|
|
class ProcessStats:
|
|
syscall_count: c_uint64
|
|
total_time: c_uint64
|
|
max_latency: c_uint64
|
|
|
|
@bpf
|
|
@map
|
|
def stats() -> HashMap:
|
|
return HashMap(
|
|
key=c_uint32,
|
|
value=ProcessStats,
|
|
max_entries=1024
|
|
)
|
|
|
|
@bpf
|
|
@section("tracepoint/syscalls/sys_enter_read")
|
|
def track_syscalls(ctx: c_void_p) -> c_int64:
|
|
process_id = pid()
|
|
|
|
# Lookup existing stats
|
|
s = stats.lookup(process_id)
|
|
|
|
if s:
|
|
# Update existing stats
|
|
s.syscall_count = s.syscall_count + 1
|
|
stats.update(process_id, s)
|
|
else:
|
|
# Create new stats
|
|
new_stats = ProcessStats()
|
|
new_stats.syscall_count = 1
|
|
new_stats.total_time = 0
|
|
new_stats.max_latency = 0
|
|
stats.update(process_id, new_stats)
|
|
|
|
return 0
|
|
```
|
|
|
|
### With Perf Events
|
|
|
|
Send struct data to userspace using PerfEventArray:
|
|
|
|
```python
|
|
from pythonbpf import bpf, struct, map, section
|
|
from pythonbpf.maps import PerfEventArray
|
|
from pythonbpf.helper import pid, ktime, comm
|
|
from ctypes import c_void_p, c_int64, c_uint32, c_uint64
|
|
|
|
@bpf
|
|
@struct
|
|
class ProcessEvent:
|
|
timestamp: c_uint64
|
|
pid: c_uint32
|
|
ppid: c_uint32
|
|
comm: str(16)
|
|
|
|
@bpf
|
|
@map
|
|
def events() -> PerfEventArray:
|
|
return PerfEventArray(key_size=c_uint32, value_size=c_uint32)
|
|
|
|
@bpf
|
|
@section("tracepoint/sched/sched_process_fork")
|
|
def trace_fork(ctx: c_void_p) -> c_int64:
|
|
event = ProcessEvent()
|
|
event.timestamp = ktime()
|
|
event.pid = pid()
|
|
comm(event.comm) # Fills event.comm with process name
|
|
|
|
# Send to userspace
|
|
events.output(event)
|
|
|
|
return 0
|
|
```
|
|
|
|
### With Ring Buffers
|
|
|
|
```python
|
|
from pythonbpf import bpf, struct, map, section
|
|
from pythonbpf.maps import RingBuffer
|
|
|
|
@bpf
|
|
@struct
|
|
class FileEvent:
|
|
timestamp: c_uint64
|
|
pid: c_uint32
|
|
filename: str(256)
|
|
|
|
@bpf
|
|
@map
|
|
def events() -> RingBuffer:
|
|
return RingBuffer(max_entries=4096)
|
|
|
|
@bpf
|
|
@section("tracepoint/syscalls/sys_enter_openat")
|
|
def trace_open(ctx: c_void_p) -> c_int64:
|
|
event = FileEvent()
|
|
event.timestamp = ktime()
|
|
event.pid = pid()
|
|
|
|
events.output(event)
|
|
|
|
return 0
|
|
```
|
|
|
|
## Field Access and Modification
|
|
|
|
### Reading Fields
|
|
|
|
Access struct fields using dot notation:
|
|
|
|
```python
|
|
event = Event()
|
|
ts = event.timestamp
|
|
process_id = event.pid
|
|
```
|
|
|
|
### Writing Fields
|
|
|
|
Assign values to fields:
|
|
|
|
```python
|
|
event = Event()
|
|
event.timestamp = ktime()
|
|
event.pid = pid()
|
|
comm(event.comm)
|
|
```
|
|
|
|
## StructType Class
|
|
|
|
PythonBPF provides a `StructType` class for working with struct metadata:
|
|
|
|
```python
|
|
from pythonbpf.structs import StructType
|
|
|
|
# Define a struct
|
|
@bpf
|
|
@struct
|
|
class MyStruct:
|
|
field1: c_uint64
|
|
field2: c_uint32
|
|
|
|
# Access struct information (from userspace)
|
|
# This is typically used internally by the compiler
|
|
```
|
|
|
|
## Complex Examples
|
|
|
|
### Network Packet Event
|
|
|
|
```python
|
|
from pythonbpf import bpf, struct, map, section
|
|
from pythonbpf.maps import RingBuffer
|
|
from pythonbpf.helper import ktime, XDP_PASS
|
|
from ctypes import c_void_p, c_int64, c_uint8, c_uint16, c_uint32, c_uint64
|
|
|
|
@bpf
|
|
@struct
|
|
class PacketEvent:
|
|
timestamp: c_uint64
|
|
src_ip: c_uint32
|
|
dst_ip: c_uint32
|
|
src_port: c_uint16
|
|
dst_port: c_uint16
|
|
protocol: c_uint8
|
|
length: c_uint16
|
|
|
|
@bpf
|
|
@map
|
|
def packets() -> RingBuffer:
|
|
return RingBuffer(max_entries=8192)
|
|
|
|
@bpf
|
|
@section("xdp")
|
|
def capture_packets(ctx: c_void_p) -> c_int64:
|
|
pkt = PacketEvent()
|
|
pkt.timestamp = ktime()
|
|
# Parse packet data from ctx...
|
|
|
|
packets.output(pkt)
|
|
|
|
return XDP_PASS
|
|
```
|
|
|
|
### Process Lifecycle Tracking
|
|
|
|
```python
|
|
@bpf
|
|
@struct
|
|
class ProcessLifecycle:
|
|
pid: c_uint32
|
|
ppid: c_uint32
|
|
start_time: c_uint64
|
|
exit_time: c_uint64
|
|
exit_code: c_int32
|
|
comm: str(16)
|
|
|
|
@bpf
|
|
@map
|
|
def process_info() -> HashMap:
|
|
return HashMap(
|
|
key=c_uint32,
|
|
value=ProcessLifecycle,
|
|
max_entries=4096
|
|
)
|
|
|
|
@bpf
|
|
@section("tracepoint/sched/sched_process_fork")
|
|
def track_fork(ctx: c_void_p) -> c_int64:
|
|
process_id = pid()
|
|
|
|
info = ProcessLifecycle()
|
|
info.pid = process_id
|
|
info.start_time = ktime()
|
|
|
|
process_info.update(process_id, info)
|
|
|
|
return 0
|
|
|
|
@bpf
|
|
@section("tracepoint/sched/sched_process_exit")
|
|
def track_exit(ctx: c_void_p) -> c_int64:
|
|
process_id = pid()
|
|
|
|
info = process_info.lookup(process_id)
|
|
if info:
|
|
info.exit_time = ktime()
|
|
process_info.update(process_id, info)
|
|
|
|
return 0
|
|
```
|
|
|
|
## Troubleshooting
|
|
|
|
### Struct Size Issues
|
|
|
|
If you encounter size-related errors:
|
|
* Check for excessive padding
|
|
* Verify field types are correct
|
|
* Consider reordering fields
|
|
|
|
### Initialization Problems
|
|
|
|
If fields aren't initialized correctly:
|
|
* Always initialize all fields explicitly
|
|
* Set default values where appropriate
|
|
* Use helper functions for dynamic values
|
|
|
|
### Type Mismatch Errors
|
|
|
|
If you get type errors:
|
|
* Ensure field types match assignments
|
|
* Check that imported types are from `ctypes`
|
|
* Verify nested struct definitions
|
|
|
|
## Reading Struct Data in Userspace
|
|
|
|
After capturing struct data, read it in Python:
|
|
|
|
```python
|
|
from pylibbpf import BpfMap
|
|
|
|
# Read from map
|
|
map_obj = BpfMap(b, stats)
|
|
for key, value_bytes in map_obj.items():
|
|
value = Event.from_buffer_copy(value_bytes)
|
|
print(f"PID: {value.pid}, Comm: {value.comm.decode()}")
|
|
```
|
|
|
|
## Next Steps
|
|
|
|
* Learn about {doc}`maps` for storing struct data
|
|
* Explore {doc}`helpers` for populating struct fields
|
|
* See {doc}`compilation` to understand how structs are compiled
|