LIBNFTABLES(3)

πŸ’– LIBNFTABLES(3) β€” the libnftables manual, but make it make sense πŸ’–

okay bestie, so this is libnftables β€” the nftables frontend library β€” and yes it's a section 3 page, which means it's a C library situation. but do not panic. we're gonna get through this together and you're gonna slay it, fr fr. 🫢


✨ NAME ✨

libnftables β€” the nftables frontend library, aka the whole reason your app can talk to nftables without you hand-rolling netlink messages like some kind of gremlin πŸ’…


✨ SYNOPSIS ✨

#include <nftables/libnftables.h>

struct nft_ctx *nft_ctx_new(uint32_t flags);
void nft_ctx_free(struct nft_ctx *ctx);

bool nft_ctx_get_dry_run(struct nft_ctx *ctx);
void nft_ctx_set_dry_run(struct nft_ctx *ctx, bool dry);

unsigned int nft_ctx_input_get_flags(struct nft_ctx *ctx);
unsigned int nft_ctx_input_set_flags(struct nft_ctx *ctx, unsigned int flags);

unsigned int nft_ctx_output_get_flags(struct nft_ctx *ctx);
void nft_ctx_output_set_flags(struct nft_ctx *ctx, unsigned int flags);

unsigned int nft_ctx_output_get_debug(struct nft_ctx *ctx);
void nft_ctx_output_set_debug(struct nft_ctx *ctx, unsigned int mask);

FILE *nft_ctx_set_output(struct nft_ctx *ctx, FILE *fp);
int nft_ctx_buffer_output(struct nft_ctx *ctx);
int nft_ctx_unbuffer_output(struct nft_ctx *ctx);
const char *nft_ctx_get_output_buffer(struct nft_ctx *ctx);

FILE *nft_ctx_set_error(struct nft_ctx *ctx, FILE *fp);
int nft_ctx_buffer_error(struct nft_ctx *ctx);
int nft_ctx_unbuffer_error(struct nft_ctx *ctx);
const char *nft_ctx_get_error_buffer(struct nft_ctx *ctx);

int nft_ctx_add_include_path(struct nft_ctx *ctx, const char *path);
void nft_ctx_clear_include_paths(struct nft_ctx *ctx);

int nft_ctx_add_var(struct nft_ctx *ctx, const char *var);
void nft_ctx_clear_vars(struct nft_ctx \*ctx);

int nft_run_cmd_from_buffer(struct nft_ctx* *nft, const char *buf);
int nft_run_cmd_from_filename(struct nft_ctx *nft,
                              const char *filename);

and when you link it, you link with -lnftables. that's the whole vibe. πŸŽ€


✨ DESCRIPTION ✨

so this library was literally designed with nftables integration into applications in mind β€” like, that's its whole main character purpose. because of that, the API is kept as simple as possible, which β€” and I'm gonna be honest with you here bestie β€” somewhat limits its flexibility. tradeoffs, we love them. 😌

BUT. because it supports JSON markup for both input and output, you can still get a lot of convenience in constructing and parsing your input and output data by using a third-party library, like libjansson. so you're not stuck. πŸ¦‹

at the very basic level, here's the whole ritual:

  1. allocate a new object of type struct nft_ctx using the nft_ctx_new() function
  2. pass commands via nft_run_cmd_from_buffer() or nft_run_cmd_from_filename()

and that's it, you're in business. ✨

by default, any output gets written to stdout (or stderr for error messages, obviously). if you wanna change those file pointers, nft_ctx_set_output() and nft_ctx_set_error() are right there for you. and on top of that, you can have the library buffer any output for later retrieval as a static buffer β€” check nft_ctx_buffer_output() and nft_ctx_buffer_error() for the deets. πŸ’–


🌸 nft_ctx_new() and nft_ctx_free() 🌸

these two are your nft context management girlies. if you want to use the library at all, you have to allocate at least one context object β€” no context, no service, periodt. the context holds all the temporary data: caches, library configuration, and (if you enabled them) the output and error buffers. it's giving ✨personal assistant✨.

nft_ctx_new() allocates and returns a brand new context object for you. the flags parameter is unused at this point and should be set to zero. for your convenience, the macro NFT_CTX_DEFAULT is defined to that value. so just use that and move on with your life. 🫢

nft_ctx_free() frees the context object pointed to by ctx β€” including any caches or buffers it may be holding. clean up after yourself, bestie, we don't leave messes. πŸ’…


