Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add option to pass an ELF file to the plugin instead of an array of BPF byte code #80

Merged
merged 1 commit into from
Jan 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/Build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ jobs:

steps:
- uses: actions/checkout@v3
with:
submodules: 'recursive'

- name: Install prerequisites - Ubuntu-22.04
if: inputs.platform == 'ubuntu-22.04'
Expand Down Expand Up @@ -126,7 +128,7 @@ jobs:
if: inputs.enable_coverage == true
run: |
mkdir -p coverage
lcov --capture --directory build --include '${{github.workspace}}/*' --output-file coverage/lcov.info
lcov --capture --directory build --include '${{github.workspace}}/*' --output-file coverage/lcov.info --exclude '${{github.workspace}}/external/*'

- name: Coveralls Parallel
if: inputs.enable_coverage == true
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
submodules: 'recursive'

# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "external/elfio"]
path = external/elfio
url = https://github.com/serge1/ELFIO.git
1 change: 1 addition & 0 deletions external/elfio
Submodule elfio added at 6fc23e
49 changes: 43 additions & 6 deletions include/bpf_conformance.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ typedef enum class _bpf_conformance_test_result
TEST_RESULT_UNKNOWN
} bpf_conformance_test_result_t;

typedef enum class _bpf_conformance_test_CPU_version
typedef enum class _bpf_conformance_test_cpu_version
{
v1 = 1,
v2 = 2,
v3 = 3,
} bpf_conformance_test_CPU_version_t;
} bpf_conformance_test_cpu_version_t;

typedef enum class _bpf_conformance_list_instructions
{
Expand All @@ -32,6 +32,34 @@ typedef enum class _bpf_conformance_list_instructions
LIST_INSTRUCTIONS_ALL, // List all instructions.
} bpf_conformance_list_instructions_t;

typedef struct _bpf_conformance_options
{
std::optional<std::string> include_test_regex;
std::optional<std::string> exclude_test_regex;
bpf_conformance_test_cpu_version_t cpu_version;
bpf_conformance_list_instructions_t list_instructions_option;
bool debug;
bool xdp_prolog;
bool elf_format;
} bpf_conformance_options_t;

/**
* @brief Run the BPF conformance tests with the given plugin.
*
* @param[in] test_files List of test files to run.
* @param[in] plugin_path The path to the plugin to run the tests with.
* @param[in] plugin_options The options to pass to the plugin.
* @param[in] options Options controlling the behavior of the tests.
* @return The test results for each test file.
*/
std::map<std::filesystem::path, std::tuple<bpf_conformance_test_result_t, std::string>>
bpf_conformance_options(
const std::vector<std::filesystem::path>& test_files,
const std::filesystem::path& plugin_path,
const std::vector<std::string>& plugin_options,
const bpf_conformance_options_t& options);

