Programs generation

Switch-cases

bf_swich is used to generate a switch-case logic in BPF bytecode, the logic is the following:

  • Create a new bf_swich object and initialize it. Use bf_swich_get to simplify this step. A bf_swich object contains a pointer to the generated program, and the register to perform the switch comparison against.

  • Call EMIT_SWICH_OPTION to define the various cases for the switch, and the associated BPF bytecode to run.

  • Call EMIT_SWICH_DEFAULT to define the default case of the switch, this is optional.

  • Call bf_swich_generate to generate the BPF bytecode for the switch.

Once bf_swich_generate has been called, this is what the switch structure will look like in BPF bytecode:

if case 1 matches REG, jump to case 1 code
if case 2 matches REG, jump to case 2 code
else jump to default code
case 1 code
    jump after the switch
case 2 code
    jump after the switch
default code

Note

I am fully aware it’s supposed to be spelled switch and not swich , but both switch and case are reserved keywords in C, so I had to come up with a solution to avoid clashes, and swich could be pronounced similarly to switch , at least to my non-native speak ear.

Defines

_cleanup_bf_swich_

Cleanup attribute for a bf_swich variable.

bf_swich_get(program, reg)

Create, initialize, and return a new bf_swich object.

Parameters:
  • program – bf_program object to create the switch in.

  • reg – Register to use to compare the cases values to.

Returns:

A new bf_swich object.

EMIT_SWICH_OPTION(swich, imm, ...)

Add a case to the bf_swich

Parameters:
  • swich – Pointer to a valid bf_swich .

  • imm – Immediate value to compare against the switch’s register.

  • ... – BPF instructions to execute if the case matches.

EMIT_SWICH_DEFAULT(swich, ...)

Set the default instruction if no cases of the switch matches the register.

Defining a default option to a bf_swich is optional. If this macro is called twice, the existing default options will be replaced by the new ones.

Parameters:
  • swich – Pointer to a valid bf_swich .

  • ... – BPF instructions to execute if no case matches.

Functions

int bf_swich_init(struct bf_swich *swich, struct bf_program *program, enum bf_reg reg)

Initialise a bf_swich object.

Parameters:
  • swichbf_swich object to initialize, can’t be NULL.

  • program – bf_program object to generate the switch-case for. Can’t be NULL.

  • reg – Register to compare to the cases of the switch.

Returns:

0 on success, or negative errno value on failure.

void bf_swich_cleanup(struct bf_swich *swich)

Cleanup a bf_swich object.

Once this function returns, the swich object can be reused by calling bf_swich_init .

Parameters:
  • swich – The bf_swich object to clean up.

int bf_swich_add_option(struct bf_swich *swich, uint32_t imm, const struct bpf_insn *insns, size_t insns_len)

Add an option (case) to the switch object.

Parameters:
  • swichbf_swich object to add the option to. Can’t be NULL.

  • imm – Immediate value to compare the switch’s register to. If the values are equal, the option’s instructions are executed.

  • insns – Array of BPF instructions to execute if the case matches.

  • insns_len – Number of instructions in insns .

Returns:

0 on success, or negative errno value on failure.

int bf_swich_set_default(struct bf_swich *swich, const struct bpf_insn *insns, size_t insns_len)

Set the switch’s default actions if no case matches.

Parameters:
  • swichbf_swich object to set the default action for. Can’t be NULL.

  • insns – Array of BPF instructions to execute.

  • insns_len – Number of instructions in insns .

Returns:

0 on success, or negative errno value on failure.

int bf_swich_generate(struct bf_swich *swich)

Generate the bytecode for the switch.

The BPF program doesn’t contain any of the instructions of the bf_swich until this function is called.

Parameters:
  • swichbf_swich object to generate the bytecode for. Can’t be NULL.

Returns:

0 on success, or negative errno value on failure.

struct bf_swich
#include <bpfilter/cgen/swich.h>

Context used to define a switch-case structure in BPF bytecode.

Public Members

struct bf_program *program

Program to generate the switch-case in.

enum bf_reg reg

Register to compare to the various cases of the switch.

bf_list options

List of options (cases) for the switch.

struct bf_swich_option *default_opt

Default option, if no case matches the switch’s register.

Error handling

bf_jmpctx is a helper structure to manage jump instructions in the program. It is used to emit a jump instruction and automatically clean it up when the scope is exited, thanks to GCC’s cleanup attribute.

Example:

// Within a function body
{
    _cleanup_bf_jmpctx_ struct bf_jmpctx ctx =
        bf_jmpctx_get(program, BPF_JMP_IMM(BPF_JEQ, BF_REG_2, 0, 0));

    EMIT(program,
        BPF_MOV64_IMM(BF_REG_RET, program->runtime.ops->get_verdict(
            BF_VERDICT_ACCEPT)));
    EMIT(program, BPF_EXIT_INSN());
}

ctx is a variable local to the scope, marked with _cleanup_bf_jmpctx_ . The second argument to bf_jmpctx_get is the jump instruction to emit, with the correct condition. When the scope is exited, the jump instruction is automatically updated to point to the current instruction, which is after the scope.

Hence, all the instructions emitted within the scope will be executed if the condition is not met. If the condition is met, then the program execution will continue with the first instruction after the scope.