🌸 nft_ctx_get_dry_run() and nft_ctx_set_dry_run() 🌸

the dry-run setting controls whether ruleset changes are actually committed on the kernel side or not. so it lets you check whether a given operation would succeed β€” without making any actual changes to the ruleset. it's the "let me just rehearse this in the mirror first" setting. πŸ™ˆ the default is false.

nft_ctx_get_dry_run() returns the dry-run setting's value contained in ctx.

nft_ctx_set_dry_run() sets the dry-run setting in ctx to the value of dry.


🌸 nft_ctx_input_get_flags() and nft_ctx_input_set_flags() 🌸

this flags setting controls the input format. here's the enum, copied with love and precision:

enum {
        NFT_CTX_INPUT_NO_DNS = (1 << 0),
        NFT_CTX_INPUT_JSON   = (1 << 1),
};
  • NFT_CTX_INPUT_NO_DNS β€” avoid resolving IP addresses with blocking getaddrinfo(). in that case, only plain IP addresses are accepted. no DNS, no waiting, no drama. ✨
  • NFT_CTX_INPUT_JSON β€” when parsing the input, first try to interpret the input as JSON before falling back to the nftables format. this behavior is implied when you set the NFT_CTX_OUTPUT_JSON flag. iykyk. πŸ’…

nft_ctx_input_get_flags() returns the input flags setting's value in ctx.

nft_ctx_input_set_flags() sets the input flags setting in ctx to the value of val and returns the previous flags. so you can save the old vibe and restore it later. iconic. πŸ‘‘


🌸 nft_ctx_output_get_flags() and nft_ctx_output_set_flags() 🌸

this flags setting controls the output format. here's the full enum, and yes, all of it:

enum {
        NFT_CTX_OUTPUT_REVERSEDNS     = (1 << 0),
        NFT_CTX_OUTPUT_SERVICE        = (1 << 1),
        NFT_CTX_OUTPUT_STATELESS      = (1 << 2),
        NFT_CTX_OUTPUT_HANDLE         = (1 << 3),
        NFT_CTX_OUTPUT_JSON           = (1 << 4),
        NFT_CTX_OUTPUT_ECHO           = (1 << 5),
        NFT_CTX_OUTPUT_GUID           = (1 << 6),
        NFT_CTX_OUTPUT_NUMERIC_PROTO  = (1 << 7),
        NFT_CTX_OUTPUT_NUMERIC_PRIO   = (1 << 8),
        NFT_CTX_OUTPUT_NUMERIC_SYMBOL = (1 << 9),
        NFT_CTX_OUTPUT_NUMERIC_TIME   = (1 << 10),
        NFT_CTX_OUTPUT_NUMERIC_ALL    = (NFT_CTX_OUTPUT_NUMERIC_PROTO |
                                         NFT_CTX_OUTPUT_NUMERIC_PRIO  |
                                         NFT_CTX_OUTPUT_NUMERIC_SYMBOL |
                                         NFT_CTX_OUTPUT_NUMERIC_TIME),
        NFT_CTX_OUTPUT_TERSE          = (1 << 11),
};

