ebpf-for-windows/tests/end_to_end/end_to_end.cpp

3541 строка
129 KiB
C++

// Copyright (c) eBPF for Windows contributors
// SPDX-License-Identifier: MIT
#define CATCH_CONFIG_MAIN
#include "api_common.hpp"
#include "api_internal.h"
#include "bpf/bpf.h"
#include "bpf/libbpf.h"
#include "bpf2c.h"
#include "capture_helper.hpp"
#include "catch_wrapper.hpp"
#include "common_tests.h"
#include "ebpf_core.h"
#include "ebpf_tracelog.h"
#include "helpers.h"
#include "ioctl_helper.h"
#include "mock.h"
namespace ebpf {
#include "net/if_ether.h"
#include "net/ip.h"
#include "net/udp.h"
}; // namespace ebpf
#include "cxplat_passed_test_log.h"
#include "platform.h"
#include "program_helper.h"
#include "sample_test_common.h"
#include "test_helper.hpp"
#include "usersim/ke.h"
#include "watchdog.h"
#include "xdp_tests_common.h"
#include <WinSock2.h>
#include <in6addr.h>
#include <array>
#include <cguid.h>
#include <chrono>
#include <lsalookup.h>
#include <mutex>
#define _NTDEF_ // UNICODE_STRING is already defined
#include <ntsecapi.h>
#include <thread>
using namespace Platform;
CATCH_REGISTER_LISTENER(cxplat_passed_test_log)
CATCH_REGISTER_LISTENER(_watchdog)
#define NATIVE_DRIVER_SERVICE_NAME L"test_service"
#define NATIVE_DRIVER_SERVICE_NAME_2 L"test_service2"
#define SERVICE_PATH_PREFIX L"\\Registry\\Machine\\System\\CurrentControlSet\\Services\\"
#define PARAMETERS_PATH_PREFIX L"System\\CurrentControlSet\\Services\\"
#define SERVICE_PARAMETERS L"Parameters"
#define NPI_MODULE_ID L"NpiModuleId"
#define BPF_PROG_TYPE_INVALID 100
#define BPF_ATTACH_TYPE_INVALID 100
#define CONCAT(s1, s2) s1 s2
#define DECLARE_TEST_CASE(_name, _group, _function, _suffix, _execution_type) \
TEST_CASE(CONCAT(_name, _suffix), _group) { _function(_execution_type); }
#define DECLARE_NATIVE_TEST(_name, _group, _function) \
DECLARE_TEST_CASE(_name, _group, _function, "-native", EBPF_EXECUTION_NATIVE)
#if !defined(CONFIG_BPF_JIT_DISABLED)
#define DECLARE_JIT_TEST(_name, _group, _function) \
DECLARE_TEST_CASE(_name, _group, _function, "-jit", EBPF_EXECUTION_JIT)
#else
#define DECLARE_JIT_TEST(_name, _group, _function)
#endif
#if !defined(CONFIG_BPF_INTERPRETER_DISABLED)
#define DECLARE_INTERPRET_TEST(_name, _group, _function) \
DECLARE_TEST_CASE(_name, _group, _function, "-interpret", EBPF_EXECUTION_INTERPRET)
#else
#define DECLARE_INTERPRET_TEST(_name, _group, _function)
#endif
#define DECLARE_ALL_TEST_CASES(_name, _group, _function) \
DECLARE_JIT_TEST(_name, _group, _function) \
DECLARE_NATIVE_TEST(_name, _group, _function) \
DECLARE_INTERPRET_TEST(_name, _group, _function)
#define DECLARE_JIT_TEST_CASES(_name, _group, _function) \
DECLARE_JIT_TEST(_name, _group, _function) \
DECLARE_NATIVE_TEST(_name, _group, _function)
std::vector<uint8_t>
prepare_ip_packet(uint16_t ethernet_type)
{
std::vector<uint8_t> packet(
sizeof(ebpf::ETHERNET_HEADER) +
((ethernet_type == ETHERNET_TYPE_IPV4) ? sizeof(ebpf::IPV4_HEADER) : sizeof(ebpf::IPV6_HEADER)));
auto ethernet_header = reinterpret_cast<ebpf::ETHERNET_HEADER*>(packet.data());
if (ethernet_type == ETHERNET_TYPE_IPV4) {
auto ipv4_header = reinterpret_cast<ebpf::IPV4_HEADER*>(ethernet_header + 1);
ipv4_header->HeaderLength = sizeof(ebpf::IPV4_HEADER) / sizeof(uint32_t);
}
ethernet_header->Type = ntohs(ethernet_type);
return packet;
}
void
append_udp_header(uint16_t udp_length, std::vector<uint8_t>& ip_packet)
{
ip_packet.resize(ip_packet.size() + sizeof(ebpf::UDP_HEADER));
auto ethernet_header = reinterpret_cast<ebpf::ETHERNET_HEADER*>(ip_packet.data());
ebpf::UDP_HEADER* udp;
if (ethernet_header->Type == ntohs(ETHERNET_TYPE_IPV4)) {
auto ipv4_header = reinterpret_cast<ebpf::IPV4_HEADER*>(ethernet_header + 1);
ipv4_header->Protocol = IPPROTO_UDP;
udp = reinterpret_cast<ebpf::UDP_HEADER*>(ipv4_header + 1);
} else {
auto ipv6 = reinterpret_cast<ebpf::IPV6_HEADER*>(ethernet_header + 1);
ipv6->NextHeader = IPPROTO_UDP;
udp = reinterpret_cast<ebpf::UDP_HEADER*>(ipv6 + 1);
}
udp->length = udp_length;
}
std::vector<uint8_t>
prepare_udp_packet(uint16_t udp_length, uint16_t ethernet_type)
{
auto packet = prepare_ip_packet(ethernet_type);
append_udp_header(udp_length, packet);
return packet;
}
static inline int
_get_total_map_count()
{
ebpf_id_t start_id = 0;
ebpf_id_t end_id = 0;
int map_count = 0;
while (bpf_map_get_next_id(start_id, &end_id) == 0) {
map_count++;
start_id = end_id;
}
return map_count;
}
const std::array<uint8_t, 6> _test_source_mac = {0, 1, 2, 3, 4, 5};
const std::array<uint8_t, 6> _test_destination_mac = {0xa, 0xb, 0xc, 0xd, 0xe, 0xf};
struct _ipv4_address_pair
{
const in_addr& source;
const in_addr& destination;
};
struct _ipv6_address_pair
{
const in6_addr& source;
const in6_addr& destination;
};
const in_addr _test_source_ipv4 = {10, 0, 0, 1};
const in_addr _test_destination_ipv4 = {20, 0, 0, 1};
const struct _ipv4_address_pair _test_ipv4_addrs = {_test_source_ipv4, _test_destination_ipv4};
const in_addr _test2_source_ipv4 = {30, 0, 0, 1};
const in_addr _test2_destination_ipv4 = {40, 0, 0, 1};
const struct _ipv4_address_pair _test2_ipv4_addrs = {_test2_source_ipv4, _test2_destination_ipv4};
const in6_addr _test_source_ipv6 = {0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x2e, 0xfe, 0x12, 0x34};
const in6_addr _test_destination_ipv6 = {0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x2e, 0xfe, 0x56, 0x78};
const struct _ipv6_address_pair _test_ipv6_addrs = {_test_source_ipv6, _test_destination_ipv6};
const in6_addr _test2_source_ipv6 = {0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x2e, 0xfe, 0x9a, 0xbc};
const in6_addr _test2_destination_ipv6 = {0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x2e, 0xfe, 0xde, 0xf0};
const struct _ipv6_address_pair _test2_ipv6_addrs = {_test2_source_ipv6, _test2_destination_ipv6};
typedef class _ip_packet
{
public:
_ip_packet(
ADDRESS_FAMILY address_family,
_In_ const std::array<uint8_t, 6>& source_mac,
_In_ const std::array<uint8_t, 6>& destination_mac,
_In_opt_ const void* ip_addresses)
: _address_family(address_family)
{
_packet = prepare_ip_packet((address_family == AF_INET) ? ETHERNET_TYPE_IPV4 : ETHERNET_TYPE_IPV6);
set_mac_addresses(source_mac, destination_mac);
if (_address_family == AF_INET) {
(ip_addresses == nullptr) ? set_ipv4_addresses(&_test_ipv4_addrs.source, &_test_ipv4_addrs.destination)
: set_ipv4_addresses(
&(reinterpret_cast<const _ipv4_address_pair*>(ip_addresses))->source,
&(reinterpret_cast<const _ipv4_address_pair*>(ip_addresses))->destination);
} else {
(ip_addresses == nullptr) ? set_ipv6_addresses(&_test_ipv6_addrs.source, &_test_ipv6_addrs.destination)
: set_ipv6_addresses(
&(reinterpret_cast<const _ipv6_address_pair*>(ip_addresses))->source,
&(reinterpret_cast<const _ipv6_address_pair*>(ip_addresses))->destination);
}
}
uint8_t*
data()
{
return _packet.data();
}
size_t
size()
{
return _packet.size();
}
std::vector<uint8_t>&
packet()
{
return _packet;
}
void
set_mac_addresses(_In_ const std::array<uint8_t, 6>& source_mac, _In_ const std::array<uint8_t, 6>& destination_mac)
{
auto ethernet_header = reinterpret_cast<ebpf::ETHERNET_HEADER*>(_packet.data());
memcpy(ethernet_header->Source, source_mac.data(), sizeof(ethernet_header->Source));
memcpy(ethernet_header->Destination, destination_mac.data(), sizeof(ethernet_header->Destination));
}
void
set_ipv4_addresses(_In_ const in_addr* source_address, _In_ const in_addr* destination_address)
{
auto ethernet_header = reinterpret_cast<ebpf::ETHERNET_HEADER*>(_packet.data());
auto ipv4_header = reinterpret_cast<ebpf::IPV4_HEADER*>(ethernet_header + 1);
ipv4_header->SourceAddress = source_address->s_addr;
ipv4_header->DestinationAddress = destination_address->s_addr;
}
void
set_ipv6_addresses(_In_ const in6_addr* source_address, _In_ const in6_addr* destination_address)
{
auto ethernet_header = reinterpret_cast<ebpf::ETHERNET_HEADER*>(_packet.data());
auto ipv6 = reinterpret_cast<ebpf::IPV6_HEADER*>(ethernet_header + 1);
memcpy(ipv6->SourceAddress, source_address, sizeof(ebpf::ipv6_address_t));
memcpy(ipv6->DestinationAddress, destination_address, sizeof(ebpf::ipv6_address_t));
}
ADDRESS_FAMILY _address_family;
std::vector<uint8_t> _packet;
} ip_packet_t;
typedef class _udp_packet : public ip_packet_t
{
public:
_udp_packet(
ADDRESS_FAMILY address_family,
_In_ const std::array<uint8_t, 6>& source_mac = _test_source_mac,
_In_ const std::array<uint8_t, 6>& destination_mac = _test_destination_mac,
_In_opt_ const void* ip_addresses = nullptr,
uint16_t datagram_length = 1024)
: ip_packet_t{address_family, source_mac, destination_mac, ip_addresses}
{
append_udp_header(sizeof(ebpf::UDP_HEADER) + datagram_length, _packet);
}
static const ebpf::UDP_HEADER*
_get_udp_header(_In_ const uint8_t* packet_buffer, ADDRESS_FAMILY address_family)
{
auto ethernet_header = reinterpret_cast<const ebpf::ETHERNET_HEADER*>(packet_buffer);
const ebpf::UDP_HEADER* udp = nullptr;
if (address_family == AF_INET) {
auto ip = reinterpret_cast<const ebpf::IPV4_HEADER*>(ethernet_header + 1);
udp = reinterpret_cast<const ebpf::UDP_HEADER*>(ip + 1);
} else {
REQUIRE(address_family == AF_INET6);
auto ip = reinterpret_cast<const ebpf::IPV6_HEADER*>(ethernet_header + 1);
udp = reinterpret_cast<const ebpf::UDP_HEADER*>(ip + 1);
}
return udp;
}
void
set_source_port(uint16_t source_port)
{
auto udp = const_cast<ebpf::UDP_HEADER*>(_get_udp_header(_packet.data(), _address_family));
udp->srcPort = source_port;
}
void
set_destination_port(uint16_t destination_port)
{
auto udp = const_cast<ebpf::UDP_HEADER*>(_get_udp_header(_packet.data(), _address_family));
udp->destPort = destination_port;
}
} udp_packet_t;
typedef class _ip_in_ip_packet : public ip_packet_t
{
public:
_ip_in_ip_packet(
ADDRESS_FAMILY address_family,
_In_ const std::array<uint8_t, 6>& source_mac = _test_source_mac,
_In_ const std::array<uint8_t, 6>& destination_mac = _test_destination_mac,
_In_opt_ const void* outer_ip_addresses = nullptr,
_In_opt_ const void* inner_ip_addresses = nullptr)
: ip_packet_t{address_family, source_mac, destination_mac, outer_ip_addresses}
{
if (_address_family == AF_INET) {
_packet.resize(_packet.size() + sizeof(ebpf::IPV4_HEADER));
(inner_ip_addresses == nullptr)
? set_inner_ipv4_addresses(&_test2_ipv4_addrs.source, &_test2_ipv4_addrs.destination)
: set_inner_ipv4_addresses(
&(reinterpret_cast<const _ipv4_address_pair*>(inner_ip_addresses))->source,
&(reinterpret_cast<const _ipv4_address_pair*>(inner_ip_addresses))->destination);
} else {
_packet.resize(_packet.size() + sizeof(ebpf::IPV6_HEADER));
(inner_ip_addresses == nullptr)
? set_inner_ipv6_addresses(&_test2_ipv6_addrs.source, &_test2_ipv6_addrs.destination)
: set_inner_ipv6_addresses(
&(reinterpret_cast<const _ipv6_address_pair*>(inner_ip_addresses))->source,
&(reinterpret_cast<const _ipv6_address_pair*>(inner_ip_addresses))->destination);
}
}
void
set_inner_ipv4_addresses(_In_ const in_addr* source_address, _In_ const in_addr* destination_address)
{
auto ethernet_header = reinterpret_cast<ebpf::ETHERNET_HEADER*>(_packet.data());
auto outer_ipv4_header = reinterpret_cast<ebpf::IPV4_HEADER*>(ethernet_header + 1);
outer_ipv4_header->Protocol = IPPROTO_IPV4;
// Test code assumes length of IP header = sizeof(IPV4_HEADER).
auto inner_ipv4_header = outer_ipv4_header + 1;
inner_ipv4_header->SourceAddress = source_address->s_addr;
inner_ipv4_header->DestinationAddress = destination_address->s_addr;
}
void
set_inner_ipv6_addresses(_In_ const in6_addr* source_address, _In_ const in6_addr* destination_address)
{
auto ethernet_header = reinterpret_cast<ebpf::ETHERNET_HEADER*>(_packet.data());
auto outer_ipv6 = reinterpret_cast<ebpf::IPV6_HEADER*>(ethernet_header + 1);
outer_ipv6->NextHeader = IPPROTO_IPV6;
auto inner_ipv6 = outer_ipv6 + 1;
memcpy(inner_ipv6->SourceAddress, source_address, sizeof(ebpf::ipv6_address_t));
memcpy(inner_ipv6->DestinationAddress, destination_address, sizeof(ebpf::ipv6_address_t));
}
} ip_in_ip_packet_t;
#define SAMPLE_PATH ""
#define TEST_IFINDEX 17
int
ebpf_program_load(
_In_z_ const char* file_name,
bpf_prog_type prog_type,
ebpf_execution_type_t execution_type,
_Out_ bpf_object_ptr* unique_object,
_Out_ fd_t* program_fd, // File descriptor of first program in the object.
_Outptr_opt_result_maybenull_z_ const char** log_buffer)
{
*program_fd = ebpf_fd_invalid;
if (log_buffer) {
*log_buffer = nullptr;
}
unique_object->reset(nullptr);
bpf_object* new_object = bpf_object__open(file_name);
if (new_object == nullptr) {
return -errno;
}
REQUIRE(ebpf_object_set_execution_type(new_object, execution_type) == EBPF_SUCCESS);
bpf_program* program = bpf_object__next_program(new_object, nullptr);
if (prog_type != BPF_PROG_TYPE_UNSPEC) {
bpf_program__set_type(program, prog_type);
}
int error = bpf_object__load(new_object);
if (error < 0) {
if (log_buffer) {
size_t log_buffer_size;
if (program != nullptr) {
const char* log_buffer_str = bpf_program__log_buf(program, &log_buffer_size);
if (log_buffer_str != nullptr) {
*log_buffer = cxplat_duplicate_string(log_buffer_str);
}
}
}
bpf_object__close(new_object);
// Add delay to permit the native module handle cleanup to complete.
std::this_thread::sleep_for(std::chrono::milliseconds(100));
return error;
}
if (program != nullptr) {
*program_fd = bpf_program__fd(program);
}
unique_object->reset(new_object);
return 0;
}
void
droppacket_test(ebpf_execution_type_t execution_type)
{
_test_helper_end_to_end test_helper;
test_helper.initialize();
int result;
const char* error_message = nullptr;
bpf_object_ptr unique_object;
fd_t program_fd;
bpf_link_ptr link;
single_instance_hook_t hook(EBPF_PROGRAM_TYPE_XDP, EBPF_ATTACH_TYPE_XDP);
REQUIRE(hook.initialize() == EBPF_SUCCESS);
program_info_provider_t xdp_program_info;
REQUIRE(xdp_program_info.initialize(EBPF_PROGRAM_TYPE_XDP) == EBPF_SUCCESS);
const char* file_name = (execution_type == EBPF_EXECUTION_NATIVE ? "droppacket_um.dll" : "droppacket.o");
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 dropped_packet_map_fd = bpf_object__find_map_fd_by_name(unique_object.get(), "dropped_packet_map");
// Tell the program which interface to filter on.
fd_t interface_index_map_fd = bpf_object__find_map_fd_by_name(unique_object.get(), "interface_index_map");
uint32_t key = 0;
uint32_t if_index = TEST_IFINDEX;
REQUIRE(bpf_map_update_elem(interface_index_map_fd, &key, &if_index, EBPF_ANY) == EBPF_SUCCESS);
// Attach only to the single interface being tested.
REQUIRE(hook.attach_link(program_fd, &if_index, sizeof(if_index), &link) == EBPF_SUCCESS);
// Create a 0-byte UDP packet.
auto packet0 = prepare_udp_packet(0, ETHERNET_TYPE_IPV4);
uint64_t value = 1000;
REQUIRE(bpf_map_update_elem(dropped_packet_map_fd, &key, &value, EBPF_ANY) == EBPF_SUCCESS);
// Test that we drop the packet and increment the map
xdp_md_t ctx0{packet0.data(), packet0.data() + packet0.size(), 0, TEST_IFINDEX};
uint32_t hook_result;
REQUIRE(hook.fire(&ctx0, &hook_result) == EBPF_SUCCESS);
REQUIRE(hook_result == XDP_DROP);
REQUIRE(bpf_map_lookup_elem(dropped_packet_map_fd, &key, &value) == EBPF_SUCCESS);
REQUIRE(value == 1001);
REQUIRE(bpf_map_delete_elem(dropped_packet_map_fd, &key) == EBPF_SUCCESS);
REQUIRE(bpf_map_lookup_elem(dropped_packet_map_fd, &key, &value) == EBPF_SUCCESS);
REQUIRE(value == 0);
// Create a normal (not 0-byte) UDP packet.
auto packet10 = prepare_udp_packet(10, ETHERNET_TYPE_IPV4);
xdp_md_t ctx10{packet10.data(), packet10.data() + packet10.size(), 0, TEST_IFINDEX};
// Test that we don't drop the normal packet.
REQUIRE(hook.fire(&ctx10, &hook_result) == EBPF_SUCCESS);
REQUIRE(hook_result == XDP_PASS);
REQUIRE(bpf_map_lookup_elem(dropped_packet_map_fd, &key, &value) == EBPF_SUCCESS);
REQUIRE(value == 0);
// Reattach to all interfaces so we can test the ingress_ifindex field passed to the program.
hook.detach_and_close_link(&link);
if_index = 0;
REQUIRE(hook.attach_link(program_fd, &if_index, sizeof(if_index), &link) == EBPF_SUCCESS);
// Fire a 0-length UDP packet on the interface index in the map, which should be dropped.
REQUIRE(hook.fire(&ctx0, &hook_result) == EBPF_SUCCESS);
REQUIRE(hook_result == XDP_DROP);
REQUIRE(bpf_map_lookup_elem(dropped_packet_map_fd, &key, &value) == EBPF_SUCCESS);
REQUIRE(value == 1);
// Reset the count of dropped packets.
REQUIRE(bpf_map_delete_elem(dropped_packet_map_fd, &key) == EBPF_SUCCESS);
{
// Negative test: State is too small.
uint8_t state[sizeof(ebpf_execution_context_state_t) - 1] = {0};
REQUIRE(hook.batch_begin(sizeof(state), state) == EBPF_INVALID_ARGUMENT);
}
// Fire a 0-length UDP packet on the interface index in the map, using batch mode, which should be dropped.
uint8_t state[sizeof(ebpf_execution_context_state_t)] = {0};
REQUIRE(hook.batch_begin(sizeof(state), state) == EBPF_SUCCESS);
// Process 10 packets in batch mode.
for (int i = 0; i < 10; i++) {
REQUIRE(hook.batch_invoke(&ctx0, &hook_result, state) == EBPF_SUCCESS);
REQUIRE(hook_result == XDP_DROP);
}
REQUIRE(hook.batch_end(state) == EBPF_SUCCESS);
REQUIRE(bpf_map_lookup_elem(dropped_packet_map_fd, &key, &value) == EBPF_SUCCESS);
REQUIRE(value == 10);
// Reset the count of dropped packets.
REQUIRE(bpf_map_delete_elem(dropped_packet_map_fd, &key) == EBPF_SUCCESS);
// Fire a 0-length packet on any interface that is not in the map, which should be allowed.
xdp_md_t ctx4{packet0.data(), packet0.data() + packet0.size(), 0, if_index + 1};
REQUIRE(hook.fire(&ctx4, &hook_result) == EBPF_SUCCESS);
REQUIRE(hook_result == XDP_PASS);
REQUIRE(bpf_map_lookup_elem(dropped_packet_map_fd, &key, &value) == EBPF_SUCCESS);
REQUIRE(value == 0);
hook.detach_and_close_link(&link);
bpf_object__close(unique_object.release());
}
// See also divide_by_zero_test_km in api_test.cpp for the kernel-mode equivalent.
void
divide_by_zero_test_um(ebpf_execution_type_t execution_type)
{
_test_helper_end_to_end test_helper;
test_helper.initialize();
int result;
const char* error_message = nullptr;
bpf_object_ptr unique_object;
fd_t program_fd;
bpf_link_ptr link;
single_instance_hook_t hook(EBPF_PROGRAM_TYPE_SAMPLE, EBPF_ATTACH_TYPE_SAMPLE);
REQUIRE(hook.initialize() == EBPF_SUCCESS);
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 ? "divide_by_zero_um.dll" : "divide_by_zero.o");
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);
REQUIRE(hook.attach_link(program_fd, nullptr, 0, &link) == EBPF_SUCCESS);
auto packet = prepare_udp_packet(0, ETHERNET_TYPE_IPV4);
// Empty context (not used by the eBPF program).
INITIALIZE_SAMPLE_CONTEXT;
uint32_t hook_result;
REQUIRE(hook.fire(ctx, &hook_result) == EBPF_SUCCESS);
REQUIRE(hook_result == 0);
hook.detach_and_close_link(&link);
bpf_object__close(unique_object.release());
}
void
bad_map_name_um(ebpf_execution_type_t execution_type)
{
_test_helper_end_to_end test_helper;
test_helper.initialize();
int result;
const char* error_message = nullptr;
bpf_object_ptr unique_object;
fd_t program_fd;
single_instance_hook_t hook(EBPF_PROGRAM_TYPE_SAMPLE, EBPF_ATTACH_TYPE_SAMPLE);
REQUIRE(hook.initialize() == EBPF_SUCCESS);
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 ? "bad_map_name_um.dll" : "bad_map_name.o");
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 == -EINVAL);
bpf_object__close(unique_object.release());
}
typedef struct _process_entry
{
uint32_t count;
uint8_t name[64];
} process_entry_t;
typedef struct _audit_entry
{
uint64_t logon_id;
int32_t is_admin;
} audit_entry_t;
uint32_t
get_bind_count_for_pid(fd_t map_fd, uint64_t pid)
{
process_entry_t entry{};
bpf_map_lookup_elem(map_fd, &pid, &entry);
return entry.count;
}
static void
_validate_bind_audit_entry(fd_t map_fd, uint64_t pid)
{
audit_entry_t entry = {0};
int result = bpf_map_lookup_elem(map_fd, &pid, &entry);
REQUIRE(result == 0);
REQUIRE(entry.is_admin == -1);
REQUIRE(entry.logon_id != 0);
SECURITY_LOGON_SESSION_DATA* data = NULL;
result = LsaGetLogonSessionData((PLUID)&entry.logon_id, &data);
REQUIRE(result == ERROR_SUCCESS);
LsaFreeReturnBuffer(data);
}
bind_action_t
emulate_bind(std::function<ebpf_result_t(void*, uint32_t*)>& invoke, uint64_t pid, const char* appid)
{
uint32_t result;
std::string app_id = appid;
INITIALIZE_BIND_CONTEXT
ctx->app_id_start = (uint8_t*)app_id.c_str();
ctx->app_id_end = (uint8_t*)(app_id.c_str()) + app_id.size();
ctx->process_id = pid;
ctx->operation = BIND_OPERATION_BIND;
REQUIRE(invoke(reinterpret_cast<void*>(ctx), &result) == EBPF_SUCCESS);
return static_cast<bind_action_t>(result);
}
void
emulate_unbind(std::function<ebpf_result_t(void*, uint32_t*)>& invoke, uint64_t pid, const char* appid)
{
uint32_t result;
std::string app_id = appid;
INITIALIZE_BIND_CONTEXT
ctx->process_id = pid;
ctx->operation = BIND_OPERATION_UNBIND;
REQUIRE(invoke(ctx, &result) == EBPF_SUCCESS);
}
void
set_bind_limit(fd_t map_fd, uint32_t limit)
{
uint32_t limit_key = 0;
REQUIRE(bpf_map_update_elem(map_fd, &limit_key, &limit, EBPF_ANY) == EBPF_SUCCESS);
}
static uint64_t
_get_current_pid_tgid()
{
return ((uint64_t)GetCurrentProcessId() << 32 | GetCurrentThreadId());
}
void
bindmonitor_test(ebpf_execution_type_t execution_type)
{
_test_helper_end_to_end test_helper;
test_helper.initialize();
const char* error_message = nullptr;
uint64_t fake_pid = 12345;
int result;
bpf_object_ptr unique_object;
bpf_link_ptr link;
fd_t program_fd;
uint64_t process_id = _get_current_pid_tgid();
program_info_provider_t bind_program_info;
REQUIRE(bind_program_info.initialize(EBPF_PROGRAM_TYPE_BIND) == EBPF_SUCCESS);
// Note: We are deliberately using "bindmonitor_um.dll" here as we want the programs to be loaded from
// the individual dll, instead of the combined DLL. This helps in testing the DLL stub which is generated
// bpf2c.exe tool.
const char* file_name = (execution_type == EBPF_EXECUTION_NATIVE ? "bindmonitor_um.dll" : "bindmonitor.o");
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 limit_map_fd = bpf_object__find_map_fd_by_name(unique_object.get(), "limits_map");
REQUIRE(limit_map_fd > 0);
fd_t process_map_fd = bpf_object__find_map_fd_by_name(unique_object.get(), "process_map");
REQUIRE(process_map_fd > 0);
fd_t audit_map_fd = bpf_object__find_map_fd_by_name(unique_object.get(), "audit_map");
REQUIRE(audit_map_fd > 0);
single_instance_hook_t hook(EBPF_PROGRAM_TYPE_BIND, EBPF_ATTACH_TYPE_BIND);
REQUIRE(hook.initialize() == EBPF_SUCCESS);
uint32_t ifindex = 0;
REQUIRE(hook.attach_link(program_fd, &ifindex, sizeof(ifindex), &link) == EBPF_SUCCESS);
// Apply policy of maximum 2 binds per process
set_bind_limit(limit_map_fd, 2);
std::function<ebpf_result_t(void*, uint32_t*)> invoke =
[&hook](_Inout_ void* context, _Out_ uint32_t* result) -> ebpf_result_t { return hook.fire(context, result); };
// Bind first port - success
REQUIRE(emulate_bind(invoke, fake_pid, "fake_app_1") == BIND_PERMIT);
REQUIRE(get_bind_count_for_pid(process_map_fd, fake_pid) == 1);
_validate_bind_audit_entry(audit_map_fd, process_id);
// Bind second port - success
REQUIRE(emulate_bind(invoke, fake_pid, "fake_app_1") == BIND_PERMIT);
REQUIRE(get_bind_count_for_pid(process_map_fd, fake_pid) == 2);
_validate_bind_audit_entry(audit_map_fd, process_id);
// Bind third port - blocked
REQUIRE(emulate_bind(invoke, fake_pid, "fake_app_1") == BIND_DENY);
REQUIRE(get_bind_count_for_pid(process_map_fd, fake_pid) == 2);
_validate_bind_audit_entry(audit_map_fd, process_id);
// Unbind second port
emulate_unbind(invoke, fake_pid, "fake_app_1");
REQUIRE(get_bind_count_for_pid(process_map_fd, fake_pid) == 1);
_validate_bind_audit_entry(audit_map_fd, process_id);
// Unbind first port
emulate_unbind(invoke, fake_pid, "fake_app_1");
REQUIRE(get_bind_count_for_pid(process_map_fd, fake_pid) == 0);
_validate_bind_audit_entry(audit_map_fd, process_id);
// Bind from two apps to test enumeration
REQUIRE(emulate_bind(invoke, fake_pid, "fake_app_1") == BIND_PERMIT);
REQUIRE(get_bind_count_for_pid(process_map_fd, fake_pid) == 1);
_validate_bind_audit_entry(audit_map_fd, process_id);
fake_pid = 54321;
REQUIRE(emulate_bind(invoke, fake_pid, "fake_app_2") == BIND_PERMIT);
REQUIRE(get_bind_count_for_pid(process_map_fd, fake_pid) == 1);
_validate_bind_audit_entry(audit_map_fd, process_id);
uint64_t pid;
REQUIRE(bpf_map_get_next_key(process_map_fd, NULL, &pid) == 0);
REQUIRE(pid != 0);
REQUIRE(bpf_map_get_next_key(process_map_fd, &pid, &pid) == 0);
REQUIRE(pid != 0);
REQUIRE(bpf_map_get_next_key(process_map_fd, &pid, &pid) < 0);
REQUIRE(errno == ENOENT);
hook.detach_and_close_link(&link);
bpf_object__close(unique_object.release());
}
void
bindmonitor_tailcall_test(ebpf_execution_type_t execution_type)
{
_test_helper_end_to_end test_helper;
test_helper.initialize();
const char* error_message = nullptr;
uint64_t fake_pid = 12345;
int result;
bpf_object_ptr unique_object;
bpf_link_ptr link;
fd_t program_fd;
program_info_provider_t bind_program_info;
REQUIRE(bind_program_info.initialize(EBPF_PROGRAM_TYPE_BIND) == EBPF_SUCCESS);
const char* file_name =
(execution_type == EBPF_EXECUTION_NATIVE ? "bindmonitor_tailcall_um.dll" : "bindmonitor_tailcall.o");
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 limit_map_fd = bpf_object__find_map_fd_by_name(unique_object.get(), "limits_map");
REQUIRE(limit_map_fd > 0);
fd_t process_map_fd = bpf_object__find_map_fd_by_name(unique_object.get(), "process_map");
REQUIRE(process_map_fd > 0);
// Set up tail calls.
struct bpf_program* callee0 = bpf_object__find_program_by_name(unique_object.get(), "BindMonitor_Callee0");
REQUIRE(callee0 != nullptr);
fd_t callee0_fd = bpf_program__fd(callee0);
REQUIRE(callee0_fd > 0);
struct bpf_program* callee1 = bpf_object__find_program_by_name(unique_object.get(), "BindMonitor_Callee1");
REQUIRE(callee1 != nullptr);
fd_t callee1_fd = bpf_program__fd(callee1);
REQUIRE(callee1_fd > 0);
fd_t prog_map_fd = bpf_object__find_map_fd_by_name(unique_object.get(), "prog_array_map");
REQUIRE(prog_map_fd > 0);
uint32_t index = 0;
REQUIRE(bpf_map_update_elem(prog_map_fd, &index, &callee0_fd, 0) == 0);
index = 1;
REQUIRE(bpf_map_update_elem(prog_map_fd, &index, &callee1_fd, 0) == 0);
// Validate various maps.
// Validate map-in-maps with "inner_id".
struct bpf_map* outer_map = bpf_object__find_map_by_name(unique_object.get(), "dummy_outer_map");
REQUIRE(outer_map != nullptr);
int outer_map_fd = bpf_map__fd(outer_map);
REQUIRE(outer_map_fd > 0);
// Validate map-in-maps with "inner_idx".
struct bpf_map* outer_idx_map = bpf_object__find_map_by_name(unique_object.get(), "dummy_outer_idx_map");
REQUIRE(outer_idx_map != nullptr);
int outer_idx_map_fd = bpf_map__fd(outer_idx_map);
REQUIRE(outer_idx_map_fd > 0);
single_instance_hook_t hook(EBPF_PROGRAM_TYPE_BIND, EBPF_ATTACH_TYPE_BIND);
REQUIRE(hook.initialize() == EBPF_SUCCESS);
uint32_t ifindex = 0;
REQUIRE(hook.attach_link(program_fd, &ifindex, sizeof(ifindex), &link) == EBPF_SUCCESS);
// Apply policy of maximum 2 binds per process
set_bind_limit(limit_map_fd, 2);
std::function<ebpf_result_t(void*, uint32_t*)> invoke =
[&hook](_Inout_ void* context, _Out_ uint32_t* result) -> ebpf_result_t { return hook.fire(context, result); };
// Bind first port - success
REQUIRE(emulate_bind(invoke, fake_pid, "fake_app_1") == BIND_PERMIT);
REQUIRE(get_bind_count_for_pid(process_map_fd, fake_pid) == 1);
// Bind second port - success
REQUIRE(emulate_bind(invoke, fake_pid, "fake_app_1") == BIND_PERMIT);
REQUIRE(get_bind_count_for_pid(process_map_fd, fake_pid) == 2);
// Bind third port - blocked
REQUIRE(emulate_bind(invoke, fake_pid, "fake_app_1") == BIND_DENY);
REQUIRE(get_bind_count_for_pid(process_map_fd, fake_pid) == 2);
// Unbind second port
emulate_unbind(invoke, fake_pid, "fake_app_1");
REQUIRE(get_bind_count_for_pid(process_map_fd, fake_pid) == 1);
// Unbind first port
emulate_unbind(invoke, fake_pid, "fake_app_1");
REQUIRE(get_bind_count_for_pid(process_map_fd, fake_pid) == 0);
// Bind from two apps to test enumeration
REQUIRE(emulate_bind(invoke, fake_pid, "fake_app_1") == BIND_PERMIT);
REQUIRE(get_bind_count_for_pid(process_map_fd, fake_pid) == 1);
fake_pid = 54321;
REQUIRE(emulate_bind(invoke, fake_pid, "fake_app_2") == BIND_PERMIT);
REQUIRE(get_bind_count_for_pid(process_map_fd, fake_pid) == 1);
uint64_t pid;
REQUIRE(bpf_map_get_next_key(process_map_fd, NULL, &pid) == 0);
REQUIRE(pid != 0);
REQUIRE(bpf_map_get_next_key(process_map_fd, &pid, &pid) == 0);
REQUIRE(pid != 0);
REQUIRE(bpf_map_get_next_key(process_map_fd, &pid, &pid) < 0);
REQUIRE(errno == ENOENT);
hook.detach_and_close_link(&link);
index = 0;
REQUIRE(bpf_map_update_elem(prog_map_fd, &index, &ebpf_fd_invalid, 0) == 0);
index = 1;
REQUIRE(bpf_map_update_elem(prog_map_fd, &index, &ebpf_fd_invalid, 0) == 0);
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)
{
_test_helper_end_to_end test_helper;
test_helper.initialize();
const char* error_message = nullptr;
int result;
bpf_object_ptr unique_object;
bpf_link_ptr link;
fd_t program_fd;
program_info_provider_t bind_program_info;
REQUIRE(bind_program_info.initialize(EBPF_PROGRAM_TYPE_BIND) == EBPF_SUCCESS);
const char* file_name =
(execution_type == EBPF_EXECUTION_NATIVE ? "bindmonitor_ringbuf_um.dll" : "bindmonitor_ringbuf.o");
// Load and attach a bind eBPF program that uses a ring buffer map to notify about bind operations.
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 process_map_fd = bpf_object__find_map_fd_by_name(unique_object.get(), "process_map");
REQUIRE(process_map_fd > 0);
single_instance_hook_t hook(EBPF_PROGRAM_TYPE_BIND, EBPF_ATTACH_TYPE_BIND);
REQUIRE(hook.initialize() == EBPF_SUCCESS);
REQUIRE(hook.attach_link(program_fd, nullptr, 0, &link) == EBPF_SUCCESS);
// Create a list of fake app IDs and set it to event context.
std::string fake_app_ids_prefix = "fake_app";
std::vector<std::vector<char>> fake_app_ids;
for (int i = 0; i < RING_BUFFER_TEST_EVENT_COUNT; i++) {
std::string temp = fake_app_ids_prefix + std::to_string(i);
std::vector<char> fake_app_id(temp.begin(), temp.end());
fake_app_ids.push_back(fake_app_id);
}
uint64_t fake_pid = 12345;
std::function<ebpf_result_t(void*, uint32_t*)> invoke =
[&hook](_Inout_ void* context, _Out_ uint32_t* result) -> ebpf_result_t { return hook.fire(context, result); };
// Test multiple subscriptions to the same ring buffer map, to ensure that the ring buffer map will continue
// to provide notifications to the subscriber.
for (int i = 0; i < 3; i++) {
ring_buffer_api_test_helper(process_map_fd, fake_app_ids, [&](int i) {
// Emulate bind operation.
std::vector<char> fake_app_id = fake_app_ids[i];
fake_app_id.push_back('\0');
REQUIRE(emulate_bind(invoke, fake_pid + i, fake_app_id.data()) == BIND_PERMIT);
});
}
hook.detach_and_close_link(&link);
bpf_object__close(unique_object.release());
}
static void
_utility_helper_functions_test(ebpf_execution_type_t execution_type)
{
_test_helper_end_to_end test_helper;
test_helper.initialize();
single_instance_hook_t hook(EBPF_PROGRAM_TYPE_SAMPLE, EBPF_ATTACH_TYPE_SAMPLE);
REQUIRE(hook.initialize() == EBPF_SUCCESS);
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 ? "test_utility_helpers_um.dll" : "test_utility_helpers.o");
program_load_attach_helper_t program_helper;
program_helper.initialize(
file_name, BPF_PROG_TYPE_SAMPLE, "test_utility_helpers", execution_type, nullptr, 0, hook);
bpf_object* object = program_helper.get_object();
// Dummy context (not used by the eBPF program).
INITIALIZE_SAMPLE_CONTEXT
uint32_t hook_result;
REQUIRE(hook.fire(ctx, &hook_result) == EBPF_SUCCESS);
REQUIRE(hook_result == 0);
verify_utility_helper_results(object, true);
}
void
map_test(ebpf_execution_type_t execution_type)
{
_test_helper_end_to_end test_helper;
test_helper.initialize();
int result;
const char* error_message = nullptr;
bpf_object_ptr unique_object;
fd_t program_fd;
bpf_link_ptr link;
single_instance_hook_t hook(EBPF_PROGRAM_TYPE_SAMPLE, EBPF_ATTACH_TYPE_SAMPLE);
REQUIRE(hook.initialize() == EBPF_SUCCESS);
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 ? "map_um.dll" : "map.o");
result =
ebpf_program_load(file_name, BPF_PROG_TYPE_SAMPLE, 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);
REQUIRE(hook.attach_link(program_fd, nullptr, 0, &link) == EBPF_SUCCESS);
uint32_t hook_result;
INITIALIZE_SAMPLE_CONTEXT
REQUIRE(hook.fire(ctx, &hook_result) == EBPF_SUCCESS);
// Program should return 0 if all the map tests pass.
REQUIRE(hook_result >= 0);
hook.detach_and_close_link(&link);
bpf_object__close(unique_object.release());
}
DECLARE_ALL_TEST_CASES("droppacket", "[end_to_end]", droppacket_test);
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);
TEST_CASE("enum programs", "[end_to_end]")
{
_test_helper_end_to_end test_helper;
test_helper.initialize();
const char* error_message = nullptr;
ebpf_api_program_info_t* program_data = nullptr;
uint32_t result;
REQUIRE(
(result = ebpf_enumerate_programs(SAMPLE_PATH "test_sample_ebpf.o", true, &program_data, &error_message),
ebpf_free_string(error_message),
error_message = nullptr,
result == 0));
for (auto current_program = program_data; current_program != nullptr; current_program = current_program->next) {
ebpf_stat_t* stat = current_program->stats;
REQUIRE(strcmp(stat->key, "Instructions") == 0);
REQUIRE(stat->value == 40);
}
ebpf_free_programs(program_data);
ebpf_free_string(error_message);
}
TEST_CASE("verify section", "[end_to_end][deprecated]")
{
_test_helper_end_to_end test_helper;
test_helper.initialize();
const char* error_message = nullptr;
const char* report = nullptr;
uint32_t result;
program_info_provider_t sample_test_program_info;
REQUIRE(sample_test_program_info.initialize(EBPF_PROGRAM_TYPE_SAMPLE) == EBPF_SUCCESS);
ebpf_api_verifier_stats_t stats;
#pragma warning(suppress : 4996) // deprecated
REQUIRE(
(result = ebpf_api_elf_verify_section_from_file(
SAMPLE_PATH "test_sample_ebpf.o",
"sample_ext",
nullptr,
EBPF_VERIFICATION_VERBOSITY_NORMAL,
&report,
&error_message,
&stats),
ebpf_free_string(error_message),
error_message = nullptr,
result == 0));
REQUIRE(report != nullptr);
ebpf_free_string(report);
}
TEST_CASE("verify program", "[end_to_end]")
{
_test_helper_end_to_end test_helper;
test_helper.initialize();
const char* error_message = nullptr;
const char* report = nullptr;
uint32_t result;
program_info_provider_t sample_test_program_info;
REQUIRE(sample_test_program_info.initialize(EBPF_PROGRAM_TYPE_SAMPLE) == EBPF_SUCCESS);
ebpf_api_verifier_stats_t stats;
REQUIRE(
(result = ebpf_api_elf_verify_program_from_file(
SAMPLE_PATH "test_sample_ebpf.o",
"sample_ext",
"test_program_entry",
nullptr,
EBPF_VERIFICATION_VERBOSITY_NORMAL,
&report,
&error_message,
&stats),
ebpf_free_string(error_message),
error_message = nullptr,
result == 0));
REQUIRE(report != nullptr);
ebpf_free_string(report);
}
TEST_CASE("verify program with invalid program type", "[end_to_end]")
{
_test_helper_end_to_end test_helper;
test_helper.initialize();
const char* error_message = nullptr;
const char* report = nullptr;
uint32_t result;
program_info_provider_t sample_test_program_info;
REQUIRE(sample_test_program_info.initialize(EBPF_PROGRAM_TYPE_BIND) == EBPF_SUCCESS);
ebpf_api_verifier_stats_t stats;
result = ebpf_api_elf_verify_program_from_file(
SAMPLE_PATH "test_sample_ebpf.o",
"sample_ext",
"test_program_entry",
&EBPF_PROGRAM_TYPE_UNSPECIFIED,
EBPF_VERIFICATION_VERBOSITY_NORMAL,
&report,
&error_message,
&stats);
REQUIRE(result == 1);
REQUIRE(error_message != nullptr);
ebpf_free_string(error_message);
}
static void
_cgroup_load_test(
_In_z_ const char* file,
_In_z_ const char* name,
ebpf_program_type_t& program_type,
ebpf_attach_type_t& attach_type,
ebpf_execution_type_t execution_type)
{
int result;
const char* error_message = nullptr;
fd_t program_fd;
_test_helper_end_to_end test_helper;
test_helper.initialize();
single_instance_hook_t hook(program_type, attach_type);
REQUIRE(hook.initialize() == EBPF_SUCCESS);
program_info_provider_t program_info;
REQUIRE(program_info.initialize(program_type) == EBPF_SUCCESS);
bpf_object_ptr unique_object;
result = ebpf_program_load(file, 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);
bpf_program* program = bpf_object__find_program_by_name(unique_object.get(), name);
REQUIRE(program != nullptr);
uint32_t compartment_id = 0;
REQUIRE(hook.attach(program, &compartment_id, sizeof(compartment_id)) == EBPF_SUCCESS);
REQUIRE(hook.detach(ebpf_fd_invalid, &compartment_id, sizeof(compartment_id)) == EBPF_SUCCESS);
compartment_id = 1;
REQUIRE(hook.attach(program, &compartment_id, sizeof(compartment_id)) == EBPF_SUCCESS);
REQUIRE(hook.detach(program_fd, &compartment_id, sizeof(compartment_id)) == EBPF_SUCCESS);
bpf_object__close(unique_object.release());
}
static void
_cgroup_sock_addr_load_test(
_In_z_ const char* file,
_In_z_ const char* name,
ebpf_attach_type_t& attach_type,
ebpf_execution_type_t execution_type)
{
_cgroup_load_test(file, name, EBPF_PROGRAM_TYPE_CGROUP_SOCK_ADDR, attach_type, execution_type);
}
#define DECLARE_CGROUP_SOCK_ADDR_LOAD_TEST2(file, name, attach_type, name_suffix, file_suffix, execution_type) \
TEST_CASE("cgroup_sockaddr_load_test_" name "_" #attach_type "_" name_suffix, "[cgroup_sock_addr]") \
{ \
_cgroup_sock_addr_load_test(file file_suffix, name, attach_type, execution_type); \
}
#if !defined(CONFIG_BPF_JIT_DISABLED)
#define DECLARE_CGROUP_SOCK_ADDR_LOAD_JIT_TEST(file, name, attach_type) \
DECLARE_CGROUP_SOCK_ADDR_LOAD_TEST2(file, name, attach_type, "jit", ".o", EBPF_EXECUTION_JIT)
#else
#define DECLARE_CGROUP_SOCK_ADDR_LOAD_JIT_TEST(file, name, attach_type)
#endif
#define DECLARE_CGROUP_SOCK_ADDR_LOAD_NATIVE_TEST(file, name, attach_type) \
DECLARE_CGROUP_SOCK_ADDR_LOAD_TEST2(file, name, attach_type, "native", "_um.dll", EBPF_EXECUTION_NATIVE)
#define DECLARE_CGROUP_SOCK_ADDR_LOAD_TEST(file, name, attach_type) \
DECLARE_CGROUP_SOCK_ADDR_LOAD_JIT_TEST(file, name, attach_type) \
DECLARE_CGROUP_SOCK_ADDR_LOAD_NATIVE_TEST(file, name, attach_type)
DECLARE_CGROUP_SOCK_ADDR_LOAD_TEST(
SAMPLE_PATH "cgroup_sock_addr", "authorize_connect4", EBPF_ATTACH_TYPE_CGROUP_INET4_CONNECT);
DECLARE_CGROUP_SOCK_ADDR_LOAD_TEST(
SAMPLE_PATH "cgroup_sock_addr", "authorize_connect6", EBPF_ATTACH_TYPE_CGROUP_INET6_CONNECT);
DECLARE_CGROUP_SOCK_ADDR_LOAD_TEST(
SAMPLE_PATH "cgroup_sock_addr", "authorize_recv_accept4", EBPF_ATTACH_TYPE_CGROUP_INET4_RECV_ACCEPT);
DECLARE_CGROUP_SOCK_ADDR_LOAD_TEST(
SAMPLE_PATH "cgroup_sock_addr", "authorize_recv_accept6", EBPF_ATTACH_TYPE_CGROUP_INET6_RECV_ACCEPT);
#if !defined(CONFIG_BPF_JIT_DISABLED)
TEST_CASE("cgroup_sockops_load_test", "[cgroup_sockops]")
{
_cgroup_load_test(
"sockops.o",
"connection_monitor",
EBPF_PROGRAM_TYPE_SOCK_OPS,
EBPF_ATTACH_TYPE_CGROUP_SOCK_OPS,
EBPF_EXECUTION_JIT);
}
#endif
TEST_CASE("verify_test0", "[sample_extension]")
{
_test_helper_end_to_end test_helper;
test_helper.initialize();
program_info_provider_t sample_extension_program_info;
REQUIRE(sample_extension_program_info.initialize(EBPF_PROGRAM_TYPE_SAMPLE) == EBPF_SUCCESS);
const char* error_message = nullptr;
const char* report = nullptr;
uint32_t result;
ebpf_api_verifier_stats_t stats;
REQUIRE(
(result = ebpf_api_elf_verify_program_from_file(
SAMPLE_PATH "test_sample_ebpf.o",
"sample_ext",
"test_program_entry",
nullptr,
EBPF_VERIFICATION_VERBOSITY_NORMAL,
&report,
&error_message,
&stats),
ebpf_free_string(error_message),
error_message = nullptr,
result == 0));
REQUIRE(report != nullptr);
ebpf_free_string(report);
}
TEST_CASE("verify_test1", "[sample_extension]")
{
_test_helper_end_to_end test_helper;
test_helper.initialize();
program_info_provider_t sample_extension_program_info;
REQUIRE(sample_extension_program_info.initialize(EBPF_PROGRAM_TYPE_SAMPLE) == EBPF_SUCCESS);
const char* error_message = nullptr;
const char* report = nullptr;
uint32_t result;
ebpf_api_verifier_stats_t stats;
REQUIRE(
(result = ebpf_api_elf_verify_program_from_file(
SAMPLE_PATH "test_sample_ebpf.o",
"sample_ext",
nullptr,
nullptr,
EBPF_VERIFICATION_VERBOSITY_NORMAL,
&report,
&error_message,
&stats),
ebpf_free_string(error_message),
error_message = nullptr,
result == 0));
REQUIRE(report != nullptr);
ebpf_free_string(report);
REQUIRE(result == 0);
}
#if !defined(CONFIG_BPF_INTERPRETER_DISABLED)
TEST_CASE("map_pinning_test", "[end_to_end]")
{
_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 bind_program_info;
REQUIRE(bind_program_info.initialize(EBPF_PROGRAM_TYPE_BIND) == EBPF_SUCCESS);
result = ebpf_program_load(
SAMPLE_PATH "bindmonitor.o",
BPF_PROG_TYPE_UNSPEC,
EBPF_EXECUTION_INTERPRET,
&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);
single_instance_hook_t hook(EBPF_PROGRAM_TYPE_BIND, EBPF_ATTACH_TYPE_BIND);
REQUIRE(hook.initialize() == EBPF_SUCCESS);
std::string process_maps_name = "bindmonitor::process_map";
std::string limit_maps_name = "bindmonitor::limits_map";
REQUIRE(bpf_object__find_map_by_name(unique_object.get(), "process_map") != nullptr);
REQUIRE(bpf_object__find_map_by_name(unique_object.get(), "limits_map") != nullptr);
REQUIRE(
bpf_map__pin(bpf_object__find_map_by_name(unique_object.get(), "process_map"), process_maps_name.c_str()) ==
EBPF_SUCCESS);
REQUIRE(
bpf_map__pin(bpf_object__find_map_by_name(unique_object.get(), "limits_map"), limit_maps_name.c_str()) ==
EBPF_SUCCESS);
fd_t fd = bpf_obj_get(process_maps_name.c_str());
REQUIRE(fd != ebpf_fd_invalid);
Platform::_close(fd);
fd = bpf_obj_get(limit_maps_name.c_str());
REQUIRE(fd != ebpf_fd_invalid);
Platform::_close(fd);
REQUIRE(
bpf_map__unpin(bpf_object__find_map_by_name(unique_object.get(), "process_map"), process_maps_name.c_str()) ==
EBPF_SUCCESS);
REQUIRE(
bpf_map__unpin(bpf_object__find_map_by_name(unique_object.get(), "limits_map"), limit_maps_name.c_str()) ==
EBPF_SUCCESS);
REQUIRE(bpf_obj_get(limit_maps_name.c_str()) == -ENOENT);
REQUIRE(bpf_obj_get(process_maps_name.c_str()) == -ENOENT);
bpf_object__close(unique_object.release());
}
#endif
#if !defined(CONFIG_BPF_JIT_DISABLED)
TEST_CASE("enumerate_and_query_programs", "[end_to_end]")
{
_test_helper_end_to_end test_helper;
test_helper.initialize();
uint32_t program_id;
uint32_t next_program_id;
const char* error_message = nullptr;
int result;
const char* file_name = nullptr;
const char* section_name = nullptr;
bpf_object_ptr unique_object[2];
fd_t program_fds[2] = {0};
program_info_provider_t sample_program_info;
REQUIRE(sample_program_info.initialize(EBPF_PROGRAM_TYPE_SAMPLE) == EBPF_SUCCESS);
result = ebpf_program_load(
SAMPLE_PATH "test_sample_ebpf.o",
BPF_PROG_TYPE_UNSPEC,
EBPF_EXECUTION_JIT,
&unique_object[0],
&program_fds[0],
&error_message);
if (error_message) {
printf("ebpf_program_load failed with %s\n", error_message);
ebpf_free((void*)error_message);
}
REQUIRE(result == 0);
result = ebpf_program_load(
SAMPLE_PATH "test_sample_ebpf.o",
BPF_PROG_TYPE_UNSPEC,
EBPF_EXECUTION_INTERPRET,
&unique_object[1],
&program_fds[1],
&error_message);
if (error_message) {
printf("ebpf_program_load failed with %s\n", error_message);
ebpf_free((void*)error_message);
}
REQUIRE(result == 0);
ebpf_execution_type_t type;
program_id = 0;
REQUIRE(bpf_prog_get_next_id(program_id, &next_program_id) == 0);
program_id = next_program_id;
fd_t program_fd = bpf_prog_get_fd_by_id(program_id);
REQUIRE(program_fd > 0);
REQUIRE(ebpf_program_query_info(program_fd, &type, &file_name, &section_name) == EBPF_SUCCESS);
Platform::_close(program_fd);
REQUIRE(type == EBPF_EXECUTION_JIT);
REQUIRE(strcmp(file_name, SAMPLE_PATH "test_sample_ebpf.o") == 0);
ebpf_free_string(file_name);
file_name = nullptr;
REQUIRE(strcmp(section_name, "sample_ext") == 0);
ebpf_free_string(section_name);
section_name = nullptr;
REQUIRE(bpf_prog_get_next_id(program_id, &next_program_id) == 0);
program_id = next_program_id;
program_fd = bpf_prog_get_fd_by_id(program_id);
REQUIRE(program_fd > 0);
REQUIRE(ebpf_program_query_info(program_fd, &type, &file_name, &section_name) == EBPF_SUCCESS);
Platform::_close(program_fd);
REQUIRE(type == EBPF_EXECUTION_INTERPRET);
REQUIRE(strcmp(file_name, SAMPLE_PATH "test_sample_ebpf.o") == 0);
REQUIRE(strcmp(section_name, "sample_ext") == 0);
ebpf_free_string(file_name);
ebpf_free_string(section_name);
file_name = nullptr;
section_name = nullptr;
REQUIRE(bpf_prog_get_next_id(program_id, &next_program_id) == -ENOENT);
for (int i = 0; i < _countof(unique_object); i++) {
bpf_object__close(unique_object[i].release());
}
}
#endif
TEST_CASE("pinned_map_enum", "[end_to_end]")
{
_test_helper_end_to_end test_helper;
test_helper.initialize();
ebpf_test_pinned_map_enum();
}
#if !defined(CONFIG_BPF_JIT_DISABLED)
// This test uses ebpf_link_close() to test implicit detach.
TEST_CASE("implicit_detach", "[end_to_end]")
{
// This test case does the following:
// 1. Close program handle. An implicit detach should happen and program
// object should be deleted.
// 2. Close link handle. The link object should be deleted.
_test_helper_end_to_end test_helper;
test_helper.initialize();
int result = 0;
bpf_object_ptr unique_object;
fd_t program_fd;
const char* error_message = nullptr;
bpf_link* link = nullptr;
single_instance_hook_t hook(EBPF_PROGRAM_TYPE_SAMPLE, EBPF_ATTACH_TYPE_SAMPLE);
REQUIRE(hook.initialize() == EBPF_SUCCESS);
program_info_provider_t sample_program_info;
REQUIRE(sample_program_info.initialize(EBPF_PROGRAM_TYPE_SAMPLE) == EBPF_SUCCESS);
result = ebpf_program_load(
SAMPLE_PATH "test_sample_ebpf.o",
BPF_PROG_TYPE_UNSPEC,
EBPF_EXECUTION_JIT,
&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);
REQUIRE(hook.attach_link(program_fd, nullptr, 0, &link) == EBPF_SUCCESS);
// Call bpf_object__close() which will close the program fd. That should
// detach the program from the hook and unload the program.
bpf_object__close(unique_object.release());
uint32_t program_id;
REQUIRE(bpf_prog_get_next_id(0, &program_id) == -ENOENT);
// Close link handle (without detaching). This should delete the link
// object. ebpf_object_tracking_terminate() which is called when the test
// exits checks if all the objects in EC have been deleted.
hook.close_link(link);
}
// This test uses bpf_link__disconnect() and bpf_link__destroy() to test
// implicit detach.
TEST_CASE("implicit_detach_2", "[end_to_end]")
{
// This test case does the following:
// 1. Close program handle. An implicit detach should happen and the program
// object should be deleted.
// 2. Close link handle. The link object should be deleted.
_test_helper_end_to_end test_helper;
test_helper.initialize();
int result = 0;
bpf_object_ptr unique_object;
fd_t program_fd;
const char* error_message = nullptr;
bpf_link* link = nullptr;
single_instance_hook_t hook(EBPF_PROGRAM_TYPE_SAMPLE, EBPF_ATTACH_TYPE_SAMPLE);
REQUIRE(hook.initialize() == EBPF_SUCCESS);
program_info_provider_t sample_program_info;
REQUIRE(sample_program_info.initialize(EBPF_PROGRAM_TYPE_SAMPLE) == EBPF_SUCCESS);
result = ebpf_program_load(
SAMPLE_PATH "test_sample_ebpf.o",
BPF_PROG_TYPE_UNSPEC,
EBPF_EXECUTION_JIT,
&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);
REQUIRE(hook.attach_link(program_fd, nullptr, 0, &link) == EBPF_SUCCESS);
// Call bpf_object__close() which will close the program fd. That should
// detach the program from the hook and unload the program.
bpf_object__close(unique_object.release());
uint32_t program_id;
REQUIRE(bpf_prog_get_next_id(0, &program_id) == -ENOENT);
// Close link handle (without detaching). This should delete the link
// object. ebpf_object_tracking_terminate() which is called when the test
// exits checks if all the objects in the execution context have been deleted.
bpf_link__disconnect(link);
REQUIRE(bpf_link__destroy(link) == 0);
}
#endif
#if !defined(CONFIG_BPF_INTERPRETER_DISABLED)
TEST_CASE("explicit_detach", "[end_to_end]")
{
// This test case does the following:
// 1. Call detach API and then close the link handle. The link object
// should be deleted.
// 2. Close program handle. The program object should be deleted.
_test_helper_end_to_end test_helper;
test_helper.initialize();
bpf_object_ptr unique_object;
fd_t program_fd;
bpf_link_ptr link;
int result;
const char* error_message = nullptr;
single_instance_hook_t hook(EBPF_PROGRAM_TYPE_SAMPLE, EBPF_ATTACH_TYPE_SAMPLE);
REQUIRE(hook.initialize() == EBPF_SUCCESS);
program_info_provider_t sample_program_info;
REQUIRE(sample_program_info.initialize(EBPF_PROGRAM_TYPE_SAMPLE) == EBPF_SUCCESS);
result = ebpf_program_load(
SAMPLE_PATH "test_sample_ebpf.o",
BPF_PROG_TYPE_UNSPEC,
EBPF_EXECUTION_INTERPRET,
&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);
REQUIRE(hook.attach_link(program_fd, nullptr, 0, &link) == EBPF_SUCCESS);
// Detach and close link handle.
// ebpf_object_tracking_terminate() which is called when the test
// exits checks if all the objects in EC have been deleted.
hook.detach_and_close_link(&link);
// Close program handle.
bpf_object__close(unique_object.release());
uint32_t program_id;
REQUIRE(bpf_prog_get_next_id(0, &program_id) == -ENOENT);
}
TEST_CASE("implicit_explicit_detach", "[end_to_end]")
{
// This test case does the following:
// 1. Close the program handle so that an implicit detach happens.
// 2. Explicitly call detach and then close the link handle. Explicit
// detach in this step should be a no-op.
_test_helper_end_to_end test_helper;
test_helper.initialize();
bpf_object_ptr unique_object;
fd_t program_fd;
bpf_link_ptr link;
int result;
const char* error_message = nullptr;
single_instance_hook_t hook(EBPF_PROGRAM_TYPE_SAMPLE, EBPF_ATTACH_TYPE_SAMPLE);
REQUIRE(hook.initialize() == EBPF_SUCCESS);
program_info_provider_t sample_program_info;
REQUIRE(sample_program_info.initialize(EBPF_PROGRAM_TYPE_SAMPLE) == EBPF_SUCCESS);
result = ebpf_program_load(
SAMPLE_PATH "test_sample_ebpf.o",
BPF_PROG_TYPE_UNSPEC,
EBPF_EXECUTION_INTERPRET,
&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);
REQUIRE(hook.attach_link(program_fd, nullptr, 0, &link) == EBPF_SUCCESS);
// Close program handle. That should detach the program from the hook
// and unload the program.
bpf_object__close(unique_object.release());
uint32_t program_id;
REQUIRE(bpf_prog_get_next_id(0, &program_id) == -ENOENT);
// Detach and close link handle.
// ebpf_object_tracking_terminate() which is called when the test
// exits checks if all the objects in EC have been deleted.
hook.detach_and_close_link(&link);
}
#endif
TEST_CASE("create_map", "[end_to_end]")
{
_test_helper_end_to_end test_helper;
test_helper.initialize();
fd_t map_fd;
uint32_t key = 0;
uint64_t value = 10;
int element_count = 2;
map_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, nullptr, sizeof(uint32_t), sizeof(uint64_t), 5, nullptr);
REQUIRE(map_fd > 0);
for (int i = 0; i < element_count; i++) {
REQUIRE(bpf_map_update_elem(map_fd, &key, &value, EBPF_ANY) == EBPF_SUCCESS);
key++;
value++;
}
key = 0;
value = 10;
for (int i = 0; i < element_count; i++) {
uint64_t read_value;
REQUIRE(bpf_map_lookup_elem(map_fd, &key, &read_value) == EBPF_SUCCESS);
REQUIRE(read_value == value);
key++;
value++;
}
Platform::_close(map_fd);
}
TEST_CASE("create_map_name", "[end_to_end]")
{
_test_helper_end_to_end test_helper;
test_helper.initialize();
fd_t map_fd;
uint32_t key = 0;
uint64_t value = 10;
int element_count = 2;
const char* map_name = "array_map";
map_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, map_name, sizeof(uint32_t), sizeof(uint64_t), 5, nullptr);
REQUIRE(map_fd > 0);
for (int i = 0; i < element_count; i++) {
REQUIRE(bpf_map_update_elem(map_fd, &key, &value, EBPF_ANY) == EBPF_SUCCESS);
key++;
value++;
}
key = 0;
value = 10;
for (int i = 0; i < element_count; i++) {
uint64_t read_value;
REQUIRE(bpf_map_lookup_elem(map_fd, &key, &read_value) == EBPF_SUCCESS);
REQUIRE(read_value == value);
key++;
value++;
}
Platform::_close(map_fd);
}
static void
_xdp_reflect_packet_test(ebpf_execution_type_t execution_type, ADDRESS_FAMILY address_family)
{
_test_helper_end_to_end test_helper;
test_helper.initialize();
single_instance_hook_t hook(EBPF_PROGRAM_TYPE_XDP_TEST, EBPF_ATTACH_TYPE_XDP_TEST);
REQUIRE(hook.initialize() == EBPF_SUCCESS);
program_info_provider_t xdp_program_info;
REQUIRE(xdp_program_info.initialize(EBPF_PROGRAM_TYPE_XDP_TEST) == EBPF_SUCCESS);
uint32_t ifindex = 0;
const char* file_name = (execution_type == EBPF_EXECUTION_NATIVE ? "reflect_packet_um.dll" : "reflect_packet.o");
program_load_attach_helper_t program_helper;
program_helper.initialize(
file_name, BPF_PROG_TYPE_XDP_TEST, "reflect_packet", execution_type, &ifindex, sizeof(ifindex), hook);
// Dummy UDP datagram with fake IP and MAC addresses.
udp_packet_t packet(address_family);
packet.set_destination_port(ntohs(REFLECTION_TEST_PORT));
xdp_md_t ctx{packet.data(), packet.data() + packet.size(), 0, TEST_IFINDEX};
uint32_t hook_result;
REQUIRE(hook.fire(&ctx, &hook_result) == EBPF_SUCCESS);
REQUIRE(hook_result == XDP_TX);
ebpf::ETHERNET_HEADER* ethernet_header = reinterpret_cast<ebpf::ETHERNET_HEADER*>(ctx.data);
REQUIRE(memcmp(ethernet_header->Destination, _test_source_mac.data(), sizeof(ethernet_header->Destination)) == 0);
REQUIRE(memcmp(ethernet_header->Source, _test_destination_mac.data(), sizeof(ethernet_header->Source)) == 0);
if (address_family == AF_INET) {
ebpf::IPV4_HEADER* ipv4_header = reinterpret_cast<ebpf::IPV4_HEADER*>(ethernet_header + 1);
REQUIRE(ipv4_header->SourceAddress == _test_destination_ipv4.s_addr);
REQUIRE(ipv4_header->DestinationAddress == _test_source_ipv4.s_addr);
} else {
ebpf::IPV6_HEADER* ipv6 = reinterpret_cast<ebpf::IPV6_HEADER*>(ethernet_header + 1);
REQUIRE(memcmp(ipv6->SourceAddress, &_test_destination_ipv6, sizeof(ebpf::ipv6_address_t)) == 0);
REQUIRE(memcmp(ipv6->DestinationAddress, &_test_source_ipv6, sizeof(ebpf::ipv6_address_t)) == 0);
}
}
static void
_xdp_reflect_packet_test_v4(ebpf_execution_type_t execution_type)
{
_xdp_reflect_packet_test(execution_type, AF_INET);
}
static void
_xdp_reflect_packet_test_v6(ebpf_execution_type_t execution_type)
{
_xdp_reflect_packet_test(execution_type, AF_INET6);
}
static void
_xdp_encap_reflect_packet_test(ebpf_execution_type_t execution_type, ADDRESS_FAMILY address_family)
{
_test_helper_end_to_end test_helper;
test_helper.initialize();
single_instance_hook_t hook(EBPF_PROGRAM_TYPE_XDP_TEST, EBPF_ATTACH_TYPE_XDP_TEST);
REQUIRE(hook.initialize() == EBPF_SUCCESS);
program_info_provider_t xdp_program_info;
REQUIRE(xdp_program_info.initialize(EBPF_PROGRAM_TYPE_XDP_TEST) == EBPF_SUCCESS);
uint32_t ifindex = 0;
const char* file_name =
(execution_type == EBPF_EXECUTION_NATIVE ? "encap_reflect_packet_um.dll" : "encap_reflect_packet.o");
program_load_attach_helper_t program_helper;
program_helper.initialize(
file_name, BPF_PROG_TYPE_XDP_TEST, "encap_reflect_packet", execution_type, &ifindex, sizeof(ifindex), hook);
// Dummy UDP datagram with fake IP and MAC addresses.
udp_packet_t packet(address_family);
packet.set_destination_port(ntohs(REFLECTION_TEST_PORT));
// Dummy context (not used by the eBPF program).
xdp_md_helper_t ctx(packet.packet());
uint32_t hook_result;
REQUIRE(hook.fire(&ctx, &hook_result) == EBPF_SUCCESS);
REQUIRE(hook_result == XDP_TX);
ebpf::ETHERNET_HEADER* ethernet_header = reinterpret_cast<ebpf::ETHERNET_HEADER*>(ctx.data);
REQUIRE(memcmp(ethernet_header->Destination, _test_source_mac.data(), sizeof(ethernet_header->Destination)) == 0);
REQUIRE(memcmp(ethernet_header->Source, _test_destination_mac.data(), sizeof(ethernet_header->Source)) == 0);
if (address_family == AF_INET) {
ebpf::IPV4_HEADER* ipv4_header = reinterpret_cast<ebpf::IPV4_HEADER*>(ethernet_header + 1);
REQUIRE(ipv4_header->SourceAddress == _test_destination_ipv4.s_addr);
REQUIRE(ipv4_header->DestinationAddress == _test_source_ipv4.s_addr);
REQUIRE(ipv4_header->Protocol == IPPROTO_IPV4);
ebpf::IPV4_HEADER* inner_ipv4_header = reinterpret_cast<ebpf::IPV4_HEADER*>(
reinterpret_cast<uint8_t*>(ipv4_header) + (ipv4_header->HeaderLength * sizeof(uint32_t)));
REQUIRE(inner_ipv4_header->SourceAddress == _test_destination_ipv4.s_addr);
REQUIRE(inner_ipv4_header->DestinationAddress == _test_source_ipv4.s_addr);
} else {
ebpf::IPV6_HEADER* ipv6 = reinterpret_cast<ebpf::IPV6_HEADER*>(ethernet_header + 1);
REQUIRE(memcmp(ipv6->SourceAddress, &_test_destination_ipv6, sizeof(ebpf::ipv6_address_t)) == 0);
REQUIRE(memcmp(ipv6->DestinationAddress, &_test_source_ipv6, sizeof(ebpf::ipv6_address_t)) == 0);
REQUIRE(ipv6->NextHeader == IPPROTO_IPV6);
ebpf::IPV6_HEADER* inner_ipv6 = ipv6 + 1;
REQUIRE(memcmp(inner_ipv6->SourceAddress, &_test_destination_ipv6, sizeof(ebpf::ipv6_address_t)) == 0);
REQUIRE(memcmp(inner_ipv6->DestinationAddress, &_test_source_ipv6, sizeof(ebpf::ipv6_address_t)) == 0);
}
}
static void
_xdp_encap_reflect_packet_test_v4(ebpf_execution_type_t execution_type)
{
_xdp_encap_reflect_packet_test(execution_type, AF_INET);
}
static void
_xdp_encap_reflect_packet_test_v6(ebpf_execution_type_t execution_type)
{
_xdp_encap_reflect_packet_test(execution_type, AF_INET6);
}
#if !defined(CONFIG_BPF_INTERPRETER_DISABLED)
TEST_CASE("printk", "[end_to_end]")
{
_test_helper_end_to_end test_helper;
test_helper.initialize();
single_instance_hook_t hook(EBPF_PROGRAM_TYPE_BIND, EBPF_ATTACH_TYPE_BIND);
REQUIRE(hook.initialize() == EBPF_SUCCESS);
program_info_provider_t bind_program_info;
REQUIRE(bind_program_info.initialize(EBPF_PROGRAM_TYPE_BIND) == EBPF_SUCCESS);
uint32_t ifindex = 0;
program_load_attach_helper_t program_helper;
program_helper.initialize(
SAMPLE_PATH "printk.o", BPF_PROG_TYPE_BIND, "func", EBPF_EXECUTION_INTERPRET, &ifindex, sizeof(ifindex), hook);
// The current bind hook only works with IPv4, so compose a sample IPv4 context.
SOCKADDR_IN addr = {AF_INET};
addr.sin_port = htons(80);
INITIALIZE_BIND_CONTEXT
ctx->process_id = GetCurrentProcessId();
ctx->protocol = 2;
ctx->socket_address_length = sizeof(addr);
memcpy(&ctx->socket_address, &addr, ctx->socket_address_length);
capture_helper_t capture;
std::vector<std::string> output;
uint32_t hook_result = 0;
errno_t error = capture.begin_capture();
if (error == NO_ERROR) {
usersim_trace_logging_set_enabled(true, EBPF_TRACELOG_LEVEL_INFO, EBPF_TRACELOG_KEYWORD_PRINTK);
#pragma warning(suppress : 28193) // hook_fire_result is examined
ebpf_result_t hook_fire_result = hook.fire(ctx, &hook_result);
usersim_trace_logging_set_enabled(false, 0, 0);
output = capture.buffer_to_printk_vector(capture.get_stdout_contents());
REQUIRE(hook_fire_result == EBPF_SUCCESS);
}
std::vector<std::string> expected_output = {
"Hello, world",
"Hello, world",
"PID: " + std::to_string(ctx->process_id) + " using %u",
"PID: " + std::to_string(ctx->process_id) + " using %lu",
"PID: " + std::to_string(ctx->process_id) + " using %llu",
"PID: " + std::to_string(ctx->process_id) + " PROTO: 2",
"PID: " + std::to_string(ctx->process_id) + " PROTO: 2 ADDRLEN: 16",
"100% done"};
REQUIRE(output.size() == expected_output.size());
size_t output_length = 0;
for (int i = 0; i < output.size(); i++) {
REQUIRE(output[i] == expected_output[i]);
output_length += output[i].length();
}
// Six of the printf calls in the program should fail and return -1
// so subtract 6 from the length to get the expected return value.
REQUIRE(hook_result == output_length - 6);
}
#endif
DECLARE_ALL_TEST_CASES("xdp-reflect-v4", "[xdp_tests]", _xdp_reflect_packet_test_v4);
DECLARE_ALL_TEST_CASES("xdp-reflect-v6", "[xdp_tests]", _xdp_reflect_packet_test_v6);
DECLARE_ALL_TEST_CASES("xdp-encap-reflect-v4", "[xdp_tests]", _xdp_encap_reflect_packet_test_v4);
DECLARE_ALL_TEST_CASES("xdp-encap-reflect-v6", "[xdp_tests]", _xdp_encap_reflect_packet_test_v6);
#if !defined(CONFIG_BPF_INTERPRETER_DISABLED) || !defined(CONFIG_BPF_JIT_DISABLED)
static void
_xdp_decapsulate_permit_packet_test(ebpf_execution_type_t execution_type, ADDRESS_FAMILY address_family)
{
_test_helper_end_to_end test_helper;
test_helper.initialize();
single_instance_hook_t hook(EBPF_PROGRAM_TYPE_XDP_TEST, EBPF_ATTACH_TYPE_XDP_TEST);
REQUIRE(hook.initialize() == EBPF_SUCCESS);
program_info_provider_t xdp_program_info;
REQUIRE(xdp_program_info.initialize(EBPF_PROGRAM_TYPE_XDP_TEST) == EBPF_SUCCESS);
uint32_t ifindex = 0;
program_load_attach_helper_t program_helper;
program_helper.initialize(
SAMPLE_PATH "decap_permit_packet.o",
BPF_PROG_TYPE_XDP_TEST,
"decapsulate_permit_packet",
execution_type,
&ifindex,
sizeof(ifindex),
hook);
// Dummy IP in IP packet with fake IP and MAC addresses.
ip_in_ip_packet_t packet(address_family);
size_t offset = sizeof(ebpf::ETHERNET_HEADER);
offset += (address_family == AF_INET) ? sizeof(ebpf::IPV4_HEADER) : sizeof(ebpf::IPV6_HEADER);
uint8_t* inner_ip_header = packet.packet().data() + offset;
std::vector<uint8_t> inner_ip_datagram(inner_ip_header, packet.packet().data() + packet.packet().size());
uint32_t hook_result;
xdp_md_helper_t ctx(packet.packet());
REQUIRE(hook.fire(&ctx, &hook_result) == EBPF_SUCCESS);
REQUIRE(hook_result == XDP_PASS);
ebpf::ETHERNET_HEADER* ethernet_header = reinterpret_cast<ebpf::ETHERNET_HEADER*>(ctx.data);
if (address_family == AF_INET) {
ebpf::IPV4_HEADER* ipv4_header = reinterpret_cast<ebpf::IPV4_HEADER*>(ethernet_header + 1);
REQUIRE(memcmp(ipv4_header, inner_ip_datagram.data(), inner_ip_datagram.size()) == 0);
} else {
ebpf::IPV6_HEADER* ipv6 = reinterpret_cast<ebpf::IPV6_HEADER*>(ethernet_header + 1);
REQUIRE(memcmp(ipv6, inner_ip_datagram.data(), inner_ip_datagram.size()) == 0);
}
}
#endif
#if !defined(CONFIG_BPF_JIT_DISABLED)
TEST_CASE("xdp-decapsulate-permit-v4-jit", "[xdp_tests]")
{
_xdp_decapsulate_permit_packet_test(EBPF_EXECUTION_JIT, AF_INET);
}
TEST_CASE("xdp-decapsulate-permit-v6-jit", "[xdp_tests]")
{
_xdp_decapsulate_permit_packet_test(EBPF_EXECUTION_JIT, AF_INET6);
}
#endif
#if !defined(CONFIG_BPF_INTERPRETER_DISABLED)
TEST_CASE("xdp-decapsulate-permit-v4-interpret", "[xdp_tests]")
{
_xdp_decapsulate_permit_packet_test(EBPF_EXECUTION_INTERPRET, AF_INET);
}
TEST_CASE("xdp-decapsulate-permit-v6-interpret", "[xdp_tests]")
{
_xdp_decapsulate_permit_packet_test(EBPF_EXECUTION_INTERPRET, AF_INET6);
}
TEST_CASE("link_tests", "[end_to_end]")
{
_test_helper_end_to_end test_helper;
test_helper.initialize();
single_instance_hook_t hook(EBPF_PROGRAM_TYPE_SAMPLE, EBPF_ATTACH_TYPE_SAMPLE);
REQUIRE(hook.initialize() == EBPF_SUCCESS);
program_info_provider_t sample_program_info;
REQUIRE(sample_program_info.initialize(EBPF_PROGRAM_TYPE_SAMPLE) == EBPF_SUCCESS);
program_load_attach_helper_t program_helper;
program_helper.initialize(
SAMPLE_PATH "bpf.o", BPF_PROG_TYPE_SAMPLE, "func", EBPF_EXECUTION_INTERPRET, nullptr, 0, hook);
// Dummy context (not used by the eBPF program).
INITIALIZE_SAMPLE_CONTEXT
uint32_t result;
REQUIRE(hook.fire(ctx, &result) == EBPF_SUCCESS);
bpf_program* program = bpf_object__find_program_by_name(program_helper.get_object(), "func");
REQUIRE(program != nullptr);
// Test the case where the provider only permits a single program to be attached.
REQUIRE(hook.attach(program) == EBPF_EXTENSION_FAILED_TO_LOAD);
hook.detach();
}
#endif
static void
_map_reuse_test(ebpf_execution_type_t execution_type)
{
_test_helper_end_to_end test_helper;
test_helper.initialize();
single_instance_hook_t hook(EBPF_PROGRAM_TYPE_SAMPLE, EBPF_ATTACH_TYPE_SAMPLE);
REQUIRE(hook.initialize() == EBPF_SUCCESS);
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 ? "map_reuse_um.dll" : "map_reuse.o");
// First create and pin the maps manually.
int inner_map_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, nullptr, sizeof(__u32), sizeof(__u32), 1, nullptr);
REQUIRE(inner_map_fd > 0);
bpf_map_create_opts opts = {.inner_map_fd = (uint32_t)inner_map_fd};
int outer_map_fd = bpf_map_create(BPF_MAP_TYPE_HASH_OF_MAPS, nullptr, sizeof(__u32), sizeof(fd_t), 1, &opts);
REQUIRE(outer_map_fd > 0);
// Verify we can insert the inner map into the outer map.
__u32 outer_key = 0;
int error = bpf_map_update_elem(outer_map_fd, &outer_key, &inner_map_fd, 0);
REQUIRE(error == 0);
// Pin the outer map.
error = bpf_obj_pin(outer_map_fd, "/ebpf/global/outer_map");
REQUIRE(error == 0);
// The outer map name created above should not have a name.
bpf_map_info info;
uint32_t info_size = sizeof(info);
REQUIRE(bpf_obj_get_info_by_fd(outer_map_fd, &info, &info_size) == 0);
REQUIRE(info.name[0] == 0);
int port_map_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, nullptr, sizeof(__u32), sizeof(__u32), 1, nullptr);
REQUIRE(port_map_fd > 0);
// Pin port map.
error = bpf_obj_pin(port_map_fd, "/ebpf/global/port_map");
REQUIRE(error == 0);
// Add an entry in the inner map.
__u32 key = 0;
__u32 value = 200;
error = bpf_map_update_elem(inner_map_fd, &key, &value, BPF_ANY);
REQUIRE(error == 0);
program_load_attach_helper_t program_helper;
program_helper.initialize(file_name, BPF_PROG_TYPE_SAMPLE, "lookup_update", EBPF_EXECUTION_ANY, nullptr, 0, hook);
// The outer map we created earlier should still not have a name even though there is a name in the file,
// since the unnamed map was reused.
REQUIRE(bpf_obj_get_info_by_fd(outer_map_fd, &info, &info_size) == 0);
REQUIRE(info.name[0] == 0);
INITIALIZE_SAMPLE_CONTEXT
uint32_t hook_result;
REQUIRE(hook.fire(ctx, &hook_result) == EBPF_SUCCESS);
REQUIRE(hook_result == 200);
key = 0;
__u32 port_map_value;
REQUIRE(bpf_map_lookup_elem(port_map_fd, &key, &port_map_value) == EBPF_SUCCESS);
REQUIRE(port_map_value == 200);
REQUIRE(_get_total_map_count() == 4);
Platform::_close(outer_map_fd);
Platform::_close(inner_map_fd);
Platform::_close(port_map_fd);
REQUIRE(ebpf_object_unpin("/ebpf/global/outer_map") == EBPF_SUCCESS);
REQUIRE(ebpf_object_unpin("/ebpf/global/port_map") == EBPF_SUCCESS);
}
DECLARE_JIT_TEST_CASES("map_reuse", "[end_to_end]", _map_reuse_test);
// Try to reuse a map of the wrong type.
static void
_wrong_map_reuse_test(ebpf_execution_type_t execution_type)
{
_test_helper_end_to_end test_helper;
test_helper.initialize();
single_instance_hook_t hook(EBPF_PROGRAM_TYPE_SAMPLE, EBPF_ATTACH_TYPE_SAMPLE);
REQUIRE(hook.initialize() == EBPF_SUCCESS);
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 ? "map_reuse_um.dll" : "map_reuse.o");
// First create and pin the maps manually.
int inner_map_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, nullptr, sizeof(__u32), sizeof(__u32), 1, nullptr);
REQUIRE(inner_map_fd > 0);
bpf_map_create_opts opts = {.inner_map_fd = (uint32_t)inner_map_fd};
int outer_map_fd = bpf_map_create(BPF_MAP_TYPE_HASH_OF_MAPS, nullptr, sizeof(__u32), sizeof(fd_t), 1, &opts);
REQUIRE(outer_map_fd > 0);
// Pin the outer map and port map to the wrong paths so they won't match what is in the ebpf program.
REQUIRE(bpf_obj_pin(outer_map_fd, "/ebpf/global/port_map") == 0);
int port_map_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, nullptr, sizeof(__u32), sizeof(__u32), 1, nullptr);
REQUIRE(port_map_fd > 0);
// Pin port map.
REQUIRE(bpf_obj_pin(port_map_fd, "/ebpf/global/outer_map") == 0);
// Open eBPF program file.
bpf_object_ptr object;
{
bpf_object* local_object = bpf_object__open(file_name);
REQUIRE(local_object != nullptr);
object.reset(local_object);
}
bpf_program* program = bpf_object__next_program(object.get(), nullptr);
REQUIRE(program != nullptr);
// Try to load the program. This should fail because the maps can't be reused.
REQUIRE(bpf_object__load(object.get()) == -EINVAL);
Platform::_close(outer_map_fd);
Platform::_close(inner_map_fd);
Platform::_close(port_map_fd);
REQUIRE(ebpf_object_unpin("/ebpf/global/outer_map") == EBPF_SUCCESS);
REQUIRE(ebpf_object_unpin("/ebpf/global/port_map") == EBPF_SUCCESS);
}
DECLARE_JIT_TEST_CASES("wrong_map_reuse", "[end_to_end]", _wrong_map_reuse_test);
static void
_auto_pinned_maps_test(ebpf_execution_type_t execution_type)
{
_test_helper_end_to_end test_helper;
test_helper.initialize();
single_instance_hook_t hook(EBPF_PROGRAM_TYPE_SAMPLE, EBPF_ATTACH_TYPE_SAMPLE);
REQUIRE(hook.initialize() == EBPF_SUCCESS);
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 ? "map_reuse_um.dll" : "map_reuse.o");
program_load_attach_helper_t program_helper;
program_helper.initialize(file_name, BPF_PROG_TYPE_SAMPLE, "lookup_update", EBPF_EXECUTION_ANY, nullptr, 0, hook);
fd_t outer_map_fd = bpf_obj_get("/ebpf/global/outer_map");
REQUIRE(outer_map_fd > 0);
int inner_map_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, nullptr, sizeof(__u32), sizeof(__u32), 1, nullptr);
REQUIRE(inner_map_fd > 0);
__u32 outer_key = 0;
int error = bpf_map_update_elem(outer_map_fd, &outer_key, &inner_map_fd, 0);
REQUIRE(error == 0);
// Add an entry in the inner map.
__u32 key = 0;
__u32 value = 200;
error = bpf_map_update_elem(inner_map_fd, &key, &value, BPF_ANY);
REQUIRE(error == 0);
fd_t port_map_fd = bpf_obj_get("/ebpf/global/port_map");
REQUIRE(port_map_fd > 0);
INITIALIZE_SAMPLE_CONTEXT
uint32_t hook_result;
REQUIRE(hook.fire(ctx, &hook_result) == EBPF_SUCCESS);
REQUIRE(hook_result == 200);
key = 0;
__u32 port_map_value;
REQUIRE(bpf_map_lookup_elem(port_map_fd, &key, &port_map_value) == EBPF_SUCCESS);
REQUIRE(port_map_value == 200);
Platform::_close(outer_map_fd);
Platform::_close(inner_map_fd);
Platform::_close(port_map_fd);
REQUIRE(ebpf_object_unpin("/ebpf/global/outer_map") == EBPF_SUCCESS);
REQUIRE(ebpf_object_unpin("/ebpf/global/port_map") == EBPF_SUCCESS);
}
DECLARE_JIT_TEST_CASES("auto_pinned_maps", "[end_to_end]", _auto_pinned_maps_test);
#if !defined(CONFIG_BPF_JIT_DISABLED)
TEST_CASE("auto_pinned_maps_custom_path", "[end_to_end]")
{
_test_helper_end_to_end test_helper;
test_helper.initialize();
single_instance_hook_t hook(EBPF_PROGRAM_TYPE_SAMPLE, EBPF_ATTACH_TYPE_SAMPLE);
REQUIRE(hook.initialize() == EBPF_SUCCESS);
program_info_provider_t sample_program_info;
REQUIRE(sample_program_info.initialize(EBPF_PROGRAM_TYPE_SAMPLE) == EBPF_SUCCESS);
struct bpf_object_open_opts opts = {0};
opts.pin_root_path = "/custompath/global";
bpf_object_ptr object;
{
struct bpf_object* local_object = bpf_object__open_file("map_reuse.o", &opts);
REQUIRE(local_object != nullptr);
object.reset(local_object);
}
// Load the program.
REQUIRE(bpf_object__load(object.get()) == 0);
struct bpf_program* program = bpf_object__find_program_by_name(object.get(), "lookup_update");
REQUIRE(program != nullptr);
// Attach should now succeed.
bpf_link_ptr link;
{
struct bpf_link* local_link = bpf_program__attach(program);
REQUIRE(local_link != nullptr);
link.reset(local_link);
}
fd_t outer_map_fd = bpf_obj_get("/custompath/global/outer_map");
REQUIRE(outer_map_fd > 0);
int inner_map_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, nullptr, sizeof(__u32), sizeof(__u32), 1, nullptr);
REQUIRE(inner_map_fd > 0);
__u32 outer_key = 0;
int error = bpf_map_update_elem(outer_map_fd, &outer_key, &inner_map_fd, 0);
REQUIRE(error == 0);
// Add an entry in the inner map.
__u32 key = 0;
__u32 value = 200;
error = bpf_map_update_elem(inner_map_fd, &key, &value, BPF_ANY);
REQUIRE(error == 0);
fd_t port_map_fd = bpf_obj_get("/custompath/global/port_map");
REQUIRE(port_map_fd > 0);
INITIALIZE_SAMPLE_CONTEXT
uint32_t hook_result;
REQUIRE(hook.fire(ctx, &hook_result) == EBPF_SUCCESS);
REQUIRE(hook_result == 200);
key = 0;
__u32 port_map_value;
REQUIRE(bpf_map_lookup_elem(port_map_fd, &key, &port_map_value) == EBPF_SUCCESS);
REQUIRE(port_map_value == 200);
REQUIRE(_get_total_map_count() == 4);
Platform::_close(outer_map_fd);
Platform::_close(inner_map_fd);
Platform::_close(port_map_fd);
REQUIRE(ebpf_object_unpin("/custompath/global/outer_map") == EBPF_SUCCESS);
REQUIRE(ebpf_object_unpin("/custompath/global/port_map") == EBPF_SUCCESS);
}
#endif
static void
_map_reuse_invalid_test(ebpf_execution_type_t execution_type)
{
_test_helper_end_to_end test_helper;
test_helper.initialize();
single_instance_hook_t hook(EBPF_PROGRAM_TYPE_SAMPLE, EBPF_ATTACH_TYPE_SAMPLE);
REQUIRE(hook.initialize() == EBPF_SUCCESS);
program_info_provider_t sample_program_info;
REQUIRE(sample_program_info.initialize(EBPF_PROGRAM_TYPE_SAMPLE) == EBPF_SUCCESS);
// Create and pin a map with a different map type than in ELF file.
int map_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, nullptr, sizeof(__u32), sizeof(__u32), 1, nullptr);
REQUIRE(map_fd > 0);
// Pin the map.
int error = bpf_obj_pin(map_fd, "/ebpf/global/outer_map");
REQUIRE(error == 0);
int port_map_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, nullptr, sizeof(__u32), sizeof(__u32), 1, nullptr);
REQUIRE(port_map_fd > 0);
// Pin port map.
error = bpf_obj_pin(port_map_fd, "/ebpf/global/port_map");
REQUIRE(error == 0);
// Load BPF object from ELF file. Loading the program should fail as the
// map type for map pinned at "/ebpf/global/outer_map" does not match.
bpf_object_ptr unique_object;
fd_t program_fd;
const char* file_name = (execution_type == EBPF_EXECUTION_NATIVE ? "map_reuse_um.dll" : "map_reuse.o");
int result =
ebpf_program_load(file_name, BPF_PROG_TYPE_SAMPLE, EBPF_EXECUTION_ANY, &unique_object, &program_fd, nullptr);
REQUIRE(result == -EINVAL);
Platform::_close(map_fd);
Platform::_close(port_map_fd);
REQUIRE(ebpf_object_unpin("/ebpf/global/outer_map") == EBPF_SUCCESS);
REQUIRE(ebpf_object_unpin("/ebpf/global/port_map") == EBPF_SUCCESS);
}
DECLARE_JIT_TEST_CASES("map_reuse_invalid", "[end_to_end]", _map_reuse_invalid_test);
static void
_map_reuse_2_test(ebpf_execution_type_t execution_type)
{
_test_helper_end_to_end test_helper;
test_helper.initialize();
single_instance_hook_t hook(EBPF_PROGRAM_TYPE_SAMPLE, EBPF_ATTACH_TYPE_SAMPLE);
REQUIRE(hook.initialize() == EBPF_SUCCESS);
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 ? "map_reuse_2_um.dll" : "map_reuse_2.o");
// First create and pin the maps manually.
int inner_map_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, nullptr, sizeof(__u32), sizeof(__u32), 1, nullptr);
REQUIRE(inner_map_fd > 0);
bpf_map_create_opts opts = {.inner_map_fd = (uint32_t)inner_map_fd};
int outer_map_fd = bpf_map_create(BPF_MAP_TYPE_HASH_OF_MAPS, nullptr, sizeof(__u32), sizeof(fd_t), 1, &opts);
REQUIRE(outer_map_fd > 0);
// Verify we can insert the inner map into the outer map.
__u32 outer_key = 0;
int error = bpf_map_update_elem(outer_map_fd, &outer_key, &inner_map_fd, 0);
REQUIRE(error == 0);
// Pin the outer map.
error = bpf_obj_pin(outer_map_fd, "/ebpf/global/outer_map");
REQUIRE(error == 0);
int port_map_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, nullptr, sizeof(__u32), sizeof(__u32), 1, nullptr);
REQUIRE(outer_map_fd > 0);
// Pin port map.
error = bpf_obj_pin(port_map_fd, "/ebpf/global/port_map");
REQUIRE(error == 0);
// Add an entry in the inner map.
__u32 key = 0;
__u32 value = 200;
error = bpf_map_update_elem(inner_map_fd, &key, &value, BPF_ANY);
REQUIRE(error == 0);
program_load_attach_helper_t program_helper;
program_helper.initialize(file_name, BPF_PROG_TYPE_SAMPLE, "lookup_update", EBPF_EXECUTION_ANY, nullptr, 0, hook);
INITIALIZE_SAMPLE_CONTEXT
uint32_t hook_result;
REQUIRE(hook.fire(ctx, &hook_result) == EBPF_SUCCESS);
REQUIRE(hook_result == 200);
key = 0;
__u32 port_map_value;
REQUIRE(bpf_map_lookup_elem(port_map_fd, &key, &port_map_value) == EBPF_SUCCESS);
REQUIRE(port_map_value == 200);
REQUIRE(_get_total_map_count() == 4);
Platform::_close(outer_map_fd);
Platform::_close(inner_map_fd);
Platform::_close(port_map_fd);
// The below two objects were pinned by this UM test.
REQUIRE(ebpf_object_unpin("/ebpf/global/outer_map") == EBPF_SUCCESS);
REQUIRE(ebpf_object_unpin("/ebpf/global/port_map") == EBPF_SUCCESS);
// This object was auto-pinned while loading the program.
REQUIRE(ebpf_object_unpin("/ebpf/global/inner_map") == EBPF_SUCCESS);
}
DECLARE_JIT_TEST_CASES("map_reuse_2", "[end_to_end]", _map_reuse_2_test);
static void
_map_reuse_3_test(ebpf_execution_type_t execution_type)
{
_test_helper_end_to_end test_helper;
test_helper.initialize();
single_instance_hook_t hook(EBPF_PROGRAM_TYPE_SAMPLE, EBPF_ATTACH_TYPE_SAMPLE);
REQUIRE(hook.initialize() == EBPF_SUCCESS);
program_info_provider_t sample_program_info;
REQUIRE(sample_program_info.initialize(EBPF_PROGRAM_TYPE_SAMPLE) == EBPF_SUCCESS);
// First create and pin the maps manually.
int inner_map_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, nullptr, sizeof(__u32), sizeof(__u32), 1, nullptr);
REQUIRE(inner_map_fd > 0);
bpf_map_create_opts opts = {.inner_map_fd = (uint32_t)inner_map_fd};
int outer_map_fd = bpf_map_create(BPF_MAP_TYPE_HASH_OF_MAPS, nullptr, sizeof(__u32), sizeof(fd_t), 1, &opts);
REQUIRE(outer_map_fd > 0);
// Verify we can insert the inner map into the outer map.
__u32 outer_key = 0;
int error = bpf_map_update_elem(outer_map_fd, &outer_key, &inner_map_fd, 0);
REQUIRE(error == 0);
// Pin the outer map.
error = bpf_obj_pin(outer_map_fd, "/ebpf/global/outer_map");
REQUIRE(error == 0);
// Pin the inner map.
error = bpf_obj_pin(inner_map_fd, "/ebpf/global/inner_map");
REQUIRE(error == 0);
int port_map_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, nullptr, sizeof(__u32), sizeof(__u32), 1, nullptr);
REQUIRE(outer_map_fd > 0);
// Pin port map.
error = bpf_obj_pin(port_map_fd, "/ebpf/global/port_map");
REQUIRE(error == 0);
// Add an entry in the inner map.
__u32 key = 0;
__u32 value = 200;
error = bpf_map_update_elem(inner_map_fd, &key, &value, BPF_ANY);
REQUIRE(error == 0);
const char* file_name = (execution_type == EBPF_EXECUTION_NATIVE ? "map_reuse_2_um.dll" : "map_reuse_2.o");
program_load_attach_helper_t program_helper;
program_helper.initialize(file_name, BPF_PROG_TYPE_SAMPLE, "lookup_update", EBPF_EXECUTION_ANY, nullptr, 0, hook);
INITIALIZE_SAMPLE_CONTEXT
uint32_t hook_result;
REQUIRE(hook.fire(ctx, &hook_result) == EBPF_SUCCESS);
REQUIRE(hook_result == 200);
key = 0;
__u32 port_map_value;
REQUIRE(bpf_map_lookup_elem(port_map_fd, &key, &port_map_value) == EBPF_SUCCESS);
REQUIRE(port_map_value == 200);
REQUIRE(_get_total_map_count() == 3);
Platform::_close(outer_map_fd);
Platform::_close(inner_map_fd);
Platform::_close(port_map_fd);
REQUIRE(ebpf_object_unpin("/ebpf/global/outer_map") == EBPF_SUCCESS);
REQUIRE(ebpf_object_unpin("/ebpf/global/inner_map") == EBPF_SUCCESS);
REQUIRE(ebpf_object_unpin("/ebpf/global/port_map") == EBPF_SUCCESS);
}
DECLARE_JIT_TEST_CASES("map_reuse_3", "[end_to_end]", _map_reuse_3_test);
static void
_create_service_helper(
_In_z_ const wchar_t* file_name,
_In_z_ const wchar_t* service_name,
_In_ const GUID* provider_module_id,
_Out_ SC_HANDLE* service_handle)
{
std::wstring parameters_path(PARAMETERS_PATH_PREFIX);
REQUIRE(Platform::_create_service(service_name, file_name, service_handle) == ERROR_SUCCESS);
parameters_path = parameters_path + service_name + L"\\" + SERVICE_PARAMETERS;
REQUIRE(Platform::_create_registry_key(HKEY_LOCAL_MACHINE, parameters_path.c_str()) == ERROR_SUCCESS);
REQUIRE(
Platform::_update_registry_value(
HKEY_LOCAL_MACHINE, parameters_path.c_str(), REG_BINARY, NPI_MODULE_ID, provider_module_id, sizeof(GUID)) ==
ERROR_SUCCESS);
}
#if !defined(CONFIG_BPF_JIT_DISABLED) || !defined(CONFIG_BPF_INTERPRETER_DISABLED)
TEST_CASE("ebpf_program_load_bytes-name-gen", "[end-to-end]")
{
_test_helper_end_to_end test_helper;
test_helper.initialize();
single_instance_hook_t hook(EBPF_PROGRAM_TYPE_SAMPLE, EBPF_ATTACH_TYPE_SAMPLE);
REQUIRE(hook.initialize() == EBPF_SUCCESS);
program_info_provider_t sample_program_info;
REQUIRE(sample_program_info.initialize(EBPF_PROGRAM_TYPE_SAMPLE) == EBPF_SUCCESS);
// Try with a valid set of instructions.
struct ebpf_inst instructions[] = {
{0xb7, R0_RETURN_VALUE, 0}, // r0 = 0
{INST_OP_EXIT}, // return r0
};
uint32_t insn_cnt = _countof(instructions);
const bpf_prog_type_t prog_type = BPF_PROG_TYPE_SAMPLE;
const ebpf_program_type_t* program_type = ebpf_get_ebpf_program_type(prog_type);
REQUIRE(program_type != nullptr);
REQUIRE(insn_cnt != 0);
fd_t program_fd;
#pragma warning(suppress : 28193) // result is examined
ebpf_result_t result = ebpf_program_load_bytes(
program_type,
nullptr,
EBPF_EXECUTION_ANY,
reinterpret_cast<const ebpf_inst*>(instructions),
insn_cnt,
nullptr,
0,
&program_fd);
REQUIRE(result == EBPF_SUCCESS);
REQUIRE(program_fd >= 0);
// Now query the program info and verify it matches what we set.
bpf_prog_info program_info = {};
uint32_t program_info_size = sizeof(program_info);
REQUIRE(bpf_obj_get_info_by_fd(program_fd, &program_info, &program_info_size) == 0);
REQUIRE(program_info_size == sizeof(program_info));
REQUIRE(program_info.nr_map_ids == 0);
REQUIRE(program_info.map_ids == 0);
REQUIRE(program_info.name != NULL);
// Name should contain SHA256 hash in hex (minus last char to stay under BPF_OBJ_NAME_LEN).
REQUIRE(strlen(program_info.name) == 63);
REQUIRE(program_info.type == prog_type);
Platform::_close(program_fd);
}
#endif
// Load a native module with non-existing driver.
TEST_CASE("load_native_program_negative", "[end-to-end]")
{
_test_helper_end_to_end test_helper;
test_helper.initialize();
GUID provider_module_id;
SC_HANDLE service_handle = nullptr;
std::wstring service_path(SERVICE_PATH_PREFIX);
ebpf_handle_t module_handle = ebpf_handle_invalid;
size_t count_of_maps = 0;
size_t count_of_programs = 0;
set_native_module_failures(true);
REQUIRE(UuidCreate(&provider_module_id) == RPC_S_OK);
// Creating valid service with non-existing driver.
_create_service_helper(L"fake_program.dll", NATIVE_DRIVER_SERVICE_NAME, &provider_module_id, &service_handle);
// Load native module. It should fail.
service_path = service_path + NATIVE_DRIVER_SERVICE_NAME;
REQUIRE(
test_ioctl_load_native_module(
service_path, &provider_module_id, &module_handle, &count_of_maps, &count_of_programs) ==
ERROR_PATH_NOT_FOUND);
}
// Load native module by passing invalid service name to EC.
TEST_CASE("load_native_program_negative2", "[end-to-end]")
{
_test_helper_end_to_end test_helper;
test_helper.initialize();
GUID provider_module_id;
SC_HANDLE service_handle = nullptr;
std::wstring service_path(L"");
ebpf_handle_t module_handle = ebpf_handle_invalid;
size_t count_of_maps = 0;
size_t count_of_programs = 0;
set_native_module_failures(true);
REQUIRE(UuidCreate(&provider_module_id) == RPC_S_OK);
_create_service_helper(
L"test_sample_ebpf_um.dll", NATIVE_DRIVER_SERVICE_NAME, &provider_module_id, &service_handle);
// Create invalid service path and pass to EC.
service_path += NATIVE_DRIVER_SERVICE_NAME_2;
// Load native module. It should fail.
REQUIRE(
test_ioctl_load_native_module(
service_path, &provider_module_id, &module_handle, &count_of_maps, &count_of_programs) ==
ERROR_PATH_NOT_FOUND);
}
// Load native module and then try to reload the same module.
TEST_CASE("load_native_program_negative3", "[end-to-end]")
{
#define MAP_COUNT 2
#define PROGRAM_COUNT 1
_test_helper_end_to_end test_helper;
test_helper.initialize();
GUID provider_module_id = GUID_NULL;
std::wstring service_path(SERVICE_PATH_PREFIX);
size_t count_of_maps = 0;
size_t count_of_programs = 0;
int error;
const char* error_message = nullptr;
bpf_object_ptr unique_object;
fd_t program_fd;
std::wstring file_path(L"test_sample_ebpf_um.dll");
const wchar_t* service_name = nullptr;
ebpf_handle_t module_handle = ebpf_handle_invalid;
ebpf_handle_t map_handles[MAP_COUNT];
ebpf_handle_t program_handles[PROGRAM_COUNT];
program_info_provider_t sample_program_info;
REQUIRE(sample_program_info.initialize(EBPF_PROGRAM_TYPE_SAMPLE) == EBPF_SUCCESS);
// Load a valid native module.
error = ebpf_program_load(
"test_sample_ebpf_um.dll",
BPF_PROG_TYPE_UNSPEC,
EBPF_EXECUTION_NATIVE,
&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(error == 0);
// Get the service name that was created.
REQUIRE(get_service_details_for_file(file_path, &service_name, &provider_module_id) == EBPF_SUCCESS);
set_native_module_failures(true);
// Try to reload the same native module. It should fail.
service_path = service_path + service_name;
REQUIRE(
test_ioctl_load_native_module(
service_path, &provider_module_id, &module_handle, &count_of_maps, &count_of_programs) ==
ERROR_OBJECT_ALREADY_EXISTS);
// Try to load the programs from the same module again. It should fail.
REQUIRE(
test_ioctl_load_native_programs(&provider_module_id, MAP_COUNT, map_handles, PROGRAM_COUNT, program_handles) ==
ERROR_OBJECT_ALREADY_EXISTS);
bpf_object__close(unique_object.release());
// Now that we have closed the object, try to load programs from the same module again. This should
// fail as the module should now be marked as "unloading".
REQUIRE(
test_ioctl_load_native_programs(&provider_module_id, MAP_COUNT, map_handles, PROGRAM_COUNT, program_handles) !=
ERROR_SUCCESS);
}
// Load native module and then try to load programs with incorrect params.
TEST_CASE("load_native_program_negative4", "[end-to-end]")
{
#define PROGRAM_COUNT 1
_test_helper_end_to_end test_helper;
test_helper.initialize();
GUID provider_module_id = GUID_NULL;
SC_HANDLE service_handle = nullptr;
std::wstring service_path(SERVICE_PATH_PREFIX);
size_t count_of_maps = 0;
size_t count_of_programs = 0;
std::wstring file_path(L"test_sample_ebpf_um.dll");
_test_handle_helper module_handle;
ebpf_handle_t program_handles[PROGRAM_COUNT];
REQUIRE(UuidCreate(&provider_module_id) == RPC_S_OK);
// First try to load native program without loading the native module.
REQUIRE(
test_ioctl_load_native_programs(&provider_module_id, 0, nullptr, PROGRAM_COUNT, program_handles) ==
ERROR_PATH_NOT_FOUND);
// Creating valid service with valid driver.
_create_service_helper(
L"test_sample_ebpf_um.dll", NATIVE_DRIVER_SERVICE_NAME, &provider_module_id, &service_handle);
// Load native module. It should succeed.
service_path = service_path + NATIVE_DRIVER_SERVICE_NAME;
REQUIRE(
test_ioctl_load_native_module(
service_path,
&provider_module_id,
module_handle.get_handle_pointer(),
&count_of_maps,
&count_of_programs) == ERROR_SUCCESS);
// Try to load the programs by passing wrong map and program handles size. This should fail.
REQUIRE(
test_ioctl_load_native_programs(&provider_module_id, 0, nullptr, PROGRAM_COUNT, program_handles) ==
ERROR_INVALID_PARAMETER);
// Delete the created service.
Platform::_delete_service(service_handle);
}
// Try to load a .sys in user mode.
TEST_CASE("load_native_program_negative5", "[end_to_end]")
{
_test_helper_end_to_end test_helper;
test_helper.initialize();
int result;
const char* error_message = nullptr;
bpf_object_ptr unique_object;
fd_t program_fd;
single_instance_hook_t hook(EBPF_PROGRAM_TYPE_SAMPLE, EBPF_ATTACH_TYPE_SAMPLE);
REQUIRE(hook.initialize() == EBPF_SUCCESS);
program_info_provider_t sample_program_info;
REQUIRE(sample_program_info.initialize(EBPF_PROGRAM_TYPE_SAMPLE) == EBPF_SUCCESS);
set_native_module_failures(true);
result = ebpf_program_load(
"map.sys", BPF_PROG_TYPE_UNSPEC, EBPF_EXECUTION_ANY, &unique_object, &program_fd, &error_message);
REQUIRE(result == -ENOENT);
}
// Load native module twice.
TEST_CASE("load_native_program_negative6", "[end-to-end]")
{
_test_helper_end_to_end test_helper;
test_helper.initialize();
GUID provider_module_id;
SC_HANDLE service_handle = nullptr;
SC_HANDLE service_handle2 = nullptr;
std::wstring service_path(SERVICE_PATH_PREFIX);
std::wstring service_path2(SERVICE_PATH_PREFIX);
_test_handle_helper module_handle;
_test_handle_helper module_handle2;
size_t count_of_maps = 0;
size_t count_of_programs = 0;
set_native_module_failures(true);
REQUIRE(UuidCreate(&provider_module_id) == RPC_S_OK);
// Create a valid service with valid driver.
_create_service_helper(
L"test_sample_ebpf_um.dll", NATIVE_DRIVER_SERVICE_NAME, &provider_module_id, &service_handle);
// Load native module. It should succeed.
service_path = service_path + NATIVE_DRIVER_SERVICE_NAME;
REQUIRE(
test_ioctl_load_native_module(
service_path,
&provider_module_id,
module_handle.get_handle_pointer(),
&count_of_maps,
&count_of_programs) == ERROR_SUCCESS);
// Create a new service with same driver and same module id.
_create_service_helper(
L"test_sample_ebpf_um.dll", NATIVE_DRIVER_SERVICE_NAME_2, &provider_module_id, &service_handle2);
set_native_module_failures(true);
// Load native module. It should fail.
service_path2 = service_path2 + NATIVE_DRIVER_SERVICE_NAME_2;
REQUIRE(
test_ioctl_load_native_module(
service_path2,
&provider_module_id,
module_handle2.get_handle_pointer(),
&count_of_maps,
&count_of_programs) == ERROR_OBJECT_ALREADY_EXISTS);
}
// Load native module and then use module handle for a different purpose.
TEST_CASE("native_module_handle_test_negative", "[end-to-end]")
{
_test_helper_end_to_end test_helper;
test_helper.initialize();
GUID provider_module_id;
SC_HANDLE service_handle = nullptr;
std::wstring service_path(SERVICE_PATH_PREFIX);
ebpf_handle_t module_handle = ebpf_handle_invalid;
size_t count_of_maps = 0;
size_t count_of_programs = 0;
set_native_module_failures(true);
REQUIRE(UuidCreate(&provider_module_id) == RPC_S_OK);
// Create a valid service with valid driver.
_create_service_helper(
L"test_sample_ebpf_um.dll", NATIVE_DRIVER_SERVICE_NAME, &provider_module_id, &service_handle);
// Load native module. It should succeed.
service_path = service_path + NATIVE_DRIVER_SERVICE_NAME;
REQUIRE(
test_ioctl_load_native_module(
service_path, &provider_module_id, &module_handle, &count_of_maps, &count_of_programs) == ERROR_SUCCESS);
// Create an fd for the module handle.
fd_t module_fd = Platform::_open_osfhandle(module_handle, 0);
REQUIRE(module_fd != ebpf_fd_invalid);
// Try to use the native module fd as a program or map fd.
bpf_prog_info program_info = {};
uint32_t program_info_size = sizeof(program_info);
REQUIRE(bpf_obj_get_info_by_fd(module_fd, &program_info, &program_info_size) == -EINVAL);
Platform::_close(module_fd);
}
TEST_CASE("ebpf_get_program_type_by_name invalid name", "[end-to-end]")
{
_test_helper_end_to_end test_helper;
test_helper.initialize();
ebpf_program_type_t program_type;
ebpf_attach_type_t attach_type;
REQUIRE(ebpf_get_program_type_by_name("invalid_name", &program_type, &attach_type) == EBPF_KEY_NOT_FOUND);
// Now set verification in progress and try again.
set_verification_in_progress(true);
REQUIRE(ebpf_get_program_type_by_name("invalid_name", &program_type, &attach_type) == EBPF_KEY_NOT_FOUND);
}
TEST_CASE("ebpf_get_program_type_name invalid types", "[end-to-end]")
{
_test_helper_end_to_end test_helper;
test_helper.initialize();
ebpf_program_type_t program_type = EBPF_PROGRAM_TYPE_UNSPECIFIED;
// First try with EBPF_PROGRAM_TYPE_UNSPECIFIED.
const char* name1 = ebpf_get_program_type_name(&program_type);
REQUIRE(name1 == nullptr);
// Try with a random program type GUID.
REQUIRE(UuidCreate(&program_type) == RPC_S_OK);
const char* name2 = ebpf_get_program_type_name(&program_type);
REQUIRE(name2 == nullptr);
}
TEST_CASE("get_ebpf_attach_type", "[end_to_end]")
{
_test_helper_end_to_end test_helper;
test_helper.initialize();
// First test a valid input.
const ebpf_attach_type_t* attach_type = get_ebpf_attach_type(BPF_ATTACH_TYPE_BIND);
REQUIRE(attach_type != nullptr);
REQUIRE(IsEqualGUID(*attach_type, EBPF_ATTACH_TYPE_BIND) != 0);
// Try with BPF_ATTACH_TYPE_UNSPEC.
REQUIRE(get_ebpf_attach_type(BPF_ATTACH_TYPE_UNSPEC) == nullptr);
// Try with invalid bpf attach type.
REQUIRE(get_ebpf_attach_type((bpf_attach_type_t)BPF_ATTACH_TYPE_INVALID) == nullptr);
}
TEST_CASE("get_bpf_program_type", "[end_to_end]")
{
_test_helper_end_to_end test_helper;
test_helper.initialize();
// First test a valid input.
REQUIRE(get_bpf_program_type(&EBPF_PROGRAM_TYPE_SAMPLE) == BPF_PROG_TYPE_SAMPLE);
// Try with EBPF_PROGRAM_TYPE_UNSPECIFIED.
REQUIRE(get_bpf_program_type(&EBPF_PROGRAM_TYPE_UNSPECIFIED) == BPF_PROG_TYPE_UNSPEC);
// Try with invalid program type.
GUID invalid_program_type;
REQUIRE(UuidCreate(&invalid_program_type) == RPC_S_OK);
REQUIRE(get_bpf_program_type(&invalid_program_type) == BPF_PROG_TYPE_UNSPEC);
}
TEST_CASE("ebpf_get_ebpf_program_type", "[end_to_end]")
{
_test_helper_end_to_end test_helper;
test_helper.initialize();
// Try with BPF_PROG_TYPE_UNSPEC.
const ebpf_program_type_t* program_type = ebpf_get_ebpf_program_type(BPF_PROG_TYPE_UNSPEC);
REQUIRE(program_type != nullptr);
REQUIRE(IsEqualGUID(EBPF_PROGRAM_TYPE_UNSPECIFIED, *program_type) != 0);
// Try a valid bpf prog type.
program_type = ebpf_get_ebpf_program_type(BPF_PROG_TYPE_SAMPLE);
REQUIRE(program_type != nullptr);
REQUIRE(IsEqualGUID(EBPF_PROGRAM_TYPE_SAMPLE, *program_type) != 0);
// Try an invalid bpf prog type.
program_type = ebpf_get_ebpf_program_type((bpf_prog_type_t)BPF_PROG_TYPE_INVALID);
REQUIRE(program_type == nullptr);
}
TEST_CASE("get_bpf_attach_type", "[end_to_end]")
{
_test_helper_end_to_end test_helper;
test_helper.initialize();
// Try with EBPF_ATTACH_TYPE_SAMPLE.
REQUIRE(get_bpf_attach_type(&EBPF_ATTACH_TYPE_SAMPLE) == BPF_ATTACH_TYPE_SAMPLE);
// Try with EBPF_ATTACH_TYPE_UNSPECIFIED.
REQUIRE(get_bpf_attach_type(&EBPF_ATTACH_TYPE_UNSPECIFIED) == BPF_ATTACH_TYPE_UNSPEC);
// Try with invalid attach type.
GUID invalid_attach_type;
REQUIRE(UuidCreate(&invalid_attach_type) == RPC_S_OK);
REQUIRE(get_bpf_attach_type(&invalid_attach_type) == BPF_ATTACH_TYPE_UNSPEC);
}
TEST_CASE("test_ebpf_object_set_execution_type", "[end_to_end]")
{
_test_helper_end_to_end test_helper;
test_helper.initialize();
// First open a .dll file
bpf_object* native_object = bpf_object__open("test_sample_ebpf_um.dll");
REQUIRE(native_object != nullptr);
// Try to set incorrect execution type.
REQUIRE(ebpf_object_set_execution_type(native_object, EBPF_EXECUTION_JIT) == EBPF_INVALID_ARGUMENT);
REQUIRE(ebpf_object_set_execution_type(native_object, EBPF_EXECUTION_INTERPRET) == EBPF_INVALID_ARGUMENT);
// The following should succeed.
REQUIRE(ebpf_object_set_execution_type(native_object, EBPF_EXECUTION_ANY) == EBPF_SUCCESS);
REQUIRE(ebpf_object_get_execution_type(native_object) == EBPF_EXECUTION_NATIVE);
REQUIRE(ebpf_object_set_execution_type(native_object, EBPF_EXECUTION_NATIVE) == EBPF_SUCCESS);
REQUIRE(ebpf_object_get_execution_type(native_object) == EBPF_EXECUTION_NATIVE);
bpf_object__close(native_object);
// Open a .o file
bpf_object* jit_object = bpf_object__open("test_sample_ebpf.o");
REQUIRE(jit_object != nullptr);
// Try to set incorrect execution type.
REQUIRE(ebpf_object_set_execution_type(jit_object, EBPF_EXECUTION_NATIVE) == EBPF_INVALID_ARGUMENT);
// The following should succeed.
REQUIRE(ebpf_object_set_execution_type(jit_object, EBPF_EXECUTION_ANY) == EBPF_SUCCESS);
REQUIRE(ebpf_object_get_execution_type(jit_object) == EBPF_EXECUTION_JIT);
REQUIRE(ebpf_object_set_execution_type(jit_object, EBPF_EXECUTION_JIT) == EBPF_SUCCESS);
REQUIRE(ebpf_object_get_execution_type(jit_object) == EBPF_EXECUTION_JIT);
REQUIRE(ebpf_object_set_execution_type(jit_object, EBPF_EXECUTION_INTERPRET) == EBPF_SUCCESS);
REQUIRE(ebpf_object_get_execution_type(jit_object) == EBPF_EXECUTION_INTERPRET);
bpf_object__close(jit_object);
}
static void
extension_reload_test_common(_In_ const char* file_name, ebpf_execution_type_t execution_type)
{
_test_helper_end_to_end test_helper;
test_helper.initialize();
// Empty context (not used by the eBPF program).
INITIALIZE_SAMPLE_CONTEXT
// Try loading without the extension loaded.
bpf_object_ptr unique_test_sample_ebpf_object;
int program_fd = -1;
const char* error_message = nullptr;
int result;
// Should fail.
REQUIRE(
ebpf_program_load(
file_name,
BPF_PROG_TYPE_UNSPEC,
execution_type,
&unique_test_sample_ebpf_object,
&program_fd,
&error_message) != 0);
ebpf_free((void*)error_message);
// Load the program with the extension loaded.
{
single_instance_hook_t hook(EBPF_PROGRAM_TYPE_SAMPLE, EBPF_ATTACH_TYPE_SAMPLE);
REQUIRE(hook.initialize() == EBPF_SUCCESS);
program_info_provider_t sample_program_info;
REQUIRE(sample_program_info.initialize(EBPF_PROGRAM_TYPE_SAMPLE) == EBPF_SUCCESS);
result = ebpf_program_load(
file_name,
BPF_PROG_TYPE_UNSPEC,
execution_type,
&unique_test_sample_ebpf_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);
bpf_link* link = nullptr;
// Attach only to the single interface being tested.
REQUIRE(hook.attach_link(program_fd, nullptr, 0, &link) == EBPF_SUCCESS);
bpf_link__disconnect(link);
bpf_link__destroy(link);
// Program should run.
uint32_t hook_result = MAXUINT32;
REQUIRE(hook.fire(ctx, &hook_result) == EBPF_SUCCESS);
REQUIRE(hook_result == 42);
// Unload the extension (sample_program_info and hook will be destroyed).
}
// Reload the extension provider with unchanged data.
{
single_instance_hook_t hook(EBPF_PROGRAM_TYPE_SAMPLE, EBPF_ATTACH_TYPE_SAMPLE);
REQUIRE(hook.initialize() == EBPF_SUCCESS);
program_info_provider_t sample_program_info;
REQUIRE(sample_program_info.initialize(EBPF_PROGRAM_TYPE_SAMPLE) == EBPF_SUCCESS);
// Program should re-attach to the hook.
// Program should run.
uint32_t hook_result = MAXUINT32;
REQUIRE(hook.fire(ctx, &hook_result) == EBPF_SUCCESS);
REQUIRE(hook_result == 42);
}
// Reload the extension provider with missing helper function.
{
ebpf_helper_function_addresses_t changed_helper_function_address_table = {
.header = EBPF_HELPER_FUNCTION_ADDRESSES_HEADER,
.helper_function_count = 0,
.helper_function_address = nullptr};
ebpf_program_data_t changed_program_data = _test_ebpf_sample_extension_program_data;
changed_program_data.program_type_specific_helper_function_addresses = &changed_helper_function_address_table;
single_instance_hook_t hook(EBPF_PROGRAM_TYPE_SAMPLE, EBPF_ATTACH_TYPE_SAMPLE);
REQUIRE(hook.initialize() == EBPF_SUCCESS);
program_info_provider_t sample_program_info;
REQUIRE(sample_program_info.initialize(EBPF_PROGRAM_TYPE_SAMPLE, &changed_program_data) == EBPF_SUCCESS);
// Program should re-attach to the hook.
// Program should not run.
uint32_t hook_result = MAXUINT32;
REQUIRE(hook.fire(ctx, &hook_result) != EBPF_SUCCESS);
REQUIRE(hook_result != 42);
}
// Reload the extension provider with changed helper function data.
{
ebpf_helper_function_prototype_t helper_function_prototypes[5];
std::copy(
_sample_ebpf_extension_helper_function_prototype,
_sample_ebpf_extension_helper_function_prototype + 5,
helper_function_prototypes);
// Change the return type of the helper function from EBPF_RETURN_TYPE_INTEGER to
// EBPF_RETURN_TYPE_PTR_TO_MAP_VALUE_OR_NULL.
helper_function_prototypes[0].return_type = EBPF_RETURN_TYPE_PTR_TO_MAP_VALUE_OR_NULL;
ebpf_program_info_t changed_program_info = _sample_ebpf_extension_program_info;
changed_program_info.program_type_specific_helper_prototype = helper_function_prototypes;
ebpf_program_data_t changed_program_data = _test_ebpf_sample_extension_program_data;
changed_program_data.program_info = &changed_program_info;
single_instance_hook_t hook(EBPF_PROGRAM_TYPE_SAMPLE, EBPF_ATTACH_TYPE_SAMPLE);
REQUIRE(hook.initialize() == EBPF_SUCCESS);
program_info_provider_t sample_program_info;
REQUIRE(sample_program_info.initialize(EBPF_PROGRAM_TYPE_SAMPLE, &changed_program_data) == EBPF_SUCCESS);
// Program should re-attach to the hook.
// Program should not run.
uint32_t hook_result = MAXUINT32;
REQUIRE(hook.fire(ctx, &hook_result) != EBPF_SUCCESS);
REQUIRE(hook_result != 42);
}
// Reload the extension again with original data.
{
ebpf_program_data_t changed_program_data = _test_ebpf_sample_extension_program_data;
single_instance_hook_t hook(EBPF_PROGRAM_TYPE_SAMPLE, EBPF_ATTACH_TYPE_SAMPLE);
REQUIRE(hook.initialize() == EBPF_SUCCESS);
program_info_provider_t sample_program_info;
REQUIRE(sample_program_info.initialize(EBPF_PROGRAM_TYPE_SAMPLE, &changed_program_data) == EBPF_SUCCESS);
// Program should re-attach to the hook.
// Program should run.
uint32_t hook_result = MAXUINT32;
REQUIRE(hook.fire(ctx, &hook_result) == EBPF_SUCCESS);
REQUIRE(hook_result == 42);
}
// Reload the extension with changed context_header support.
{
ebpf_program_data_t changed_program_data = _test_ebpf_sample_extension_program_data;
changed_program_data.capabilities.value = 0;
single_instance_hook_t hook(EBPF_PROGRAM_TYPE_SAMPLE, EBPF_ATTACH_TYPE_SAMPLE);
REQUIRE(hook.initialize() == EBPF_SUCCESS);
program_info_provider_t sample_program_info;
REQUIRE(sample_program_info.initialize(EBPF_PROGRAM_TYPE_SAMPLE, &changed_program_data) == EBPF_SUCCESS);
// Program should re-attach to the hook.
// Program should not run.
uint32_t hook_result = MAXUINT32;
REQUIRE(hook.fire(ctx, &hook_result) != EBPF_SUCCESS);
REQUIRE(hook_result != 42);
}
// Reload the extension again with original data
{
ebpf_program_data_t changed_program_data = _test_ebpf_sample_extension_program_data;
single_instance_hook_t hook(EBPF_PROGRAM_TYPE_SAMPLE, EBPF_ATTACH_TYPE_SAMPLE);
REQUIRE(hook.initialize() == EBPF_SUCCESS);
program_info_provider_t sample_program_info;
REQUIRE(sample_program_info.initialize(EBPF_PROGRAM_TYPE_SAMPLE, &changed_program_data) == EBPF_SUCCESS);
// Program should re-attach to the hook.
// Program should run.
uint32_t hook_result = MAXUINT32;
REQUIRE(hook.fire(ctx, &hook_result) == EBPF_SUCCESS);
REQUIRE(hook_result == 42);
}
}
static void
extension_reload_test(ebpf_execution_type_t execution_type)
{
const char* file_name = execution_type == EBPF_EXECUTION_NATIVE ? "test_sample_ebpf_um.dll" : "test_sample_ebpf.o";
extension_reload_test_common(file_name, execution_type);
}
DECLARE_ALL_TEST_CASES("extension_reload_test", "[end_to_end]", extension_reload_test);
static void
_extension_reload_test_implicit_context(ebpf_execution_type_t execution_type)
{
const char* file_name = execution_type == EBPF_EXECUTION_NATIVE ? "test_sample_implicit_helpers_um.dll"
: "test_sample_implicit_helpers.o";
extension_reload_test_common(file_name, execution_type);
}
DECLARE_ALL_TEST_CASES(
"extension_reload_test_implicit_context", "[end_to_end]", _extension_reload_test_implicit_context);
// This test tests resource reclamation and clean-up after a premature/abnormal user mode application exit.
TEST_CASE("close_unload_test", "[close_cleanup]")
{
_test_helper_end_to_end test_helper;
test_helper.initialize();
const char* error_message = nullptr;
int result;
bpf_object_ptr unique_object;
bpf_link_ptr link;
fd_t program_fd;
program_info_provider_t bind_program_info;
REQUIRE(bind_program_info.initialize(EBPF_PROGRAM_TYPE_BIND) == EBPF_SUCCESS);
const char* file_name = "bindmonitor_tailcall_um.dll";
result = ebpf_program_load(
file_name, BPF_PROG_TYPE_UNSPEC, EBPF_EXECUTION_NATIVE, &unique_object, &program_fd, &error_message);
if (error_message) {
printf("ebpf_program_load failed with %s\n", error_message);
free((void*)error_message);
}
REQUIRE(result == 0);
// Set up tail calls.
struct bpf_program* callee0 = bpf_object__find_program_by_name(unique_object.get(), "BindMonitor_Callee0");
REQUIRE(callee0 != nullptr);
fd_t callee0_fd = bpf_program__fd(callee0);
REQUIRE(callee0_fd > 0);
struct bpf_program* callee1 = bpf_object__find_program_by_name(unique_object.get(), "BindMonitor_Callee1");
REQUIRE(callee1 != nullptr);
fd_t callee1_fd = bpf_program__fd(callee1);
REQUIRE(callee1_fd > 0);
fd_t prog_map_fd = bpf_object__find_map_fd_by_name(unique_object.get(), "prog_array_map");
REQUIRE(prog_map_fd > 0);
uint32_t index = 0;
REQUIRE(bpf_map_update_elem(prog_map_fd, &index, &callee0_fd, 0) == 0);
index = 1;
REQUIRE(bpf_map_update_elem(prog_map_fd, &index, &callee1_fd, 0) == 0);
single_instance_hook_t hook(EBPF_PROGRAM_TYPE_BIND, EBPF_ATTACH_TYPE_BIND);
REQUIRE(hook.initialize() == EBPF_SUCCESS);
uint32_t ifindex = 0;
REQUIRE(hook.attach_link(program_fd, &ifindex, sizeof(ifindex), &link) == EBPF_SUCCESS);
// These are needed to prevent the memory leak detector from flagging a memory leak.
hook.detach_and_close_link(&link);
// The block of commented code after this comment is for documentation purposes only.
//
// A well-behaved user mode application _should_ call these calls to correctly free the allocated objects. In case
// of careless applications that do not do so (or even well behaved applications, when they crash or terminate for
// some reason before getting to this point), the 'premature application close' event handling _should_ take care
// of reclaiming and free'ing such objects.
//
// In a user-mode unit test case such as this one, the 'premature application close' event is simulated/handled in
// the context of the bpf_object__close() api, so a call to that api is mandatory for such tests. All unit tests
// belonging to the '[close_cleanup]' unit-test class will show this behavior.
//
// For an identical test meant for execution on the native (kernel mode ebpf-for-windows driver), this event will
// be handled by the kernel mode driver on test application termination. Such a test application _should_ _not_
// call bpf_object__close() api either.
//
/*
--- DO NOT REMOVE OR UN-COMMENT ---
//
// index = 0;
// REQUIRE(bpf_map_update_elem(prog_map_fd, &index, &ebpf_fd_invalid, 0) == 0);
//
// index = 1;
// REQUIRE(bpf_map_update_elem(prog_map_fd, &index, &ebpf_fd_invalid, 0) == 0);
*/
bpf_object__close(unique_object.release());
}
// This test tests the case where a program is inserted multiple times with different keys into the same map.
TEST_CASE("multiple_map_insert", "[close_cleanup]")
{
_test_helper_end_to_end test_helper;
test_helper.initialize();
const char* error_message = nullptr;
int result;
bpf_object_ptr unique_object;
bpf_link_ptr link;
fd_t program_fd;
program_info_provider_t bind_program_info;
REQUIRE(bind_program_info.initialize(EBPF_PROGRAM_TYPE_BIND) == EBPF_SUCCESS);
const char* file_name = "bindmonitor_tailcall_um.dll";
result = ebpf_program_load(
file_name, BPF_PROG_TYPE_UNSPEC, EBPF_EXECUTION_NATIVE, &unique_object, &program_fd, &error_message);
if (error_message) {
printf("ebpf_program_load failed with %s\n", error_message);
free((void*)error_message);
}
REQUIRE(result == 0);
// Set up tail calls.
struct bpf_program* callee0 = bpf_object__find_program_by_name(unique_object.get(), "BindMonitor_Callee0");
REQUIRE(callee0 != nullptr);
fd_t callee0_fd = bpf_program__fd(callee0);
REQUIRE(callee0_fd > 0);
struct bpf_program* callee1 = bpf_object__find_program_by_name(unique_object.get(), "BindMonitor_Callee1");
REQUIRE(callee1 != nullptr);
fd_t callee1_fd = bpf_program__fd(callee1);
REQUIRE(callee1_fd > 0);
fd_t prog_map_fd = bpf_object__find_map_fd_by_name(unique_object.get(), "prog_array_map");
REQUIRE(prog_map_fd > 0);
uint32_t index = 0;
REQUIRE(bpf_map_update_elem(prog_map_fd, &index, &callee0_fd, 0) == 0);
index = 1;
REQUIRE(bpf_map_update_elem(prog_map_fd, &index, &callee1_fd, 0) == 0);
// Insert the same program for multiple keys in the same map.
index = 2;
REQUIRE(bpf_map_update_elem(prog_map_fd, &index, &callee1_fd, 0) == 0);
index = 4;
REQUIRE(bpf_map_update_elem(prog_map_fd, &index, &callee1_fd, 0) == 0);
index = 7;
REQUIRE(bpf_map_update_elem(prog_map_fd, &index, &callee1_fd, 0) == 0);
single_instance_hook_t hook(EBPF_PROGRAM_TYPE_BIND, EBPF_ATTACH_TYPE_BIND);
REQUIRE(hook.initialize() == EBPF_SUCCESS);
uint32_t ifindex = 0;
REQUIRE(hook.attach_link(program_fd, &ifindex, sizeof(ifindex), &link) == EBPF_SUCCESS);
// These are needed to prevent the memory leak detector from flagging a memory leak.
hook.detach_and_close_link(&link);
/*
--- DO NOT REMOVE OR UN-COMMENT ---
// Please refer to the detailed comment in the 'close_unload_test' test for explanation.
//
// index = 0;
// REQUIRE(bpf_map_update_elem(prog_map_fd, &index, &ebpf_fd_invalid, 0) == 0);
//
// index = 1;
// REQUIRE(bpf_map_update_elem(prog_map_fd, &index, &ebpf_fd_invalid, 0) == 0);
*/
bpf_object__close(unique_object.release());
}
void
test_no_limit_map_entries(ebpf_map_type_t type, bool max_entries_limited)
{
uint32_t max_entries = 2;
fd_t inner_map_fd = ebpf_fd_invalid;
fd_t map_fd = ebpf_fd_invalid;
void* value = nullptr;
uint32_t key_size = 0;
uint32_t value_size = 0;
void* key = nullptr;
#define IS_LRU_MAP(type) ((type) == BPF_MAP_TYPE_LRU_HASH || (type) == BPF_MAP_TYPE_LRU_PERCPU_HASH)
#define IS_PERCPU_MAP(type) ((type) == BPF_MAP_TYPE_PERCPU_HASH || (type) == BPF_MAP_TYPE_LRU_PERCPU_HASH)
#define IS_LPM_MAP(type) ((type) == BPF_MAP_TYPE_LPM_TRIE)
#define IS_NESTED_MAP(type) ((type) == BPF_MAP_TYPE_HASH_OF_MAPS)
typedef struct _lpm_trie_key
{
uint32_t prefix_length;
uint32_t value;
} lpm_trie_key_t;
lpm_trie_key_t trie_key = {0};
if (IS_NESTED_MAP(type)) {
// First create and pin the maps manually.
inner_map_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, nullptr, sizeof(int32_t), sizeof(int32_t), 1, nullptr);
REQUIRE(inner_map_fd > 0);
bpf_map_create_opts opts = {.inner_map_fd = (uint32_t)inner_map_fd};
key_size = sizeof(int32_t);
value_size = sizeof(fd_t);
map_fd = bpf_map_create(BPF_MAP_TYPE_HASH_OF_MAPS, nullptr, key_size, value_size, 1, &opts);
REQUIRE(map_fd > 0);
} else {
key_size = IS_LPM_MAP(type) ? sizeof(lpm_trie_key_t) : sizeof(int32_t);
value_size = sizeof(int32_t);
map_fd = bpf_map_create(type, nullptr, key_size, value_size, max_entries, nullptr);
REQUIRE(map_fd > 0);
}
// Update value_size for percpu maps for read / update operations.
if (IS_PERCPU_MAP(type)) {
value_size = EBPF_PAD_8(value_size) * static_cast<size_t>(libbpf_num_possible_cpus());
}
std::vector<uint8_t> per_cpu_value(value_size);
auto compute_key = [&](uint32_t* i) -> void* {
if (IS_LPM_MAP(type)) {
trie_key.prefix_length = 32;
trie_key.value = *i;
return &trie_key;
} else {
return i;
}
};
// Add `max_entries` entries to the map.
for (uint32_t i = 0; i < max_entries; i++) {
key = compute_key(&i);
if (IS_PERCPU_MAP(type)) {
value = per_cpu_value.data();
} else {
value = IS_NESTED_MAP(type) ? &inner_map_fd : (int32_t*)&i;
}
REQUIRE(bpf_map_update_elem(map_fd, key, value, 0) == 0);
}
// Add one more entry to the map.
if (IS_PERCPU_MAP(type)) {
value = per_cpu_value.data();
} else {
value = IS_NESTED_MAP(type) ? &inner_map_fd : (int32_t*)&max_entries;
}
// In case of LRU_HASH, the insert will succeed, but the oldest entry will be removed.
int expected_error = (!max_entries_limited || IS_LRU_MAP(type)) ? 0 : -ENOSPC;
key = compute_key(&max_entries);
REQUIRE(bpf_map_update_elem(map_fd, key, value, 0) == (max_entries_limited ? expected_error : 0));
// In case of LRU_HASH, check that the number of entries is still `max_entries`.
if (IS_LRU_MAP(type) && max_entries_limited) {
uint32_t entries_count = 0;
lpm_trie_key_t local_key = {0};
void* old_key = nullptr;
void* next_key = &local_key;
while (bpf_map_get_next_key(map_fd, old_key, next_key) == 0) {
old_key = next_key;
entries_count++;
}
REQUIRE(entries_count == max_entries);
}
}
// This test case tests the map limits of various hash table based map types.
TEST_CASE("test_map_entries_limit", "[end_to_end]")
{
_test_helper_end_to_end test_helper;
test_helper.initialize();
// The below hash table based map types do not have a limit on the number of entries.
// 1. BPF_MAP_TYPE_HASH
// 2. BPF_MAP_TYPE_PERCPU_HASH
// 3. BPF_MAP_TYPE_HASH_OF_MAPS
// 4. BPF_MAP_TYPE_LPM_TRIE
test_no_limit_map_entries(BPF_MAP_TYPE_HASH, false);
test_no_limit_map_entries(BPF_MAP_TYPE_PERCPU_HASH, false);
test_no_limit_map_entries(BPF_MAP_TYPE_HASH_OF_MAPS, false);
test_no_limit_map_entries(BPF_MAP_TYPE_LPM_TRIE, false);
// The below hash table based map types have a limit on the number of entries.
// 1. BPF_MAP_TYPE_LRU_HASH
// 2. BPF_MAP_TYPE_LRU_PERCPU_HASH
test_no_limit_map_entries(BPF_MAP_TYPE_LRU_HASH, true);
test_no_limit_map_entries(BPF_MAP_TYPE_LRU_PERCPU_HASH, true);
}
// This test validates that a different program type (XDP in this case) cannot call
// a helper function that is not implemented for that program type. Program load should
// fail for such a program.
void
test_invalid_bpf_get_socket_cookie(ebpf_execution_type_t execution_type)
{
_test_helper_end_to_end test_helper;
test_helper.initialize();
int result;
const char* error_message = nullptr;
bpf_object_ptr unique_object;
fd_t program_fd;
program_info_provider_t xdp_program_info;
REQUIRE(xdp_program_info.initialize(EBPF_PROGRAM_TYPE_XDP) == EBPF_SUCCESS);
const char* file_name =
(execution_type == EBPF_EXECUTION_NATIVE ? "xdp_invalid_socket_cookie_um.dll" : "xdp_invalid_socket_cookie.o");
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 == -22);
}
TEST_CASE("invalid_bpf_get_socket_cookie", "[end_to_end]")
{
#if !defined(CONFIG_BPF_JIT_DISABLED)
test_invalid_bpf_get_socket_cookie(EBPF_EXECUTION_JIT);
#endif
#if !defined(CONFIG_BPF_INTERPRETER_DISABLED)
test_invalid_bpf_get_socket_cookie(EBPF_EXECUTION_INTERPRET);
#endif
test_invalid_bpf_get_socket_cookie(EBPF_EXECUTION_NATIVE);
}
static void
_implicit_context_helpers_test(ebpf_execution_type_t execution_type)
{
_test_helper_end_to_end test_helper;
test_helper.initialize();
INITIALIZE_SAMPLE_CONTEXT
single_instance_hook_t hook(EBPF_PROGRAM_TYPE_SAMPLE, EBPF_ATTACH_TYPE_SAMPLE);
REQUIRE(hook.initialize() == EBPF_SUCCESS);
program_info_provider_t sample_program_info;
REQUIRE(sample_program_info.initialize(EBPF_PROGRAM_TYPE_SAMPLE) == EBPF_SUCCESS);
uint32_t data1 = 1;
uint32_t data2 = 2;
const char* file_name = (execution_type == EBPF_EXECUTION_NATIVE) ? "test_sample_implicit_helpers_um.dll"
: "test_sample_implicit_helpers.o";
ctx->helper_data_1 = data1;
ctx->helper_data_2 = data2;
// Try loading without the extension loaded.
bpf_object_ptr unique_test_sample_ebpf_object;
int program_fd = -1;
const char* error_message = nullptr;
int result;
result = ebpf_program_load(
file_name, BPF_PROG_TYPE_UNSPEC, execution_type, &unique_test_sample_ebpf_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);
bpf_link* link = nullptr;
// Attach only to the single interface being tested.
REQUIRE(hook.attach_link(program_fd, nullptr, 0, &link) == EBPF_SUCCESS);
bpf_link__disconnect(link);
bpf_link__destroy(link);
// Program should run.
uint32_t hook_result = MAXUINT32;
REQUIRE(hook.fire(ctx, &hook_result) == EBPF_SUCCESS);
REQUIRE(hook_result == 42);
// Read the output_map and check the values.
fd_t output_map_fd = bpf_object__find_map_fd_by_name(unique_test_sample_ebpf_object.get(), "output_map");
REQUIRE(output_map_fd > 0);
helper_values_t data = {0};
uint32_t key = 0;
REQUIRE(bpf_map_lookup_elem(output_map_fd, &key, &data) == 0);
REQUIRE(data.value_1 == data1);
REQUIRE(data.value_2 == data2 + 10);
}
DECLARE_ALL_TEST_CASES("implicit_context_helpers_test", "[end_to_end]", _implicit_context_helpers_test);