Skip to content
Snippets Groups Projects
Commit 74f969ed authored by Martin Teichmann's avatar Martin Teichmann
Browse files

allow running ebpf XDP on the network driver

parent 1ff9e978
No related branches found
No related tags found
No related merge requests found
......@@ -47,15 +47,28 @@ for synchronization::
Once attached, our little program will be executed each time a packet
arrives on the interface. We can read the result in a loop::
for i in range(100):
for i in range(10):
await sleep(0.1)
print("packets arrived so far:", c.count)
With ``attach`` the program is attached indefinitely on the interface,
even beyond the end of the program. Use ``detach`` to detach it, or you
may use the async contextmanager ``run`` to detach automatically, as in::
async with c.run("eth0"):
await sleep(1)
print("packets arrived so far:", c.count)
Note that here we access the member variable ``count`` from user space.
While generating EBPF, the code generator knows it needs to write out
commands to access that variable from EBPF, once accessed outside of
generation context, we access it from the user side.
Both ``attach`` and ``detach`` have an additional parameter ``flags`` to
choose in which mode to attach the program, use ``XDPFlags.SKB_MODE`` (the
default) to use the generic kernel driver, or ``XDPFlags.DRV_MODE`` to let
the interface device driver run the program.
For reference, this is the full example:
.. literalinclude:: /examples/count.py
......@@ -158,11 +171,11 @@ race conditions.
Fixed-point arithmetic
~~~~~~~~~~~~~~~~~~~~~~
as a bonus beyond standard ebpf, we support fixed-point values as a type `x`.
as a bonus beyond standard ebpf, we support fixed-point values as a type ``x``.
Within ebpf they are calculated as per-10000, so a 0.2 is represented as
20000. From outside, the variables seem to be doubles. Vaguely following
Python, all true divisions `/` result in a fixed-point result, while all
floor divisions `//` result in a standard integer. Some examples:
Python, all true divisions ``/`` result in a fixed-point result, while all
floor divisions ``//`` result in a standard integer. Some examples::
class FixedPoint(EPBF):
array_map = ArrayMap()
......
......@@ -36,13 +36,19 @@ class XDPExitCode(Enum):
REDIRECT = 4
class XDPFlags(Enum):
SKB_MODE = 2
DRV_MODE = 4 # XDP done by the network driver
class XDRFD(DatagramProtocol):
"""just implement enough of the NETLINK protocol to attach programs"""
def __init__(self, ifindex, fd, future):
def __init__(self, ifindex, fd, future, flags):
self.ifindex = ifindex
self.fd = fd
self.seq = None
self.flags = flags
self.future = future
def connection_made(self, transport):
......@@ -73,7 +79,7 @@ class XDRFD(DatagramProtocol):
self.fd,
8,
3, # IFLA_XDP_FLAGS,
2)
self.flags.value)
transport.sendto(p, (0, 0))
def datagram_received(self, data, addr):
......@@ -82,12 +88,15 @@ class XDRFD(DatagramProtocol):
ln, type, flags, seq, pid = unpack("IHHII", data[pos : pos+16])
if type == 3: # DONE
self.future.set_result(0)
return
elif type == 2: # ERROR
errno, *args = unpack("iIHHII", data[pos+16 : pos+36])
if errno != 0:
self.future.set_result(errno)
self.future.set_exception(OSError(errno, os.strerror(-errno)))
return
if flags & 2 == 0: # not a multipart message
self.future.set_result(0)
return
pos += ln
......@@ -149,32 +158,40 @@ class XDP(EBPF):
self.packetSize = PacketSize(self)
async def _netlink(self, ifindex, fd):
async def _netlink(self, ifindex, fd, flags):
future = Future()
transport, proto = await get_event_loop().create_datagram_endpoint(
lambda: XDRFD(ifindex, fd, future),
lambda: XDRFD(ifindex, fd, future, flags),
family=AF_NETLINK, proto=NETLINK_ROUTE)
await future
transport.get_extra_info("socket").close()
try:
await future
finally:
transport.get_extra_info("socket").close()
async def attach(self, network):
async def attach(self, network, flags=XDPFlags.SKB_MODE):
"""attach this program to a `network`"""
ifindex = if_nametoindex(network)
fd, _ = self.load(log_level=1)
await self._netlink(ifindex, fd)
await self._netlink(ifindex, fd, flags)
async def detach(self, network):
async def detach(self, network, flags=XDPFlags.SKB_MODE):
"""attach this program from a `network`"""
ifindex = if_nametoindex(network)
await self._netlink(ifindex, -1)
@asynccontextmanager
async def run(self, network):
async def run(self, network, flags=XDPFlags.SKB_MODE):
"""attach this program to a `network` during context
attach this program to the `network` while the context
manager is running, and detach it afterwards."""
ifindex = if_nametoindex(network)
fd, _ = self.load(log_level=1)
await self._netlink(ifindex, fd)
os.close(fd)
try:
await self._netlink(ifindex, fd, flags)
finally:
os.close(fd)
try:
yield
finally:
await self._netlink(ifindex, -1)
await self._netlink(ifindex, -1, flags)
from asyncio import get_event_loop, sleep
from ebpfcat.hashmap import HashMap
from ebpfcat.xdp import XDP, XDPExitCode
from ebpfcat.xdp import XDP, XDPExitCode, XDPFlags
class Count(XDP):
license = "GPL"
......@@ -15,11 +15,11 @@ class Count(XDP):
async def main():
c = Count()
await c.attach("eth0")
for i in range(100):
await sleep(0.1)
print("packets arrived so far:", c.count)
async with c.run("eth0", XDPFlags.DRV_MODE):
for i in range(10):
await sleep(0.1)
print("packets arrived so far:", c.count)
if __name__ == "__main__":
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment