-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
78aa80d
commit c643276
Showing
4 changed files
with
262 additions
and
0 deletions.
There are no files selected for viewing
25 changes: 25 additions & 0 deletions
25
bpf/src/main/java/me/bechberger/ebpf/bpf/map/BPFQueue.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
112
bpf/src/main/java/me/bechberger/ebpf/bpf/map/BPFQueueAndStack.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
25
bpf/src/main/java/me/bechberger/ebpf/bpf/map/BPFStack.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
100
bpf/src/test/java/me/bechberger/ebpf/bpf/QueueMapTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
} |