Skip to content

Commit

Permalink
UM API to write into a ring buffer map. (#3689)
Browse files Browse the repository at this point in the history
* UM API to write into a ring buffer map.

* PR Feedback.

* PR feedback.

* PR comments.
  • Loading branch information
shankarseal authored Jul 17, 2024
1 parent 7ee9edf commit 6b75459
Show file tree
Hide file tree
Showing 8 changed files with 218 additions and 13 deletions.
3 changes: 2 additions & 1 deletion ebpfapi/Source.def
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ EXPORTS
ebpf_program_attach
ebpf_program_attach_by_fd
ebpf_program_query_info
ebpf_ring_buffer_map_write
ebpf_store_delete_program_information
ebpf_store_delete_section_information
ebpf_store_update_program_information_array
Expand All @@ -139,4 +140,4 @@ EXPORTS
libbpf_prog_type_by_name
libbpf_strerror
ring_buffer__new
ring_buffer__free
ring_buffer__free
14 changes: 14 additions & 0 deletions include/ebpf_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,20 @@ extern "C"
_Must_inspect_result_ ebpf_result_t
ebpf_program_test_run(fd_t program_fd, _Inout_ ebpf_test_run_options_t* options) EBPF_NO_EXCEPT;

/**
* @brief Write data into the ring buffer map.
*
* @param [in] ring_buffer_map_fd ring buffer map file descriptor.
* @param [in] data Pointer to data to be written.
* @param [in] data_length Length of data to be written.
* @retval EPBF_SUCCESS Successfully wrote record into ring buffer.
* @retval EBPF_OUT_OF_SPACE Unable to output to ring buffer due to inadequate space.
* @retval EBPF_NO_MEMORY Out of memory.
*/
_Must_inspect_result_ ebpf_result_t
ebpf_ring_buffer_map_write(
fd_t ring_buffer_map_fd, _In_reads_bytes_(data_length) const void* data, size_t data_length) EBPF_NO_EXCEPT;

#ifdef __cplusplus
}
#endif
93 changes: 84 additions & 9 deletions libs/api/ebpf_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4332,7 +4332,7 @@ CATCH_NO_MEMORY_EBPF_RESULT

