bfcli commands and global options

bfcli is the command line tool for defining and managing bpfilter filtering rules. It calls libbpfilter directly to generate, load, and manage BPF programs.

Global options

The following options apply to all bfcli commands and must be specified before the object and action arguments.

  • --bpffs-path BPFFS_PATH: path to the BPF filesystem directory. Defaults to /sys/fs/bpf. bpfilter pins BPF objects under a bpfilter subdirectory at this path.

  • --with-bpf-token: associate a BPF token to every bpf() system call. Required when running in user namespaces. The token is created from the bpffs at --bpffs-path. Only supported for kernel v6.9+.

  • --verbose VERBOSE_FLAG: enable verbose output. Can be specified multiple times. Supported values:

    • debug: enable debug logs.

    • bpf: insert log messages into BPF programs to log failed kernel function calls. View with bpftool prog tracelog or cat /sys/kernel/debug/tracing/trace_pipe.

    • bytecode: dump a program’s bytecode before loading it.

Commands

bfcli commands are structured as bfcli OBJECT ACTION. The commands and actions supported by bfcli are described below.

For the commands supporting it, --dry-run will parse the chains and check that their matchers, sets, log, and mark are supported by their hook, then stop: nothing is generated, loaded, or attached, so you don’t need to be root. A successful dry run doesn’t mean the chain will load: for example, bpfilter only checks if the hook supports REDIRECT when it generates the program.

ruleset set

Define a new ruleset: replace all the existing chains with the ruleset provided. Replacement is not atomic: the existing chains are removed before the new ones are loaded. If one of the new chains fails to load or attach, bfcli will remove all the chains, including the ones it already applied.

Chains with valid hook options defined are attached to their hook. Chains without hook options are only loaded into the kernel.

Options
  • --from-str RULESET: read and apply the ruleset definition from the command line.

  • --from-file FILE: read FILE and apply the ruleset contained in it.

  • --dry-run: parse and validate the ruleset, but do not apply it.

--from-str and --from-file are mutually exclusive.

Example

$ sudo bfcli ruleset set --from-file myruleset.bf
$ sudo bfcli ruleset set --from-str "chain my_xdp_chain BF_HOOK_XDP ACCEPT rule (ip4.saddr) in {192.168.1.1} ACCEPT"

ruleset get

Print the ruleset: request all the chains and rules with counters values.

Options
  • --no-set-content: omit set elements from the output. Sets are still printed with their name (if named) and key, but their content is replaced with the number of elements they contain. Useful when sets are large enough that printing every element makes the output unreadable.

Example

$ sudo bfcli ruleset get
  chain my_tc_chain BF_HOOK_TC_INGRESS{ifindex=2} ACCEPT
      counters policy 87 packets 9085 bytes; error 0 packets 0 bytes
      rule
          ip4.saddr eq 192.168.1.1
          counters 2 packets 196 bytes
          ACCEPT

ruleset flush

Remove all the chains and rules. Once this command completes, bpfilter doesn’t contain any filtering rules.

Examples

$ sudo bfcli ruleset get
  chain my_tc_chain BF_HOOK_TC_INGRESS{ifindex=2} ACCEPT
      counters policy 87 packets 9085 bytes; error 0 packets 0 bytes
      rule
          ip4.saddr eq 192.168.1.1
          counters 2 packets 196 bytes
          ACCEPT
$ sudo bfcli ruleset flush
$ sudo bfcli ruleset get
$ # Empty ruleset

chain set

Generate and load a chain into the kernel. If the chain definition contains hook options, bpfilter will attach it to its hook. Any existing chain with the same name (attached or not) will be discarded and replaced with the new one. The existing chain is removed before the new one is loaded: if the new chain fails to load or attach, the previous one is not restored.

If you want to update an existing chain without downtime, use bfcli chain update instead.

Options
  • --from-str CHAIN: read the chain to set from the command line arguments.

  • --from-file FILEPATH: read the chain from a file.

  • --name NAME: if --from-str or --from-file provide multiple chains, NAME specifies which one to use.

  • --dry-run: parse and validate the chain, but do not apply it.

Examples

$ # Create an empty XDP chain, do not attach it
$ sudo bfcli chain set --from-str "chain my_xdp_chain BF_HOOK_XDP ACCEPT"
$ sudo bfcli chain get --name my_xdp_chain
  chain my_xdp_chain BF_HOOK_XDP ACCEPT
      counters policy 0 packets 0 bytes; error 0 packets 0 bytes

