From 2422a15c80815718ce6c3e1528822676dcc65407 Mon Sep 17 00:00:00 2001 From: Martin Teichmann <martin.teichmann@xfel.eu> Date: Mon, 21 Dec 2020 02:36:22 +0000 Subject: [PATCH] the start of ebpf --- __init__.py | 0 bpf.py | 45 +++++++++++++++++ ebpf.py | 139 +++++++++++++++++++++++++++++++++++++++++++++++++++ ebpf_test.py | 79 +++++++++++++++++++++++++++++ 4 files changed, 263 insertions(+) create mode 100644 __init__.py create mode 100644 bpf.py create mode 100644 ebpf.py create mode 100644 ebpf_test.py diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bpf.py b/bpf.py new file mode 100644 index 0000000..4d1cf1a --- /dev/null +++ b/bpf.py @@ -0,0 +1,45 @@ +from ctypes import CDLL, c_int, get_errno, cast, c_void_p, create_string_buffer +from struct import pack + +from os import strerror + +libc = CDLL("libc.so.6", use_errno=True) + +def addrof(ptr): + return cast(ptr, c_void_p).value + +def bpf(cmd, fmt, *args): + attr = pack(fmt, *args) + ret = libc.syscall(386, c_int(cmd), attr, len(attr)) + if ret == -1: + raise OSError(get_errno(), strerror(get_errno())) + return ret + +def create_map(map_type, key_size, value_size, max_entries): + return bpf(0, "IIII", map_type, key_size, value_size, max_entries) + +def lookup_elem(fd, key, size): + value = create_string_buffer(size) + bpf(1, "IQQQ", fd, addrof(key), addrof(value), 0) + return value.value + +def update_elem(fd, key, value, flags): + return bpf(2, "IQQQ", fd, addrof(key), addrof(value), flags) + +def prog_load(prog_type, insns, license, + log_level=0, log_size=4096, kern_version=0): + if log_level == 0: + log_buf = 0 + log_size = 0 + else: + log_buf = addrof(create_string_buffer(log_size)) + license = license.encode("utf8") + bpf(5, "IIQQIIQI", prog_type, int(len(insns) // 8), addrof(license), + log_level, log_size, log_buf, kern_version) + if log_level != 0: + return log_buf.value + +if __name__ == "__main__": + fd = create_map(1, 4, 4, 10) + update_elem(fd, b"asdf", b"ckde", 0) + print(lookup_elem(fd, b"asdf", 4)) diff --git a/ebpf.py b/ebpf.py new file mode 100644 index 0000000..adda0fa --- /dev/null +++ b/ebpf.py @@ -0,0 +1,139 @@ +from collections import namedtuple +from struct import pack + +from .bpf import prog_load + +Instruction = namedtuple("Instruction", + ["opcode", "dst", "src", "off", "imm"]) + +def augassign(opcode): + def ret(self, value): + if isinstance(value, int): + return Instruction(opcode + 3 * self.long, self.no, 0, 0, value) + elif isinstance(value, Register) and self.long == value.long: + return Instruction(opcode + 8 + 3 * self.long, + self.no, value.no, 0, 0) + else: + return NotImplemented + return ret + + +class Sum: + def __init__(self, no, offset): + self.no = no + self.offset = offset + + def __add__(self, value): + if isinstance(value, int): + return Sum(self.no, self.offset + value) + else: + return NotImplemented + + __radd__ = __add__ + + def __sub__(self, value): + if isinstance(value, int): + return Sum(self.no, self.offset - value) + else: + return NotImplemented + + +class Register: + offset = 0 + + def __init__(self, no, ebpf, long): + self.no = no + self.ebpf = ebpf + self.long = long + + __iadd__ = augassign(4) + __isub__ = augassign(0x14) + __imul__ = augassign(0x24) + __itruediv__ = augassign(0x34) + __ior__ = augassign(0x44) + __iand__ = augassign(0x54) + __ilshift__ = augassign(0x64) + __irshift__ = augassign(0x74) + __imod__ = augassign(0x94) + __ixor__ = augassign(0xa4) + + def __add__(self, value): + if isinstance(value, int) and self.long: + return Sum(self.no, value) + else: + return NotImplemented + + __radd__ = __add__ + + def __sub__(self, value): + if isinstance(value, int) and self.long: + return Sum(self.no, -value) + else: + return NotImplemented + + +class Memory: + def __init__(self, ebpf, bits): + self.ebpf = ebpf + self.bits = bits + + def __setitem__(self, addr, value): + if isinstance(value, int): + self.ebpf.append(0x62 + self.bits, addr.no, 0, addr.offset, value) + elif isinstance(value, Register): + self.ebpf.append(0x63 + self.bits, addr.no, value.no, + addr.offset, 0) + else: + raise RuntimeError("cannot compile") + + def __getitem__(self, addr): + pass + + +class RegisterDesc: + def __init__(self, no, long): + self.no = no + self.long = long + + def __get__(self, instance, owner=None): + if instance is None: + return self + else: + return Register(self.no, instance, self.long) + + def __set__(self, instance, value): + if isinstance(value, int): + instance.append(0xb4 + 3 * self.long, self.no, 0, 0, value) + elif isinstance(value, Register) and self.long == value.long: + instance.append(0xbc + 3 * self.long, self.no, value.no, 0, 0) + elif isinstance(value, Instruction): + instance.opcodes.append(value) + else: + raise RuntimeError("cannot compile") + + +class EBPF: + def __init__(self, prog_type=0, license="", kern_version=0): + self.opcodes = [] + self.prog_type = prog_type + self.license = license + self.kern_version = kern_version + + def append(self, opcode, dst, src, off, imm): + self.opcodes.append(Instruction(opcode, dst, src, off, imm)) + + def assemble(self): + return b"".join( + pack("<BBHI", i.opcode, i.dst << 4 | i.src, i.off, i.imm) + for i in self.opcodes) + + def load(self, log_level=0, log_size=4096): + return prog_load(self.prog_type, self.assemble(), self.license, + log_level, log_size, self.kern_version) + + +for i in range(10): + setattr(EBPF, f"r{i}", RegisterDesc(i, True)) + +for i in range(10): + setattr(EBPF, f"s{i}", RegisterDesc(i, False)) diff --git a/ebpf_test.py b/ebpf_test.py new file mode 100644 index 0000000..7b3ff93 --- /dev/null +++ b/ebpf_test.py @@ -0,0 +1,79 @@ +from unittest import TestCase, main + +from .ebpf import EBPF, Instruction + + +class Tests(TestCase): + def test_assemble(self): + e = EBPF() + e.append(0x24, 3, 4, 0x2c3d, 0x2d3e4f5e) + self.assertEqual(e.assemble(), b"$4=,^O>-") + + def test_assign(self): + e = EBPF() + e.r5 = 7 + e.r6 = e.r3 + self.assertEqual(e.opcodes, + [Instruction(0xb7, 5, 0, 0, 7), + Instruction(0xbf, 6, 3, 0, 0)]) + + def test_short(self): + e = EBPF() + e.s3 = 7 + e.s4 = e.s1 + e.s2 += 3 + e.s5 += e.s6 + self.assertEqual(e.opcodes, + [Instruction(0xb4, 3, 0, 0, 7), + Instruction(0xbc, 4, 1, 0, 0), + Instruction(opcode=4, dst=2, src=0, off=0, imm=3), + Instruction(opcode=0xc, dst=5, src=6, off=0, imm=0)]) + + def test_augassign(self): + e = EBPF() + e.r5 += 7 + e.r3 += e.r6 + e.r4 -= 3 + e.r4 -= e.r7 + e.r4 *= 3 + e.r4 *= e.r7 + e.r4 /= 3 + e.r4 /= e.r7 + e.r4 |= 3 + e.r4 |= e.r7 + e.r4 &= 3 + e.r4 &= e.r7 + e.r4 <<= 3 + e.r4 <<= e.r7 + e.r4 >>= 3 + e.r4 >>= e.r7 + e.r4 %= 3 + e.r4 %= e.r7 + e.r4 ^= 3 + e.r4 ^= e.r7 + + self.assertEqual(e.opcodes, + [Instruction(opcode=7, dst=5, src=0, off=0, imm=7), + Instruction(opcode=15, dst=3, src=6, off=0, imm=0), + Instruction(opcode=0x17, dst=4, src=0, off=0, imm=3), + Instruction(opcode=0x1f, dst=4, src=7, off=0, imm=0), + Instruction(opcode=0x27, dst=4, src=0, off=0, imm=3), + Instruction(opcode=0x2f, dst=4, src=7, off=0, imm=0), + Instruction(opcode=0x37, dst=4, src=0, off=0, imm=3), + Instruction(opcode=0x3f, dst=4, src=7, off=0, imm=0), + Instruction(opcode=0x47, dst=4, src=0, off=0, imm=3), + Instruction(opcode=0x4f, dst=4, src=7, off=0, imm=0), + Instruction(opcode=0x57, dst=4, src=0, off=0, imm=3), + Instruction(opcode=0x5f, dst=4, src=7, off=0, imm=0), + Instruction(opcode=0x67, dst=4, src=0, off=0, imm=3), + Instruction(opcode=0x6f, dst=4, src=7, off=0, imm=0), + Instruction(opcode=0x77, dst=4, src=0, off=0, imm=3), + Instruction(opcode=0x7f, dst=4, src=7, off=0, imm=0), + Instruction(opcode=0x97, dst=4, src=0, off=0, imm=3), + Instruction(opcode=0x9f, dst=4, src=7, off=0, imm=0), + Instruction(opcode=0xa7, dst=4, src=0, off=0, imm=3), + Instruction(opcode=0xaf, dst=4, src=7, off=0, imm=0)]) + + +if __name__ == "__main__": + main() -- GitLab