// Backwards compatibility wrapper.
/**
* @brief Run the BPF conformance tests with the given plugin.
*
Expand All @@ -40,19 +68,28 @@ typedef enum class _bpf_conformance_list_instructions
* @param[in] plugin_options The options to pass to the plugin.
* @param[in] include_test_regex A regex that matches the tests to include.
* @param[in] exclude_test_regex A regex that matches the tests to exclude.
* @param[in] CPU_version The CPU version to run the tests with.
* @param[in] cpu_version The CPU version to run the tests with.
* @param[in] list_instructions_option Option controlling which instructions to list.
* @param[in] debug Print debug information.
* @return The test results for each test file.
*/
std::map<std::filesystem::path, std::tuple<bpf_conformance_test_result_t, std::string>>
[[deprecated]] inline std::map<std::filesystem::path, std::tuple<bpf_conformance_test_result_t, std::string>>
bpf_conformance(
const std::vector<std::filesystem::path>& test_files,
const std::filesystem::path& plugin_path,
const std::vector<std::string>& plugin_options,
std::optional<std::string> include_test_regex = std::nullopt,
std::optional<std::string> exclude_test_regex = std::nullopt,
bpf_conformance_test_CPU_version_t CPU_version = bpf_conformance_test_CPU_version_t::v3,
bpf_conformance_test_cpu_version_t cpu_version = bpf_conformance_test_cpu_version_t::v3,
bpf_conformance_list_instructions_t list_instructions_option =
bpf_conformance_list_instructions_t::LIST_INSTRUCTIONS_NONE,
bool debug = false);
bool debug = false)
{
bpf_conformance_options_t options;
options.include_test_regex = include_test_regex;
options.exclude_test_regex = exclude_test_regex;
options.cpu_version = cpu_version;
options.list_instructions_option = list_instructions_option;
options.debug = debug;
return bpf_conformance_options(test_files, plugin_path, plugin_options, options);
}
148 changes: 85 additions & 63 deletions libbpf_plugin/libbpf_plugin.cc
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#define bpf_stats_type bpf_stats_type_fake
enum bpf_stats_type_fake {};
#include <bpf/bpf.h>
#include <bpf/libbpf.h>
#undef bpf_stats_type_fake