$ # Create an empty TC chain and attach it
$ sudo bfcli chain set --from-str "chain my_tc_chain BF_HOOK_TC_INGRESS{ifindex=2} ACCEPT"
$ sudo bfcli chain get --name my_tc_chain
  chain my_tc_chain BF_HOOK_TC_INGRESS{ifindex=2} ACCEPT
      counters policy 35 packets 4091 bytes; error 0 packets 0 bytes

chain get

Print a chain.

Options
  • --name NAME: name of the chain to print.

  • --no-set-content: omit set elements from the output. Sets are still printed with their name (if named) and key, but their content is replaced with the number of elements they contain. Useful when sets are large enough that printing every element makes the output unreadable.

Examples

$ # Create a Netfilter chain and print it
$ sudo bfcli chain set --from-str "chain my_input_chain BF_HOOK_NF_LOCAL_IN{priorities=101-102} ACCEPT"
$ sudo bfcli chain get --name my_input_chain
  chain my_input_chain BF_HOOK_NF_LOCAL_IN{priorities=101-102} ACCEPT
      counters policy 1161 packets 149423 bytes; error 0 packets 0 bytes

$ # Print a chain with a large set, eliding the set's content
$ sudo bfcli chain get --name my_filter --no-set-content
  chain my_filter BF_HOOK_XDP{ifindex=2} ACCEPT
      counters policy 0 packets 0 bytes; error 0 packets 0 bytes
      set blocklist (ip4.saddr) in { /* 4096 elements, content elided */ }
      rule
          (ip4.saddr) in blocklist
          DROP

chain logs

Print a chain’s log entries.

bfcli will print log entries as they are published by the chain. Hit Ctrl+C to quit.

Every log entry begins with a shared header: the receive timestamp, the matching rule’s index, and the applied verdict. The remaining fields depend on the hook type:

  • For packet-based hooks using packet-layer logging, the header also includes the matched packet size. It is followed by each requested layer’s protocol headers (see Rules, actions, and verdicts). If a requested layer could not be processed by the chain, the corresponding output will be truncated.

  • For packet-based hooks using 5-tuple logging, the source and destination addresses and ports are printed on a single line with the transport protocol.

  • For BF_HOOK_CGROUP_SOCK_ADDR_* hooks, the entry includes destination address, destination port, process ID, and process name. If the process runs in a PID namespace other than the initial one, its namespace-local process ID is printed too. Sendmsg hooks additionally include the source address.

Options
  • --name NAME: name of the chain to print the log entries for.

Examples

$ # Create an XDP chain with logs and print the logs
$ sudo bfcli chain set --from-str "
  chain my_input_chain BF_HOOK_XDP{ifindex=2} ACCEPT
      rule
          meta.l4_proto tcp
          log transport
          CONTINUE
  "
$ sudo bfcli chain logs --name my_input_chain
  [15:14:32.652085] Rule #0 matched 66 bytes with verdict CONTINUE
    TCP       : 52719 → 22    [ack]
                seq=1643155516 ack=1290470623 win=4618

  [15:14:32.652842] Rule #0 matched 66 bytes with verdict CONTINUE
    TCP       : 52719 → 22    [ack]
                seq=1643155516 ack=1290470723 win=4619

  [...]

chain load

Generate and load a chain into the kernel. Hook options are ignored.

If a chain with the same name already exists, it won’t be replaced: bfcli will return an error. See bfcli chain set or bfcli chain update to replace an existing chain.

Options
  • --from-str CHAIN: read the chain to set from the command line arguments.

  • --from-file FILEPATH: read the chain from a file.

  • --name NAME: if --from-str or --from-file provide multiple chains, NAME specifies which one to use.

  • --dry-run: parse and validate the chain, but do not apply it.

Examples

$ # Create an XDP chain and print it
$ sudo bfcli chain load --from-str "chain my_xdp_chain BF_HOOK_XDP ACCEPT"
$ sudo bfcli chain get --name my_xdp_chain
  chain my_xdp_chain BF_HOOK_XDP ACCEPT
      counters policy 0 packets 0 bytes; error 0 packets 0 bytes

$ # Create a single chain from a string containing 2 chains. Hook options are ignored.
$ sudo bfcli chain load --name my_other_xdp_chain --from-str "
    chain my_next_xdp_chain BF_HOOK_XDP DROP
    chain my_other_xdp_chain BF_HOOK_XDP ACCEPT"