_Must_inspect_result_ ebpf_result_t
ebpf_ring_buffer_map_subscribe(
fd_t ring_buffer_map_fd,
fd_t map_fd,
_Inout_opt_ void* sample_callback_context,
ring_buffer_sample_fn sample_callback,
_Outptr_ ring_buffer_subscription_t** subscription) NO_EXCEPT_TRY
Expand All @@ -4346,22 +4346,41 @@ ebpf_ring_buffer_map_subscribe(

ebpf_result_t result = EBPF_SUCCESS;

*subscription = nullptr;
uint32_t key_size = 0;
uint32_t value_size = 0;
uint32_t max_entries = 0;
uint32_t type;

ebpf_ring_buffer_subscription_ptr local_subscription = std::make_unique<ebpf_ring_buffer_subscription_t>();
ebpf_handle_t map_handle = _get_handle_from_file_descriptor(map_fd);
if (map_handle == ebpf_handle_invalid) {
result = EBPF_INVALID_FD;
EBPF_RETURN_RESULT(result);
}

local_subscription->ring_buffer_map_handle = ebpf_handle_invalid;
result = _get_map_descriptor_properties(map_handle, &type, &key_size, &value_size, &max_entries);
if (result != EBPF_SUCCESS) {
EBPF_RETURN_RESULT(result);
}

// Get the handle to ring buffer map.
ebpf_handle_t ring_buffer_map_handle = _get_handle_from_file_descriptor(ring_buffer_map_fd);
if (ring_buffer_map_handle == ebpf_handle_invalid) {
result = EBPF_INVALID_FD;
if (type != BPF_MAP_TYPE_RINGBUF) {
result = EBPF_INVALID_ARGUMENT;
EBPF_LOG_MESSAGE_ERROR(
EBPF_TRACELOG_LEVEL_ERROR,
EBPF_TRACELOG_KEYWORD_API,
"ring_buffer__new API is called on a map that is not of the ring buffer type.",
result);
EBPF_RETURN_RESULT(result);
}

*subscription = nullptr;

ebpf_ring_buffer_subscription_ptr local_subscription = std::make_unique<ebpf_ring_buffer_subscription_t>();

local_subscription->ring_buffer_map_handle = ebpf_handle_invalid;

if (!Platform::DuplicateHandle(
reinterpret_cast<ebpf_handle_t>(GetCurrentProcess()),
ring_buffer_map_handle,
map_handle,
reinterpret_cast<ebpf_handle_t>(GetCurrentProcess()),
&local_subscription->ring_buffer_map_handle,
0,
Expand Down Expand Up @@ -4429,6 +4448,62 @@ ebpf_ring_buffer_map_subscribe(
}
CATCH_NO_MEMORY_EBPF_RESULT

_Must_inspect_result_ ebpf_result_t
ebpf_ring_buffer_map_write(fd_t map_fd, _In_reads_bytes_(data_length) const void* data, size_t data_length)
NO_EXCEPT_TRY
{
EBPF_LOG_ENTRY();
ebpf_result_t result = EBPF_SUCCESS;
ebpf_handle_t map_handle = ebpf_handle_invalid;
ebpf_protocol_buffer_t request_buffer;
ebpf_operation_ring_buffer_map_write_data_request_t* request;

if (!data || !data_length) {
return EBPF_INVALID_ARGUMENT;
}

try {
uint32_t key_size = 0;
uint32_t value_size = 0;
uint32_t max_entries = 0;
uint32_t type;

map_handle = _get_handle_from_file_descriptor(map_fd);
if (map_handle == ebpf_handle_invalid) {
result = EBPF_INVALID_FD;
EBPF_RETURN_RESULT(result);
}

result = _get_map_descriptor_properties(map_handle, &type, &key_size, &value_size, &max_entries);
if (result != EBPF_SUCCESS) {
EBPF_RETURN_RESULT(result);
}

if (type != BPF_MAP_TYPE_RINGBUF) {
result = EBPF_INVALID_ARGUMENT;
EBPF_LOG_MESSAGE_ERROR(
EBPF_TRACELOG_LEVEL_ERROR,
EBPF_TRACELOG_KEYWORD_API,
"ebpf_ring_buffer_map_write API is called on a map that is not of the ring buffer type.",
result);
EBPF_RETURN_RESULT(result);
}

request_buffer.resize(EBPF_OFFSET_OF(ebpf_operation_ring_buffer_map_write_data_request_t, data) + data_length);
request = reinterpret_cast<_ebpf_operation_ring_buffer_map_write_data_request*>(request_buffer.data());
request->header.length = static_cast<uint16_t>(request_buffer.size());
request->header.id = ebpf_operation_id_t::EBPF_OPERATION_RING_BUFFER_MAP_WRITE_DATA;
request->map_handle = (uint64_t)map_handle;
std::copy((uint8_t*)data, (uint8_t*)data + data_length, request->data);

result = win32_error_code_to_ebpf_result(invoke_ioctl(request_buffer));
} catch (const std::bad_alloc&) {
EBPF_RETURN_RESULT(EBPF_NO_MEMORY);
}
EBPF_RETURN_RESULT(result);
}
CATCH_NO_MEMORY_EBPF_RESULT

bool
ebpf_ring_buffer_map_unsubscribe(_In_ _Post_invalid_ ring_buffer_subscription_t* subscription) NO_EXCEPT_TRY
{
Expand Down
6 changes: 6 additions & 0 deletions libs/api/libbpf_map.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,11 @@ ring_buffer__new(int map_fd, ring_buffer_sample_fn sample_cb, void* ctx, const s
ebpf_result result = EBPF_SUCCESS;
ring_buffer_t* local_ring_buffer = nullptr;

if (sample_cb == nullptr) {
result = EBPF_INVALID_ARGUMENT;
goto Exit;
}

try {
std::unique_ptr<ring_buffer_t> ring_buffer = std::make_unique<ring_buffer_t>();
ring_buffer_subscription_t* subscription = nullptr;
Expand All @@ -378,6 +383,7 @@ ring_buffer__new(int map_fd, ring_buffer_sample_fn sample_cb, void* ctx, const s
}
Exit:
if (result != EBPF_SUCCESS) {
errno = libbpf_result_err(result);
EBPF_LOG_FUNCTION_ERROR(result);
}
EBPF_RETURN_POINTER(ring_buffer_t*, local_ring_buffer);
Expand Down
43 changes: 43 additions & 0 deletions libs/execution_context/ebpf_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -2021,6 +2021,11 @@ _ebpf_core_protocol_ring_buffer_map_query_buffer(

if (ebpf_map_get_definition(map)->type != BPF_MAP_TYPE_RINGBUF) {
result = EBPF_INVALID_ARGUMENT;
EBPF_LOG_MESSAGE_ERROR(
EBPF_TRACELOG_LEVEL_ERROR,
EBPF_TRACELOG_KEYWORD_CORE,
"query buffer operation called on a map that is not of the ring buffer type.",
result);
goto Exit;
}

Expand Down Expand Up @@ -2053,6 +2058,11 @@ _ebpf_core_protocol_ring_buffer_map_async_query(

if (ebpf_map_get_definition(map)->type != BPF_MAP_TYPE_RINGBUF) {
result = EBPF_INVALID_ARGUMENT;
EBPF_LOG_MESSAGE_ERROR(
EBPF_TRACELOG_LEVEL_ERROR,
EBPF_TRACELOG_KEYWORD_CORE,
"async query operation called on a map that is not of the ring buffer type.",
result);
goto Exit;
}

Expand All @@ -2073,6 +2083,38 @@ _ebpf_core_protocol_ring_buffer_map_async_query(
return result;
}

static ebpf_result_t
_ebpf_core_protocol_ring_buffer_map_write_data(_In_ const ebpf_operation_ring_buffer_map_write_data_request_t* request)
{
ebpf_map_t* map = NULL;
size_t data_length = 0;
ebpf_result_t result =
EBPF_OBJECT_REFERENCE_BY_HANDLE(request->map_handle, EBPF_OBJECT_MAP, (ebpf_core_object_t**)&map);
if (result != EBPF_SUCCESS) {
goto Exit;
}
if (ebpf_map_get_definition(map)->type != BPF_MAP_TYPE_RINGBUF) {
result = EBPF_INVALID_ARGUMENT;
EBPF_LOG_MESSAGE_ERROR(
EBPF_TRACELOG_LEVEL_ERROR,
EBPF_TRACELOG_KEYWORD_CORE,
"write data operation called on a map that is not of the ring buffer type.",
result);
goto Exit;
}
result = ebpf_safe_size_t_subtract(
request->header.length,
EBPF_OFFSET_OF(ebpf_operation_ring_buffer_map_write_data_request_t, data),
&data_length);
if (result != EBPF_SUCCESS) {
goto Exit;
}
result = ebpf_ring_buffer_map_output(map, (uint8_t*)request->data, data_length);
Exit:
EBPF_OBJECT_RELEASE_REFERENCE((ebpf_core_object_t*)map);
EBPF_RETURN_RESULT(result);
}

static void*
_ebpf_core_map_find_element(ebpf_map_t* map, const uint8_t* key)
{
Expand Down Expand Up @@ -2606,6 +2648,7 @@ static ebpf_protocol_handler_t _ebpf_protocol_handlers[] = {
DECLARE_PROTOCOL_HANDLER_FIXED_REQUEST_NO_REPLY(bind_map, PROTOCOL_ALL_MODES),
DECLARE_PROTOCOL_HANDLER_FIXED_REQUEST_FIXED_REPLY(ring_buffer_map_query_buffer, PROTOCOL_ALL_MODES),
DECLARE_PROTOCOL_HANDLER_FIXED_REQUEST_FIXED_REPLY_ASYNC(ring_buffer_map_async_query, PROTOCOL_ALL_MODES),
DECLARE_PROTOCOL_HANDLER_VARIABLE_REQUEST_NO_REPLY(ring_buffer_map_write_data, data, PROTOCOL_ALL_MODES),
DECLARE_PROTOCOL_HANDLER_VARIABLE_REQUEST_FIXED_REPLY(load_native_module, data, PROTOCOL_NATIVE_MODE),
DECLARE_PROTOCOL_HANDLER_FIXED_REQUEST_VARIABLE_REPLY(load_native_programs, data, PROTOCOL_NATIVE_MODE),
DECLARE_PROTOCOL_HANDLER_VARIABLE_REQUEST_VARIABLE_REPLY_ASYNC(program_test_run, data, data, PROTOCOL_ALL_MODES),
Expand Down
8 changes: 8 additions & 0 deletions libs/execution_context/ebpf_protocol.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ typedef enum _ebpf_operation_id
EBPF_OPERATION_BIND_MAP,
EBPF_OPERATION_RING_BUFFER_MAP_QUERY_BUFFER,
EBPF_OPERATION_RING_BUFFER_MAP_ASYNC_QUERY,
EBPF_OPERATION_RING_BUFFER_MAP_WRITE_DATA,
EBPF_OPERATION_LOAD_NATIVE_MODULE,
EBPF_OPERATION_LOAD_NATIVE_PROGRAMS,
EBPF_OPERATION_PROGRAM_TEST_RUN,
Expand Down Expand Up @@ -390,6 +391,13 @@ typedef struct _ebpf_operation_ring_buffer_map_async_query_reply
ebpf_ring_buffer_map_async_query_result_t async_query_result;
} ebpf_operation_ring_buffer_map_async_query_reply_t;

typedef struct _ebpf_operation_ring_buffer_map_write_data_request
{
struct _ebpf_operation_header header;
ebpf_handle_t map_handle;
uint8_t data[1];
} ebpf_operation_ring_buffer_map_write_data_request_t;

typedef struct _ebpf_operation_load_native_module_request
{
struct _ebpf_operation_header header;
Expand Down
41 changes: 41 additions & 0 deletions tests/end_to_end/end_to_end.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -880,6 +880,46 @@ bindmonitor_tailcall_test(ebpf_execution_type_t execution_type)
bpf_object__close(unique_object.release());
}

void
negative_ring_buffer_test(ebpf_execution_type_t execution_type)
{
_test_helper_end_to_end test_helper;
test_helper.initialize();

const char* error_message = nullptr;
int result;
bpf_object_ptr unique_object;
fd_t program_fd;

program_info_provider_t sample_program_info;
REQUIRE(sample_program_info.initialize(EBPF_PROGRAM_TYPE_SAMPLE) == EBPF_SUCCESS);

const char* file_name = (execution_type == EBPF_EXECUTION_NATIVE ? "bpf_call_um.dll" : "bpf_call.o");

// Load eBPF program.
result =
ebpf_program_load(file_name, BPF_PROG_TYPE_UNSPEC, execution_type, &unique_object, &program_fd, &error_message);

if (error_message) {
printf("ebpf_program_load failed with %s\n", error_message);
ebpf_free((void*)error_message);
}
REQUIRE(result == 0);

fd_t map_fd = bpf_object__find_map_fd_by_name(unique_object.get(), "map");
REQUIRE(map_fd > 0);

// Calls to ring buffer APIs on this map (array_map) must fail.
REQUIRE(
ring_buffer__new(
map_fd, [](void*, void*, size_t) { return 0; }, nullptr, nullptr) == nullptr);
REQUIRE(libbpf_get_error(nullptr) == EINVAL);
uint8_t data = 0;
REQUIRE(ebpf_ring_buffer_map_write(map_fd, &data, sizeof(data)) == EBPF_INVALID_ARGUMENT);

bpf_object__close(unique_object.release());
}

void
bindmonitor_ring_buffer_test(ebpf_execution_type_t execution_type)
{
Expand Down Expand Up @@ -1016,6 +1056,7 @@ DECLARE_ALL_TEST_CASES("divide_by_zero", "[end_to_end]", divide_by_zero_test_um)
DECLARE_ALL_TEST_CASES("bindmonitor", "[end_to_end]", bindmonitor_test);
DECLARE_ALL_TEST_CASES("bindmonitor-tailcall", "[end_to_end]", bindmonitor_tailcall_test);
DECLARE_ALL_TEST_CASES("bindmonitor-ringbuf", "[end_to_end]", bindmonitor_ring_buffer_test);
DECLARE_ALL_TEST_CASES("negative_ring_buffer_test", "[end_to_end]", negative_ring_buffer_test);
DECLARE_ALL_TEST_CASES("utility-helpers", "[end_to_end]", _utility_helper_functions_test);
DECLARE_ALL_TEST_CASES("map", "[end_to_end]", map_test);
DECLARE_ALL_TEST_CASES("bad_map_name", "[end_to_end]", bad_map_name_um);
Expand Down
23 changes: 20 additions & 3 deletions tests/libs/common/common_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -189,17 +189,30 @@ void
ring_buffer_api_test_helper(
fd_t ring_buffer_map, std::vector<std::vector<char>>& expected_records, std::function<void(int)> generate_event)
{
std::vector<std::vector<char>> records = expected_records;

// Add a couple of interleaved messages to the records vector.
std::string message = "Interleaved message #1";
std::vector<char> record(message.begin(), message.end());
record.push_back('\0');
records.push_back(record);
record[record.size() - 2] = '2';
records.push_back(record);

// Ring buffer event callback context.
std::unique_ptr<ring_buffer_test_event_context_t> context = std::make_unique<ring_buffer_test_event_context_t>();
context->test_event_count = RING_BUFFER_TEST_EVENT_COUNT;
context->test_event_count = RING_BUFFER_TEST_EVENT_COUNT + 2;

context->records = &expected_records;
context->records = &records;

// Generate events prior to subscribing for ring buffer events.
for (int i = 0; i < RING_BUFFER_TEST_EVENT_COUNT / 2; i++) {
generate_event(i);
}

// Write an interleaved message to the ring buffer map.
REQUIRE(ebpf_ring_buffer_map_write(ring_buffer_map, message.c_str(), message.length() + 1) == EBPF_SUCCESS);

// Get the std::future from the promise field in ring buffer event context, which should be in ready state
// once notifications for all events are received.
auto ring_buffer_event_callback = context->ring_buffer_event_promise.get_future();
Expand All @@ -215,10 +228,14 @@ ring_buffer_api_test_helper(
generate_event(i);
}

// Write another interleaved message to the ring buffer map.
message[message.length() - 1] = '2';
REQUIRE(ebpf_ring_buffer_map_write(ring_buffer_map, message.c_str(), message.length() + 1) == EBPF_SUCCESS);

// Wait for event handler getting notifications for all RING_BUFFER_TEST_EVENT_COUNT events.
REQUIRE(ring_buffer_event_callback.wait_for(1s) == std::future_status::ready);

REQUIRE(context->matched_entry_count == RING_BUFFER_TEST_EVENT_COUNT);
REQUIRE(context->matched_entry_count == context->test_event_count);

// Mark the event context as canceled, such that the event callback stops processing events.
context->canceled = true;
Expand Down

0 comments on commit 6b75459

Please sign in to comment.