Defines

_cleanup_bf_jmpctx_

Cleanup attribute for a bf_jmpctx variable.

bf_jmpctx_get(program, insn)

Create a new bf_jmpctx variable.

Parameters:
  • program – The program to emit the jump instruction to. It must be non-NULL.

  • insn – The jump instruction to emit.

Returns:

A new bf_jmpctx variable.

Functions

void bf_jmpctx_cleanup(struct bf_jmpctx *ctx)

Cleanup function for bf_jmpctx.

Parameters:
  • ctx – The bf_jmpctx variable to clean up.

struct bf_jmpctx
#include <bpfilter/cgen/jmp.h>

Public Members

struct bf_program *program

A helper structure to manage jump instructions in the program.

The program to emit the jump instruction to.

size_t insn_idx

The index of the jump instruction in the program’s image.

Printing debug messages

bpfilter defines a way for generated BPF programs to print log messages through bpf_trace_printk . This requires:

  • A set of bf_printer_* primitives to manipulate the printer context during the bytecode generation.

  • A EMIT_PRINT macro to insert BPF instructions to print a given string.

  • A BPF map, created by bpfilter before the BPF programs are attached to the kernel.

The printer context bf_printer stores all the log messages to be printed by the generated BPF programs. Log messages are deduplicated to limit memory usage.

During the BPF programs generation, EMIT_PRINT is used to print a given log message from a BPF program. Under the hood, this macro will insert the log message into the global printer context, so it can be used by the BPF programs at runtime.

Before the BPF programs are attached to their hook in the kernel, bpfilter will create a BPF map to contain a unique string, which is the concatenation of all the log messages defined during the generation step. The various BPF programs will be updated to request their log messages from this map directly.

Note

All the message strings are stored in a single BPF map entry in order to benefit from BPF_PSEUDO_MAP_VALUE which allows lookup free direct value access for maps. Hence, using a unique instruction, bpfilter can load the map’s file descriptor and get the address of a message in the buffer. See https://lore.kernel.org/bpf/20190409210910.32048-2-daniel@iogearbox.net.

Defines

_cleanup_bf_printer_
EMIT_PRINT(program, msg)

Emit BPF instructions to print a log message.

This function will insert mulitple instruction into the BPF program to load a given log message from a BPF map into a register, store its size, and call bpf_trace_printk() to print the message.

Warning

As every EMIT_* macro, EMIT_PRINT() will call return if an error occurs. Hence, it must be used within a function that returns an integer.

Parameters:
  • program – Program to emit the instructions to. Must not be NULL.

  • msg – Log message to print.

Functions

int bf_printer_new(struct bf_printer **printer)

Allocate and initialise a new printer context.

Parameters:
  • printer – On success, contains a valid printer context.

Returns:

0 on success, or negative errno value on failure.

int bf_printer_new_from_marsh(struct bf_printer **printer, const struct bf_marsh *marsh)

Allocate a new printer context and intialise it from serialised data.

Parameters:
  • printer – On success, points to the newly allocated and initialised printer context. Can’t be NULL.

  • marsh – Serialised data to use to initialise the printer message.

Returns:

0 on success, or negative errno value on error.

void bf_printer_free(struct bf_printer **printer)

Deinitialise and deallocate a printer context.

Parameters:
  • printer – Printer context. Can’t be NULL.

int bf_printer_marsh(const struct bf_printer *printer, struct bf_marsh **marsh)

Serialise a printer context.

Parameters:
  • printer – Printer context to serialise. Can’t be NULL.

  • marsh – On success, contains the serialised printer context. Can’t be NULL.

Returns:

0 on success, or negative errno value on failure.

void bf_printer_dump(const struct bf_printer *printer, prefix_t *prefix)

Dump the content of the printer structure.

Parameters:
  • printer – Printer object to dump. Can’t be NULL.

  • prefix – Prefix to use for the dump. Can be NULL.

size_t bf_printer_msg_offset(const struct bf_printer_msg *msg)

Return the offset of a specific printer message.

Parameters:
  • msg – Printer message. Can’t be NULL.

Returns:

Offset of msg in the concatenated messages buffer.

size_t bf_printer_msg_len(const struct bf_printer_msg *msg)

Return the length of a specific printer message.

Parameters:
  • msg – Printer message. Can’t be NULL.

Returns:

Length of msg, including the trailing nul termination character.

const struct bf_printer_msg *bf_printer_add_msg(struct bf_printer *printer, const char *str)

Add a new message to the printer.

Parameters:
  • printer – Printer context. Can’t be NULL.

  • str – Message to add to the context. A copy of the buffer is made.

Returns:

The printer message if it was successfuly added to the context, NULL otherwise.

int bf_printer_assemble(const struct bf_printer *printer, void **str, size_t *str_len)

Assemble the messages defined inside the printer into a single nul-separated string.

Parameters:
  • printer – Printer containing the messages to assemble. Can’t be NULL.

  • str – On success, contains the pointer to the result string. Can’t be NULL.

  • str_len – On success, contains the length of the result string, including the nul termination character. Can’t be NULL.

Returns:

0 on success, or negative errno value on failure.