Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#4235 - added pin/unpin netsh commands for maps and programs #4254

Merged
merged 18 commits into from
Mar 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions libs/ebpfnetsh/maps.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
#include <string>
#include <vector>

std::string
down_cast_from_wstring(const std::wstring& wide_string);

// The following function uses windows specific type as an input to match
// definition of "FN_HANDLE_CMD" in public file of NetSh.h
unsigned long
Expand Down Expand Up @@ -69,3 +72,89 @@ handle_ebpf_show_maps(
}
return NO_ERROR;
}

template <typename F>
unsigned long
handle_ebpf_pinunpin_map_common(
LPCWSTR machine, LPWSTR* argv, DWORD current_index, DWORD argc, DWORD flags, LPCVOID data, BOOL* done, F&& f)
{
UNREFERENCED_PARAMETER(machine);
UNREFERENCED_PARAMETER(flags);
UNREFERENCED_PARAMETER(data);
UNREFERENCED_PARAMETER(done);

TAG_TYPE tags[] = {
{TOKEN_ID, NS_REQ_PRESENT, FALSE},
{TOKEN_PINPATH, NS_REQ_ZERO, FALSE},
};
const int ID_INDEX = 0;
const int PINPATH_INDEX = 1;

unsigned long tag_type[_countof(tags)]{};

unsigned long status =
PreprocessCommand(nullptr, argv, current_index, argc, tags, _countof(tags), 0, _countof(tags), tag_type);
if (status != EBPF_SUCCESS) {
return status;
}

std::string pinpath;
uint32_t id = 0;

for (int i = 0; (status == NO_ERROR) && ((i + current_index) < argc); i++) {
switch (tag_type[i]) {
case PINPATH_INDEX:
pinpath = down_cast_from_wstring(std::wstring(argv[current_index + i]));
break;

case ID_INDEX:
id = wcstoul(argv[current_index + i], nullptr, 0);
break;
}
}

auto fd = bpf_map_get_fd_by_id(id);
if (fd < 0)
return ERROR_INVALID_PARAMETER;

if (pinpath.empty()) {
bpf_map_info info{};
uint32_t size = sizeof(info);
if (bpf_obj_get_info_by_fd(fd, &info, &size) == 0) {
pinpath = info.name;
}
}
if (!pinpath.empty()) {
status = f(fd, pinpath.c_str());
} else {
status = ERROR_INVALID_PARAMETER;
}
Platform::_close(fd);

return status;
}

// The following function uses windows specific type as an input to match
// definition of "FN_HANDLE_CMD" in public file of NetSh.h
unsigned long
handle_ebpf_pin_map(
LPCWSTR machine, LPWSTR* argv, DWORD current_index, DWORD argc, DWORD flags, LPCVOID data, BOOL* done)
{
return handle_ebpf_pinunpin_map_common(
machine, argv, current_index, argc, flags, data, done, [](auto fd, auto pinpath) {
return bpf_obj_pin(fd, pinpath);
});
}

// The following function uses windows specific type as an input to match
// definition of "FN_HANDLE_CMD" in public file of NetSh.h
unsigned long
handle_ebpf_unpin_map(
LPCWSTR machine, LPWSTR* argv, DWORD current_index, DWORD argc, DWORD flags, LPCVOID data, BOOL* done)
{
// Unpin map from a specific pin path.
return handle_ebpf_pinunpin_map_common(
machine, argv, current_index, argc, flags, data, done, [](auto, auto pinpath) {
return ebpf_object_unpin(pinpath);
});
}
2 changes: 2 additions & 0 deletions libs/ebpfnetsh/maps.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ extern "C"
{
#endif

FN_HANDLE_CMD handle_ebpf_pin_map;
FN_HANDLE_CMD handle_ebpf_show_maps;
FN_HANDLE_CMD handle_ebpf_unpin_map;

#ifdef __cplusplus
}
Expand Down
86 changes: 86 additions & 0 deletions libs/ebpfnetsh/programs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -838,3 +838,89 @@ handle_ebpf_show_programs(
}
return status;
}