$ sudo bfcli chain get --name my_other_xdp_chain
  chain my_other_xdp_chain BF_HOOK_XDP ACCEPT
      counters policy 0 packets 0 bytes; error 0 packets 0 bytes

chain attach

Attach a loaded chain to its hook.

Only loaded chains (not attached) can be attached. See bfcli chain set and bfcli chain update if you want to update an existing chain.

See Hooks for a list of available hook options.

Options
  • --name NAME: name of the chain to attach.

  • --option OPTION: hook-specific options to attach the chain to its hook. See Hooks.

Examples

$ # Load and attach an XDP chain, print it
$ sudo bfcli chain load --from-str "chain my_xdp_chain BF_HOOK_XDP ACCEPT"
$ sudo bfcli chain attach --name my_xdp_chain --option ifindex=2
$ sudo bfcli chain get --name my_xdp_chain
  chain my_xdp_chain BF_HOOK_XDP{ifindex=2} ACCEPT
      counters policy 101 packets 11714 bytes; error 0 packets 0 bytes

chain update

Update an existing chain. The new chain will atomically update the existing one. Hook options are ignored. The new chain will replace the existing chain with the same name, and must use the same hook: the update fails otherwise. Counters are reset to zero. If the new chain fails to load, the existing chain is left unchanged.

If you want to modify the hook options, use bfcli chain set instead.

Options
  • --from-str CHAIN: read the chain to set from the command line arguments.

  • --from-file FILEPATH: read the chain from a file.

  • --name NAME: if --from-str or --from-file provide multiple chains, NAME specifies which one to use.

  • --dry-run: parse and validate the chain, but do not apply it.

Examples

$ # Set an XDP chain and update it
$ sudo bfcli chain set --from-str "chain my_xdp_chain BF_HOOK_XDP{ifindex=2} ACCEPT"
$ sudo bfcli chain get --name my_xdp_chain
  chain my_xdp_chain BF_HOOK_XDP{ifindex=2} ACCEPT
      counters policy 307 packets 36544 bytes; error 0 packets 0 bytes
$ sudo bfcli chain update --from-str "
      chain my_xdp_chain BF_HOOK_XDP{ifindex=2} ACCEPT
          rule
              ip4.proto eq icmp
              counter
              DROP"
$ sudo bfcli chain get --name my_xdp_chain
  chain my_xdp_chain BF_HOOK_XDP{ifindex=2} ACCEPT
      counters policy 204 packets 24074 bytes; error 0 packets 0 bytes
      rule
          ip4.proto eq icmp
          counters 0 packets 0 bytes
          DROP

chain update-set

Atomically update the content of a named set in a chain using delta operations. This is more efficient than replacing the entire chain when you only need to modify set membership. Counters are preserved across the update.

Sets can grow without limit through update-set. To provision room upfront for a known number of elements, define the set with min-size (see Sets).

Options
  • --name NAME: name of the chain containing the set.

  • --set-name NAME: name of the set to update.

  • --add ELEMENT: element to add to the set. Can be specified multiple times.

  • --remove ELEMENT: element to remove from the set. Can be specified multiple times.

At least one of --add or --remove must be specified.

Examples

$ # Create a chain with a named set
$ sudo bfcli chain set --from-str "
      chain my_filter BF_HOOK_XDP{ifindex=2} ACCEPT
          set blocklist (ip4.saddr) in { 192.168.1.100 }
          rule
              (ip4.saddr) in blocklist
              DROP"

$ # Add addresses to the blocklist
$ sudo bfcli chain update-set --name my_filter --set-name blocklist --add 192.168.1.101 --add 192.168.1.102

$ # Remove an address from the blocklist
$ sudo bfcli chain update-set --name my_filter --set-name blocklist --remove 192.168.1.100

chain flush

Detach, unload, and discard an existing chain.

Options
  • --name NAME: name of the chain to flush.

Examples

$ # Set an XDP chain and flush it
$ sudo bfcli chain set --from-str "chain my_xdp_chain BF_HOOK_XDP ACCEPT"
$ sudo bfcli chain get --name my_xdp_chain
  chain my_xdp_chain BF_HOOK_XDP ACCEPT
      counters policy 0 packets 0 bytes; error 0 packets 0 bytes
$ sudo bfcli chain flush --name my_xdp_chain
$ sudo bfcli chain get --name my_xdp_chain
  error  : chain 'my_xdp_chain' not found: No such file or directory