* Add stress test for tail calls

* Added the code generated bindmonitor_mt_tailcall files

* Added the test case description to readme.md

* Update tests/stress/km/stress_tests_km.cpp

Co-authored-by: Dave Thaler <dthaler@microsoft.com>

* Update tests/stress/km/stress_tests_km.cpp

Co-authored-by: Dave Thaler <dthaler@microsoft.com>

* Addressed PR comments and Fixed merge conflicts with latest

* Fixed function name

* Regenerated the expected files, after the macro MAX TAIL CNT change done yesterday

* Update tests/stress/readme.md

Co-authored-by: Dave Thaler <dthaler@microsoft.com>

* Removed clean_up tail call

* Add bind in loop

* updated the readme

* Addressed PR comments

* Changed LOG_VERBOSE to LOG_ERROR

* Updated readme and addressed PR comment

---------

Co-authored-by: Dave Thaler <dthaler@microsoft.com>
This commit is contained in:
Sharmi 2023-06-06 22:21:47 -07:00 коммит произвёл GitHub
Родитель 8dead3834e
Коммит 8adab0de10
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
9 изменённых файлов: 13353 добавлений и 0 удалений

Просмотреть файл

@ -650,6 +650,18 @@ SPDX-License-Identifier: MIT
<Component Id="BINDMONITOR_RINGBUF_UM.PDB" DiskId="1" Guid="{FB8688DC-43FB-475A-ADB5-B669A97A435C}">
<File Id="BINDMONITOR_RINGBUF_UM.PDB" Name="bindmonitor_ringbuf_um.pdb" Source="$(var.SolutionDir)$(var.Platform)\$(var.Configuration)\bindmonitor_ringbuf_um.pdb" />
</Component>
<Component Id="BINDMONITOR_MT_TAILCALL.O" DiskId="1" Guid="{5AF0DDDA-205B-440D-9F2F-B3554A22F2B1}">
<File Id="BINDMONITOR_MT_TAILCALL.O" Name="bindmonitor_mt_tailcall.o" Source="$(var.SolutionDir)$(var.Platform)\$(var.Configuration)\bindmonitor_mt_tailcall.o" />
</Component>
<Component Id="BINDMONITOR_MT_TAILCALL.SYS" DiskId="1" Guid="{BAFF9117-CF1D-40DD-8B4A-5B72F181DB99}">
<File Id="BINDMONITOR_MT_TAILCALL.SYS" Name="bindmonitor_mt_tailcall.sys" Source="$(var.SolutionDir)$(var.Platform)\$(var.Configuration)\bindmonitor_mt_tailcall.sys" />
</Component>
<Component Id="BINDMONITOR_MT_TAILCALL_UM.DLL" DiskId="1" Guid="{72FEF59C-A7D8-4BB5-B11E-5A125073723E}">
<File Id="BINDMONITOR_MT_TAILCALL_UM.DLL" Name="bindmonitor_mt_tailcall_um.dll" Source="$(var.SolutionDir)$(var.Platform)\$(var.Configuration)\bindmonitor_mt_tailcall_um.dll" />
</Component>
<Component Id="BINDMONITOR_MT_TAILCALL_UM.PDB" DiskId="1" Guid="{70AD50CC-C376-4FD4-900E-1157F9A74A12}">
<File Id="BINDMONITOR_MT_TAILCALL_UM.PDB" Name="bindmonitor_mt_tailcall_um.pdb" Source="$(var.SolutionDir)$(var.Platform)\$(var.Configuration)\bindmonitor_mt_tailcall_um.pdb" />
</Component>
<Component Id="BINDMONITOR_TAILCALL.O" DiskId="1" Guid="{6A37AE43-179A-46BD-97D5-3D02B6807765}">
<File Id="BINDMONITOR_TAILCALL.O" Name="bindmonitor_tailcall.o" Source="$(var.SolutionDir)$(var.Platform)\$(var.Configuration)\bindmonitor_tailcall.o" />
</Component>

