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

allow running ebpf XDP on the network driver

See merge request !14
parents 8f931e34 91d49228
No related branches found
No related tags found
1 merge request!14allow running ebpf XDP on the network driver
Pipeline #98370 passed
...@@ -47,15 +47,28 @@ for synchronization:: ...@@ -47,15 +47,28 @@ for synchronization::
Once attached, our little program will be executed each time a packet Once attached, our little program will be executed each time a packet
arrives on the interface. We can read the result in a loop:: 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) await sleep(0.1)
print("packets arrived so far:", c.count) 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. Note that here we access the member variable ``count`` from user space.
While generating EBPF, the code generator knows it needs to write out While generating EBPF, the code generator knows it needs to write out
commands to access that variable from EBPF, once accessed outside of commands to access that variable from EBPF, once accessed outside of
generation context, we access it from the user side. 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: For reference, this is the full example:
.. literalinclude:: /examples/count.py .. literalinclude:: /examples/count.py
...@@ -158,11 +171,11 @@ race conditions. ...@@ -158,11 +171,11 @@ race conditions.
Fixed-point arithmetic 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 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 20000. From outside, the variables seem to be doubles. Vaguely following
Python, all true divisions `/` result in a fixed-point result, while all Python, all true divisions ``/`` result in a fixed-point result, while all
floor divisions `//` result in a standard integer. Some examples: floor divisions ``//`` result in a standard integer. Some examples::
class FixedPoint(EPBF): class FixedPoint(EPBF):
array_map = ArrayMap() array_map = ArrayMap()
......
...@@ -36,13 +36,19 @@ class XDPExitCode(Enum): ...@@ -36,13 +36,19 @@ class XDPExitCode(Enum):
REDIRECT = 4 REDIRECT = 4
class XDPFlags(Enum):
SKB_MODE = 2
DRV_MODE = 4 # XDP done by the network driver
class XDRFD(DatagramProtocol): class XDRFD(DatagramProtocol):
"""just implement enough of the NETLINK protocol to attach programs""" """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.ifindex = ifindex
self.fd = fd self.fd = fd
self.seq = None self.seq = None
self.flags = flags
self.future = future self.future = future
def connection_made(self, transport): def connection_made(self, transport):
...@@ -73,7 +79,7 @@ class XDRFD(DatagramProtocol): ...@@ -73,7 +79,7 @@ class XDRFD(DatagramProtocol):
self.fd, self.fd,
8, 8,
3, # IFLA_XDP_FLAGS, 3, # IFLA_XDP_FLAGS,
2) self.flags.value)
transport.sendto(p, (0, 0)) transport.sendto(p, (0, 0))
def datagram_received(self, data, addr): def datagram_received(self, data, addr):
...@@ -82,12 +88,15 @@ class XDRFD(DatagramProtocol): ...@@ -82,12 +88,15 @@ class XDRFD(DatagramProtocol):
ln, type, flags, seq, pid = unpack("IHHII", data[pos : pos+16]) ln, type, flags, seq, pid = unpack("IHHII", data[pos : pos+16])
if type == 3: # DONE if type == 3: # DONE
self.future.set_result(0) self.future.set_result(0)
return
elif type == 2: # ERROR elif type == 2: # ERROR
errno, *args = unpack("iIHHII", data[pos+16 : pos+36]) errno, *args = unpack("iIHHII", data[pos+16 : pos+36])
if errno != 0: 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 if flags & 2 == 0: # not a multipart message
self.future.set_result(0) self.future.set_result(0)
return
pos += ln pos += ln
...@@ -149,32 +158,40 @@ class XDP(EBPF): ...@@ -149,32 +158,40 @@ class XDP(EBPF):
self.packetSize = PacketSize(self) self.packetSize = PacketSize(self)
async def _netlink(self, ifindex, fd): async def _netlink(self, ifindex, fd, flags):
future = Future() future = Future()
transport, proto = await get_event_loop().create_datagram_endpoint( 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) family=AF_NETLINK, proto=NETLINK_ROUTE)
await future try:
transport.get_extra_info("socket").close() 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`""" """attach this program to a `network`"""
ifindex = if_nametoindex(network) ifindex = if_nametoindex(network)
fd, _ = self.load(log_level=1) 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`""" """attach this program from a `network`"""
ifindex = if_nametoindex(network) ifindex = if_nametoindex(network)
await self._netlink(ifindex, -1) await self._netlink(ifindex, -1)
@asynccontextmanager @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) ifindex = if_nametoindex(network)
fd, _ = self.load(log_level=1) fd, _ = self.load(log_level=1)
await self._netlink(ifindex, fd) try:
os.close(fd) await self._netlink(ifindex, fd, flags)
finally:
os.close(fd)
try: try:
yield yield
finally: finally:
await self._netlink(ifindex, -1) await self._netlink(ifindex, -1, flags)
from asyncio import get_event_loop, sleep from asyncio import get_event_loop, sleep
from ebpfcat.hashmap import HashMap from ebpfcat.hashmap import HashMap
from ebpfcat.xdp import XDP, XDPExitCode from ebpfcat.xdp import XDP, XDPExitCode, XDPFlags
class Count(XDP): class Count(XDP):
license = "GPL" license = "GPL"
...@@ -15,11 +15,10 @@ class Count(XDP): ...@@ -15,11 +15,10 @@ class Count(XDP):
async def main(): async def main():
c = Count() c = Count()
await c.attach("eth0") async with c.run("eth0", XDPFlags.DRV_MODE):
for i in range(10):
for i in range(100): await sleep(0.1)
await sleep(0.1) print(c.count)
print(c.count)
if __name__ == "__main__": 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