526 строки
19 KiB
C++
526 строки
19 KiB
C++
// Copyright (c) Microsoft Corporation
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
#include <windows.h>
|
|
#include <netsh.h> // Must be included after windows.h
|
|
#include <string.h>
|
|
#include "bpf.h"
|
|
#include "capture_helper.hpp"
|
|
#include "catch_wrapper.hpp"
|
|
#include "elf.h"
|
|
#pragma warning(push)
|
|
#pragma warning(disable : 4200)
|
|
#include "libbpf.h"
|
|
#pragma warning(pop)
|
|
#include "maps.h"
|
|
#include "platform.h"
|
|
#include "programs.h"
|
|
#include "test_helper.hpp"
|
|
|
|
#pragma region
|
|
// Mock Netsh.exe APIs.
|
|
|
|
DWORD WINAPI
|
|
PreprocessCommand(
|
|
_In_opt_ HANDLE hModule,
|
|
_Inout_updates_(dwArgCount) LPWSTR* ppwcArguments,
|
|
_In_ DWORD dwCurrentIndex,
|
|
_In_ DWORD dwArgCount,
|
|
_Inout_updates_opt_(dwTagCount) TAG_TYPE* pttTags,
|
|
_In_ DWORD dwTagCount,
|
|
_In_ DWORD dwMinArgs,
|
|
_In_ DWORD dwMaxArgs,
|
|
_Out_writes_opt_(dwArgCount - dwCurrentIndex) DWORD* pdwTagType)
|
|
{
|
|
UNREFERENCED_PARAMETER(hModule);
|
|
|
|
DWORD argc = dwArgCount - dwCurrentIndex;
|
|
if (argc < dwMinArgs || argc > dwMaxArgs) {
|
|
return ERROR_INVALID_SYNTAX;
|
|
}
|
|
|
|
if (!pttTags || !pdwTagType) {
|
|
return ERROR_INVALID_SYNTAX;
|
|
}
|
|
|
|
for (DWORD i = 0; i < argc; i++) {
|
|
PWSTR equals = wcschr(ppwcArguments[dwCurrentIndex + i], L'=');
|
|
PWSTR tagName = nullptr;
|
|
if (equals) {
|
|
tagName = _wcsdup(ppwcArguments[dwCurrentIndex + i]);
|
|
if (tagName == nullptr) {
|
|
return ERROR_OUTOFMEMORY;
|
|
}
|
|
tagName[equals - ppwcArguments[dwCurrentIndex + i]] = 0;
|
|
|
|
// Advance past the tag.
|
|
ppwcArguments[dwCurrentIndex + i] = ++equals;
|
|
}
|
|
|
|
// Find which tag this argument goes with.
|
|
DWORD dwTagIndex;
|
|
for (dwTagIndex = 0; dwTagIndex < dwTagCount; dwTagIndex++) {
|
|
if ((tagName == nullptr && !pttTags[dwTagIndex].bPresent) ||
|
|
(tagName != nullptr && wcsncmp(pttTags[dwTagIndex].pwszTag, tagName, wcslen(tagName)) == 0)) {
|
|
pttTags[dwTagIndex].bPresent = true;
|
|
pdwTagType[i] = dwTagIndex;
|
|
break;
|
|
}
|
|
}
|
|
if (tagName) {
|
|
free((void*)tagName);
|
|
}
|
|
if (dwTagIndex == dwTagCount) {
|
|
// Tag not found.
|
|
return ERROR_INVALID_SYNTAX;
|
|
}
|
|
}
|
|
|
|
// See if any required tags are absent.
|
|
for (DWORD i = 0; i < dwTagCount; i++) {
|
|
if (!pttTags[i].bPresent && (pttTags[i].dwRequired & NS_REQ_PRESENT)) {
|
|
return ERROR_INVALID_SYNTAX;
|
|
}
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
DWORD
|
|
MatchEnumTag(HANDLE hModule, LPCWSTR pwcArg, DWORD dwNumArg, const TOKEN_VALUE* pEnumTable, PDWORD pdwValue)
|
|
{
|
|
UNREFERENCED_PARAMETER(hModule);
|
|
|
|
for (DWORD i = 0; i < dwNumArg; i++) {
|
|
if (wcscmp(pwcArg, pEnumTable[i].pwszToken) == 0) {
|
|
*pdwValue = pEnumTable[i].dwValue;
|
|
return NO_ERROR;
|
|
}
|
|
}
|
|
return ERROR_NOT_FOUND;
|
|
}
|
|
#pragma endregion
|
|
|
|
static std::string
|
|
_run_netsh_command(
|
|
_In_ FN_HANDLE_CMD* command,
|
|
_In_opt_z_ const wchar_t* arg1,
|
|
_In_opt_z_ const wchar_t* arg2,
|
|
_In_opt_z_ const wchar_t* arg3,
|
|
_Out_ int* result)
|
|
{
|
|
capture_helper_t capture;
|
|
errno_t error = capture.begin_capture();
|
|
if (error != NO_ERROR) {
|
|
*result = error;
|
|
return "Couldn't capture output\n";
|
|
}
|
|
|
|
// Copy args into an array.
|
|
PWSTR argv[3] = {};
|
|
int argc = 0;
|
|
if (arg1 != nullptr) {
|
|
argv[argc++] = (PWSTR)arg1;
|
|
}
|
|
if (arg2 != nullptr) {
|
|
argv[argc++] = (PWSTR)arg2;
|
|
}
|
|
if (arg3 != nullptr) {
|
|
argv[argc++] = (PWSTR)arg3;
|
|
}
|
|
|
|
error = command(nullptr, argv, 0, argc, 0, 0, nullptr);
|
|
if (error != 0) {
|
|
*result = error;
|
|
return capture.get_stderr_contents();
|
|
}
|
|
|
|
*result = NO_ERROR;
|
|
return capture.get_stdout_contents();
|
|
}
|
|
|
|
TEST_CASE("show disassembly bpf.o", "[netsh][disassembly]")
|
|
{
|
|
int result;
|
|
std::string output = _run_netsh_command(handle_ebpf_show_disassembly, L"bpf.o", nullptr, nullptr, &result);
|
|
REQUIRE(result == NO_ERROR);
|
|
REQUIRE(
|
|
output == " 0: r0 = 42\n"
|
|
" 1: exit\n\n");
|
|
}
|
|
|
|
TEST_CASE("show disassembly bpf.o nosuchsection", "[netsh][disassembly]")
|
|
{
|
|
int result;
|
|
std::string output = _run_netsh_command(handle_ebpf_show_disassembly, L"bpf.o", L"nosuchsection", nullptr, &result);
|
|
REQUIRE(result == ERROR_SUPPRESS_OUTPUT);
|
|
REQUIRE(output == "error: Can't find section nosuchsection in file bpf.o\n");
|
|
}
|
|
|
|
TEST_CASE("show disassembly nosuchfile.o", "[netsh][disassembly]")
|
|
{
|
|
int result;
|
|
std::string output = _run_netsh_command(handle_ebpf_show_disassembly, L"nosuchfile.o", nullptr, nullptr, &result);
|
|
REQUIRE(result == ERROR_SUPPRESS_OUTPUT);
|
|
REQUIRE(output == "error: No such file or directory opening nosuchfile.o\n");
|
|
}
|
|
|
|
TEST_CASE("show sections nosuchfile.o", "[netsh][sections]")
|
|
{
|
|
int result;
|
|
std::string output = _run_netsh_command(handle_ebpf_show_sections, L"nosuchfile.o", nullptr, nullptr, &result);
|
|
REQUIRE(result == ERROR_SUPPRESS_OUTPUT);
|
|
REQUIRE(output == "error: No such file or directory opening nosuchfile.o\n");
|
|
}
|
|
|
|
TEST_CASE("show sections bpf.o", "[netsh][sections]")
|
|
{
|
|
int result;
|
|
std::string output = _run_netsh_command(handle_ebpf_show_sections, L"bpf.o", nullptr, nullptr, &result);
|
|
REQUIRE(result == NO_ERROR);
|
|
REQUIRE(
|
|
output == "\n"
|
|
" Section Type # Maps Size\n"
|
|
"==================== ========= ====== ======\n"
|
|
" xdp_prog xdp 0 2\n");
|
|
}
|
|
|
|
TEST_CASE("show sections bpf.o xdp_prog", "[netsh][sections]")
|
|
{
|
|
int result;
|
|
std::string output = _run_netsh_command(handle_ebpf_show_sections, L"bpf.o", L"xdp_prog", nullptr, &result);
|
|
REQUIRE(result == NO_ERROR);
|
|
REQUIRE(
|
|
output == "\n"
|
|
"Section : xdp_prog\n"
|
|
"Program Type : xdp\n"
|
|
"# Maps : 0\n"
|
|
"Size : 2 instructions\n"
|
|
"adjust_head : 0\n"
|
|
"arith : 0\n"
|
|
"arith32 : 0\n"
|
|
"arith64 : 1\n"
|
|
"assign : 1\n"
|
|
"basic_blocks : 2\n"
|
|
"call_1 : 0\n"
|
|
"call_mem : 0\n"
|
|
"call_nomem : 0\n"
|
|
"joins : 0\n"
|
|
"jumps : 0\n"
|
|
"load : 0\n"
|
|
"load_store : 0\n"
|
|
"map_in_map : 0\n"
|
|
"other : 2\n"
|
|
"packet_access: 0\n"
|
|
"store : 0\n");
|
|
}
|
|
|
|
TEST_CASE("show verification nosuchfile.o", "[netsh][verification]")
|
|
{
|
|
int result;
|
|
std::string output = _run_netsh_command(handle_ebpf_show_verification, L"nosuchfile.o", nullptr, nullptr, &result);
|
|
REQUIRE(result == ERROR_SUPPRESS_OUTPUT);
|
|
REQUIRE(output == "error: No such file or directory opening nosuchfile.o\n");
|
|
}
|
|
|
|
TEST_CASE("show verification bpf.o", "[netsh][verification]")
|
|
{
|
|
int result;
|
|
std::string output = _run_netsh_command(handle_ebpf_show_verification, L"bpf.o", nullptr, nullptr, &result);
|
|
REQUIRE(result == NO_ERROR);
|
|
REQUIRE(
|
|
output == "\n"
|
|
"\n"
|
|
"0 errors\n"
|
|
"Verification succeeded\n"
|
|
"Program terminates within 6 instructions\n");
|
|
}
|
|
|
|
TEST_CASE("show verification droppacket.o", "[netsh][verification]")
|
|
{
|
|
_test_helper_libbpf test_helper;
|
|
|
|
int result;
|
|
std::string output = _run_netsh_command(handle_ebpf_show_verification, L"droppacket.o", L"xdp", nullptr, &result);
|
|
REQUIRE(result == NO_ERROR);
|
|
REQUIRE(
|
|
output == "\n"
|
|
"\n"
|
|
"0 errors\n"
|
|
"Verification succeeded\n"
|
|
"Program terminates within 114 instructions\n");
|
|
}
|
|
|
|
TEST_CASE("show verification droppacket_unsafe.o", "[netsh][verification]")
|
|
{
|
|
_test_helper_libbpf test_helper;
|
|
|
|
int result;
|
|
std::string output =
|
|
_run_netsh_command(handle_ebpf_show_verification, L"droppacket_unsafe.o", L"xdp", nullptr, &result);
|
|
REQUIRE(result == ERROR_SUPPRESS_OUTPUT);
|
|
REQUIRE(
|
|
output == "Verification failed\n"
|
|
"\n"
|
|
"Verification report:\n"
|
|
"\n"
|
|
"2: r2 = *(u8 *)(r1 + 9)\n"
|
|
" Upper bound must be at most packet_size (valid_access(r1.offset+9, width=1))\n"
|
|
"4: r1 = *(u16 *)(r1 + 24)\n"
|
|
" Upper bound must be at most packet_size (valid_access(r1.offset+24, width=2))\n"
|
|
"\n"
|
|
"2 errors\n"
|
|
"\n");
|
|
}
|
|
|
|
TEST_CASE("pin first", "[netsh][programs]")
|
|
{
|
|
_test_helper_libbpf test_helper;
|
|
|
|
// Load a program to show.
|
|
int result;
|
|
std::string output =
|
|
_run_netsh_command(handle_ebpf_add_program, L"reflect_packet.o", L"xdp", L"pinned=reflect", &result);
|
|
REQUIRE(strcmp(output.c_str(), "Loaded with ID 65537\n") == 0);
|
|
REQUIRE(result == NO_ERROR);
|
|
|
|
// Show programs in normal (table) format.
|
|
output = _run_netsh_command(handle_ebpf_show_programs, nullptr, nullptr, nullptr, &result);
|
|
REQUIRE(result == NO_ERROR);
|
|
REQUIRE(
|
|
output == "\n"
|
|
" ID Pins Links Mode Name\n"
|
|
"====== ==== ===== ========= ====================\n"
|
|
" 65537 1 1 JIT reflect_packet\n"
|
|
"131073 0 0 JIT encap_reflect_packet\n");
|
|
|
|
output = _run_netsh_command(handle_ebpf_delete_program, L"65537", nullptr, nullptr, &result);
|
|
REQUIRE(output == "Unpinned 65537 from reflect\n");
|
|
REQUIRE(result == NO_ERROR);
|
|
REQUIRE(bpf_object__next(nullptr) == nullptr);
|
|
}
|
|
|
|
TEST_CASE("show programs", "[netsh][programs]")
|
|
{
|
|
_test_helper_libbpf test_helper;
|
|
|
|
// Load a program to show.
|
|
int result;
|
|
std::string output =
|
|
_run_netsh_command(handle_ebpf_add_program, L"tail_call.o", L"pinned=mypinname", nullptr, &result);
|
|
REQUIRE(strcmp(output.c_str(), "Loaded with ID 196609\n") == 0);
|
|
REQUIRE(result == NO_ERROR);
|
|
|
|
// Show programs in normal (table) format.
|
|
output = _run_netsh_command(handle_ebpf_show_programs, L"xdp", nullptr, nullptr, &result);
|
|
REQUIRE(result == NO_ERROR);
|
|
REQUIRE(
|
|
output == "\n"
|
|
" ID Pins Links Mode Name\n"
|
|
"====== ==== ===== ========= ====================\n"
|
|
"196609 1 1 JIT caller\n"
|
|
"262145 0 0 JIT callee\n");
|
|
|
|
// Test filtering by "attached=yes".
|
|
output = _run_netsh_command(handle_ebpf_show_programs, L"attached=yes", nullptr, nullptr, &result);
|
|
REQUIRE(result == NO_ERROR);
|
|
REQUIRE(
|
|
output == "\n"
|
|
" ID Pins Links Mode Name\n"
|
|
"====== ==== ===== ========= ====================\n"
|
|
"196609 1 1 JIT caller\n");
|
|
|
|
// Test filtering by "attached=no".
|
|
output = _run_netsh_command(handle_ebpf_show_programs, L"attached=no", nullptr, nullptr, &result);
|
|
REQUIRE(result == NO_ERROR);
|
|
REQUIRE(
|
|
output == "\n"
|
|
" ID Pins Links Mode Name\n"
|
|
"====== ==== ===== ========= ====================\n"
|
|
"262145 0 0 JIT callee\n");
|
|
|
|
// Test filtering by "pinned=yes".
|
|
output = _run_netsh_command(handle_ebpf_show_programs, L"pinned=yes", nullptr, nullptr, &result);
|
|
REQUIRE(result == NO_ERROR);
|
|
REQUIRE(
|
|
output == "\n"
|
|
" ID Pins Links Mode Name\n"
|
|
"====== ==== ===== ========= ====================\n"
|
|
"196609 1 1 JIT caller\n");
|
|
|
|
// Test filtering by "pinned=no".
|
|
output = _run_netsh_command(handle_ebpf_show_programs, L"pinned=no", nullptr, nullptr, &result);
|
|
REQUIRE(result == NO_ERROR);
|
|
REQUIRE(
|
|
output == "\n"
|
|
" ID Pins Links Mode Name\n"
|
|
"====== ==== ===== ========= ====================\n"
|
|
"262145 0 0 JIT callee\n");
|
|
|
|
// Test verbose output format.
|
|
output = _run_netsh_command(handle_ebpf_show_programs, L"level=verbose", nullptr, nullptr, &result);
|
|
REQUIRE(result == NO_ERROR);
|
|
REQUIRE(
|
|
output == "\n"
|
|
"ID : 196609\n"
|
|
"File name : tail_call.o\n"
|
|
"Section : xdp_prog\n"
|
|
"Name : caller\n"
|
|
"Mode : JIT\n"
|
|
"# map IDs : 2\n"
|
|
"# pinned paths : 1\n"
|
|
"# links : 1\n"
|
|
"\n"
|
|
"ID : 262145\n"
|
|
"File name : tail_call.o\n"
|
|
"Section : xdp_prog/0\n"
|
|
"Name : callee\n"
|
|
"Mode : JIT\n"
|
|
"# map IDs : 0\n"
|
|
"# pinned paths : 0\n"
|
|
"# links : 0\n");
|
|
|
|
output = _run_netsh_command(handle_ebpf_delete_program, L"196609", nullptr, nullptr, &result);
|
|
REQUIRE(output == "Unpinned 196609 from mypinname\n");
|
|
REQUIRE(result == NO_ERROR);
|
|
REQUIRE(bpf_object__next(nullptr) == nullptr);
|
|
}
|
|
|
|
TEST_CASE("set program", "[netsh][programs]")
|
|
{
|
|
_test_helper_libbpf test_helper;
|
|
|
|
int result;
|
|
std::string output = _run_netsh_command(handle_ebpf_add_program, L"tail_call.o", nullptr, nullptr, &result);
|
|
REQUIRE(strcmp(output.c_str(), "Loaded with ID 196609\n") == 0);
|
|
REQUIRE(result == NO_ERROR);
|
|
|
|
// Detach the program. This won't delete the program since
|
|
// the containing object is still associated with the netsh process,
|
|
// and could still be enumerated by it with bpf_object__next().
|
|
output = _run_netsh_command(handle_ebpf_set_program, L"196609", L"", nullptr, &result);
|
|
REQUIRE(output == "");
|
|
REQUIRE(result == ERROR_OKAY);
|
|
REQUIRE(bpf_object__next(nullptr) != nullptr);
|
|
|
|
// Try to detach an unattached program.
|
|
output = _run_netsh_command(handle_ebpf_set_program, L"196609", L"", nullptr, &result);
|
|
REQUIRE(output == "error 1168: could not detach program\n");
|
|
REQUIRE(result == ERROR_SUPPRESS_OUTPUT);
|
|
|
|
RPC_WSTR attach_type_string;
|
|
REQUIRE(UuidToStringW(&EBPF_ATTACH_TYPE_XDP, &attach_type_string) == 0);
|
|
|
|
// Attach the program.
|
|
output = _run_netsh_command(handle_ebpf_set_program, L"196609", (PCWSTR)attach_type_string, nullptr, &result);
|
|
REQUIRE(output == "");
|
|
REQUIRE(result == ERROR_OKAY);
|
|
|
|
// Detach the program again.
|
|
output = _run_netsh_command(handle_ebpf_set_program, L"196609", L"", nullptr, &result);
|
|
REQUIRE(output == "");
|
|
REQUIRE(result == ERROR_OKAY);
|
|
|
|
// Verify we can delete a detached program.
|
|
RpcStringFreeW(&attach_type_string);
|
|
output = _run_netsh_command(handle_ebpf_delete_program, L"196609", nullptr, nullptr, &result);
|
|
REQUIRE(output == "");
|
|
REQUIRE(result == NO_ERROR);
|
|
REQUIRE(bpf_object__next(nullptr) == nullptr);
|
|
|
|
// Verify the program ID doesn't exist any more.
|
|
output = _run_netsh_command(handle_ebpf_show_programs, nullptr, nullptr, nullptr, &result);
|
|
REQUIRE(
|
|
output == "\n"
|
|
" ID Pins Links Mode Name\n"
|
|
"====== ==== ===== ========= ====================\n");
|
|
REQUIRE(result == NO_ERROR);
|
|
}
|
|
|
|
TEST_CASE("show maps", "[netsh][maps]")
|
|
{
|
|
_test_helper_end_to_end test_helper;
|
|
|
|
// Create maps to show.
|
|
int outer_map_fd = bpf_create_map(BPF_MAP_TYPE_HASH_OF_MAPS, sizeof(__u32), sizeof(__u32), 2, 0);
|
|
REQUIRE(outer_map_fd > 0);
|
|
|
|
int inner_map_fd = bpf_create_map(BPF_MAP_TYPE_ARRAY, sizeof(__u32), sizeof(__u32), 1, 0);
|
|
REQUIRE(inner_map_fd > 0);
|
|
|
|
int result;
|
|
std::string output = _run_netsh_command(handle_ebpf_show_maps, nullptr, nullptr, nullptr, &result);
|
|
REQUIRE(result == NO_ERROR);
|
|
|
|
REQUIRE(
|
|
output == "\n"
|
|
" Key Value Max Inner\n"
|
|
" Map Type Size Size Entries Index\n"
|
|
"================== ==== ===== ======= =====\n"
|
|
" Hash of maps 4 4 2 0\n"
|
|
" Array 4 4 1 0\n");
|
|
|
|
Platform::_close(inner_map_fd);
|
|
Platform::_close(outer_map_fd);
|
|
}
|
|
|
|
TEST_CASE("delete pinned program", "[netsh][programs]")
|
|
{
|
|
_test_helper_libbpf test_helper;
|
|
|
|
// Load a program unpinned.
|
|
int result;
|
|
std::string output = _run_netsh_command(handle_ebpf_add_program, L"tail_call.o", nullptr, nullptr, &result);
|
|
REQUIRE(strcmp(output.c_str(), "Loaded with ID 196609\n") == 0);
|
|
REQUIRE(result == NO_ERROR);
|
|
|
|
// Pin the program.
|
|
output = _run_netsh_command(handle_ebpf_set_program, L"196609", L"pinned=mypinname", nullptr, &result);
|
|
REQUIRE(result == ERROR_OKAY);
|
|
REQUIRE(output == "");
|
|
|
|
// Verify we can delete a pinned program.
|
|
output = _run_netsh_command(handle_ebpf_delete_program, L"196609", nullptr, nullptr, &result);
|
|
REQUIRE(output == "Unpinned 196609 from mypinname\n");
|
|
REQUIRE(result == NO_ERROR);
|
|
REQUIRE(bpf_object__next(nullptr) == nullptr);
|
|
|
|
// Verify the program ID doesn't exist any more.
|
|
output = _run_netsh_command(handle_ebpf_show_programs, nullptr, nullptr, nullptr, &result);
|
|
REQUIRE(
|
|
output == "\n"
|
|
" ID Pins Links Mode Name\n"
|
|
"====== ==== ===== ========= ====================\n");
|
|
REQUIRE(result == NO_ERROR);
|
|
}
|
|
|
|
TEST_CASE("unpin program", "[netsh][programs]")
|
|
{
|
|
_test_helper_libbpf test_helper;
|
|
|
|
// Load a program pinned.
|
|
int result;
|
|
std::string output = _run_netsh_command(handle_ebpf_add_program, L"tail_call.o", L"xdp", L"mypinname", &result);
|
|
REQUIRE(strcmp(output.c_str(), "Loaded with ID 196609\n") == 0);
|
|
REQUIRE(result == NO_ERROR);
|
|
|
|
// Unpin the program.
|
|
output = _run_netsh_command(handle_ebpf_set_program, L"196609", L"", nullptr, &result);
|
|
REQUIRE(result == ERROR_OKAY);
|
|
REQUIRE(output == "");
|
|
|
|
// Verify we can delete the unpinned program.
|
|
output = _run_netsh_command(handle_ebpf_delete_program, L"196609", nullptr, nullptr, &result);
|
|
REQUIRE(output == "Unpinned 196609 from mypinname\n");
|
|
REQUIRE(result == NO_ERROR);
|
|
REQUIRE(bpf_object__next(nullptr) == nullptr);
|
|
|
|
// Verify the program ID doesn't exist any more.
|
|
output = _run_netsh_command(handle_ebpf_show_programs, nullptr, nullptr, nullptr, &result);
|
|
REQUIRE(
|
|
output == "\n"
|
|
" ID Pins Links Mode Name\n"
|
|
"====== ==== ===== ========= ====================\n");
|
|
REQUIRE(result == NO_ERROR);
|
|
}
|