and now the explanation era, one by one: πŸŽ€

  • NFT_CTX_OUTPUT_REVERSEDNS β€” reverse DNS lookups get performed for IP addresses when printing. ⚠️ heads up though: this may add significant delay to list commands, depending on how fast your DNS resolver is. so like, know what you're doing.
  • NFT_CTX_OUTPUT_SERVICE β€” print port numbers as services, as described in the /etc/services file. cute names instead of numbers, we love to see it.
  • NFT_CTX_OUTPUT_STATELESS β€” if stateless output has been requested, then stateful data is not printed. "stateful data" means those objects that carry run-time data β€” for example, the counter statement holds packet and byte counter values, which makes it stateful. so this flag is like "don't show me the live numbers, ty." ✨
  • NFT_CTX_OUTPUT_HANDLE β€” when things get inserted into the ruleset, some elements get assigned a unique handle for identification purposes. for example, when deleting a table or chain, it can be identified either by name or by handle. but rules, on the other hand, must be deleted by handle, because there is literally no other way to uniquely identify them. this flag makes ruleset listings include handle values. super important for rules, no cap. πŸ’€
  • NFT_CTX_OUTPUT_JSON β€” if enabled at compile-time, libnftables accepts input in JSON format and can print output in JSON format too. see libnftables-json(5) for a description of the supported schema. this flag enables the JSON output format. and if the flag is set, the input will first be tried as JSON format, before falling back to nftables format. also, this flag implies NFT_CTX_INPUT_JSON. two birds, one flag. πŸ¦‹
  • NFT_CTX_OUTPUT_ECHO β€” the echo setting makes libnftables print the changes once they're committed to the kernel, just like a running instance of nft monitor would. and amongst other things, this lets you retrieve an added rule's handle atomically. chef's kiss. πŸ‘‘
  • NFT_CTX_OUTPUT_GUID β€” display UID and GID as described in the /etc/passwd and /etc/group files.
  • NFT_CTX_OUTPUT_NUMERIC_PROTO β€” display layer 4 protocol numerically.
  • NFT_CTX_OUTPUT_NUMERIC_PRIO β€” display base chain priority numerically.
  • NFT_CTX_OUTPUT_NUMERIC_SYMBOL β€” display expression datatype as a numeric value.
  • NFT_CTX_OUTPUT_NUMERIC_TIME β€” display time, day and hour values in numeric format.
  • NFT_CTX_OUTPUT_NUMERIC_ALL β€” display all numerically. all of it. the whole numeric vibe. πŸ”₯
  • NFT_CTX_OUTPUT_TERSE β€” if terse output has been requested, then the contents of sets are not printed. short and sweet, literally.

nft_ctx_output_get_flags() returns the output flags setting's value in ctx.

nft_ctx_output_set_flags() sets the output flags setting in ctx to the value of val.


🌸 nft_ctx_output_get_debug() and nft_ctx_output_set_debug() 🌸

libnftables supports separate debugging of different parts of its internals β€” because apparently one giant blob of debug output wasn't chaotic enough, so now we get ✨curated✨ chaos. to facilitate this, debugging output is controlled via a bit mask. the bits are defined as such:

enum nft_debug_level {
        NFT_DEBUG_SCANNER               = 0x1,
        NFT_DEBUG_PARSER                = 0x2,
        NFT_DEBUG_EVALUATION            = 0x4,
        NFT_DEBUG_NETLINK               = 0x8,
        NFT_DEBUG_MNL                   = 0x10,
        NFT_DEBUG_PROTO_CTX             = 0x20,
        NFT_DEBUG_SEGTREE               = 0x40,
};

what each one gives you:

  • NFT_DEBUG_SCANNER β€” print LEX debug output.
  • NFT_DEBUG_PARSER β€” print YACC debug output.
  • NFT_DEBUG_EVALUATION β€” print debug information about the evaluation phase.
  • NFT_DEBUG_NETLINK β€” print netlink debug output.
  • NFT_DEBUG_MNL β€” print libmnl debug output.
  • NFT_DEBUG_PROTO_CTX β€” print protocol context debug output.
  • NFT_DEBUG_SEGTREE β€” print segtree (i.e. interval sets) debug output. 🌈

nft_ctx_output_get_debug() returns the debug output setting's value in ctx.

nft_ctx_output_set_debug() sets the debug output setting in ctx to the value of mask.


🌸 Controlling library standard and error output 🌸

by default, any output from the library (like, say, after a list command) is written to stdout, and any error messages are written to stderr. standard behavior, no notes. ✨

but to give applications control over all that, there are functions to assign custom file pointers β€” plus you can have the library buffer what would have been written, so you can retrieve it later from a static buffer. this buffer is guaranteed to be null-terminated and must not be freed. πŸ™…β™€οΈ do not free it. i'm serious. put the free() down.

⚠️ also important: the retrieval functions rewind the buffer position indicator. and further library output will probably overwrite the buffer content β€” and potentially render it invalid (due to reallocation). so like, grab what you need and don't hold onto it like it's forever. πŸ’€

nft_ctx_set_output() and nft_ctx_set_error() set the output or error file pointer in ctx to the value of fp. they return the previous value, which is genuinely useful for temporary file pointer overrides β€” save the old one, do your thing, put it back. on error, these functions return NULL. this only happens if fp is NULL or invalid (tested using the ferror() function).

nft_ctx_buffer_output() and nft_ctx_buffer_error() enable library standard or error output buffering. the functions return zero on success, non-zero otherwise. that non-zero case may happen if the internal call to fopencookie() failed. 😭

nft_ctx_unbuffer_output() and nft_ctx_unbuffer_error() disable library standard or error output buffering. on failure, the functions return non-zero β€” which may only happen if buffering was not enabled at the time the function was called. so if you get a non-zero here, bestie, you never turned it on in the first place. πŸ™ˆ