/**
Expand Down Expand Up @@ -56,43 +57,87 @@ bytes_to_ebpf_inst(std::vector<uint8_t> bytes)
return instructions;
}

/**
* @brief Create a prolog that loads the packet memory into R1 and the lenght into R2.
*
* @param[in] size Expected size of the packet.
* @return Vector of bpf_insn that represents the prolog.
*/
std::vector<bpf_insn>
generate_xdp_prolog(int size)
{
// Create a prolog that converts the BPF program to one that can be loaded
// at the XDP attach point.
// This involves:
// 1. Copying the ctx->data into r1.
// 2. Copying the ctx->data_end - ctx->data into r2.
// 3. Satisfying the verifier that r2 is the length of the packet.
return {
{0xb7, 0x0, 0x0, 0x0, -1}, // mov64 r0, -1
{0xbf, 0x6, 0x1, 0x0, 0x0}, // mov r6, r1
{0x61, 0x1, 0x6, 0x0, 0x0}, // ldxw r1, [r6+0]
{0x61, 0x2, 0x6, 0x4, 0x0}, // ldxw r2, [r6+4]
{0xbf, 0x3, 0x1, 0x0, 0x0}, // mov r3, r1
{0x7, 0x3, 0x0, 0x0, size}, // add r3, size
{0xbd, 0x3, 0x2, 0x1, 0x0}, // jle r3, r2, +1
{0x95, 0x0, 0x0, 0x0, 0x0}, // exit
{0xb7, 0x2, 0x0, 0x0, size}, // mov r2, size
// Decode BPF instructions from program string and load them into the kernel.
int load_bpf_instructions(const std::string& program_string, size_t memory_length, std::string& log) {
std::vector<bpf_insn> program = bytes_to_ebpf_inst(base16_decode(program_string));

// Load program into kernel.
constexpr uint32_t log_size = 1024;
log.resize(log_size);
#ifdef USE_DEPRECATED_LOAD_PROGRAM
int fd = bpf_load_program(
BPF_PROG_TYPE_XDP,
reinterpret_cast<const bpf_insn*>(program.data()),
static_cast<uint32_t>(program.size()),
"MIT",
0,
&log[0],
log_size);
#else
bpf_prog_load_opts opts{
.sz = sizeof(opts),
.attempts = 1,
.expected_attach_type = BPF_XDP,
.log_size = log_size,
.log_buf = &log[0],
};
int fd = bpf_prog_load(
BPF_PROG_TYPE_XDP,
"conformance_test",
"MIT",
reinterpret_cast<const bpf_insn*>(program.data()),
program.size(),
&opts);
#endif
return fd;
}

int load_elf_file(const std::string& file_contents, std::string& log)
{
std::vector<uint8_t> bytes = base16_decode(file_contents);
struct bpf_object* object = nullptr;
struct bpf_program* program = nullptr;
struct bpf_map* map = nullptr;
int fd = -1;
int error = 0;
bool result = false;
bpf_object_open_opts opts = {};
opts.sz = sizeof(opts);
opts.relaxed_maps = true;

// Load ELF file from memory
object = bpf_object__open_mem(bytes.data(), bytes.size(), &opts);
if (!object) {
log = "Failed to load ELF file";
return -1;
}

if (bpf_object__load(object) < 0) {
log = "Failed to load ELF file";
return -1;
}

// Find the first XDP program.
bpf_object__for_each_program(program, object) {
if (bpf_program__get_type(program) == BPF_PROG_TYPE_XDP) {
fd = bpf_program__fd(program);
break;
}
}

return fd;
}

/**
* @brief This program reads BPF instructions from stdin and memory contents from
* the first agument. It then executes the BPF program and prints the
* the first argument. It then executes the BPF program and prints the
* value of r0 at the end of execution.
*/
int
main(int argc, char** argv)
{
bool debug = false;
bool elf = false;
std::vector<std::string> args(argv, argv + argc);
if (args.size() > 0) {
args.erase(args.begin());
Expand All @@ -101,7 +146,7 @@ main(int argc, char** argv)
std::string memory_string;

if (args.size() > 0 && args[0] == "--help") {
std::cout << "usage: " << argv[0] << " [--program <base16 program bytes>] [<base16 memory bytes>] [--debug]" << std::endl;
std::cout << "usage: " << argv[0] << " [--program <base16 program bytes>] [<base16 memory bytes>] [--debug] [--elf]" << std::endl;
return 1;
}

Expand All @@ -114,7 +159,7 @@ main(int argc, char** argv)
}

// Next parameter is optional memory contents.
if (args.size() > 0 && args[0] != "--debug") {
if (args.size() > 0 && !args[0].starts_with("--")) {
memory_string = args[0];
args.erase(args.begin());
}
Expand All @@ -124,49 +169,26 @@ main(int argc, char** argv)
args.erase(args.begin());
}

if (args.size() > 0 && args[0] == "--elf") {
elf = true;
args.erase(args.begin());
}

if (args.size() > 0 && args[0].size() > 0) {
std::cerr << "Unexpected arguments: " << args[0] << std::endl;
return 1;
}

std::vector<bpf_insn> program = bytes_to_ebpf_inst(base16_decode(program_string));
std::vector<uint8_t> memory = base16_decode(memory_string);

// Add prolog if program accesses memory.
if (memory.size() > 0) {
auto prolog_instructions = generate_xdp_prolog(memory.size());
program.insert(program.begin(), prolog_instructions.begin(), prolog_instructions.end());
std::string log;
int fd = -1;
if (!elf) {
fd = load_bpf_instructions(program_string, memory.size(), log);
}
else {
fd = load_elf_file(program_string, log);
}

// Load program into kernel.
constexpr uint32_t log_size = 1024;
std::string log;
log.resize(log_size);
#ifdef USE_DEPRECATED_LOAD_PROGRAM
int fd = bpf_load_program(
BPF_PROG_TYPE_XDP,
reinterpret_cast<const bpf_insn*>(program.data()),
static_cast<uint32_t>(program.size()),
"MIT",
0,
&log[0],
log_size);
#else
bpf_prog_load_opts opts{
.sz = sizeof(opts),
.attempts = 1,
.expected_attach_type = BPF_XDP,
.log_size = log_size,
.log_buf = &log[0],
};
int fd = bpf_prog_load(
BPF_PROG_TYPE_XDP,
"conformance_test",
"MIT",
reinterpret_cast<const bpf_insn*>(program.data()),
program.size(),
&opts);
#endif
if (fd < 0) {
if (debug)
std::cout << "Failed to load program: " << log << std::endl;
Expand Down
Loading