Tests¶
Test harness¶
The test harness is a set of convenience functions used to ease testing of bpfilter
.
Test¶
Main file to include to perform tests. This header defines convenience macros to create tests and test results.
Defines
-
NOT_NULL¶
Macro to use when checking if
NULL
parameters are properly asserted on:// Ensure path can't be NULL expect_assert_failure(bf_read_file(NULL, NOT_NULL, 0)); // Ensure buf can't be NULL expect_assert_failure(bf_read_file(NOT_NULL, NULL, 0));
-
Test(group, name)¶
Create a new test.
Tests are defined in their section so they can be easily discovered at runtime time.
- Parameters:
group – Test group, can be filtered on to run all the tests in a single group.
name – Name of the test.
-
bf_test_fail(fmt, ...)¶
Fail a test with an error message.
- Parameters:
fmt – Message format, similar to
printf()
format.... – Format arguments.
-
assert_success(x)¶
Assert that
x
evaluates to a success.- Parameters:
x – Expression to evaluate. If the expression evaluates to
0
, it is considered succeeded, and the assertion succeeds.
-
assert_error(x)¶
Assert that
x
evaluates to an error.- Parameters:
x – Expression to evaluate. If the expression evaluates to
< 0
, it is considered failed, and the assertion succeeds.
-
_free_bf_test_¶
-
_free_bf_test_group_¶
-
_free_bf_test_suite_¶
-
_free_bf_test_filter_¶
Typedefs
-
typedef void (*bf_test_cb)(void **state)¶
Functions
-
int bf_test_new(bf_test **test, const char *name, bf_test_cb cb)¶
-
int bf_test_group_new(bf_test_group **group, const char *name)¶
-
void bf_test_group_free(bf_test_group **group)¶
-
void bf_test_group_dump(const bf_test_group *group, prefix_t *prefix)¶
-
int bf_test_group_add_test(bf_test_group *group, const char *test_name, bf_test_cb cb)¶
-
bf_test *bf_test_group_get_test(bf_test_group *group, const char *test_name)¶
-
int bf_test_group_make_cmtests(bf_test_group *group)¶
-
int bf_test_suite_new(bf_test_suite **suite)¶
-
void bf_test_suite_free(bf_test_suite **suite)¶
-
void bf_test_suite_dump(const bf_test_suite *suite, prefix_t *prefix)¶
-
void bf_test_suite_print(const bf_test_suite *suite)¶
-
int bf_test_suite_add_test(bf_test_suite *suite, const char *group_name, const char *test_name, bf_test_cb cb)¶
-
int bf_test_suite_add_symbol(bf_test_suite *suite, struct bf_test_sym *sym)¶
-
bf_test_group *bf_test_suite_get_group(bf_test_suite *suite, const char *group_name)¶
-
int bf_test_suite_make_cmtests(const bf_test_suite *suite)¶
-
int bf_test_discover_test_suite(bf_test_suite **suite)¶
Discover the test suite in the current ELF file.
Parse the sections in the current ELF file to discover the symbols in the
.bf_test
section and create the associated test suite.- Parameters:
suite – Discovered test suite. Can’t be
NULL
. On success, this argument will be point to a valid test suite.
- Returns:
0 on success, or a negative errno value on error.
-
int bf_test_filter_new(bf_test_filter **filter)¶
-
void bf_test_filter_free(bf_test_filter **filter)¶
-
int bf_test_filter_add_pattern(bf_test_filter *filter, const char *pattern)¶
-
bool bf_test_filter_matches(bf_test_filter *filter, const char *str)¶
-
struct bf_test¶
- #include <harness/test.h>
Test
-
struct bf_test_group¶
- #include <harness/test.h>
Test group.
A test group contains one or more tests.
Symbols¶
bpfilter
stores the test functions in a custom .bf_test
section in the ELF binary. This way, the tests can be fetched at runtime from the current binary, allowing for tests autodiscovery (which CMocka doesn’t support).
bf_test_get_symbols()
will read the sections in the ELF file it runs from and return all the symbols located in the .bf_test
section.
Defines
-
_free_bf_test_sym_¶
Functions
-
int bf_test_sym_new(struct bf_test_sym **sym, const char *name, void *cb)¶
-
void bf_test_sym_free(struct bf_test_sym **sym)¶
-
void bf_test_sym_dump(struct bf_test_sym *sym)¶
-
int bf_test_get_symbols(bf_list *symbols)¶
-
struct bf_test_sym¶
- #include <harness/sym.h>
Mocks¶
Mock functions from bpfilter
or from the standard library. Mocking function allows the tester to call a stub and force the function to return a predefined value. Mocks can be used to trigger a specific code path or prevent a system call (which would modify the system or require elevated privileges).
Mocks must be declared in harness/mock.h
with bf_test_mock_declare()
and implemented in harness/mock.c
with bf_test_mock_define()
. Then, add the mocked function to bf_test_mock()
in harness/CMakeLists.txt
.
In your tests, create the mock with bf_test_mock_get(function, retval)
. retval
is the value you expect the mock to return when called. By default, the mock expects to return this value only once and never be called again. To configure a different behavior, use bf_test_mock_get_empty()
and bf_test_mock_will_return()
or bf_test_mock_will_return_always()
. Use _clean_bf_test_mock_
to limit your mock to the current scope.
Using a mock to ensure _bf_print_msg_new()
fails if malloc()
fails:
// Create a mock for malloc which will return NULL once.
_clean_bf_test_mock bf_test_mock _ bf_test_mock_get(malloc, NULL);
// Expect the function to fail if malloc fails.
assert_error(_bf_printer_msg_new(&msg));
This module also defines convenience function to simulate a runtime environment such as creating a temporary file to marsh the daemon into.
Defines
-
_free_tmp_file_¶
-
_clean_bf_test_mock_¶
-
bf_test_mock_get(name, retval)¶
-
bf_test_mock_empty(name)¶
-
bf_test_mock_will_return(mock, value)¶
-
bf_test_mock_will_return_always(mock, value)¶
Functions
-
char *bf_test_filepath_new_rw(void)¶
-
void bf_test_filepath_free(char **path)¶
-
void bf_test_mock_clean(bf_test_mock *mock)¶
-
struct bf_test_mock¶
- #include <harness/mock.h>
Process¶
The functions defined in this file are used to manage an external process. They are inspired by the Python subprocess
module.
bf_test_process
represents the process to manipulate, it must be initialized using bf_test_process_init()
with the correct command and arguments.
bf_test_process_start()
will fork the current process, and run the pre-defined command in the new thread. Two file descriptors will be available to read the forked process’ stdout
and stderr
streams (use bf_test_process_stdout()
and bf_test_process_stderr()
to do so).
The forked process can terminate by itself, in which case you need to wait for it anyway using bf_test_process_wait()
. You can also kill the process manually by calling bf_test_process_kill()
to send a SIGTERM
signal, then calling bf_test_process_wait()
. The last option is to call bf_test_process_stop()
which will kill it and wait.
Lastly, cleanup the resources allocated for the process with bf_test_process_clean()
.
Defines
-
_cleanup_bf_test_process_¶
Functions
-
int bf_test_process_init(struct bf_test_process *process, const char *cmd, char **args, size_t nargs)¶
-
void bf_test_process_clean(struct bf_test_process *process)¶
-
int bf_test_process_start(struct bf_test_process *process)¶
Start the process.
Fork the current process to start the requested process. Open two file descriptor to communicate with the forked process (
stdout
andstderr
). Once started, the process can be waited on, killed, or stopped. Usebf_test_process_stdout()
andbf_test_process_stderr()
to access it standard output and error buffers.If this function succeeds,
bf_test_process_wait()
orbf_test_process_stop()
must called before cleaning the process.- Parameters:
process – The process to start. Can’t be
NULL
.
- Returns:
0 on success, or a negative errno value on error.
-
int bf_test_process_wait(struct bf_test_process *process)¶
Wait for the process to terminate.
This function will hang until the process has completed.
- Parameters:
process – The process to wait on. Can’t be NULL.
- Returns:
The return code of the process as a non-negative integer, or a negative errno value on error.
-
int bf_test_process_kill(struct bf_test_process *process)¶
Kill the process by sending
SIGTERM
.- Parameters:
process – The process to kill. Can’t be
NULL
.
- Returns:
0 on success, or a negative errno value on error.
-
int bf_test_process_stop(struct bf_test_process *process)¶
Force the process to stop and wait for it.
This function is equivalent to calling
bf_test_process_kill()
thenbf_test_process_wait()
.- Parameters:
process – The process to stop. Can’t be
NULL
.
- Returns:
The return code of the process as a non-negative integer, or a negative errno value on error.
-
int bf_run(const char *cmd, char **args, size_t nargs)¶
Run a command in a forked process.
This function won’t kill the process but only wait on it. If you call
bf_run()
with a command that doesn’t return, this function will hang indefinitely.- Parameters:
cmd – Command to run in the process.
args – Array of arguments to provide to the process.
nargs – Number of arguments in
args
.
- Returns:
The return code of the process as a non-negative integer, or a negative errno value on error.
-
const char *bf_test_process_stdout(struct bf_test_process *process)¶
Read the process’
stdout
stream.The buffer returned by
bf_test_process_stdout()
is dynamically allocated and is owned by the caller.- Parameters:
process – Process to read the
stdout
stream from.
- Returns:
Buffer containing the process’
stdout
stream, orNULL
on error.
-
const char *bf_test_process_stderr(struct bf_test_process *process)¶
Read the process’
stderr
stream.The buffer returned by
bf_test_process_stderr()
is dynamically allocated and is owned by the caller.- Parameters:
process – Process to read the
stderr
stream from.
- Returns:
Buffer containing the process’
stderr
stream, orNULL
on error.
-
struct bf_test_process¶
- #include <harness/process.h>
Public Members
-
const char *cmd¶
Command to run in the process.
-
char **args¶
Array of arguments as
char
pointers.
-
size_t nargs¶
Number of arguments in
args
.
-
pid_t pid¶
PID of the process, only valid while the process is alive.
-
int out_fd¶
File descriptor of the process’
stdout
stream.
-
int err_fd¶
File descriptor of the process’
stderr
stream.
-
const char *cmd¶
Daemon¶
bf_test_daemon represents a handle to manage the bpfilter
daemon. Based on the primitives defined in harness/process.h
.
Defines
-
_cleanup_bf_test_daemon_¶
Enums
-
enum bf_test_daemon_option¶
Options to configure the daemon.
Not all the options defined for
bpfilter
need to be defined below.Values:
-
enumerator BF_TEST_DAEMON_TRANSIENT = 1 << 0¶
-
enumerator BF_TEST_DAEMON_NO_CLI = 1 << 1¶
-
enumerator BF_TEST_DAEMON_NO_IPTABLES = 1 << 2¶
-
enumerator BF_TEST_DAEMON_NO_NFTABLES = 1 << 3¶
-
enumerator _BF_TEST_DAEMON_LAST = BF_TEST_DAEMON_NO_NFTABLES¶
-
enumerator BF_TEST_DAEMON_TRANSIENT = 1 << 0¶
Functions
-
int bf_test_daemon_init(struct bf_test_daemon *daemon, const char *path, uint32_t options)¶
Initialize a new daemon object.
Note
bf_test_daemon_init()
assumes none of the options defined inbf_test_daemon_option
require an argument. If this assumption is erroneous, the logic used to parse the options need to be modified!- Parameters:
daemon – The daemon object to initialize. Can’t be
NULL
.path – Path to the
bpfilter
binary. IfNULL
, the firstbpfilter
binary found in$PATH
will be used.options – Command line options to start the daemon with. See
bf_test_daemon_option
for the list of available options.
- Returns:
0 on success, or a negative errno value on error.
-
void bf_test_daemon_clean(struct bf_test_daemon *daemon)¶
Cleanup a daemon object.
- Parameters:
daemon – Daemon object to cleanup. Can’t be
NULL
.
-
int bf_test_daemon_start(struct bf_test_daemon *daemon)¶
Start a daemon process.
Once the process is started, this function will wait for a specific log from the daemon to validate the process is up and running (and didn’t exit).
- Parameters:
daemon – Daemon object to start the daemon process for. Can’t be
NULL
.
- Returns:
0 on success, or a negative errno value on error.
-
int bf_test_daemon_stop(struct bf_test_daemon *daemon)¶
Stop a daemon process.
- Parameters:
daemon – Daemon object to stop the daemon process for. Can’t be
NULL
.
- Returns:
The return code of the daemon process as an integer >= 0 on success, or a negative errno value on error.
-
struct bf_test_daemon¶
- #include <harness/daemon.h>
Public Members
-
struct bf_test_process process¶
-
struct bf_test_process process¶
Filters¶
Convenience functions to easily create matchers, rules, and chains in order to test bpfilter
. Those functions are wrapper around the actual API (i.e. bf_matcher_new()
, bf_rule_new()
, bf_chain_new()
) which cut corners when it comes to error handling (e.g. you can’t retrieve the actual error code).
Some wrappers expect NULL
-terminated array of pointers, they will take ownership of the pointers and free them if an error occurs during the object creation. Valid pointers in the array located after a NULL
entry won’t be processed nor freed, and asan
will raise an error. See bf_rule_get()
and bf_chain_get()
.
Functions
-
struct bf_hook_opts bf_hook_opts_get(enum bf_hook_opt opt, ...)¶
Create a new hook options object.
bf_hook_opts_get()
expects pairs ofbf_hook_opt
key and value, with the last variadic argument being-1
:bf_hook_opts_get( BF_HOOK_OPT_IFINDEX, 2, BF_HOOK_OPT_NAME, "my_bpf_program", -1 );
- Parameters:
opt – First hook option. This parameter is required as C requires at least one explicit parameter.
- Returns:
A
bf_hook_opts
structure filled with the arguments passed to the function. If an error occurs, an error message is printed and thebf_hook_opts
structure is filled with0
.
-
struct bf_matcher *bf_matcher_get(enum bf_matcher_type type, enum bf_matcher_op op, const void *payload, size_t payload_len)¶
Create a new matcher.
See
bf_matcher_new()
for details of the arguments.- Returns:
0 on success, or a negative errno value on error.
-
struct bf_rule *bf_rule_get(bool counters, enum bf_verdict verdict, struct bf_matcher **matchers)¶
Create a new rule.
See
bf_rule_new()
for details of the arguments.- Returns:
0 on success, or a negative errno value on error.
-
struct bf_chain *bf_chain_get(enum bf_hook hook, struct bf_hook_opts hook_opts, enum bf_verdict policy, struct bf_set **sets, struct bf_rule **rules)¶
Create a new chain.
See
bf_chain_get()
for details of the arguments.- Returns:
0 on success, or a negative errno value on error.
Program¶
This module defines bf_test_prog
to manipulate a BPF program generated by bpfilter
.
Once a bf_test_prog
object has been created, use bf_test_prog_open()
to link it to a BPF program attach to the system using the program’s name.
Defines
-
_free_bf_test_prog_¶
Functions
-
struct bf_test_prog *bf_test_prog_get(const char *name)¶
-
int bf_test_prog_new(struct bf_test_prog **prog)¶
-
void bf_test_prog_free(struct bf_test_prog **prog)¶
-
int bf_test_prog_open(struct bf_test_prog *prog, const char *name)¶
-
int bf_test_prog_run(const struct bf_test_prog *prog, uint32_t expect, const struct bf_test_packet *pkt)¶
Call
BPF_PROG_TEST_RUN
on the program.- Parameters:
prog – Program to test run. Can’t be NULL.
expect – Expected return value of the program, depends on the program type.
pkt – Test packet to send to the BPF program. Can’t be NULL.
- Returns:
0 if the call succeeded and the BPF program’s return value is equal to
expect
.< 0 if the call failed.
> 0 if the call succeeded but the BPF program’s return value is different from
expect
.
-
struct bf_test_packet¶
- #include <harness/prog.h>
Unit tests¶
Warning
In progress.
End-to-end tests¶
End-to-end tests are designed to validate the bytecode generated by bpfilter
through the following workflow:
Start the
bpfilter
daemon.Send a chain to the daemon to be translated into a BPF program.
Use
BPF_PROG_TEST_RUN
with a dummy packet and validate the program’s return code.
Run end-to-end tests with make e2e
. root
privileges are required to start the daemon and call bpf(BPF_PROG_TEST_RUN)
.
The test packets are generated using a Python script and Scapy: the scripts creates packets.h
which is included in the end-to-end tests sources. See tests/e2e/genpkts.py
.
Adding a new end-to-end test
End-to-end tests are defined in tests/e2e
and use cmocka
as the testing library. To add a new end-to-end test:
Add a new
cmocka
test in a source file undertests/e2e
.Create a chain: use the primitives in Filters to easily create chains, rules, and matchers. Remember to set the
attach=no
option on the chain to avoid blocking your host’s traffic!Send the chain to the daemon. The
e2e
main()
function should create a new instance of the daemon for each test group, so you don’t have to do it yourself. Usebf_cli_set_chain()
to send the chain to the daemon.Get the file descriptor of the generated BPF program as a
bf_test_prog
. Usebf_test_prog_get()
to avoid the boilerplate of creating thebf_test_prog
object, allocating it, and opening the file descriptor. The BPF program identified by its name, which you control through the hook attributename
.Send a dummy packet to your program and validate the return value with
bf_test_prog_run()
.
Example
The example below will create an empty chain with a default ACCEPT
policy. We expect the generated XDP program to return XDP_PASS
(which is 2
).
Test(xdp, default_policy)
{
_cleanup_bf_chain_ struct bf_chain *chain = bf_chain_get(
BF_HOOK_XDP,
bf_hook_opts_get(
BF_HOOK_OPT_IFINDEX, 2,
BF_HOOK_OPT_NAME, "bf_e2e_testprog",
BF_HOOK_OPT_ATTACH, false,
-1
),
BF_VERDICT_ACCEPT,
NULL,
(struct bf_rule *[]) {
NULL,
}
);
_free_bf_test_prog_ struct bf_test_prog *prog = NULL;
if (bf_cli_set_chain(chain) < 0)
bf_test_fail("failed to send the chain to the daemon");
assert_non_null(prog = bf_test_prog_get("bf_e2e_testprog"));
assert_success(bf_test_prog_run(prog, 2, &pkt_local_ip6_tcp));
}
Benchmarking¶
Warning
In progress.