Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
E
ebpfCAT
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Wiki
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Model registry
Operate
Environments
Monitor
Incidents
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
Martin Teichmann
ebpfCAT
Commits
418855e2
Commit
418855e2
authored
2 years ago
by
Martin Teichmann
Browse files
Options
Downloads
Plain Diff
Write a lot of documentation
See merge request
!16
parents
b74d970d
f65e38e5
Loading
Loading
1 merge request
!16
Write a lot of documentation
Pipeline
#98716
passed
2 years ago
Changes
3
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
ebpfcat/ebpf.rst
+79
-52
79 additions, 52 deletions
ebpfcat/ebpf.rst
ebpfcat/xdp.py
+56
-6
56 additions, 6 deletions
ebpfcat/xdp.py
known_hosts
+1
-0
1 addition, 0 deletions
known_hosts
with
136 additions
and
58 deletions
ebpfcat/ebpf.rst
+
79
−
52
View file @
418855e2
...
...
@@ -20,13 +20,13 @@ incoming packages.
We start with declaring the variables that we want to see both in the
XDP program and in user space::
from ebpfcat.
hash
map import
Hash
Map
from ebpfcat.
array
map import
Array
Map
from ebpfcat.xdp import XDP, XDPExitCode
class Count(XDP):
license = "GPL" # the Linux kernel wants to know that...
userspace =
Hash
Map()
userspace =
Array
Map()
count = userspace.globalVar() # declare a variable in the map
Next comes the program that we want to run in the kernel. Note that this
...
...
@@ -73,28 +73,48 @@ For reference, this is the full example:
.. literalinclude:: /examples/count.py
Conditional statement
s
----
------------------
Map
s
----
During code generation, all code needs to be executed. This means that
we cannot use a Python ``if`` statement, as
th
e
n the
code actually does not
get executed, so no code would be generated. So we replace ``if`` statements
by Python ``with`` statements like so:
:
Maps are used to communicate to the outside world. They look like instance
variables. They may be used from wi
th
i
n the
EBPF program, and once it is
loaded also from everywhere else. There are two flavors: `arraymap.ArrayMap`
and `hashmap.HashMap`. They have different use cases
:
with self.some_variable > 6 as Else:
do_someting
with Else:
do_something_else
Array Maps
~~~~~~~~~~
certainly an ``Else`` statement may be omitted if not needed.
Array maps are share memory between EBPF programs and user space. All programs
as well as user space are accessing the memory at the same time, so concurrent
access may lead to problems. An exception is the in-place addition operator
`+=`, which works under a lock, but only if the variable is of 4 or 8
bytes size.
No loops
--------
Otherwise variables may be declared in all sizes. The declaration is like so::
There is no way to declare a loop, simply because EBPF does not allow it.
You may simply write a ``for`` loop in Python as long as everything can
be calculated at generation time, but this just means that the code will show
up in the EPBF as often as the loop is iterated at generation time.
class MyProgram(EBPF):
array_map = ArrayMap()
a_byte_variable = array_map.globalVar("B")
an_integer_variable = array_map.globalVar("i")
those variables can be accessed both from within the ebpf program, as from
outside. Both sides are actually accessing the same memory, so be aware of
race conditions.
Hash Maps
~~~~~~~~~
all hash map variables have a fixed size of 8 bytes. Accessing them is
rather slow, but is done with proper locking: concurrent access is possible.
When accessing them from user space, they are read from the kernel each time
anew. They are declared as follows::
class MyProgram(EBPF):
hash_map = HashMap()
a_variable = hash_map.globalVar()
They are used as normal variables, like in `self.a_variable = 5`, both
in EBPF and from user space once loaded.
Accessing the packet
--------------------
...
...
@@ -134,7 +154,7 @@ is too small (by default ``XDPExitCode.PASS``). So the above example becomes::
class Program(XDP):
minimumPacketSize = 16
userspace =
Hash
Map()
userspace =
Array
Map()
count = userspace.globalVar()
def program(self):
...
...
@@ -149,7 +169,7 @@ example simplifies to::
class Program(XDP):
minimumPacketSize = 16
userspace =
Hash
Map()
userspace =
Array
Map()
count = userspace.globalVar()
etherType = PacketVar(12, "!H") # use network byte order
...
...
@@ -157,48 +177,55 @@ example simplifies to::
with self.etherType == 0x800:
self.count += 1
Programming
-----------
Maps
----
The actual XDP program is a class that inherits from ``XDP``. The class body
contains all variable declarations, and a method ``program`` which is the
program proper. It is executed by Python, and while executing an EPBF program
is created, which can then be loaded into the linux kernel.
Maps are used to communicate to the outside world. They look like instance
variables. They may be used from within the EBPF program, and once it is
loaded also from everywhere else. There are two flavors: `hashmap.HashMap`
and `arraymap.ArrayMap`. They have different use cases:
Expressions
~~~~~~~~~~~
Hash Maps
~~~~~~~~~
Once a variable is declared, it can be used very close to normal Python syntax.
Standard arithmetic works, like ``self.distance = self.speed * self.time``,
given that all are declared variables. Note that you cannot use usual Python
variables, as accessing them does not generate any EBPF code. Use local
variables for that.
all hash map variables have a fixed size of 8 bytes. Accessing them is
rather slow, but is done with proper locking: concurrent access is possible.
When accessing them from user space, they are read from the kernel each time
anew. They are declared as follows::
Local variables
~~~~~~~~~~~~~~~
class MyProgram(EBPF):
hash_map = HashMap()
a_variable = hash_map.globalVar()
local variables are seen only by one EBPF program, they cannot be seen by
other programs or user space. They are declared in the class body like this::
They are used as normal variables, like in `self.a_variable = 5`, both
in EBPF and from user space once loaded.
class Program(XDP):
local_variable = LocalVar("I")
Array Maps
~~~~~~~~~~
Conditional statements
~~~~~~~~~~~~~~~~~~~~~~
During code generation, all code needs to be executed. This means that
we cannot use a Python ``if`` statement, as then the code actually does not
get executed, so no code would be generated. So we replace ``if`` statements
by Python ``with`` statements like so::
from an EBPF program's perspective, all EPBF programs are accessing the same
variables at the same time. So concurrent access may lead to problems. An
exception is the in-place addition operator `+=`, which works under a lock,
but only if the variable is of 4 or 8 bytes size.
with self.some_variable > 6 as Else:
do_someting
with Else:
do_something_else
Otherwise variables may be declared in all sizes. The declaration is like so::
certainly an ``Else`` statement may be omitted if not needed.
class MyProgram(EBPF):
array_map = ArrayMap()
a_byte_variable = array_map.globalVar("B")
an_integer_variable = array_map.globalVar("i")
No loops
~~~~~~~~
There is no way to declare a loop, simply because EBPF does not allow it.
You may simply write a ``for`` loop in Python as long as everything can
be calculated at generation time, but this just means that the code will show
up in the EPBF as often as the loop is iterated at generation time.
those variables can be accessed both from within the ebpf program, as from
outside. Both sides are actually accessing the same memory, so be aware of
race conditions.
Fixed-point arithmetic
~~~~~~~~~~~~~~~~~~~~~~
...
...
This diff is collapsed.
Click to expand it.
ebpfcat/xdp.py
+
56
−
6
View file @
418855e2
...
...
@@ -153,6 +153,17 @@ class PacketSize:
class
PacketVar
(
MemoryDesc
):
"""
descriptor to access packet data from an XDP program
Declare packet variables as such::
class Program(XDP):
etherType = PacketVar(12,
"
!H
"
)
:param address: the start address within the packet
:param fmt: the data type of the variable, following the
conventions from the :module:`struct` module.
"""
base_register
=
9
def
__init__
(
self
,
address
,
fmt
):
...
...
@@ -164,7 +175,34 @@ class PacketVar(MemoryDesc):
class
XDP
(
EBPF
):
"""
the base class for XDP programs
"""
"""
the base class for XDP programs
XDP programs inherit from this class and define a :meth:`program`
which contains the actual EBPF program. In the class body, variables
are declared using :class:`ebpf.LocalVar`, :class:`PacketVar` and
:class:`arraymap.ArrayMap`.
.. attribute:: minimumPacketSize
set this to an integer value to declare the minimum size of
a packet. You will only be able to access that many bytes in
the packet. If you need something dynamic, use :var:`packetSize`
instead.
.. attribute:: defaultExitCode
The default exit code should the packet be smaller than
``minimumPacketSize``. Defaults to ``XDPExitCode.PASS``.
.. attribute:: packetSize
compare this value to a number in your program to allow at
least that many bytes being read. As an example, to assure
at least 20 bytes may be read one would write::
with self.packetSize > 20:
pass
"""
minimumPacketSize
=
None
defaultExitCode
=
XDPExitCode
.
PASS
...
...
@@ -196,22 +234,34 @@ class XDP(EBPF):
transport
.
get_extra_info
(
"
socket
"
).
close
()
async
def
attach
(
self
,
network
,
flags
=
XDPFlags
.
SKB_MODE
):
"""
attach this program to a `network`
"""
"""
attach this program to a ``network``
:param network: the name of the network interface,
like ``
"
eth0
"
``
:param flags: one of the :class:`XDPFlags`
"""
ifindex
=
if_nametoindex
(
network
)
fd
,
_
=
self
.
load
(
log_level
=
1
)
await
self
.
_netlink
(
ifindex
,
fd
,
flags
)
async
def
detach
(
self
,
network
,
flags
=
XDPFlags
.
SKB_MODE
):
"""
attach this program from a `network`
"""
"""
attach this program from a ``network``
:param network: the name of the network interface,
like ``
"
eth0
"
``
:param flags: one of the :class:`XDPFlags`
"""
ifindex
=
if_nametoindex
(
network
)
await
self
.
_netlink
(
ifindex
,
-
1
)
@asynccontextmanager
async
def
run
(
self
,
network
,
flags
=
XDPFlags
.
SKB_MODE
):
"""
attach this program to a `network` during context
"""
attach this program to a ``network`` during context
attach this program to the ``network`` while the context
manager is running, and detach it afterwards.
attach this program to the `network` while the context
manager is running, and detach it afterwards.
"""
:param network: the name of the network interface,
like ``
"
eth0
"
``
:param flags: one of the :class:`XDPFlags`
"""
ifindex
=
if_nametoindex
(
network
)
fd
,
_
=
self
.
load
(
log_level
=
1
)
try
:
...
...
This diff is collapsed.
Click to expand it.
known_hosts
0 → 100644
+
1
−
0
View file @
418855e2
exflqr30526,131.169.220.113 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCcPp1A0EeC72+tSXKqfVfIlxAKq3Bp7CA2LfSbYD9hw4SKp6UhGcM8R5eQ5wnoMCoJ4IwqMqA0Mi24K9kgQxpAao6GOh3Yx+YznLtKGZRx3EYUM+9s+fDEzmZvymZF5aBqfwx6j1aUiSKciVbo/DNjU0PxuTjliicZqqOaaPVZPMHJA9GZuvwvfxcaFp8fDWAMSRKr01rihcJZljXLe8ZLBZTYt8i+W561WrgZiGztLxaBMT9o+MbEkHsUor5xBidW6NOH1TkQw3wiVp0vl6/ca9VskMIr3OuWCdce6YqR3GC3wBJMMgUeFxyKr3t0k1b4Voxbcy1dh0ObaFPniBAv
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment