Skip to content

Commit

Permalink
Add queue and stack
Browse files Browse the repository at this point in the history
  • Loading branch information
parttimenerd committed Aug 30, 2024
1 parent 78aa80d commit c643276
Show file tree
Hide file tree
Showing 4 changed files with 262 additions and 0 deletions.
25 changes: 25 additions & 0 deletions bpf/src/main/java/me/bechberger/ebpf/bpf/map/BPFQueue.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package me.bechberger.ebpf.bpf.map;

import me.bechberger.ebpf.annotations.bpf.BPFMapClass;
import me.bechberger.ebpf.type.BPFType;

/**
* A FIFO queue
*/
@BPFMapClass(
cTemplate = """
struct {
__uint (type, BPF_MAP_TYPE_QUEUE);
__type (value, $c1);
__uint (max_entries, $maxEntries);
} $field SEC(".maps");
""",
javaTemplate = """
new $class<>($fd, $b1)
""")
public class BPFQueue<V> extends BPFQueueAndStack<V> {

public BPFQueue(FileDescriptor fd, BPFType<V> valueType) {
super(fd, MapTypeId.QUEUE, valueType);
}
}
112 changes: 112 additions & 0 deletions bpf/src/main/java/me/bechberger/ebpf/bpf/map/BPFQueueAndStack.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package me.bechberger.ebpf.bpf.map;

import me.bechberger.ebpf.annotations.bpf.*;
import me.bechberger.ebpf.bpf.BPFError;
import me.bechberger.ebpf.bpf.raw.Lib;
import me.bechberger.ebpf.bpf.raw.Lib_2;
import me.bechberger.ebpf.type.BPFType;
import me.bechberger.ebpf.type.Ptr;
import org.jetbrains.annotations.Nullable;

import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
import java.util.Objects;

/**
* "BPF_MAP_TYPE_QUEUE provides FIFO storage and BPF_MAP_TYPE_STACK provides
* LIFO storage for BPF programs. These maps support peek, pop and push operations
* that are exposed to BPF programs through the respective helpers."
* <a href="https://docs.kernel.org/next/bpf/map_queue_stack.html">docs.kernel.org</a>
*/
public abstract class BPFQueueAndStack<V> extends BPFMap {

private final BPFType<V> valueType;

/**
* Create a new map
*
* @param mapType type of the map
* @param fd file descriptor of the map
* @throws BPFMapTypeMismatch if the type of the map does not match the expected type
*/
BPFQueueAndStack(FileDescriptor fd, MapTypeId mapType, BPFType<V> valueType) {
super(mapType, fd);
if (mapType != MapTypeId.STACK && mapType != MapTypeId.QUEUE) {
throw new BPFError("Map type must be either STACK or QUEUE, but got " + mapType);
}
this.valueType = valueType;
}

/**
* Push a value onto the stack or the back of the queue
* <p>Usage in ebpf:</p>
* Update the value in the map with the given key
* @param value value if pointery, otherwise an lvalue (like a variable)
* @return success?
* @see me.bechberger.ebpf.runtime.helpers.BPFHelpers#bpf_map_update_elem(Ptr, Ptr, Ptr, long)
*/
@BuiltinBPFFunction("!bpf_map_push_elem(&$this, $pointery$arg1, BPF_ANY)")
public boolean push(V value) {
try (var arena = Arena.ofConfined()) {
var valueSegment = valueType.allocate(arena, Objects.requireNonNull(value));
var ret = Lib.bpf_map_update_elem(fd.fd(), MemorySegment.NULL, valueSegment, Lib_2.BPF_ANY());
return ret == 0;
}
}

// long bpf_map_peek_elem(struct bpf_map *map, void *value)
/**
* Peek at the value at the top of the stack or the front of the queue
*/
@BPFFunctionAlternative("bpf_peek")
public @Nullable V peek() {
try (var arena = Arena.ofConfined()) {
var valueSegment = valueType.allocate(arena);
var ret = Lib.bpf_map_lookup_elem(fd.fd(), MemorySegment.NULL, valueSegment);
if (ret != 0) {
return null;
}
return valueType.parseMemory(valueSegment);
}
}

/**
* Get the value from the top of the stack or the front of the queue, in eBPF
*
* @param value value if pointery, otherwise an lvalue (like a variable)
* @return true if the value was peeked, false on error
* @see me.bechberger.ebpf.runtime.helpers.BPFHelpers#bpf_map_peek_elem(Ptr, Ptr)
*/
@BuiltinBPFFunction("!bpf_map_peek_elem(&$this, $pointery$arg1)")
public boolean bpf_peek(V value) {
throw new MethodIsBPFRelatedFunction();
}

/**
* Pop the value from the top of the stack or the front of the queue
*
* @return the value, or null if the stack/queue is empty
*/
public @Nullable V pop() {
try (var arena = Arena.ofConfined()) {
var valueSegment = valueType.allocate(arena);
var ret = Lib.bpf_map_lookup_and_delete_elem(fd.fd(), MemorySegment.NULL, valueSegment);
if (ret != 0) {
return null;
}
return valueType.parseMemory(valueSegment);
}
}

/**
* Pop the value from the top of the stack or the front of the queue, in eBPF
*
* @param value value if pointery, otherwise an lvalue (like a variable)
* @return true if the value was popped, false on error
* @see me.bechberger.ebpf.runtime.helpers.BPFHelpers#bpf_map_pop_elem(Ptr, Ptr)
*/
@BuiltinBPFFunction("!bpf_map_pop_elem(&$this, $pointery$arg1)")
public boolean bpf_pop(V value) {
throw new MethodIsBPFRelatedFunction();
}
}
25 changes: 25 additions & 0 deletions bpf/src/main/java/me/bechberger/ebpf/bpf/map/BPFStack.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package me.bechberger.ebpf.bpf.map;

import me.bechberger.ebpf.annotations.bpf.BPFMapClass;
import me.bechberger.ebpf.type.BPFType;

/**
* A LIFO stack
*/
@BPFMapClass(
cTemplate = """
struct {
__uint (type, BPF_MAP_TYPE_STACK);
__type (value, $c1);
__uint (max_entries, $maxEntries);
} $field SEC(".maps");
""",
javaTemplate = """
new $class<>($fd, $b1)
""")
public class BPFStack<V> extends BPFQueueAndStack<V> {

public BPFStack(FileDescriptor fd, BPFType<V> valueType) {
super(fd, MapTypeId.STACK, valueType);
}
}
100 changes: 100 additions & 0 deletions bpf/src/test/java/me/bechberger/ebpf/bpf/QueueMapTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package me.bechberger.ebpf.bpf;

import me.bechberger.ebpf.annotations.bpf.BPF;
import me.bechberger.ebpf.annotations.bpf.BPFMapDefinition;
import me.bechberger.ebpf.bpf.map.BPFQueue;
import me.bechberger.ebpf.bpf.map.BPFRingBuffer;
import me.bechberger.ebpf.runtime.OpenDefinitions.open_how;
import me.bechberger.ebpf.runtime.interfaces.SystemCallHooks;
import me.bechberger.ebpf.type.Ptr;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class QueueMapTest {

@BPF(license = "GPL")
public static abstract class KernelLandTestProgram extends BPFProgram implements SystemCallHooks {

@BPFMapDefinition(maxEntries = 2)
BPFQueue<Integer> queue;
final GlobalVariable<Boolean> alreadyPut = new GlobalVariable<>(false);
final GlobalVariable<Boolean> worked = new GlobalVariable<>(false);

@Override
public void enterOpenat2(int dfd, String filename, Ptr<open_how> how) {
if (!alreadyPut.get()) {
var value = 1;
queue.push(value);
value = 2;
queue.push(value);
worked.set(false);
// expect that peek is 1 and pop is 1, then peek is 2 and pop is 2
queue.bpf_peek(value);
if (value != 1) {
return;
}
queue.bpf_pop(value);
if (value != 1) {
return;
}
queue.bpf_peek(value);
if (value != 2) {
return;
}
queue.bpf_pop(value);
if (value != 2) {
return;
}
if (queue.bpf_peek(value)) {
return;
}
if (queue.bpf_pop(value)) {
return;
}
worked.set(true);
alreadyPut.set(true);
}
}
}

@Test
public void testKernelLand() throws InterruptedException {
try (var program = BPFProgram.load(KernelLandTestProgram.class)) {
program.autoAttachPrograms();
TestUtil.triggerOpenAt();
while (!program.alreadyPut.get()) {
Thread.sleep(100);
}
assertTrue(program.worked.get());
}
}

@BPF
public static abstract class UserLandTestProgram extends BPFProgram {
@BPFMapDefinition(maxEntries = 2)
BPFQueue<Integer> queue;
}

@Test
public void testUserLand() {
try (var program = BPFProgram.load(UserLandTestProgram.class)) {
var queue = program.queue;
queue.push(1);
queue.push(2);
var value = queue.peek();
assertTrue(value != null && value == 1);
value = queue.pop();
assertTrue(value != null && value == 1);
value = queue.peek();
assertTrue(value != null && value == 2);
value = queue.pop();
assertTrue(value != null && value == 2);
value = queue.peek();
assertNull(value);
value = queue.pop();
assertNull(value);
}
}
}

0 comments on commit c643276

Please sign in to comment.