diff --git a/ebpfcat/arraymap.py b/ebpfcat/arraymap.py index 05fadec4644753e0306480a241fc69a3a12b7334..288869d6fa32d4d977a3680e08ed7b76b238ef11 100644 --- a/ebpfcat/arraymap.py +++ b/ebpfcat/arraymap.py @@ -29,7 +29,7 @@ class ArrayGlobalVarDesc(MemoryDesc): def __init__(self, map, fmt): self.map = map self.fmt = fmt - self.fixed = fmt == "f" + self.fixed = fmt == "x" def fmt_addr(self, ebpf): return self.fmt, ebpf.__dict__[self.name] @@ -43,7 +43,7 @@ class ArrayGlobalVarDesc(MemoryDesc): if instance.ebpf.loaded: fmt, addr = self.fmt_addr(instance) data = instance.ebpf.__dict__[self.map.name].data - if fmt == "f": + if fmt == "x": return unpack_from("q", data, addr)[0] / Expression.FIXED_BASE else: ret = unpack_from(fmt, data, addr) @@ -57,7 +57,7 @@ class ArrayGlobalVarDesc(MemoryDesc): def __set__(self, instance, value): if instance.ebpf.loaded: fmt, addr = self.fmt_addr(instance) - if fmt == "f": + if fmt == "x": fmt = "q" value = int(value * Expression.FIXED_BASE) if not isinstance(value, tuple): @@ -87,7 +87,7 @@ class ArrayMap(Map): for prog in chain([ebpf], ebpf.subprograms): for k, v in prog.__class__.__dict__.items(): if isinstance(v, ArrayGlobalVarDesc): - collection.append((8 if v.fmt == "f" else calcsize(v.fmt), + collection.append((8 if v.fmt == "x" else calcsize(v.fmt), prog, k)) collection.sort(key=lambda t: t[0], reverse=True) position = 0 diff --git a/ebpfcat/ebpf.py b/ebpfcat/ebpf.py index 0750830d9b90e069099c75fdd956a368bc8555df..c21bd3e651f777cb0070913e150f7555fe40f8db 100644 --- a/ebpfcat/ebpf.py +++ b/ebpfcat/ebpf.py @@ -870,7 +870,7 @@ class Memory(Expression): bits_to_opcode = {32: Opcode.W, 16: Opcode.H, 8: Opcode.B, 64: Opcode.DW} fmt_to_opcode = {'I': Opcode.W, 'H': Opcode.H, 'B': Opcode.B, 'Q': Opcode.DW, 'i': Opcode.W, 'h': Opcode.H, 'b': Opcode.B, 'q': Opcode.DW, - 'A': Opcode.W, 'f': Opcode.DW} + 'A': Opcode.W, 'x': Opcode.DW} def __init__(self, ebpf, fmt, address): self.ebpf = ebpf @@ -922,7 +922,7 @@ class Memory(Expression): @property def fixed(self): - return isinstance(self.fmt, str) and self.fmt == "f" + return isinstance(self.fmt, str) and self.fmt == "x" def __invert__(self): if not isinstance(self.fmt, tuple) or self.fmt[1] != 1: @@ -992,11 +992,11 @@ class MemoryDesc: ebpf.append(Opcode.ST + bits, self.base_register, 0, addr, value) return - if self.fmt == "f" and not value.fixed: + if self.fmt == "x" and not value.fixed: value = value * Expression.FIXED_BASE - elif self.fmt != "f" and value.fixed: + elif self.fmt != "x" and value.fixed: value = value / Expression.FIXED_BASE - with value.calculate(None, isinstance(fmt, str) and fmt in 'qQf' + with value.calculate(None, isinstance(fmt, str) and fmt in 'qQx' ) as (src, _): ebpf.append(opcode + bits, self.base_register, src, addr, 0) @@ -1007,10 +1007,12 @@ class LocalVar(MemoryDesc): def __init__(self, fmt='I'): self.fmt = fmt - self.fixed = fmt == "f" + self.fixed = fmt == "x" def __set_name__(self, owner, name): - if isinstance(self.fmt, str): + if self.fmt == "x": + size = 8 + elif isinstance(self.fmt, str): size = calcsize(self.fmt) else: # this is to support bit addressing, mostly for testing size = 1 @@ -1041,7 +1043,7 @@ class MemoryMap: offset = 0 if isinstance(value, IAdd): value = value.value - if self.fmt == "f": + if self.fmt == "x": value = int(value * self.FIXED_BASE) if not isinstance(value, Expression): with self.ebpf.get_free_register(None) as src: @@ -1054,7 +1056,7 @@ class MemoryMap: elif isinstance(value, Expression): opcode = Opcode.STX else: - if self.fmt == "f": + if self.fmt == "x": value = int(value * self.FIXED_BASE) self.ebpf.append(Opcode.ST + Memory.fmt_to_opcode[self.fmt], dst, 0, offset, value) @@ -1236,13 +1238,13 @@ class EBPF: self.mh = MemoryMap(self, "h") self.mi = MemoryMap(self, "i") self.mq = MemoryMap(self, "q") - self.mf = MemoryMap(self, "f") + self.mx = MemoryMap(self, "x") self.r = RegisterArray(self, True, False) self.sr = RegisterArray(self, True, True) self.w = RegisterArray(self, False, False) self.sw = RegisterArray(self, False, True) - self.f = RegisterArray(self, True, True, True) + self.x = RegisterArray(self, True, True, True) self.owners = {1, 10} @@ -1367,7 +1369,7 @@ class EBPF: stmp = TemporaryDesc(None, "sr") wtmp = TemporaryDesc(None, "w") swtmp = TemporaryDesc(None, "sw") - ftmp = TemporaryDesc(None, "f") + xtmp = TemporaryDesc(None, "x") for i in range(11): @@ -1383,7 +1385,7 @@ for i in range(10): setattr(EBPF, f"sw{i}", RegisterDesc(i, "sw")) for i in range(10): - setattr(EBPF, f"f{i}", RegisterDesc(i, "f")) + setattr(EBPF, f"x{i}", RegisterDesc(i, "x")) class SubProgram: diff --git a/ebpfcat/ebpf.rst b/ebpfcat/ebpf.rst index 1f107f9b1f8e41a607374ba296fbd4859f9a4d4d..cfc68387f5ea54382d3599fc7ccd2f8a065f097e 100644 --- a/ebpfcat/ebpf.rst +++ b/ebpfcat/ebpf.rst @@ -68,9 +68,9 @@ 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:: - with self.some_variable > 6 as cond: + with self.some_variable > 6 as Else: do_someting - with cond.Else(): + with Else: do_something_else certainly an ``Else`` statement may be omitted if not needed. @@ -144,16 +144,33 @@ 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. -Otherwise variables may be declared in all sizes. Additionally, one can mark -which variables are supposed to be written from user space to the EBPF, -as opposed to just being read. The declaration is like so:: +Otherwise variables may be declared in all sizes. The declaration is like so:: class MyProgram(EBPF): array_map = ArrayMap() - a_read_var = array_map.globalVar("B") # one byte read-only variable - a_write_var = array_map.globalVar("i", write=True) # read-write integer - -the array map has methods to access the variables: - -.. autoclass:: ebpfcat.arraymap.ArrayMapAccess - :members: + 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. + +Fixed-point arithmetic +~~~~~~~~~~~~~~~~~~~~~~ + +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: + + class FixedPoint(EPBF): + array_map = ArrayMap() + fixed_var = array_map.globalVar("x") # declare a fixed-point variable + normal_var = array_map.globalVar("i") + + def program(self): + self.fixed_var = 3.5 # automatically converted to fixed + self.normal_var = self.fixed_var # automatically truncated + self.fixed_var = self.normal_var / 5 # keep decimals + self.fixed_var = self.normal_var // 5 # floor division diff --git a/ebpfcat/ebpf_test.py b/ebpfcat/ebpf_test.py index 0ffdac5d947384d2c6304752c651bc6fe41d158e..c61bb959b4ec94b3d99f0f7bca470aeeaa9bf53b 100644 --- a/ebpfcat/ebpf_test.py +++ b/ebpfcat/ebpf_test.py @@ -165,32 +165,32 @@ class Tests(TestCase): def test_fixed(self): e = EBPF() e.owners = {0, 1, 2, 3, 4, 5, 6} - e.f1 = e.r2 + 3 - e.f3 = e.r4 + 3.5 - e.f5 = e.f6 + 3 - e.r1 = e.r2 + e.f3 - e.f4 = e.f5 + e.f6 - e.r1 = 2 - e.f2 + e.x1 = e.r2 + 3 + e.x3 = e.r4 + 3.5 + e.x5 = e.x6 + 3 + e.r1 = e.r2 + e.x3 + e.x4 = e.x5 + e.x6 + e.r1 = 2 - e.x2 e.r3 = 3.4 - e.r4 - e.r5 = e.f6 % 4 - - e.f1 = e.r2 * 3 - e.f3 = e.r4 * 3.5 - e.f5 = e.f6 * 3 - e.r1 = e.r2 * e.f3 - e.f4 = e.f5 * e.f6 - - e.f1 = e.r2 / 3 - e.f3 = e.r4 / 3.5 - e.f5 = e.f6 / 3 - e.r1 = e.r2 / e.f3 - e.f4 = e.f5 / e.f6 - - e.f1 = e.r2 // 3 - e.f3 = e.r4 // 3.5 - e.f5 = e.f6 // 3 - e.r1 = e.r2 // e.f3 - e.f4 = e.f5 // e.f6 + e.r5 = e.x6 % 4 + + e.x1 = e.r2 * 3 + e.x3 = e.r4 * 3.5 + e.x5 = e.x6 * 3 + e.r1 = e.r2 * e.x3 + e.x4 = e.x5 * e.x6 + + e.x1 = e.r2 / 3 + e.x3 = e.r4 / 3.5 + e.x5 = e.x6 / 3 + e.r1 = e.r2 / e.x3 + e.x4 = e.x5 / e.x6 + + e.x1 = e.r2 // 3 + e.x3 = e.r4 // 3.5 + e.x5 = e.x6 // 3 + e.r1 = e.r2 // e.x3 + e.x4 = e.x5 // e.x6 self.maxDiff = None self.assertEqual(e.opcodes, [ @@ -278,14 +278,14 @@ class Tests(TestCase): b = LocalVar('H') c = LocalVar('i') d = LocalVar('Q') - lf = LocalVar('f') + lx = LocalVar('x') e = Local(ProgType.XDP, "GPL") e.a = 5 e.b = e.c >> 3 e.d = e.r1 - e.lf = 7 - e.b = e.f1 + e.lx = 7 + e.b = e.x1 self.assertEqual(e.opcodes, [ Instruction(opcode=O.B+O.ST, dst=10, src=0, off=-1, imm=5), @@ -293,7 +293,7 @@ class Tests(TestCase): Instruction(opcode=O.ARSH, dst=0, src=0, off=0, imm=3), Instruction(opcode=O.REG+O.STX, dst=10, src=0, off=-4, imm=0), Instruction(opcode=O.DW+O.STX, dst=10, src=1, off=-16, imm=0), - Instruction(opcode=O.DW+O.ST, dst=10, src=0, off=-20, imm=700000), + Instruction(opcode=O.DW+O.ST, dst=10, src=0, off=-24, imm=700000), Instruction(opcode=O.LONG+O.REG+O.MOV, dst=0, src=1, off=0, imm=0), Instruction(opcode=O.DIV, dst=0, src=0, off=0, imm=100000), Instruction(opcode=O.REG+O.STX, dst=10, src=0, off=-4, imm=0), @@ -504,13 +504,13 @@ class Tests(TestCase): e.r5 = 7 with Else: e.r7 = 8 - with e.f4 > 3: + with e.x4 > 3: pass - with 3 > e.f4: + with 3 > e.x4: pass with e.r4 > 3.5: pass - with e.f4 > e.f2: + with e.x4 > e.x2: pass self.assertEqual(e.opcodes, [ Instruction(opcode=0xb5, dst=2, src=0, off=2, imm=3), @@ -735,7 +735,7 @@ class Tests(TestCase): def test_absolute(self): e = EBPF() e.r7 = abs(e.r1) - e.f3 = abs(e.f1) + e.x3 = abs(e.x1) self.assertEqual(e.opcodes, [ Instruction(opcode=O.LONG+O.REG+O.MOV, dst=7, src=1, off=0, imm=0), Instruction(opcode=O.JSGE, dst=7, src=0, off=1, imm=0), @@ -850,10 +850,10 @@ class Tests(TestCase): e.r7 = e.tmp e.tmp = 2 e.r3 = e.tmp - with e.ftmp: - e.ftmp = 3 - e.r3 = e.ftmp - e.ftmp = e.r3 * 3.5 + with e.xtmp: + e.xtmp = 3 + e.r3 = e.xtmp + e.xtmp = e.r3 * 3.5 self.assertEqual(e.opcodes, [ Instruction(opcode=O.MOV+O.LONG, dst=0, src=0, off=0, imm=7), Instruction(opcode=O.MOV+O.LONG, dst=2, src=0, off=0, imm=3), @@ -927,7 +927,7 @@ class KernelTests(TestCase): class Sub(SubProgram): br = Global.map.globalVar() bw = Global.map.globalVar("h") - bf = Global.map.globalVar("f") + bf = Global.map.globalVar("x") def program(self): self.bw = 4 diff --git a/ebpfcat/hashmap.py b/ebpfcat/hashmap.py index 8e5e61c86ab792634dff95218c3a11c8ef34e3b6..126f0c7746d91b00218b0bfa19baf48c2e852428 100644 --- a/ebpfcat/hashmap.py +++ b/ebpfcat/hashmap.py @@ -28,7 +28,7 @@ class HashGlobalVar(Expression): self.count = count self.fmt = fmt self.signed = fmt.islower() - self.fixed = fmt == "f" + self.fixed = fmt == "x" @contextmanager def get_address(self, dst, long, force=False):