template <typename F>
unsigned long
handle_ebpf_pinunpin_program_common(
LPCWSTR machine, LPWSTR* argv, DWORD current_index, DWORD argc, DWORD flags, LPCVOID data, BOOL* done, F&& f)
{
UNREFERENCED_PARAMETER(machine);
UNREFERENCED_PARAMETER(flags);
UNREFERENCED_PARAMETER(data);
UNREFERENCED_PARAMETER(done);

TAG_TYPE tags[] = {
{TOKEN_ID, NS_REQ_PRESENT, FALSE},
{TOKEN_PINPATH, NS_REQ_ZERO, FALSE},
};
const int ID_INDEX = 0;
const int PINPATH_INDEX = 1;

unsigned long tag_type[_countof(tags)]{};

unsigned long status =
PreprocessCommand(nullptr, argv, current_index, argc, tags, _countof(tags), 0, _countof(tags), tag_type);
if (status != EBPF_SUCCESS) {
return status;
}

std::string pinpath;
uint32_t id = 0;

for (int i = 0; (status == NO_ERROR) && ((i + current_index) < argc); i++) {
switch (tag_type[i]) {
case PINPATH_INDEX:
pinpath = down_cast_from_wstring(std::wstring(argv[current_index + i]));
break;

case ID_INDEX:
id = wcstoul(argv[current_index + i], nullptr, 0);
break;
}
}

auto fd = bpf_prog_get_fd_by_id(id);
if (fd < 0)
return ERROR_INVALID_PARAMETER;

if (pinpath.empty()) {
bpf_prog_info info{};
uint32_t size = sizeof(info);
if (bpf_obj_get_info_by_fd(fd, &info, &size) == 0) {
pinpath = info.name;
}
}
if (!pinpath.empty()) {
status = f(fd, pinpath.c_str());
} else {
status = ERROR_INVALID_PARAMETER;
}
Platform::_close(fd);

return status;
}

// The following function uses windows specific type as an input to match
// definition of "FN_HANDLE_CMD" in public file of NetSh.h
unsigned long
handle_ebpf_pin_program(
LPCWSTR machine, LPWSTR* argv, DWORD current_index, DWORD argc, DWORD flags, LPCVOID data, BOOL* done)
{
return handle_ebpf_pinunpin_program_common(
machine, argv, current_index, argc, flags, data, done, [](auto fd, auto pinpath) {
return bpf_obj_pin(fd, pinpath);
});
}

// The following function uses windows specific type as an input to match
// definition of "FN_HANDLE_CMD" in public file of NetSh.h
unsigned long
handle_ebpf_unpin_program(
LPCWSTR machine, LPWSTR* argv, DWORD current_index, DWORD argc, DWORD flags, LPCVOID data, BOOL* done)
{
// Unpin program from a specific pin path (not all paths).
return handle_ebpf_pinunpin_program_common(
machine, argv, current_index, argc, flags, data, done, [](auto, auto pinpath) {
return ebpf_object_unpin(pinpath);
});
}
2 changes: 2 additions & 0 deletions libs/ebpfnetsh/programs.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ extern "C"

FN_HANDLE_CMD handle_ebpf_add_program;
FN_HANDLE_CMD handle_ebpf_delete_program;
FN_HANDLE_CMD handle_ebpf_pin_program;
FN_HANDLE_CMD handle_ebpf_set_program;
FN_HANDLE_CMD handle_ebpf_show_programs;
FN_HANDLE_CMD handle_ebpf_unpin_program;

#ifdef __cplusplus
}
Expand Down
104 changes: 104 additions & 0 deletions tests/end_to_end/netsh_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1091,3 +1091,107 @@ TEST_CASE("show processes", "[netsh][processes]")
REQUIRE(result == NO_ERROR);
}
}

#if !defined(CONFIG_BPF_JIT_DISABLED) || !defined(CONFIG_BPF_INTERPRETER_DISABLED)

