ebpf-for-windows/tests/socket/socket_tests.cpp

457 строки
18 KiB
C++

// Copyright (c) Microsoft Corporation
// SPDX-License-Identifier: MIT
// This module facilitates testing various socket related eBPF program types and hooks.
#define CATCH_CONFIG_RUNNER
#include <chrono>
#include <future>
using namespace std::chrono_literals;
#include "bpf/bpf.h"
#pragma warning(push)
#pragma warning(disable : 4200)
#include "bpf/libbpf.h"
#pragma warning(pop)
#include "catch_wrapper.hpp"
#include "common_tests.h"
#include "ebpf_nethooks.h"
#include "ebpf_structs.h"
#include "socket_helper.h"
#include "socket_tests_common.h"
#include <mstcpip.h>
void
connection_test(
ADDRESS_FAMILY address_family,
_Inout_ client_socket_t& sender_socket,
_Inout_ receiver_socket_t& receiver_socket,
uint32_t protocol)
{
struct bpf_object* object = bpf_object__open("cgroup_sock_addr.o");
REQUIRE(object != nullptr);
// Load the programs.
REQUIRE(bpf_object__load(object) == 0);
const char* connect_program_name = (address_family == AF_INET) ? "authorize_connect4" : "authorize_connect6";
bpf_program* connect_program = bpf_object__find_program_by_name(object, connect_program_name);
REQUIRE(connect_program != nullptr);
const char* recv_accept_program_name =
(address_family == AF_INET) ? "authorize_recv_accept4" : "authorize_recv_accept6";
bpf_program* recv_accept_program = bpf_object__find_program_by_name(object, recv_accept_program_name);
REQUIRE(recv_accept_program != nullptr);
PSOCKADDR local_address = nullptr;
int local_address_length = 0;
sender_socket.get_local_address(local_address, local_address_length);
connection_tuple_t tuple = {0};
if (address_family == AF_INET) {
tuple.dst_ip.ipv4 = htonl(INADDR_LOOPBACK);
printf("tuple.dst_ip.ipv4 = %x\n", tuple.dst_ip.ipv4);
} else {
memcpy(tuple.dst_ip.ipv6, &in6addr_loopback, sizeof(tuple.dst_ip.ipv6));
}
tuple.dst_port = htons(SOCKET_TEST_PORT);
printf("tuple.dst_port = %x\n", tuple.dst_port);
tuple.protocol = protocol;
bpf_map* ingress_connection_policy_map = bpf_object__find_map_by_name(object, "ingress_connection_policy_map");
REQUIRE(ingress_connection_policy_map != nullptr);
bpf_map* egress_connection_policy_map = bpf_object__find_map_by_name(object, "egress_connection_policy_map");
REQUIRE(egress_connection_policy_map != nullptr);
// Update ingress and egress policy to block loopback packet on test port.
uint32_t verdict = BPF_SOCK_ADDR_VERDICT_REJECT;
REQUIRE(bpf_map_update_elem(bpf_map__fd(ingress_connection_policy_map), &tuple, &verdict, EBPF_ANY) == 0);
REQUIRE(bpf_map_update_elem(bpf_map__fd(egress_connection_policy_map), &tuple, &verdict, EBPF_ANY) == 0);
// Post an asynchronous receive on the receiver socket.
receiver_socket.post_async_receive();
// Attach the connect program at BPF_CGROUP_INET4_CONNECT.
bpf_attach_type connect_attach_type =
(address_family == AF_INET) ? BPF_CGROUP_INET4_CONNECT : BPF_CGROUP_INET6_CONNECT;
int result =
bpf_prog_attach(bpf_program__fd(const_cast<const bpf_program*>(connect_program)), 0, connect_attach_type, 0);
REQUIRE(result == 0);
// Send loopback message to test port.
const char* message = CLIENT_MESSAGE;
sockaddr_storage destination_address{};
if (address_family == AF_INET)
IN6ADDR_SETV4MAPPED((PSOCKADDR_IN6)&destination_address, &in4addr_loopback, scopeid_unspecified, 0);
else
IN6ADDR_SETLOOPBACK((PSOCKADDR_IN6)&destination_address);
sender_socket.send_message_to_remote_host(message, destination_address, SOCKET_TEST_PORT);
// The packet should be blocked by the connect program.
receiver_socket.complete_async_receive(true);
// Cancel send operation.
sender_socket.cancel_send_message();
// Update egress policy to allow packet.
verdict = BPF_SOCK_ADDR_VERDICT_PROCEED;
REQUIRE(bpf_map_update_elem(bpf_map__fd(egress_connection_policy_map), &tuple, &verdict, EBPF_ANY) == 0);
// Attach the receive/accept program at BPF_CGROUP_INET4_RECV_ACCEPT.
bpf_attach_type recv_accept_attach_type =
(address_family == AF_INET) ? BPF_CGROUP_INET4_RECV_ACCEPT : BPF_CGROUP_INET6_RECV_ACCEPT;
result = bpf_prog_attach(
bpf_program__fd(const_cast<const bpf_program*>(recv_accept_program)), 0, recv_accept_attach_type, 0);
REQUIRE(result == 0);
// Resend the packet. This time, it should be dropped by the receive/accept program.
sender_socket.send_message_to_remote_host(message, destination_address, SOCKET_TEST_PORT);
receiver_socket.complete_async_receive(true);
// Cancel send operation.
sender_socket.cancel_send_message();
// Update ingress policy to allow packet.
verdict = BPF_SOCK_ADDR_VERDICT_PROCEED;
REQUIRE(bpf_map_update_elem(bpf_map__fd(ingress_connection_policy_map), &tuple, &verdict, EBPF_ANY) == 0);
// Resend the packet. This time, it should be allowed by both the programs and the packet should reach loopback the
// destination.
sender_socket.send_message_to_remote_host(message, destination_address, SOCKET_TEST_PORT);
receiver_socket.complete_async_receive();
bpf_object__close(object);
}
TEST_CASE("connection_test_udp_v4", "[sock_addr_tests]")
{
datagram_client_socket_t datagram_client_socket(SOCK_DGRAM, IPPROTO_UDP, 0);
datagram_server_socket_t datagram_server_socket(SOCK_DGRAM, IPPROTO_UDP, SOCKET_TEST_PORT);
connection_test(AF_INET, datagram_client_socket, datagram_server_socket, IPPROTO_UDP);
}
TEST_CASE("connection_test_udp_v6", "[sock_addr_tests]")
{
datagram_client_socket_t datagram_client_socket(SOCK_DGRAM, IPPROTO_UDP, 0);
datagram_server_socket_t datagram_server_socket(SOCK_DGRAM, IPPROTO_UDP, SOCKET_TEST_PORT);
connection_test(AF_INET6, datagram_client_socket, datagram_server_socket, IPPROTO_UDP);
}
TEST_CASE("connection_test_tcp_v4", "[sock_addr_tests]")
{
stream_client_socket_t stream_client_socket(SOCK_STREAM, IPPROTO_TCP, 0);
stream_server_socket_t stream_server_socket(SOCK_STREAM, IPPROTO_TCP, SOCKET_TEST_PORT);
connection_test(AF_INET, stream_client_socket, stream_server_socket, IPPROTO_TCP);
}
TEST_CASE("connection_test_tcp_v6", "[sock_addr_tests]")
{
stream_client_socket_t stream_client_socket(SOCK_STREAM, IPPROTO_TCP, 0);
stream_server_socket_t stream_server_socket(SOCK_STREAM, IPPROTO_TCP, SOCKET_TEST_PORT);
connection_test(AF_INET6, stream_client_socket, stream_server_socket, IPPROTO_TCP);
}
TEST_CASE("attach_sock_addr_programs", "[sock_addr_tests]")
{
bpf_prog_info program_info = {};
uint32_t program_info_size = sizeof(program_info);
struct bpf_object* object = bpf_object__open("cgroup_sock_addr.o");
REQUIRE(object != nullptr);
// Load the programs.
REQUIRE(bpf_object__load(object) == 0);
bpf_program* connect4_program = bpf_object__find_program_by_name(object, "authorize_connect4");
REQUIRE(connect4_program != nullptr);
int result = bpf_prog_attach(
bpf_program__fd(const_cast<const bpf_program*>(connect4_program)),
UNSPECIFIED_COMPARTMENT_ID,
BPF_CGROUP_INET4_CONNECT,
0);
REQUIRE(result == 0);
ZeroMemory(&program_info, program_info_size);
REQUIRE(
bpf_obj_get_info_by_fd(
bpf_program__fd(const_cast<const bpf_program*>(connect4_program)), &program_info, &program_info_size) == 0);
REQUIRE(program_info.link_count == 1);
REQUIRE(program_info.map_ids == 0);
result = bpf_prog_detach(UNSPECIFIED_COMPARTMENT_ID, BPF_CGROUP_INET4_CONNECT);
REQUIRE(result == 0);
ZeroMemory(&program_info, program_info_size);
REQUIRE(
bpf_obj_get_info_by_fd(
bpf_program__fd(const_cast<const bpf_program*>(connect4_program)), &program_info, &program_info_size) == 0);
REQUIRE(program_info.link_count == 0);
bpf_program* recv_accept4_program = bpf_object__find_program_by_name(object, "authorize_recv_accept4");
REQUIRE(recv_accept4_program != nullptr);
result = bpf_prog_attach(
bpf_program__fd(const_cast<const bpf_program*>(recv_accept4_program)),
UNSPECIFIED_COMPARTMENT_ID,
BPF_CGROUP_INET4_RECV_ACCEPT,
0);
REQUIRE(result == 0);
REQUIRE(
bpf_obj_get_info_by_fd(
bpf_program__fd(const_cast<const bpf_program*>(recv_accept4_program)), &program_info, &program_info_size) ==
0);
REQUIRE(program_info.link_count == 1);
REQUIRE(program_info.map_ids == 0);
result = bpf_prog_detach2(
bpf_program__fd(const_cast<const bpf_program*>(recv_accept4_program)),
UNSPECIFIED_COMPARTMENT_ID,
BPF_CGROUP_INET4_RECV_ACCEPT);
REQUIRE(result == 0);
REQUIRE(
bpf_obj_get_info_by_fd(
bpf_program__fd(const_cast<const bpf_program*>(recv_accept4_program)), &program_info, &program_info_size) ==
0);
REQUIRE(program_info.link_count == 0);
bpf_program* connect6_program = bpf_object__find_program_by_name(object, "authorize_connect6");
REQUIRE(connect6_program != nullptr);
result = bpf_prog_attach(
bpf_program__fd(const_cast<const bpf_program*>(connect6_program)),
DEFAULT_COMPARTMENT_ID,
BPF_CGROUP_INET6_CONNECT,
0);
REQUIRE(result == 0);
bpf_program* recv_accept6_program = bpf_object__find_program_by_name(object, "authorize_recv_accept6");
REQUIRE(recv_accept6_program != nullptr);
result = bpf_prog_attach(
bpf_program__fd(const_cast<const bpf_program*>(recv_accept6_program)),
DEFAULT_COMPARTMENT_ID,
BPF_CGROUP_INET6_RECV_ACCEPT,
0);
REQUIRE(result == 0);
bpf_object__close(object);
}
void
connection_monitor_test(
ADDRESS_FAMILY address_family,
_Inout_ client_socket_t& sender_socket,
_Inout_ receiver_socket_t& receiver_socket,
uint32_t protocol,
bool disconnect)
{
struct bpf_object* object = bpf_object__open("sockops.o");
REQUIRE(object != nullptr);
// Load the programs.
REQUIRE(bpf_object__load(object) == 0);
// Ring buffer event callback context.
std::unique_ptr<ring_buffer_test_event_context_t> context = std::make_unique<ring_buffer_test_event_context_t>();
context->test_event_count = disconnect ? 4 : 2;
bpf_program* _program = bpf_object__find_program_by_name(object, "connection_monitor");
REQUIRE(_program != nullptr);
PSOCKADDR local_address = nullptr;
int local_address_length = 0;
sender_socket.get_local_address(local_address, local_address_length);
connection_tuple_t tuple{};
if (address_family == AF_INET) {
tuple.src_ip.ipv4 = htonl(INADDR_LOOPBACK);
tuple.dst_ip.ipv4 = htonl(INADDR_LOOPBACK);
} else {
memcpy(tuple.src_ip.ipv6, &in6addr_loopback, sizeof(tuple.src_ip.ipv6));
memcpy(tuple.dst_ip.ipv6, &in6addr_loopback, sizeof(tuple.src_ip.ipv6));
}
tuple.src_port = INETADDR_PORT(local_address);
tuple.dst_port = htons(SOCKET_TEST_PORT);
tuple.protocol = protocol;
NET_LUID net_luid = {};
net_luid.Info.IfType = IF_TYPE_SOFTWARE_LOOPBACK;
tuple.interface_luid = net_luid.Value;
std::vector<std::vector<char>> audit_entry_list;
audit_entry_t audit_entries[3] = {0};
// Connect outbound.
audit_entries[0].tuple = tuple;
audit_entries[0].connected = true;
audit_entries[0].outbound = true;
char* p = reinterpret_cast<char*>(&audit_entries[0]);
audit_entry_list.push_back(std::vector<char>(p, p + sizeof(audit_entry_t)));
// Connect inbound.
audit_entries[1].tuple = tuple;
audit_entries[1].connected = true;
audit_entries[1].outbound = false;
p = reinterpret_cast<char*>(&audit_entries[1]);
audit_entry_list.push_back(std::vector<char>(p, p + sizeof(audit_entry_t)));
// Disconnect.
audit_entries[2].tuple = tuple;
audit_entries[2].connected = false;
audit_entries[2].outbound = false;
p = reinterpret_cast<char*>(&audit_entries[2]);
audit_entry_list.push_back(std::vector<char>(p, p + sizeof(audit_entry_t)));
context->records = &audit_entry_list;
// Get the std::future from the promise field in ring buffer event context, which should be in ready state
// once notifications for all events are received.
auto ring_buffer_event_callback = context->ring_buffer_event_promise.get_future();
// Create a new ring buffer manager and subscribe to ring buffer events.
bpf_map* ring_buffer_map = bpf_object__find_map_by_name(object, "audit_map");
REQUIRE(ring_buffer_map != nullptr);
context->ring_buffer = ring_buffer__new(
bpf_map__fd(ring_buffer_map), (ring_buffer_sample_fn)ring_buffer_test_event_handler, context.get(), nullptr);
REQUIRE(context->ring_buffer != nullptr);
bpf_map* connection_map = bpf_object__find_map_by_name(object, "connection_map");
REQUIRE(connection_map != nullptr);
// Update connection map with loopback packet tuple.
uint32_t verdict = BPF_SOCK_ADDR_VERDICT_REJECT;
REQUIRE(bpf_map_update_elem(bpf_map__fd(connection_map), &tuple, &verdict, EBPF_ANY) == 0);
// Post an asynchronous receive on the receiver socket.
receiver_socket.post_async_receive();
// Attach the sockops program.
int result = bpf_prog_attach(bpf_program__fd(const_cast<const bpf_program*>(_program)), 0, BPF_CGROUP_SOCK_OPS, 0);
REQUIRE(result == 0);
// Send loopback message to test port.
const char* message = CLIENT_MESSAGE;
sockaddr_storage destination_address{};
if (address_family == AF_INET)
IN6ADDR_SETV4MAPPED((PSOCKADDR_IN6)&destination_address, &in4addr_loopback, scopeid_unspecified, 0);
else
IN6ADDR_SETLOOPBACK((PSOCKADDR_IN6)&destination_address);
sender_socket.send_message_to_remote_host(message, destination_address, SOCKET_TEST_PORT);
// Receive the packet on test port.
receiver_socket.complete_async_receive();
if (disconnect) {
sender_socket.close();
receiver_socket.close();
}
// Wait for event handler getting notifications for all connection audit events.
REQUIRE(ring_buffer_event_callback.wait_for(1s) == std::future_status::ready);
// Mark the event context as canceled, such that the event callback stops processing events.
context->canceled = true;
// Release the raw pointer such that the final callback frees the callback context.
ring_buffer_test_event_context_t* raw_context = context.release();
// Unsubscribe.
raw_context->unsubscribe();
bpf_object__close(object);
}
TEST_CASE("connection_monitor_test_udp_v4", "[sock_ops_tests]")
{
datagram_client_socket_t datagram_client_socket(SOCK_DGRAM, IPPROTO_UDP, 0);
datagram_server_socket_t datagram_server_socket(SOCK_DGRAM, IPPROTO_UDP, SOCKET_TEST_PORT);
connection_monitor_test(AF_INET, datagram_client_socket, datagram_server_socket, IPPROTO_UDP, false);
}
TEST_CASE("connection_monitor_test_disconnect_udp_v4", "[sock_ops_tests]")
{
datagram_client_socket_t datagram_client_socket(SOCK_DGRAM, IPPROTO_UDP, 0);
datagram_server_socket_t datagram_server_socket(SOCK_DGRAM, IPPROTO_UDP, SOCKET_TEST_PORT);
connection_monitor_test(AF_INET, datagram_client_socket, datagram_server_socket, IPPROTO_UDP, true);
}
TEST_CASE("connection_monitor_test_udp_v6", "[sock_ops_tests]")
{
datagram_client_socket_t datagram_client_socket(SOCK_DGRAM, IPPROTO_UDP, 0);
datagram_server_socket_t datagram_server_socket(SOCK_DGRAM, IPPROTO_UDP, SOCKET_TEST_PORT);
connection_monitor_test(AF_INET6, datagram_client_socket, datagram_server_socket, IPPROTO_UDP, false);
}
TEST_CASE("connection_monitor_test_disconnect_udp_v6", "[sock_ops_tests]")
{
datagram_client_socket_t datagram_client_socket(SOCK_DGRAM, IPPROTO_UDP, 0);
datagram_server_socket_t datagram_server_socket(SOCK_DGRAM, IPPROTO_UDP, SOCKET_TEST_PORT);
connection_monitor_test(AF_INET6, datagram_client_socket, datagram_server_socket, IPPROTO_UDP, true);
}
TEST_CASE("connection_monitor_test_tcp_v4", "[sock_ops_tests]")
{
stream_client_socket_t stream_client_socket(SOCK_STREAM, IPPROTO_TCP, 0);
stream_server_socket_t stream_server_socket(SOCK_STREAM, IPPROTO_TCP, SOCKET_TEST_PORT);
connection_monitor_test(AF_INET, stream_client_socket, stream_server_socket, IPPROTO_TCP, false);
}
TEST_CASE("connection_monitor_test_disconnect_tcp_v4", "[sock_ops_tests]")
{
stream_client_socket_t stream_client_socket(SOCK_STREAM, IPPROTO_TCP, 0);
stream_server_socket_t stream_server_socket(SOCK_STREAM, IPPROTO_TCP, SOCKET_TEST_PORT);
connection_monitor_test(AF_INET, stream_client_socket, stream_server_socket, IPPROTO_TCP, true);
}
TEST_CASE("connection_monitor_test_tcp_v6", "[sock_ops_tests]")
{
stream_client_socket_t stream_client_socket(SOCK_STREAM, IPPROTO_TCP, 0);
stream_server_socket_t stream_server_socket(SOCK_STREAM, IPPROTO_TCP, SOCKET_TEST_PORT);
connection_monitor_test(AF_INET6, stream_client_socket, stream_server_socket, IPPROTO_TCP, false);
}
TEST_CASE("connection_monitor_test_disconnect_tcp_v6", "[sock_ops_tests]")
{
stream_client_socket_t stream_client_socket(SOCK_STREAM, IPPROTO_TCP, 0);
stream_server_socket_t stream_server_socket(SOCK_STREAM, IPPROTO_TCP, SOCKET_TEST_PORT);
connection_monitor_test(AF_INET6, stream_client_socket, stream_server_socket, IPPROTO_TCP, true);
}
TEST_CASE("attach_sockops_programs", "[sock_ops_tests]")
{
struct bpf_object* object = bpf_object__open("sockops.o");
REQUIRE(object != nullptr);
// Load the programs.
REQUIRE(bpf_object__load(object) == 0);
bpf_program* _program = bpf_object__find_program_by_name(object, "connection_monitor");
REQUIRE(_program != nullptr);
int result = bpf_prog_attach(bpf_program__fd(const_cast<const bpf_program*>(_program)), 0, BPF_CGROUP_SOCK_OPS, 0);
REQUIRE(result == 0);
bpf_object__close(object);
}
int
main(int argc, char* argv[])
{
WSAData data;
int error = WSAStartup(2, &data);
if (error != 0) {
printf("Unable to load Winsock: %d\n", error);
return 1;
}
int result = Catch::Session().run(argc, argv);
WSACleanup();
return result;
}