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_group_¶
-
_free_bf_test_suite_¶
-
_free_bf_test_filter_¶
Typedefs
-
typedef void (*bf_test_cb)(void **state)¶
Functions
-
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)¶
-
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, bf_test *test)¶
-
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.
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)¶
-
void bf_test_mock_malloc_enable(void)¶
-
void bf_test_mock_malloc_disable(void)¶
-
bool bf_test_mock_malloc_is_enabled(void)¶
-
void *__wrap_malloc(size_t size)¶
-
void bf_test_mock_calloc_enable(void)¶
-
void bf_test_mock_calloc_disable(void)¶
-
bool bf_test_mock_calloc_is_enabled(void)¶
-
void *__wrap_calloc(size_t nmemb, size_t size)¶
-
void bf_test_mock_open_enable(void)¶
-
void bf_test_mock_open_disable(void)¶
-
bool bf_test_mock_open_is_enabled(void)¶
-
int __wrap_open(const char *pathname, int flags, mode_t mode)¶
-
void bf_test_mock_read_enable(void)¶
-
void bf_test_mock_read_disable(void)¶
-
bool bf_test_mock_read_is_enabled(void)¶
-
ssize_t __wrap_read(int fd, void *buf, size_t count)¶
-
void bf_test_mock_write_enable(void)¶
-
void bf_test_mock_write_disable(void)¶
-
bool bf_test_mock_write_is_enabled(void)¶
-
ssize_t __wrap_write(int fd, const void *buf, size_t count)¶
-
void bf_test_mock_btf__load_vmlinux_btf_enable(void)¶
-
void bf_test_mock_btf__load_vmlinux_btf_disable(void)¶
-
bool bf_test_mock_btf__load_vmlinux_btf_is_enabled(void)¶
-
struct btf *__wrap_btf__load_vmlinux_btf(void)¶
-
void bf_test_mock_nlmsg_alloc_enable(void)¶
-
void bf_test_mock_nlmsg_alloc_disable(void)¶
-
bool bf_test_mock_nlmsg_alloc_is_enabled(void)¶
-
struct nl_msg *__wrap_nlmsg_alloc()¶
-
void bf_test_mock_nlmsg_convert_enable(void)¶
-
void bf_test_mock_nlmsg_convert_disable(void)¶
-
bool bf_test_mock_nlmsg_convert_is_enabled(void)¶
-
struct nl_msg *__wrap_nlmsg_convert(struct nlmsghdr *nlh)¶
-
void bf_test_mock_nlmsg_put_enable(void)¶
-
void bf_test_mock_nlmsg_put_disable(void)¶
-
bool bf_test_mock_nlmsg_put_is_enabled(void)¶
-
struct nlmsghdr *__wrap_nlmsg_put(struct nl_msg *nlmsg, uint32_t pid, uint32_t seq, int type, int payload, int flags)¶
-
void bf_test_mock_nlmsg_append_enable(void)¶
-
void bf_test_mock_nlmsg_append_disable(void)¶
-
bool bf_test_mock_nlmsg_append_is_enabled(void)¶
-
int __wrap_nlmsg_append(struct nl_msg *nlmsg, void *data, size_t len, int pad)¶
-
void bf_test_mock_bf_bpf_obj_get_enable(void)¶
-
void bf_test_mock_bf_bpf_obj_get_disable(void)¶
-
bool bf_test_mock_bf_bpf_obj_get_is_enabled(void)¶
-
int __wrap_bf_bpf_obj_get(const char *path, int *fd)¶
-
void bf_test_mock_vsnprintf_enable(void)¶
-
void bf_test_mock_vsnprintf_disable(void)¶
-
bool bf_test_mock_vsnprintf_is_enabled(void)¶
-
int __wrap_vsnprintf(char *str, size_t size, const char *fmt, va_list args)¶
-
void bf_test_mock_snprintf_enable(void)¶
-
void bf_test_mock_snprintf_disable(void)¶
-
bool bf_test_mock_snprintf_is_enabled(void)¶
-
int __wrap_snprintf(char *str, size_t size, const char *fmt, ...)¶
-
void bf_test_mock_bf_bpf_enable(void)¶
-
void bf_test_mock_bf_bpf_disable(void)¶
-
bool bf_test_mock_bf_bpf_is_enabled(void)¶
-
int __wrap_bf_bpf(enum bpf_cmd cmd, union bpf_attr *attr)¶
-
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()
.
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
.
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_test_chain_get()
.
Defines
-
BF_E2E_NAME¶
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_set *bf_test_set_get(enum bf_set_type type, uint8_t *data[])¶
Create a new set.
bf_test_set_get( BF_SET_IP4, (uint8_t *[]) { { 0x01, 0x02, 0x03, 0x04 }, NULL, } );
The caller owns the set and is responsible for freeing it.
- Parameters:
type – Set type. Defines the key size.
data – Array of elements to fill the set with. The elements are expected to a size defined by their
type
. IfNULL
, the set is empty
- Returns:
A valid bf_set on success, or NULL on failure.
-
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_test_chain_get(enum bf_hook hook, enum bf_verdict policy, struct bf_set **sets, struct bf_rule **rules)¶
Create a new chain.
See
bf_chain_new()
for details of the arguments. The hook options are automatically set to test-friendly values:attach
: falsecgroup
:<no_cgroup>
ifindex
: 1name
:bf_e2e_xxxxxx
withxxxxxx
replaced with 6 random chars.
- Returns:
A valid chain pointer on success, or
NULL
on failure.
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 struct bf_chain *chain)¶
-
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)¶
-
struct bf_test_packet¶
- #include <harness/prog.h>
Unit tests¶
Warning
In progress.
End-to-end tests¶
Note
The end-to-end test suite is not yet part of the make test
global tests target. If the Python Scapy module is available on your system, you can run it using make e2e
.
End-to-end tests are designed to validate the bytecode generated by bpfilter
, which unit and manual tests can’t do efficiently. The end-to-end tests harness will start a fresh bpfilter
daemon for every test, generate a new chain, and use BPF_PROG_TEST_RUN
to validate the expected behaviour. The test packets fed to the tested BPF programs are generated by the Python Scapy module (see tests/e2e/genpkts.py
).
Each test is run for every available hook automatically, so the BPF bytecode generated for each flavour can be validated to ensure consistent behaviour.
Similarly to the benchmarks (make benchmark
), sudo
will be used automatically if the current user doesn’t have root
privileges.
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.
bf_test_chain_get
will automatically disable attachment of the chain and generate a custom name for the BPF program prefixed withbf_e2e_
.Run the test using
bft_e2e_test()
with the chain, the expected return value, and the generated packet name.
Example
The example below will create an empty chain with a default ACCEPT
policy.
Note
While the following example defines a chain for the XDP hook, it will be overridden by bft_e2e_test()
to be tested for every hook.
Test(policy, accept_no_rule)
{
_cleanup_bf_chain_ struct bf_chain *chain = bf_test_chain_get(
BF_HOOK_XDP,
BF_VERDICT_ACCEPT,
NULL,
(struct bf_rule *[]) {
NULL,
}
);
bft_e2e_test(chain, BF_VERDICT_ACCEPT, pkt_local_ip6_tcp);
}
Integration tests¶
bpfilter
’s repository contains patches to add support for bpfilter
to nftables
and iptables
. You first need to install nftables
’ and iptables
’ build dependencies:
# Fedora 40+
sudo dnf install -y autoconf automake gmp-devel libtool libedit-devel libmnl-devel libnftnl-devel
# Ubuntu 24.04
sudo apt-get install -y autoconf bison flex libedit-dev libgmp-dev libmnl-dev libnftnl-dev libtool
Then, you can build both from bpfilter
’s build directory:
make -C $BUILD_DIRECTORY integration
Once this command succeeds, nft
(nftables
’s command-line tool) and iptables
are available in $BUILD_DIRECTORY/tools/install
.
With either nft
or iptables
, you can now communicate directly with the bpfilter
daemon instead of the kernel by using the --bpf
flag. This allows your filtering rules to be translated into BPF programs by bpfilter
.