From 91d49228cfe4f047c218d70edd7b33ffbb5cfb93 Mon Sep 17 00:00:00 2001 From: Martin Teichmann <martin.teichmann@gmail.com> Date: Tue, 21 Feb 2023 23:01:22 +0000 Subject: [PATCH] allow running ebpf XDP on the network driver --- ebpfcat/ebpf.rst | 21 +++++++++++++++++---- ebpfcat/xdp.py | 45 +++++++++++++++++++++++++++++++-------------- examples/count.py | 11 +++++------ 3 files changed, 53 insertions(+), 24 deletions(-) diff --git a/ebpfcat/ebpf.rst b/ebpfcat/ebpf.rst index cfc6838..570e7c8 100644 --- a/ebpfcat/ebpf.rst +++ b/ebpfcat/ebpf.rst @@ -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() diff --git a/ebpfcat/xdp.py b/ebpfcat/xdp.py index 7d462c9..dee0e1e 100644 --- a/ebpfcat/xdp.py +++ b/ebpfcat/xdp.py @@ -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) diff --git a/examples/count.py b/examples/count.py index fe7b478..be333ff 100644 --- a/examples/count.py +++ b/examples/count.py @@ -1,6 +1,6 @@ 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,10 @@ class Count(XDP): async def main(): c = Count() - await c.attach("eth0") - - for i in range(100): - await sleep(0.1) - print(c.count) + async with c.run("eth0", XDPFlags.DRV_MODE): + for i in range(10): + await sleep(0.1) + print(c.count) if __name__ == "__main__": -- GitLab