Просмотреть файл

@ -48,6 +48,10 @@ $source_directory="."
"bindmonitor_ringbuf_um.dll",
"bindmonitor_ringbuf_um.pdb",
"bindmonitor_ringbuf.sys",
"bindmonitor_mt_tailcall.o",
"bindmonitor_mt_tailcall_um.dll",
"bindmonitor_mt_tailcall_um.pdb",
"bindmonitor_mt_tailcall.sys",
"bindmonitor_tailcall.o",
"bindmonitor_tailcall_um.dll",
"bindmonitor_tailcall_um.pdb",

Просмотреть файл

@ -190,6 +190,7 @@ DECLARE_TEST("bad_map_name", _test_mode::Verify)
DECLARE_TEST("bindmonitor", _test_mode::Verify)
DECLARE_TEST("bindmonitor_ringbuf", _test_mode::Verify)
DECLARE_TEST("bindmonitor_tailcall", _test_mode::Verify)
DECLARE_TEST("bindmonitor_mt_tailcall", _test_mode::Verify)
DECLARE_TEST_CUSTOM_PROGRAM_TYPE("bpf", _test_mode::Verify, std::string("xdp"))
DECLARE_TEST("bpf_call", _test_mode::Verify)
DECLARE_TEST("cgroup_sock_addr", _test_mode::Verify)

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -0,0 +1,94 @@
// Copyright (c) Microsoft Corporation
// SPDX-License-Identifier: MIT
// clang -O2 -Werror -c bindmonitor_mt_tailcall.c -o bindmonitor_mt_tailcall_jit.o
//
// For bpf code: clang -target bpf -O2 -Werror -c bindmonitor_mt_tailcall.c -o bindmonitor_mt_tailcall.o
// this passes the checker
// Whenever this sample program changes, bpf2c_tests will fail unless the
// expected files in tests\bpf2c_tests\expected are updated. The following
// script can be used to regenerate the expected files:
// generate_expected_bpf2c_output.ps1
//
// Usage:
// .\scripts\generate_expected_bpf2c_output.ps1 <build_output_path>
// Example:
// .\scripts\generate_expected_bpf2c_output.ps1 .\x64\Debug\
#include "bpf_helpers.h"
#include "ebpf_nethooks.h"
SEC("maps")
struct bpf_map_def bind_tail_call_map = {
.type = BPF_MAP_TYPE_PROG_ARRAY,
.key_size = sizeof(uint32_t),
.value_size = sizeof(uint32_t),
.max_entries = MAX_TAIL_CALL_CNT};
SEC("bind")
bind_action_t
BindMonitor_Caller(bind_md_t* ctx)
{
bpf_printk("Tail call index %d\n", 0);
bpf_tail_call(ctx, &bind_tail_call_map, 0);
return BIND_DENY;
}
// Define a macro that defines a program which tail calls a function for the bind hook.
#define DEFINE_BIND_TAIL_FUNC(x) \
SEC("bind/" #x) \
bind_action_t BindMonitor_Callee##x(bind_md_t* ctx) \
{ \
bpf_printk("Tail call index %d\n", x + 1); \
bpf_tail_call(ctx, &bind_tail_call_map, x + 1); \
\
return BIND_DENY; \
}
DEFINE_BIND_TAIL_FUNC(0)
DEFINE_BIND_TAIL_FUNC(1)
DEFINE_BIND_TAIL_FUNC(2)
DEFINE_BIND_TAIL_FUNC(3)
DEFINE_BIND_TAIL_FUNC(4)
DEFINE_BIND_TAIL_FUNC(5)
DEFINE_BIND_TAIL_FUNC(6)
DEFINE_BIND_TAIL_FUNC(7)
DEFINE_BIND_TAIL_FUNC(8)
DEFINE_BIND_TAIL_FUNC(9)
DEFINE_BIND_TAIL_FUNC(10)
DEFINE_BIND_TAIL_FUNC(11)
DEFINE_BIND_TAIL_FUNC(12)
DEFINE_BIND_TAIL_FUNC(13)
DEFINE_BIND_TAIL_FUNC(14)
DEFINE_BIND_TAIL_FUNC(15)
DEFINE_BIND_TAIL_FUNC(16)
DEFINE_BIND_TAIL_FUNC(17)
DEFINE_BIND_TAIL_FUNC(18)
DEFINE_BIND_TAIL_FUNC(19)
DEFINE_BIND_TAIL_FUNC(20)
DEFINE_BIND_TAIL_FUNC(21)
DEFINE_BIND_TAIL_FUNC(22)
DEFINE_BIND_TAIL_FUNC(23)
DEFINE_BIND_TAIL_FUNC(24)
DEFINE_BIND_TAIL_FUNC(25)
DEFINE_BIND_TAIL_FUNC(26)
DEFINE_BIND_TAIL_FUNC(27)
DEFINE_BIND_TAIL_FUNC(28)
DEFINE_BIND_TAIL_FUNC(29)
DEFINE_BIND_TAIL_FUNC(30)
// This line verifies that the BindMonitor_Caller prototype is correct by declaring a bind_hook_t
// variable with the same name as the first tail call function.
// This line is optional.
bind_hook_t BindMonitor_Caller;
SEC("bind/31")
bind_action_t
BindMonitor_Callee31(bind_md_t* ctx)
{
// This function is the last tail call function for the bind hook.
// This function returns BIND_PERMIT to allow the bind request to proceed.
return BIND_PERMIT;
}

Просмотреть файл

@ -13,6 +13,13 @@
#include "socket_helper.h"
#include "socket_tests_common.h"
// The helper function definitions in bpf_helpers.h conflict with the definitions
// in libbpf.h, but this code needs to use MAX_TAIL_CALL_CNT from bpf_helpers.h.
// Work around this by defining MAX_TAIL_CALL_CNT here.
#if !defined(MAX_TAIL_CALL_CNT)
#define MAX_TAIL_CALL_CNT 32
#endif
// Note: The 'program' and 'execution' types are not required for km tests.
static const std::map<std::string, test_program_attributes> _test_program_info = {
{{"cgroup_sock_addr"},
@ -1084,6 +1091,305 @@ _print_test_control_info(const test_control_info& test_control_info)
}
}
static void
_set_up_tailcall_program(bpf_object* object, const std::string& map_name)
{
REQUIRE(object != nullptr);
fd_t prog_map_fd = bpf_object__find_map_fd_by_name(object, map_name.c_str());
if (prog_map_fd < 0) {
LOG_ERROR(
"({}) FATAL ERROR: bpf_object__find_map_fd_by_name({}) failed. Errno:{}",
__func__,
map_name.c_str(),
errno);
REQUIRE(prog_map_fd >= 0);
}
LOG_VERBOSE("({}) Opened fd:{} for map:{}", __func__, prog_map_fd, map_name.c_str());
// Set up tail calls.
for (int index = 0; index < MAX_TAIL_CALL_CNT; index++) {
try {
std::string bind_program_name{"BindMonitor_Callee"};
bind_program_name += std::to_string(index);
// Try to get a handle to the 'BindMonitor_Callee<index>' program.
bpf_program* callee = bpf_object__find_program_by_name(object, bind_program_name.c_str());
if (callee == nullptr) {
LOG_ERROR("({}) - bpf_object__find_program_by_name() failed. errno: {}", bind_program_name, errno);
REQUIRE(callee != nullptr);
}
LOG_VERBOSE("({}) - bpf_object__find_program_by_name() success.", bind_program_name);
fd_t callee_fd = bpf_program__fd(callee);
if (callee_fd < 0) {
LOG_ERROR("({}) - bpf_program__fd() failed. errno: {}", bind_program_name, errno);
REQUIRE(callee_fd > 0);
}
LOG_VERBOSE("({}) - bpf_program__fd() for callee_fd:{} success.", bind_program_name, callee_fd);
uint32_t result = bpf_map_update_elem(prog_map_fd, &index, &callee_fd, 0);
if (result < 0) {
LOG_ERROR("({}) - bpf_map_update_elem() failed. errno: {}", bind_program_name, errno);
REQUIRE(result == ERROR_SUCCESS);
}
LOG_VERBOSE("({}) - bpf_map_update_elem() for callee_fd:{} success.", bind_program_name, callee_fd);
} catch (...) {
// No need to terminate. We don't care about user mode issues here.
}
}
}
static void
_invoke_mt_bindmonitor_tail_call_thread_function(thread_context& context)
{
// Test bind.
SOCKET socket_handle = INVALID_SOCKET;
SOCKADDR_STORAGE remote_endpoint{};
if (context.role == thread_role_type::MONITOR_IPV4) {
remote_endpoint.ss_family = AF_INET;
} else {
ASSERT(context.role == thread_role_type::MONITOR_IPV6);
remote_endpoint.ss_family = AF_INET6;
}
uint16_t remote_port = SOCKET_TEST_PORT + static_cast<uint16_t>(context.thread_index);
using sc = std::chrono::steady_clock;
auto endtime = sc::now() + std::chrono::minutes(context.duration_minutes);
while (sc::now() < endtime) {
// Now send out a small burst of TCP 'bind' attempts for the duration of the test. We do this to increase
// the probability of a collision between the program invocation and the extension being restarted at the same
// time.
constexpr uint32_t BURST_SIZE = 5;
int result = 0;
for (uint32_t i = 0; i < BURST_SIZE; i++) {
// Create a socket.
socket_handle = WSASocket(remote_endpoint.ss_family, SOCK_STREAM, IPPROTO_TCP, nullptr, 0, 0);
REQUIRE(socket_handle != INVALID_SOCKET);
INETADDR_SETANY(reinterpret_cast<PSOCKADDR>(&remote_endpoint));
SS_PORT(&remote_endpoint) = htons(remote_port);
// Forcefully bind to the same port in use using socket option SO_REUSEADDR.
// One of the use-case: multicast sockets.
const char optval = 1;
result = setsockopt(socket_handle, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
if (result != 0) {
LOG_ERROR(
"Thread[{}] setsockopt result:{} {} to port:{}",
context.thread_index,
result,
WSAGetLastError(),
remote_port);
closesocket(socket_handle);
REQUIRE(result == 0);
}
// Bind the socket.
LOG_VERBOSE("Thread[{}] - binding to port:{}", context.thread_index, remote_port);
result = bind(socket_handle, (PSOCKADDR)&remote_endpoint, sizeof(remote_endpoint));
if (result != 0) {
LOG_ERROR(
"Thread[{}] bind result:{} {} to port:{}",
context.thread_index,
result,
WSAGetLastError(),
remote_port);
closesocket(socket_handle);
REQUIRE(result == 0);
}
LOG_VERBOSE("Thread[{}] bind success to port:{}", context.thread_index, remote_port);
closesocket(socket_handle);
}
}
LOG_VERBOSE("Thread[{}] Done.", context.thread_index);
}
static std::pair<bpf_object_ptr, fd_t>
_load_attach_tail_program(
const std::string& file_name,
uint32_t thread_index,
ebpf_attach_type_t attach_type,
const std::string program_name,
bpf_prog_type program_type)
{
bpf_object_ptr object_ptr;
bpf_object* object_raw_ptr = nullptr;
bpf_link* link = nullptr;
// Get the 'object' ptr for the program associated with this thread.
object_raw_ptr = bpf_object__open(file_name.c_str());
if (object_raw_ptr == nullptr) {
LOG_ERROR(
"{}({}) FATAL ERROR: bpf_object__open({}) failed. errno:{}",
__func__,
thread_index,
file_name.c_str(),
errno);
REQUIRE(object_raw_ptr != nullptr);
}
LOG_VERBOSE("{}({}) Opened file:{}", __func__, thread_index, file_name.c_str());
// Load the program.
auto result = bpf_object__load(object_raw_ptr);
if (result != 0) {
LOG_ERROR(
"{}({}) FATAL ERROR: bpf_object__load({}) failed. errno:{}",
__func__,
thread_index,
file_name.c_str(),
errno);
REQUIRE(result == 0);
}
object_ptr.reset(object_raw_ptr);
LOG_VERBOSE("{}({}) loaded file:{}", __func__, thread_index, file_name.c_str());
// Load program by name.
bpf_program* program = bpf_object__find_program_by_name(object_raw_ptr, program_name.c_str());
if (program == nullptr) {
LOG_ERROR(
"{}({}) FATAL ERROR: bpf_object__find_program_by_name({}) failed. errno:{}",
__func__,
thread_index,
file_name.c_str(),
errno);
REQUIRE(program != nullptr);
}
LOG_VERBOSE(
"{}({}) Found program object for program:{}, file_name:{}",
__func__,
thread_index,
program->program_name,
file_name.c_str());
// Set the program type.
bpf_program__set_type(program, program_type);
// Get the fd for this program.
fd_t program_fd = bpf_program__fd(program);
if (program_fd < 0) {
LOG_ERROR(
"{}({}) FATAL ERROR: bpf_program__fd({}) failed. program:{}, errno:{}",
__func__,
thread_index,
file_name.c_str(),
program->program_name,
errno);
REQUIRE(program_fd >= 0);
}
LOG_VERBOSE(
"{}({}) Opened fd:{}, for program:{}, file_name:{}",
__func__,
thread_index,
program_fd,
program->program_name,
file_name.c_str());
result = ebpf_program_attach(program, &attach_type, nullptr, 0, &link);
if (result != ERROR_SUCCESS) {
LOG_ERROR(
"{}({}) FATAL ERROR: bpf_prog_attach({}) failed. program:{}, errno:{}",
__func__,
thread_index,
file_name.c_str(),
program->program_name,
errno);
REQUIRE(result == ERROR_SUCCESS);
}
LOG_VERBOSE(
"{}({}) Attached program:{}, file_name:{}", __func__, thread_index, program->program_name, file_name.c_str());
return std::make_pair(std::move(object_ptr), program_fd);
}
static void
_mt_bindmonitor_tail_call_invoke_program_test(const test_control_info& test_control_info)
{
WSAData data{};
auto error = WSAStartup(MAKEWORD(2, 2), &data);
REQUIRE(error == 0);
std::string file_name = "bindmonitor_mt_tailcall.sys";
std::string map_name = "bind_tail_call_map";
std::string program_name = "BindMonitor_Caller";
bpf_prog_type program_type = BPF_PROG_TYPE_BIND;
// Load the program.
auto [program_object, _] =
_load_attach_tail_program(file_name, 0, EBPF_ATTACH_TYPE_BIND, program_name, program_type);
// Set up the tail call programs.
_set_up_tailcall_program(program_object.get(), map_name);
// Needed for thread_context initialization.
constexpr uint32_t MAX_BIND_PROGRAM = 1;
// Storage for object pointers for each ebpf program file opened by bpf_object__open().
std::vector<object_table_entry> object_table(MAX_BIND_PROGRAM);
for (uint32_t index = 0; auto& entry : object_table) {
entry.available = true;
entry.lock = std::make_unique<std::mutex>();
entry.object = std::move(program_object);
entry.attach = !(index % 2) ? true : false;
entry.index = index++;
entry.reuse_count = 0;
entry.tag = 0xC001DEA2;
}
size_t total_threads = test_control_info.threads_count;
std::vector<thread_context> thread_context_table(
total_threads, {{}, {}, false, {}, thread_role_type::ROLE_NOT_SET, 0, 0, 0, false, 0, 0, object_table});
std::vector<std::thread> test_thread_table(total_threads);
for (uint32_t i = 0; i < total_threads; i++) {
// First, prepare the context for this thread.
auto& context_entry = thread_context_table[i];
context_entry.is_native_program = true;
context_entry.role = thread_role_type::MONITOR_IPV6;
context_entry.thread_index = i;
context_entry.duration_minutes = test_control_info.duration_minutes;
context_entry.extension_restart_enabled = test_control_info.extension_restart_enabled;
// Now create the thread.
auto& thread_entry = test_thread_table[i];
thread_entry =
std::move(std::thread(_invoke_mt_bindmonitor_tail_call_thread_function, std::ref(context_entry)));
}
// Another table for the 'extension restart' threads.
std::vector<std::thread> extension_restart_thread_table{};
// If requested, start the 'extension stop-and-restart' thread for extension for this program type.
std::string extension_name = {"netebpfext"};
if (test_control_info.extension_restart_enabled) {
auto restart_thread = _start_extension_restart_thread(
std::ref(extension_name), test_control_info.extension_restart_delay_ms, test_control_info.duration_minutes);
extension_restart_thread_table.push_back(std::move(restart_thread));
}
// Wait for threads to terminate.
LOG_INFO("waiting on {} test threads...", test_thread_table.size());
for (auto& t : test_thread_table) {
t.join();
}
if (test_control_info.extension_restart_enabled) {
LOG_INFO("waiting on {} extension restart threads...", extension_restart_thread_table.size());
for (auto& t : extension_restart_thread_table) {
t.join();
}
}
// Clean up Winsock.
WSACleanup();
}
TEST_CASE("jit_load_attach_detach_unload_random_v4_test", "[jit_mt_stress_test]")
{
// This test attempts to load the same JIT'ed ebpf program multiple times in different threads. This test
@ -1210,3 +1516,20 @@ TEST_CASE("sockaddr_invoke_program_test", "[native_mt_stress_test]")
_print_test_control_info(local_test_control_info);
_mt_sockaddr_invoke_program_test(local_test_control_info);
}
TEST_CASE("bindmonitor_tail_call_invoke_program_test", "[native_mt_stress_test]")
{
// Test layout:
// 1. Load the "bindmonitor_mt_tailcall.sys" native ebpf program.
// 2. Load MAX_TAIL_CALL_CNT tail call programs.
// 3. Create the specified number of threads.
// - Each thread will invoke the TCP 'bind'.
// - This will invoke MAX_TAIL_CALL_CNT tail call programs for permit.
_km_test_init();
LOG_INFO("\nStarting test *** bindmonitor_tailcall_invoke_program_test ***");
test_control_info local_test_control_info = _global_test_control_info;
_print_test_control_info(local_test_control_info);
_mt_bindmonitor_tail_call_invoke_program_test(local_test_control_info);
}

Просмотреть файл

@ -103,6 +103,24 @@ Sample command line invocations:
- Extension restart enabled.
- Delay of 250 ms between successive extension restarts.
## 1.6. bindmonitor_tail_call_invoke_program_test
This test first loads a specific native eBPF program. It then loads all the MAX_TAIL_CALL_CNT tail call programs and updates the program array map. It then creates the specified number of threads where each thread attempts a TCP 'bind' to the same port continuously in a loop. The test setup guarantees that the `thread_index` passed in each `thread_context` is unique to that thread, so that each thread gets a unique port (base_port + thread_index).
This causes the invocation of the in-kernel eBPF tail call programs to be executed in sequence. The last tail call program returns a PERMIT verdict.
This test can be run with or without the extension restart option.
Sample command line invocations:
### 1.6.1. `ebpf_stress_test_km bindmonitor_tail_call_invoke_program_test`
- Uses default values for all supported options.
### 1.6.2. `ebpf_stress_test_km -tt=32 -td=15 -vo=true -er=true -erd=250 bindmonitor_tail_call_invoke_program_test`
- Creates 32 test threads.
- Runs test for 15 minutes.
- Verbose test trace output enabled.
- Extension restart enabled.
- Delay of 250 ms between successive extension restarts.
# 2.0. ebpf_stress_test_um.exe (test sources in .\um\)