TEST_CASE("pin/unpin program", "[netsh][pin]")
{
_test_helper_netsh test_helper;
test_helper.initialize();
int result = 0;
auto output =
_run_netsh_command(handle_ebpf_add_program, L"bindmonitor.o", nullptr, L"pinpath=bindmonitor", &result);
REQUIRE(result == EBPF_SUCCESS);
const char prefix[] = "Loaded with ID";
REQUIRE(output.substr(0, sizeof(prefix) - 1) == prefix);

// Get program ID.
auto id = strtoul(output.c_str() + output.rfind(' '), nullptr, 10);
auto sid = std::to_wstring(id);
_run_netsh_command(handle_ebpf_pin_program, sid.c_str(), L"bindmonitorpin", nullptr, &result);
REQUIRE(result == EBPF_SUCCESS);

output = _run_netsh_command(handle_ebpf_show_pins, nullptr, nullptr, nullptr, &result);
REQUIRE(
output == std::format(
"\n"
" ID Type Path\n"
"======= ======= ==============\n"
" {0} Program bindmonitor\n"
" {0} Program bindmonitorpin\n",
id));

_run_netsh_command(handle_ebpf_unpin_program, sid.c_str(), L"random", nullptr, &result);
REQUIRE(result != EBPF_SUCCESS);

_run_netsh_command(handle_ebpf_unpin_program, sid.c_str(), L"bindmonitorpin", nullptr, &result);
REQUIRE(result == EBPF_SUCCESS);

output = _run_netsh_command(handle_ebpf_show_pins, nullptr, nullptr, nullptr, &result);
REQUIRE(
output == std::format(
"\n"
" ID Type Path\n"
"======= ======= ==============\n"
" {} Program bindmonitor\n",
id));

_run_netsh_command(handle_ebpf_delete_program, sid.c_str(), nullptr, nullptr, &result);
}

TEST_CASE("pin/unpin map", "[netsh][pin]")
{
_test_helper_netsh test_helper;
test_helper.initialize();
int result = 0;
auto output =
_run_netsh_command(handle_ebpf_add_program, L"bindmonitor.o", L"bind", L"pinpath=bindmonitor", &result);
REQUIRE(result == EBPF_SUCCESS);
const char prefix[] = "Loaded with ID";
REQUIRE(output.substr(0, sizeof(prefix) - 1) == prefix);
auto pid = strtoul(output.c_str() + output.rfind(' '), nullptr, 10);

output = _run_netsh_command(handle_ebpf_show_maps, nullptr, nullptr, nullptr, &result);
REQUIRE(result == EBPF_SUCCESS);

// Grab the first map ID.
auto digit = output.find_first_of("123456789");
auto id = strtoul(output.c_str() + digit, nullptr, 10);
REQUIRE(id > 0);
auto sid = std::to_wstring(id);

auto offset = output.find("audit_map", digit + 1);
REQUIRE(offset != std::string::npos);
auto pins = strtoul(output.c_str() + offset - 4, nullptr, 10);
REQUIRE(pins == 0);

// Pin map with default name (map name).
output = _run_netsh_command(handle_ebpf_pin_map, sid.c_str(), nullptr, nullptr, &result);
REQUIRE(result == EBPF_SUCCESS);

output = _run_netsh_command(handle_ebpf_show_maps, nullptr, nullptr, nullptr, &result);
REQUIRE(result == EBPF_SUCCESS);
pins = strtoul(output.c_str() + offset - 4, nullptr, 10);
REQUIRE(pins == 1);

// Pin map with custom name.
output = _run_netsh_command(handle_ebpf_pin_map, sid.c_str(), L"custompin", nullptr, &result);
REQUIRE(result == EBPF_SUCCESS);
output = _run_netsh_command(handle_ebpf_show_maps, nullptr, nullptr, nullptr, &result);
REQUIRE(result == EBPF_SUCCESS);
pins = strtoul(output.c_str() + offset - 4, nullptr, 10);
REQUIRE(pins == 2);

// Unpin twice.
output = _run_netsh_command(handle_ebpf_unpin_map, sid.c_str(), nullptr, nullptr, &result);
REQUIRE(result == EBPF_SUCCESS);
output = _run_netsh_command(handle_ebpf_unpin_map, sid.c_str(), L"custompin", nullptr, &result);
REQUIRE(result == EBPF_SUCCESS);
output = _run_netsh_command(handle_ebpf_show_maps, nullptr, nullptr, nullptr, &result);
REQUIRE(result == EBPF_SUCCESS);
pins = strtoul(output.c_str() + offset - 4, nullptr, 10);
REQUIRE(pins == 0);

_run_netsh_command(handle_ebpf_delete_program, std::to_wstring(pid).c_str(), nullptr, nullptr, &result);
}
#endif // !defined(CONFIG_BPF_JIT_DISABLED) || !defined(CONFIG_BPF_INTERPRETER_DISABLED)
Loading
Loading