and that's the end of this part β€” but we are not done, there's more coming, so don't go anywhere. πŸ’–βœ¨

🌸 a couple more buffer getters, bestie 🌸

The nft_ctx_get_output_buffer() and nft_ctx_get_error_buffer() functions hand you back a pointer to the buffered output β€” and lowkey that thing may be empty, so don't come for it if it's got nothing to say. πŸ™ˆ


πŸŽ€ nft_ctx_add_include_path() and nft_ctx_clear_include_path() πŸŽ€

So the include command in nftables rulesets? It's literally the "I'm outsourcing part of my ruleset to a whole different file" move. πŸ’… And the include path is what decides where those files get looked for. Libnftables lets you keep a list of those paths, and they get searched in order. The default include path list has exactly one entry, defined at compile time β€” typically /etc/. Periodt.

The nft_ctx_add_include_path() function extends the list of include paths in ctx by adding the one you give it in path. It returns zero on success, or non-zero if the memory allocation failed. 😭

The nft_ctx_clear_include_paths() function removes all include paths β€” yes, even the built-in default one. She's clearing the whole group chat, no survivors. πŸ’€


✨ nft_ctx_add_var() and nft_ctx_clear_vars() ✨

The define command in an nftables ruleset is how you, well, define variables. Main character energy for reusability.

The nft_ctx_add_var() function extends the list of variables in ctx. Heads up though: the variable must be given in the format key=value or it's not it. The function returns zero on success, or non-zero if the variable is malformed. 🫢

The nft_ctx_clear_vars() function removes all variables. Poof, gone, bye. πŸ¦‹


πŸ”₯ nft_run_cmd_from_buffer() and nft_run_cmd_from_filename() πŸ”₯

These are the ones doing the actual work, bestie β€” parsing your input into nftables commands and executing them. The moment everything else was building up to. πŸ‘‘

The nft_run_cmd_from_buffer() function passes the command(s) contained in buf to the library, respecting the settings and state in nft. And psst β€” buf must be null-terminated, no cap. πŸ™

The nft_run_cmd_from_filename() function passes the content of filename to the library, respecting the settings and state in nft.

Both functions return zero on success. A non-zero return code means there was an error while parsing or executing the command, and that event should come with an error message written to the library error output. So like, if it's acting up, go read the error buffer, she's trying to tell you something. 😭


🌈 EXAMPLE 🌈

Okay so here's the whole vibe in actual code, no edits, exactly as written:

#include <stdio.h>
#include <string.h>
#include <nftables/libnftables.h>

int main(void)
{
        char *list_cmd = "list ruleset";
        struct nft_ctx *nft;
        const char *output, *p;
        char buf[256];
        int rc = 0;

        nft = nft_ctx_new(NFT_CTX_DEFAULT);
        if (!nft)
                return 1;

        while (1) {
                if (nft_ctx_buffer_output(nft) ||
                    nft_run_cmd_from_buffer(nft, list_cmd)) {
                        rc = 1;
                        break;
                }
                output = nft_ctx_get_output_buffer(nft);
                if (strlen(output)) {
                        printf("\nThis is the current ruleset:\n| ");
                        for (p = output; *(p + 1); p++) {
                                if (*p == '\n')
                                        printf("\n| ");
                                else
                                        putchar(*p);
                        }
                        putchar('\n');
                } else {
                        printf("\nCurrent ruleset is empty.\n");
                }
                nft_ctx_unbuffer_output(nft);

                printf("\nEnter command ('q' to quit): ");
                fflush(stdout);
                fgets(buf, 256, stdin);
                if (strlen(buf))
                        buf[strlen(buf) - 1] = '\0';

                if (buf[0] == 'q' && buf[1] == '\0')
                        break;

                if (nft_run_cmd_from_buffer(nft, buf)) {
                        rc = 1;
                        break;
                }
        }

        nft_ctx_free(nft);
        return rc;
}

πŸ’– SEE ALSO πŸ’–


πŸ‘‘ AUTHOR πŸ‘‘

Phil Sutter <phil@nwl.cc> β€” Author. Slay, Phil. πŸ’…


And that's the whole page, bestie!! πŸŽ€ This one's from the 12/05/2025 edition of LIBNFTABLES(3) β€” go forth and buffer your output responsibly. Mwah. πŸ’–βœ¨