summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am14
-rw-r--r--src/fuzz.c541
-rw-r--r--src/lexer.l133
-rw-r--r--src/lib.c2404
-rw-r--r--src/lib.h1899
-rw-r--r--src/linux_list.h738
-rw-r--r--src/main.c220
-rw-r--r--src/parser_yy.y2990
8 files changed, 8939 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..82aee06
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,14 @@
+include $(top_srcdir)/Make_global.am
+
+AM_YFLAGS = -d -Wno-yacc
+
+noinst_PROGRAMS = knft
+
+AM_CFLAGS = ${LIBMNL_CFLAGS} ${LIBNFTNL_CFLAGS}
+
+knft_SOURCES = parser_yy.y \
+ lexer.l \
+ main.c \
+ lib.c \
+ fuzz.c
+knft_LDADD = ${LIBMNL_LIBS} ${LIBNFTNL_LIBS}
diff --git a/src/fuzz.c b/src/fuzz.c
new file mode 100644
index 0000000..28da976
--- /dev/null
+++ b/src/fuzz.c
@@ -0,0 +1,541 @@
+/*
+ * Copyright (c) 2024-2025 Pablo Neira Ayuso <pablo@netfilter.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 (or any
+ * later) as published by the Free Software Foundation.
+ */
+
+/* Funded through the NGI0 Entrust established by NLnet (https://nlnet.nl)
+ * with support from the European Commission's Next Generation Internet
+ * programme.
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/time.h>
+
+#include "fuzz.h"
+
+static const char *fuzz_type_name[] = {
+ [FUZZ_DEL_TABLE] = "deltable",
+ [FUZZ_DEL_BASECHAIN] = "delbasechain",
+ [FUZZ_DEL_CHAIN] = "delchain",
+ [FUZZ_DEL_RULE] = "delrule",
+ [FUZZ_DEL_SET] = "delset",
+ [FUZZ_FLUSH_SET] = "flushset",
+ [FUZZ_DEL_ELEM] = "delelem",
+ [FUZZ_DEL_OBJ] = "delobj",
+ [FUZZ_DUP] = "dup",
+ [FUZZ_REVERSE_COMMIT] = "reverse-commit",
+ [FUZZ_REVERSE_ABORT] = "reverse-abort",
+ [FUZZ_TABLE_DORMANT] = "table-dormant",
+ [FUZZ_TABLE_WAKEUP] = "table-wakeup",
+ [FUZZ_SWAP] = "swap",
+ [FUZZ_BOGUS] = "bogus",
+};
+
+void print_fuzz_modes(void)
+{
+ int i;
+
+ for (i = 0; i < FUZZ_MAX; i++)
+ printf("\t%s\n", fuzz_type_name[i]);
+}
+
+const char *fuzz_type_to_name(uint32_t type)
+{
+ if (type >= FUZZ_MAX)
+ return "unknown";
+
+ return fuzz_type_name[type];
+}
+
+int fuzz_type_to_value(const char *str)
+{
+ int i;
+
+ for (i = 0; i < FUZZ_MAX; i++) {
+ if (!strcmp(fuzz_type_name[i], str))
+ return i;
+ }
+
+ return -1;
+}
+
+struct line_args {
+ char n1[1024];
+ char n2[1024];
+ char n3[1024];
+ char n4[1024];
+ char n5[1024];
+ char n6[1024];
+ char n7[1024];
+ char n8[1024];
+ char n9[1024];
+ char n10[1024];
+ char n11[1024];
+ char n12[1024];
+ char n13[1024];
+ char n14[1024];
+ char n15[1024];
+};
+
+#define BUF_SIZE 1024000
+
+struct fuzz_state_ctx {
+ char buf[1024000];
+ uint32_t len;
+
+ char saved_line[1024];
+
+ uint32_t state;
+ uint32_t line;
+ uint32_t line_from;
+
+ struct line_args saved_args;
+
+ uint32_t more;
+ bool found;
+ bool found2;
+ bool done;
+};
+
+static void add_line(struct fuzz_state_ctx *ctx, const char *line)
+{
+ snprintf(&ctx->buf[ctx->len], BUF_SIZE - ctx->len, "%s", line);
+ ctx->len += strlen(line);
+}
+
+static void reverse_cmd(struct fuzz_state_ctx *ctx, const char *line, int type)
+{
+ struct line_args *args = &ctx->saved_args;
+ int ret;
+
+ add_line(ctx, line);
+
+ if (!strcmp(line, "commit();\n") ||
+ !strcmp(line, "abort();\n"))
+ return;
+
+ if (ctx->done) {
+ ctx->more++;
+ return;
+ }
+
+ switch (type) {
+ case FUZZ_DEL_TABLE:
+ case FUZZ_TABLE_DORMANT:
+ case FUZZ_TABLE_WAKEUP:
+ if (sscanf(line, "add_table(%[^,],%[^,],%[^,],%[^,],%[^)]);",
+ args->n1, args->n2, args->n3, args->n4, args->n5) == 5) {
+ ctx->found2 = 1;
+ }
+ break;
+ case FUZZ_DEL_BASECHAIN:
+ if (sscanf(line, "add_basechain(%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^)]);",
+ args->n1, args->n2, args->n3, args->n4, args->n5, args->n6, args->n7, args->n8, args->n9, args->n10, args->n11, args->n12, args->n13) == 13) {
+ ctx->found2 = 1;
+ }
+ break;
+ case FUZZ_DEL_CHAIN:
+ if (sscanf(line, "add_chain(%[^,],%[^,],%[^,],%[^,],%[^)]);",
+ args->n1, args->n2, args->n3, args->n4, args->n5) == 5) {
+ ctx->found2 = 1;
+ }
+ break;
+ case FUZZ_DEL_RULE:
+ if (sscanf(line, "add_rule(%[^,],%[^,],%[^,],%[^,],%[^)]);",
+ args->n1, args->n2, args->n3, args->n4, args->n5) == 5) {
+ ctx->found2 = 1;
+ }
+ break;
+ case FUZZ_DEL_SET:
+ case FUZZ_FLUSH_SET:
+ if (sscanf(line, "add_set(%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^)]);",
+ args->n1, args->n2, args->n3, args->n4, args->n5, args->n6, args->n7, args->n8, args->n9, args->n10, args->n11, args->n12, args->n13, args->n14, args->n15) == 15) {
+ ctx->found2 = 1;
+ }
+ break;
+ case FUZZ_DEL_OBJ:
+ if (sscanf(line, "add_obj_counter(%[^,],%[^,],%[^,],%[^,],%[^)]);",
+ args->n1, args->n2, args->n3, args->n4, args->n5) == 5) {
+ ctx->found2 = 1;
+ } else if (sscanf(line, "add_obj_quota(%[^,],%[^,],%[^,],%[^,],%[^,],%[^)]);",
+ args->n1, args->n2, args->n3, args->n4, args->n5, args->n6) == 6) {
+ ctx->found2 = 1;
+ } else if (sscanf(line, "add_obj_connlimit(%[^,],%[^,],%[^,],%[^,],%[^)]);",
+ args->n1, args->n2, args->n3, args->n4, args->n5) == 5) {
+ ctx->found2 = 1;
+ } else if (sscanf(line, "add_obj_secmark(%[^,],%[^,],%[^,],%[^)]);",
+ args->n1, args->n2, args->n3, args->n4) == 4) {
+ ctx->found2 = 1;
+ } else if (sscanf(line, "add_obj_synproxy(%[^,],%[^,],%[^,],%[^,],%[^,],%[^)]);",
+ args->n1, args->n2, args->n3, args->n4, args->n5, args->n6) == 6) {
+ ctx->found2 = 1;
+ } else if (sscanf(line, "add_obj_ct_timeout(%[^,],%[^,],%[^,],%[^,],%[^,],%[^)]);",
+ args->n1, args->n2, args->n3, args->n4, args->n5, args->n6) == 6) {
+ ctx->found2 = 1;
+ } else if (sscanf(line, "add_obj_ct_helper(%[^,],%[^,],%[^,],%[^,],%[^,],%[^)]);",
+ args->n1, args->n2, args->n3, args->n4, args->n5, args->n6) == 6) {
+ ctx->found2 = 1;
+ } else if (sscanf(line, "add_obj_ct_expect(%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^)]);",
+ args->n1, args->n2, args->n3, args->n4, args->n5, args->n6, args->n7, args->n8) == 8) {
+ ctx->found2 = 1;
+ } else if (sscanf(line, "add_obj_limit(%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^)]);",
+ args->n1, args->n2, args->n3, args->n4, args->n5, args->n6, args->n7, args->n8) == 8) {
+ ctx->found2 = 1;
+ } else if (sscanf(line, "add_obj_tunnel(%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^)]);",
+ args->n1, args->n2, args->n3, args->n4, args->n5, args->n6, args->n7, args->n8, args->n9, args->n10, args->n11, args->n12, args->n13, args->n14) == 14) {
+ ctx->found2 = 1;
+ }
+ break;
+ }
+
+ if (!ctx->found2)
+ return;
+
+ switch (type) {
+ case FUZZ_DEL_TABLE:
+ ctx->len += snprintf(&ctx->buf[ctx->len], BUF_SIZE - ctx->len, "del_table(%s,%s, NULL);\n", args->n1, args->n2);
+ break;
+ case FUZZ_DEL_BASECHAIN:
+ ctx->len += snprintf(&ctx->buf[ctx->len], BUF_SIZE - ctx->len, "del_basechain(%s,%s, NULL, NULL, NULL, NULL, NULL, NULL);\n", args->n1, args->n2);
+ break;
+ case FUZZ_DEL_CHAIN:
+ ctx->len += snprintf(&ctx->buf[ctx->len], BUF_SIZE - ctx->len, "del_chain(%s);\n", args->n1);
+ break;
+ case FUZZ_DEL_RULE:
+ ctx->len += snprintf(&ctx->buf[ctx->len], BUF_SIZE - ctx->len, "del_rule(%s,%s, NULL);\n", args->n1, args->n2);
+ break;
+ case FUZZ_DEL_SET:
+ ctx->len += snprintf(&ctx->buf[ctx->len], BUF_SIZE - ctx->len, "del_set(%s, NULL);\n", args->n1);
+ break;
+ case FUZZ_FLUSH_SET:
+ ctx->len += snprintf(&ctx->buf[ctx->len], BUF_SIZE - ctx->len, "flush_set(%s, NULL);\n", args->n1);
+ break;
+ case FUZZ_TABLE_DORMANT:
+ ctx->len += snprintf(&ctx->buf[ctx->len], BUF_SIZE - ctx->len, "add_table(%s,%s, \"0x1\",%s,%s);\n", args->n1, args->n2, args->n4, args->n5);
+ break;
+ case FUZZ_TABLE_WAKEUP:
+ ctx->len += snprintf(&ctx->buf[ctx->len], BUF_SIZE - ctx->len, "add_table(%s,%s, \"0x0\",%s,%s);\n", args->n1, args->n2, args->n4, args->n5);
+ break;
+ case FUZZ_DEL_OBJ:
+ ctx->len += snprintf(&ctx->buf[ctx->len], BUF_SIZE - ctx->len, "del_obj_unknown(%s, NULL, NULL, \"1\");\n", args->n1);
+ break;
+ }
+ ctx->line_from = ctx->line;
+ ctx->done = 1;
+}
+
+static void del_elem(struct fuzz_state_ctx *ctx, const char *line)
+{
+ struct line_args args = {};
+
+ add_line(ctx, line);
+
+ if (sscanf(line, "add_elem(%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^)]);",
+ args.n1, args.n2, args.n3, args.n4, args.n5, args.n6, args.n7, args.n8, args.n9) != 9)
+ return;
+
+ if (ctx->found) {
+ ctx->more++;
+ return;
+ }
+
+ ctx->len += snprintf(&ctx->buf[ctx->len], BUF_SIZE - ctx->len, "del_elem(%s, NULL, NULL);\n", args.n1);
+ ctx->line_from = ctx->line;
+ ctx->found = 1;
+}
+
+static void dup_line(struct fuzz_state_ctx *ctx, const char *line)
+{
+ add_line(ctx, line);
+
+ if (!strcmp(line, "commit();\n") ||
+ !strcmp(line, "abort();\n"))
+ return;
+
+ if (ctx->found) {
+ ctx->more++;
+ return;
+ }
+
+ snprintf(&ctx->buf[ctx->len], BUF_SIZE - ctx->len, "%s", line);
+ ctx->len += strlen(line);
+
+ ctx->line_from = ctx->line;
+ ctx->found = 1;
+}
+
+static void reverse_abort(struct fuzz_state_ctx *ctx, const char *line)
+{
+ if (strcmp(line, "abort();\n")) {
+ add_line(ctx, line);
+ return;
+ }
+
+ if (ctx->found) {
+ add_line(ctx, line);
+ ctx->more++;
+ return;
+ }
+
+ ctx->len += snprintf(&ctx->buf[ctx->len], BUF_SIZE - ctx->len, "commit();\n");
+ ctx->line_from = ctx->line;
+ ctx->found = 1;
+}
+
+static void reverse_commit(struct fuzz_state_ctx *ctx, const char *line)
+{
+ if (strcmp(line, "commit();\n")) {
+ add_line(ctx, line);
+ return;
+ }
+
+ if (ctx->found) {
+ add_line(ctx, line);
+ ctx->more++;
+ return;
+ }
+
+ ctx->len += snprintf(&ctx->buf[ctx->len], BUF_SIZE - ctx->len, "abort();\n");
+ ctx->line_from = ctx->line;
+ ctx->found = 1;
+}
+
+static void swap(struct fuzz_state_ctx *ctx, const char *line)
+{
+ if (!strcmp(line, "commit();\n") ||
+ !strcmp(line, "abort();\n")) {
+ if (ctx->done) {
+ if (!ctx->found) {
+ add_line(ctx, ctx->saved_line);
+ ctx->found = 1;
+ }
+ }
+ add_line(ctx, line);
+ return;
+ }
+
+ if (!strncmp(line, "add_table(", strlen("add_table(")) ||
+ !strncmp(line, "set_table(", strlen("set_table("))) {
+ add_line(ctx, line);
+ return;
+ }
+
+ if (ctx->done) {
+ add_line(ctx, line);
+ if (!ctx->found) {
+ add_line(ctx, ctx->saved_line);
+ ctx->found = 1;
+ }
+ ctx->more++;
+ return;
+ }
+
+ snprintf(ctx->saved_line, sizeof(ctx->saved_line), "%s", line);
+ ctx->line_from = ctx->line;
+ ctx->done = 1;
+}
+
+static void bogus(struct fuzz_state_ctx *ctx, const char *line)
+{
+ struct line_args args = {};
+
+ add_line(ctx, line);
+
+ if (!strcmp(line, "commit();\n") ||
+ !strcmp(line, "abort();\n"))
+ return;
+
+ if (ctx->done) {
+ ctx->more++;
+ return;
+ }
+
+ ctx->len += snprintf(&ctx->buf[ctx->len], BUF_SIZE - ctx->len, "del_table(NFPROTO_IPV4, \"bogus123456unknown\", NULL);\n");
+ ctx->line_from = ctx->line;
+ ctx->done = 1;
+}
+
+static void fuzz_step_cb(struct fuzz_state_ctx *ctx, const char *line)
+{
+ if (ctx->line++ < ctx->line_from) {
+ add_line(ctx, line);
+ return;
+ }
+
+ switch (ctx->state) {
+ case FUZZ_DEL_TABLE:
+ case FUZZ_DEL_BASECHAIN:
+ case FUZZ_DEL_CHAIN:
+ case FUZZ_DEL_RULE:
+ case FUZZ_DEL_SET:
+ case FUZZ_FLUSH_SET:
+ case FUZZ_DEL_OBJ:
+ case FUZZ_TABLE_DORMANT:
+ case FUZZ_TABLE_WAKEUP:
+ reverse_cmd(ctx, line, ctx->state);
+ break;
+ case FUZZ_DEL_ELEM:
+ del_elem(ctx, line);
+ break;
+ case FUZZ_DUP:
+ dup_line(ctx, line);
+ break;
+ case FUZZ_REVERSE_COMMIT:
+ reverse_commit(ctx, line);
+ break;
+ case FUZZ_REVERSE_ABORT:
+ reverse_abort(ctx, line);
+ break;
+ case FUZZ_SWAP:
+ swap(ctx, line);
+ break;
+ case FUZZ_BOGUS:
+ bogus(ctx, line);
+ break;
+ }
+}
+
+static FILE *fuzz_step(struct fuzz_state_ctx *ctx, FILE *fp,
+ void (*fuzz_step_cb)(struct fuzz_state_ctx *ctx, const char *line))
+{
+ const char *line;
+ char buf[1024];
+ FILE *new_fp;
+
+ ctx->line = 0;
+ ctx->len = 0;
+ ctx->more = 0;
+ ctx->found = 0;
+ ctx->done = 0;
+
+ line = fgets(buf, sizeof(buf), fp);
+ while (line) {
+ fuzz_step_cb(ctx, line);
+ line = fgets(buf, sizeof(buf), fp);
+ }
+
+ new_fp = fmemopen(ctx->buf, ctx->len, "r");
+ if (!new_fp) {
+ fprintf(stderr, "cannot open memfile\n");
+ return NULL;
+ }
+
+ return new_fp;
+}
+
+static int fuzz_print(FILE *fp)
+{
+ const char *line;
+ char buf[1024];
+ FILE *fp2;
+
+ line = fgets(buf, sizeof(buf), fp);
+ while (line) {
+ fprintf(stdout, "%s", line);
+ line = fgets(buf, sizeof(buf), fp);
+ }
+ rewind(fp);
+
+ return 0;
+}
+
+int fuzz_loop(struct fuzz_ctx *ctx, FILE *fp, int (*fuzz_cb)(FILE *fp))
+{
+ struct fuzz_state_ctx state_ctx = {
+ .state = ctx->type[ctx->index],
+ };
+ struct timeval tv_cur, tv_diff;
+ FILE *new_fp;
+ int ret = 0;
+
+ if (ctx->index >= ctx->num_types) {
+ if (ctx->debug)
+ printf("<<<< fuzz_loop backtrack STACK limit reached\n");
+
+ ctx->index--;
+ return 0;
+ }
+
+ if (ctx->debug) {
+ printf(">>>> fuzz_loop at index %d in state=%x\n",
+ ctx->index, ctx->type[ctx->index]);
+ }
+
+ while (1) {
+ new_fp = fuzz_step(&state_ctx, fp, fuzz_step_cb);
+ if (!new_fp)
+ break;
+
+ ctx->fuzz_steps++;
+ ctx->total_fuzz_steps++;
+ if (ctx->fuzz_steps % 100 == 0) {
+ gettimeofday(&tv_cur, NULL);
+ timersub(&tv_cur, &ctx->tv_start, &tv_diff);
+ printf("... fuzz steps: %u in %u.%u seconds\n",
+ ctx->fuzz_steps, tv_diff.tv_sec, tv_diff.tv_usec);
+ }
+
+ if (ctx->debug)
+ fuzz_print(new_fp);
+
+ if (fuzz_cb(new_fp) < 0) {
+ ret = -1;
+ fclose(new_fp);
+ break;
+ }
+
+ if (ctx->index < ctx->num_types) {
+ ctx->index++;
+ if (fuzz_loop(ctx, new_fp, fuzz_cb) < 0) {
+ ret = -1;
+ fclose(new_fp);
+ break;
+ }
+ }
+
+ fclose(new_fp);
+
+ if (!state_ctx.more) {
+ if (ctx->debug) {
+ printf("<<<< fuzz_loop backtrack NO MORE at index %u in state=%x\n",
+ ctx->index, ctx->type[ctx->index]);
+ }
+ if (ctx->index > 0)
+ ctx->index--;
+ break;
+ } else {
+ if (ctx->debug) {
+ printf("==== still more tries at index %u in state=%x\n",
+ ctx->index, ctx->type[ctx->index]);
+ }
+ }
+
+ rewind(fp);
+ }
+
+ return ret;
+}
+
+int fuzz(struct fuzz_ctx *ctx, FILE *fp, int (*fuzz_cb)(FILE *fp))
+{
+ struct timeval tv_cur;
+ int ret;
+
+ gettimeofday(&ctx->tv_start, NULL);
+ ctx->fuzz_steps = 0;
+
+ ret = fuzz_loop(ctx, fp, fuzz_cb);
+
+ gettimeofday(&tv_cur, NULL);
+ timersub(&tv_cur, &ctx->tv_start, &ctx->tv_delta);
+
+ return ret;
+}
diff --git a/src/lexer.l b/src/lexer.l
new file mode 100644
index 0000000..9e13448
--- /dev/null
+++ b/src/lexer.l
@@ -0,0 +1,133 @@
+%{
+/*
+ * (C) 2024-2025 by Pablo Neira Ayuso <pablo@netfilter.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+/* Funded through the NGI0 Entrust established by NLnet (https://nlnet.nl)
+ * with support from the European Commission's Next Generation Internet
+ * programme.
+ */
+
+#include <string.h>
+#include "parser_yy.h"
+
+%}
+
+%option yylineno
+%option noinput
+%option nounput
+
+ws [ \t]+
+comment #.*$
+nl [\n\r]
+
+string [a-zA-Z][a-zA-Z0-9\.\-\_]*
+quotedstring \"[^"]*\"
+null [N][U][L][L]
+
+%%
+
+"add_table" { return T_ADD_TABLE; }
+"set_table" { return T_SET_TABLE; }
+"del_table" { return T_DEL_TABLE; }
+"add_basechain" { return T_ADD_BASECHAIN; }
+"del_basechain" { return T_DEL_BASECHAIN; }
+"add_chain" { return T_ADD_CHAIN; }
+"del_chain" { return T_DEL_CHAIN; }
+"add_set" { return T_ADD_SET; }
+"set_set" { return T_SET_SET; }
+"del_set" { return T_DEL_SET; }
+"flush_set" { return T_FLUSH_SET; }
+"add_flowtable" { return T_ADD_FLOWTABLE; }
+"del_flowtable" { return T_DEL_FLOWTABLE; }
+"add_rule" { return T_ADD_RULE; }
+"del_rule" { return T_DEL_RULE; }
+"add_elem" { return T_ADD_ELEM; }
+"del_elem" { return T_DEL_ELEM; }
+"bitwise" { return T_ADD_BITWISE; }
+"byteorder" { return T_ADD_BYTEORDER; }
+"cmp" { return T_ADD_CMP; }
+"connlimit" { return T_ADD_CONNLIMIT; }
+"counter" { return T_ADD_COUNTER; }
+"ct" { return T_ADD_CT; }
+"dup" { return T_ADD_DUP; }
+"dynset" { return T_ADD_DYNSET; }
+"exthdr" { return T_ADD_EXTHDR; }
+"fib" { return T_ADD_FIB; }
+"flow_offload" { return T_ADD_FLOW_OFFLOAD; }
+"fwd" { return T_ADD_FWD; }
+"hash" { return T_ADD_HASH; }
+"immediate" { return T_ADD_IMMEDIATE; }
+"inner" { return T_ADD_INNER; }
+"last" { return T_ADD_LAST; }
+"limit" { return T_ADD_LIMIT; }
+"log" { return T_ADD_LOG; }
+"lookup" { return T_ADD_LOOKUP; }
+"masq" { return T_ADD_MASQ; }
+"match" { return T_ADD_MATCH; }
+"meta" { return T_ADD_META; }
+"nat" { return T_ADD_NAT; }
+"numgen" { return T_ADD_NUMGEN; }
+"objref" { return T_ADD_OBJREF; }
+"osf" { return T_ADD_OSF; }
+"payload" { return T_ADD_PAYLOAD; }
+"queue" { return T_ADD_QUEUE; }
+"quota" { return T_ADD_QUOTA; }
+"range" { return T_ADD_RANGE; }
+"redir" { return T_ADD_REDIR; }
+"reject" { return T_ADD_REJECT; }
+"rt" { return T_ADD_RT; }
+"socket" { return T_ADD_SOCKET; }
+"synproxy" { return T_ADD_SYNPROXY; }
+"target" { return T_ADD_TARGET; }
+"tproxy" { return T_ADD_TPROXY; }
+"tunnel" { return T_ADD_TUNNEL; }
+"xfrm" { return T_ADD_XFRM; }
+"add_obj_counter" { return T_ADD_OBJ_COUNTER; }
+"add_obj_quota" { return T_ADD_OBJ_QUOTA; }
+"add_obj_ct_helper" { return T_ADD_OBJ_CT_HELPER; }
+"add_obj_ct_expect" { return T_ADD_OBJ_CT_EXPECT; }
+"add_obj_limit" { return T_ADD_OBJ_LIMIT; }
+"add_obj_connlimit" { return T_ADD_OBJ_CONNLIMIT; }
+"add_obj_tunnel" { return T_ADD_OBJ_TUNNEL; }
+"add_obj_ct_timeout" { return T_ADD_OBJ_CT_TIMEOUT; }
+"add_obj_secmark" { return T_ADD_OBJ_SECMARK; }
+"add_obj_synproxy" { return T_ADD_OBJ_SYNPROXY; }
+"add_obj_unknown" { return T_ADD_OBJ_UNKNOWN; }
+"del_obj_unknown" { return T_DEL_OBJ_UNKNOWN; }
+"commit" { return T_COMMIT; }
+"abort" { return T_ABORT; }
+
+{null} { return NULLSTR; }
+
+{quotedstring} {
+ yytext[yyleng - 1] = '\0';
+ yylval.string = strdup(yytext + 1);
+ return QSTRING;
+ }
+
+{string} {
+ yylval.string = strdup(yytext);
+ return STRING;
+ }
+
+{comment} ;
+{ws} ;
+{nl} ;
+
+<<EOF>> { yyterminate(); }
+
+. { return yytext[0]; }
+
+%%
+
+int
+yywrap(void)
+{
+ return 1;
+}
diff --git a/src/lib.c b/src/lib.c
new file mode 100644
index 0000000..05bc188
--- /dev/null
+++ b/src/lib.c
@@ -0,0 +1,2404 @@
+/*
+ * (C) 2024-2025 by Pablo Neira Ayuso <pablo@netfilter.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+/* Funded through the NGI0 Entrust established by NLnet (https://nlnet.nl)
+ * with support from the European Commission's Next Generation Internet
+ * programme.
+ */
+
+#include <stdlib.h>
+#include <sys/select.h>
+#include <sys/time.h>
+#include <string.h>
+#include <time.h>
+#include <errno.h>
+#include <arpa/inet.h>
+#include <libmnl/libmnl.h>
+#include <libnftnl/common.h>
+#include <linux/netfilter.h>
+#include <linux/netfilter/nf_tables.h>
+#include <linux/netfilter/nf_tables_compat.h>
+#include <linux/netfilter/nfnetlink.h>
+#include <linux/netfilter/nfnetlink_hook.h>
+#include <limits.h>
+#include "lib.h"
+#include "test.h"
+
+static struct mnl_socket *__setup_socket(struct test_batch *batch)
+{
+ unsigned int newbuffsiz;
+ struct mnl_socket *nl;
+
+ nl = mnl_socket_open(NETLINK_NETFILTER);
+ if (nl == NULL) {
+ perror("mnl_socket_open");
+ exit(EXIT_FAILURE);
+ }
+
+ if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) {
+ perror("mnl_socket_bind");
+ exit(EXIT_FAILURE);
+ }
+
+ newbuffsiz = sizeof(batch->buf);
+
+ /* Rise sender buffer length to avoid hitting -EMSGSIZE */
+ setsockopt(mnl_socket_get_fd(nl), SOL_SOCKET, SO_SNDBUF,
+ &newbuffsiz, sizeof(socklen_t));
+
+ /* unpriviledged containers check for CAP_NET_ADMIN on the init_user_ns. */
+ setsockopt(mnl_socket_get_fd(nl), SOL_SOCKET, SO_SNDBUFFORCE,
+ &newbuffsiz, sizeof(socklen_t));
+
+ return nl;
+}
+
+static struct mnl_nlmsg_batch *__setup_batch(char *buf, uint32_t bufsiz)
+{
+ struct mnl_nlmsg_batch *batch;
+
+ batch = mnl_nlmsg_batch_start(buf, bufsiz);
+ nftnl_batch_begin(mnl_nlmsg_batch_current(batch), 0);
+ mnl_nlmsg_batch_next(batch);
+
+ return batch;
+}
+
+void setup_batch(struct test_batch *batch)
+{
+ INIT_LIST_HEAD(&batch->cmd);
+ batch->batch = __setup_batch(batch->buf, sizeof(batch->buf));
+ batch->nl = __setup_socket(batch);
+}
+
+static void add_cmd(struct test_batch *batch, enum test_batch_type type, void *obj)
+{
+ struct test_batch_cmd *cmd;
+
+ cmd = calloc(1, sizeof(struct test_batch_cmd));
+ if (!cmd)
+ return;
+
+ cmd->type = type;
+ cmd->obj = obj;
+ cmd->lineno = batch->lineno;
+ list_add_tail(&cmd->list, &batch->cmd);
+}
+
+static void setup_table_ctx(struct test_batch *batch,
+ const struct table *table)
+{
+ if (table) {
+ if (table->nfproto)
+ batch->ctx.family = *table->nfproto;
+ if (table->name)
+ batch->ctx.table = table->name;
+ } else {
+ batch->ctx.family = NFPROTO_UNSPEC;
+ batch->ctx.table = NULL;
+ }
+}
+
+static void setup_batch_ctx(struct test_batch *batch,
+ struct rule *rule,
+ struct set *set)
+{
+ batch->ctx.rule = rule;
+ batch->ctx.set = set;
+}
+
+void free_table(struct table *table)
+{
+ free((void *)table->userdata);
+ free((void *)table->name);
+ free(table);
+}
+
+void add_table(struct test_batch *batch, struct table *table)
+{
+ add_cmd(batch, ADD_TABLE, table);
+ setup_table_ctx(batch, table);
+}
+
+void set_table(struct test_batch *batch, struct table *table)
+{
+ add_cmd(batch, SET_TABLE, table);
+ setup_table_ctx(batch, table);
+}
+
+void del_table(struct test_batch *batch, struct table *table)
+{
+ add_cmd(batch, DEL_TABLE, table);
+}
+
+void free_chain(struct chain *chain)
+{
+ free((void *)chain->table);
+ free((void *)chain->name);
+ free((void *)chain->type);
+ free((void *)chain->dev);
+ array_free(chain->dev_array);
+ free((void *)chain->userdata);
+ free(chain);
+}
+
+void add_chain(struct test_batch *batch, struct chain *chain)
+{
+ if (batch->ctx.table) {
+ chain->table = strdup(batch->ctx.table);
+ *chain->family = batch->ctx.family;
+ }
+
+ add_cmd(batch, ADD_CHAIN, chain);
+ setup_batch_ctx(batch, NULL, NULL);
+}
+
+struct array *array_alloc(void)
+{
+ return calloc(1, sizeof(struct array));
+}
+
+bool array_add(struct array *array, void *data)
+{
+ if (array->num >= ARRAY_MAX)
+ return false;
+
+ array->data[array->num++] = data;
+
+ return true;
+}
+
+void array_free(struct array *array)
+{
+ int i;
+
+ if (!array)
+ return;
+
+ for (i = 0; i < array->num; i++)
+ free(array->data[i]);
+
+ free(array);
+}
+
+void add_basechain(struct test_batch *batch, struct chain *chain)
+{
+ if (batch->ctx.table) {
+ chain->table = strdup(batch->ctx.table);
+ *chain->family = batch->ctx.family;
+ }
+
+ add_cmd(batch, ADD_BASECHAIN, chain);
+ setup_batch_ctx(batch, NULL, NULL);
+}
+
+void del_basechain(struct test_batch *batch, struct chain *chain)
+{
+ if (batch->ctx.table) {
+ chain->table = strdup(batch->ctx.table);
+ *chain->family = batch->ctx.family;
+ }
+
+ add_cmd(batch, DEL_BASECHAIN, chain);
+}
+
+void free_flowtable(struct flowtable *flowtable)
+{
+ free((void *)flowtable->table);
+ free((void *)flowtable->name);
+ array_free(flowtable->dev_array);
+ free(flowtable);
+}
+
+void add_flowtable(struct test_batch *batch, struct flowtable *flowtable)
+{
+ if (batch->ctx.table) {
+ flowtable->table = strdup(batch->ctx.table);
+ *flowtable->family = batch->ctx.family;
+ }
+
+ add_cmd(batch, ADD_FLOWTABLE, flowtable);
+ setup_batch_ctx(batch, NULL, NULL);
+}
+
+void del_flowtable(struct test_batch *batch, struct flowtable *flowtable)
+{
+ if (batch->ctx.table) {
+ flowtable->table = strdup(batch->ctx.table);
+ *flowtable->family = batch->ctx.family;
+ }
+
+ add_cmd(batch, DEL_FLOWTABLE, flowtable);
+}
+
+void free_expr_list(struct list_head *expr_list)
+{
+ struct expr *expr, *next;
+
+ list_for_each_entry_safe(expr, next, expr_list, list) {
+ switch (expr->type) {
+ case BITWISE:
+ free_bitwise((struct bitwise *)expr);
+ break;
+ case BYTEORDER:
+ free_byteorder((struct byteorder *)expr);
+ break;
+ case CMP:
+ free_cmp((struct cmp *)expr);
+ break;
+ case COUNTER:
+ free_counter((struct counter *)expr);
+ break;
+ case CONNLIMIT:
+ free_connlimit((struct connlimit *)expr);
+ break;
+ case CT:
+ free_ct((struct ct *)expr);
+ break;
+ case DUP:
+ free_dup((struct dup *)expr);
+ break;
+ case DYNSET:
+ free_dynset((struct dynset *)expr);
+ break;
+ case EXTHDR:
+ free_exthdr((struct exthdr *)expr);
+ break;
+ case FIB:
+ free_fib((struct fib *)expr);
+ break;
+ case FWD:
+ free_fwd((struct fwd *)expr);
+ break;
+ case HASH:
+ free_hash((struct hash *)expr);
+ break;
+ case INNER:
+ free_inner((struct inner *)expr);
+ break;
+ case IMMEDIATE:
+ free_immediate((struct immediate *)expr);
+ break;
+ case LAST:
+ free_last((struct last *)expr);
+ break;
+ case LIMIT:
+ free_limit((struct limit *)expr);
+ break;
+ case LOG:
+ free_log((struct log *)expr);
+ break;
+ case LOOKUP:
+ free_lookup((struct lookup *)expr);
+ break;
+ case MASQ:
+ free_masq((struct masq *)expr);
+ break;
+ case MATCH:
+ free_match((struct match *)expr);
+ break;
+ case META:
+ free_meta((struct meta *)expr);
+ break;
+ case NAT:
+ free_nat((struct nat *)expr);
+ break;
+ case NUMGEN:
+ free_numgen((struct numgen *)expr);
+ break;
+ case OBJREF:
+ free_objref((struct objref *)expr);
+ break;
+ case OSF:
+ free_osf((struct osf *)expr);
+ break;
+ case PAYLOAD:
+ free_payload((struct payload *)expr);
+ break;
+ case QUEUE:
+ free_queue((struct queue *)expr);
+ break;
+ case QUOTA:
+ free_quota((struct quota *)expr);
+ break;
+ case RANGE:
+ free_range((struct range *)expr);
+ break;
+ case REDIR:
+ free_redir((struct redir *)expr);
+ break;
+ case REJECT:
+ free_reject((struct reject *)expr);
+ break;
+ case RT:
+ free_rt((struct rt *)expr);
+ break;
+ case SOCKET:
+ free_socket((struct socket *)expr);
+ break;
+ case SYNPROXY:
+ free_synproxy((struct synproxy *)expr);
+ break;
+ case TARGET:
+ free_target((struct target *)expr);
+ break;
+ case TPROXY:
+ free_tproxy((struct tproxy *)expr);
+ break;
+ case TUNNEL:
+ free_tunnel((struct tunnel *)expr);
+ break;
+ case XFRM:
+ free_xfrm((struct xfrm *)expr);
+ break;
+ }
+ }
+}
+
+void free_rule(struct rule *rule)
+{
+ free_expr_list(&rule->expr_list);
+ free((void *)rule->table);
+ free((void *)rule->chain);
+ free((void *)rule->userdata);
+ free(rule);
+}
+
+void add_rule(struct test_batch *batch, struct rule *rule)
+{
+ if (batch->ctx.table) {
+ rule->table = strdup(batch->ctx.table);
+ *rule->family = batch->ctx.family;
+ }
+
+ add_cmd(batch, ADD_RULE, rule);
+ setup_batch_ctx(batch, rule, NULL);
+}
+
+int add_expr(struct test_batch *batch, struct expr *expr)
+{
+ if (batch->ctx.expr_list) {
+ list_add_tail(&expr->list, batch->ctx.expr_list);
+ batch->ctx.expr_list = NULL;
+ } else if (batch->ctx.rule) {
+ list_add_tail(&expr->list, &batch->ctx.rule->expr_list);
+ } else if (batch->ctx.set) {
+ list_add_tail(&expr->list, &batch->ctx.set->expr_list);
+ } else {
+ return -1;
+ }
+
+ return 0;
+}
+
+void free_obj(struct obj *obj)
+{
+ free((void *)obj->table);
+ free((void *)obj->name);
+ free((void *)obj->userdata);
+
+ if (obj->type) {
+ switch (*obj->type) {
+ case NFT_OBJECT_COUNTER:
+ free(obj->u.counter);
+ break;
+ case NFT_OBJECT_QUOTA:
+ free(obj->u.quota);
+ break;
+ case NFT_OBJECT_LIMIT:
+ free(obj->u.limit);
+ break;
+ case NFT_OBJECT_CONNLIMIT:
+ free(obj->u.connlimit);
+ break;
+ case NFT_OBJECT_TUNNEL:
+ free(obj->u.tun);
+ break;
+ case NFT_OBJECT_CT_EXPECT:
+ free(obj->u.ct_expect);
+ break;
+ case NFT_OBJECT_SYNPROXY:
+ free(obj->u.synproxy);
+ break;
+ case NFT_OBJECT_CT_HELPER:
+ free((void *)obj->u.ct_helper->name);
+ free(obj->u.ct_helper);
+ break;
+ case NFT_OBJECT_CT_TIMEOUT:
+ array_u32_free(obj->u.ct_timeout->timeout_array);
+ free(obj->u.ct_timeout);
+ break;
+ case NFT_OBJECT_SECMARK:
+ free((void *)obj->u.secmark->ctx);
+ free(obj->u.secmark);
+ break;
+ }
+ }
+
+ free(obj);
+}
+
+void add_obj(struct test_batch *batch, struct obj *obj)
+{
+ if (batch->ctx.table) {
+ obj->table = strdup(batch->ctx.table);
+ *obj->family = batch->ctx.family;
+ }
+
+ add_cmd(batch, ADD_OBJECT, obj);
+ setup_batch_ctx(batch, NULL, NULL);
+}
+
+void del_obj(struct test_batch *batch, struct obj *obj)
+{
+ if (batch->ctx.table) {
+ obj->table = strdup(batch->ctx.table);
+ *obj->family = batch->ctx.family;
+ }
+
+ add_cmd(batch, DEL_OBJECT, obj);
+}
+
+struct array_u8 *array_u8_alloc(void)
+{
+ return calloc(1, sizeof(struct array_u8));
+}
+
+bool array_u8_add(struct array_u8 *array, const char *value)
+{
+ uint32_t val;
+ char *endptr;
+
+ if (array->num >= ARRAY_MAX)
+ return false;
+
+ val = strtoul(value, &endptr, 0);
+ if (!endptr)
+ return false;
+
+ if (val > UINT16_MAX)
+ return false;
+
+ array->data[array->num++] = val;
+ free((void *)value);
+
+ return true;
+}
+
+void array_u8_free(struct array_u8 *array)
+{
+ free(array);
+}
+
+struct array_u32 *array_u32_alloc(void)
+{
+ return calloc(1, sizeof(struct array_u32));
+}
+
+bool array_u32_add(struct array_u32 *array, const char *value)
+{
+ uint32_t val;
+ char *endptr;
+
+ if (array->num >= ARRAY_MAX)
+ return false;
+
+ val = strtoul(value, &endptr, 0);
+ if (!endptr)
+ return false;
+
+ if (val > UINT16_MAX)
+ return false;
+
+ array->data[array->num++] = val;
+ free((void *)value);
+
+ return true;
+}
+
+void array_u32_free(struct array_u32 *array)
+{
+ free(array);
+}
+
+void free_set(struct set *set)
+{
+ free_expr_list(&set->expr_list);
+ free((void *)set->userdata);
+ free((void *)set->table);
+ free((void *)set->name);
+ array_u8_free(set->field_array);
+ free(set);
+}
+
+void add_set(struct test_batch *batch, struct set *set)
+{
+ if (batch->ctx.table) {
+ set->table = strdup(batch->ctx.table);
+ *set->family = batch->ctx.family;
+ }
+
+ add_cmd(batch, ADD_SET, set);
+ setup_batch_ctx(batch, NULL, set);
+}
+
+void set_set(struct test_batch *batch, struct set *set)
+{
+ if (batch->ctx.table) {
+ set->table = strdup(batch->ctx.table);
+ *set->family = batch->ctx.family;
+ }
+
+ add_cmd(batch, SET_SET, set);
+ setup_batch_ctx(batch, NULL, set);
+}
+
+void free_elem(struct elem *elem)
+{
+ free((void *)elem->table);
+ free((void *)elem->set);
+ free((void *)elem->chain);
+ free((void *)elem->objname);
+ free((void *)elem->userdata);
+ free(elem);
+}
+
+void add_elem(struct test_batch *batch, struct elem *elem)
+{
+ if (batch->ctx.table) {
+ elem->table = strdup(batch->ctx.table);
+ *elem->family = batch->ctx.family;
+ }
+ if (batch->ctx.set) {
+ if (batch->ctx.set->name)
+ elem->set = strdup(batch->ctx.set->name);
+ if (batch->ctx.set->set_id)
+ elem->set_id = *batch->ctx.set->set_id;
+ }
+
+ add_cmd(batch, ADD_SETELEM, elem);
+}
+
+void del_elem(struct test_batch *batch, struct elem *elem)
+{
+ if (batch->ctx.table) {
+ elem->table = strdup(batch->ctx.table);
+ *elem->family = batch->ctx.family;
+ }
+ if (batch->ctx.set) {
+ if (batch->ctx.set->name)
+ elem->set = strdup(batch->ctx.set->name);
+ if (batch->ctx.set->set_id)
+ elem->set_id = *batch->ctx.set->set_id;
+ }
+
+ add_cmd(batch, DEL_SETELEM, elem);
+}
+
+static uint32_t get_nfproto(uint32_t *nfproto)
+{
+ return nfproto ? *nfproto : NFPROTO_UNSPEC;
+}
+
+static void build_table(struct test_batch *batch, struct test_batch_cmd *cmd)
+{
+ struct table *table = cmd->obj;
+ struct nlmsghdr *nlh;
+
+ nlh = nftnl_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch->batch),
+ NFT_MSG_NEWTABLE, get_nfproto(table->nfproto),
+ NLM_F_CREATE, cmd->lineno);
+
+ if (table->name)
+ mnl_attr_put_strz(nlh, NFTA_TABLE_NAME, table->name);
+ if (table->handle)
+ mnl_attr_put_u64(nlh, NFTA_TABLE_HANDLE, htobe64(*table->handle));
+ if (table->flags)
+ mnl_attr_put_u32(nlh, NFTA_TABLE_FLAGS, htonl(*table->flags));
+ if (table->userdata)
+ mnl_attr_put(nlh, NFTA_TABLE_USERDATA, strlen(table->userdata), table->userdata);
+
+ mnl_nlmsg_batch_next(batch->batch);
+}
+
+static void build_del_table(struct test_batch *batch, struct test_batch_cmd *cmd)
+{
+ struct table *table = cmd->obj;
+ struct nlmsghdr *nlh;
+
+ nlh = nftnl_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch->batch),
+ NFT_MSG_DELTABLE, get_nfproto(table->nfproto),
+ 0, cmd->lineno);
+ if (table->name)
+ mnl_attr_put_strz(nlh, NFTA_TABLE_NAME, table->name);
+ if (table->handle)
+ mnl_attr_put_u64(nlh, NFTA_TABLE_HANDLE, htobe64(*table->handle));
+ mnl_nlmsg_batch_next(batch->batch);
+}
+
+static void __build_chain(struct nlmsghdr *nlh, struct chain *chain)
+{
+ struct nlattr *nest;
+
+ if (chain->table)
+ mnl_attr_put_strz(nlh, NFTA_CHAIN_TABLE, chain->table);
+ if (chain->name)
+ mnl_attr_put_strz(nlh, NFTA_CHAIN_NAME, chain->name);
+ if (chain->flags)
+ mnl_attr_put_u32(nlh, NFTA_CHAIN_FLAGS, htonl(*chain->flags));
+ if (chain->chain_id)
+ mnl_attr_put_u32(nlh, NFTA_CHAIN_ID, htonl(*chain->chain_id));
+ if (chain->userdata)
+ mnl_attr_put(nlh, NFTA_CHAIN_USERDATA, strlen(chain->userdata), chain->userdata);
+ if (chain->handle)
+ mnl_attr_put_u64(nlh, NFTA_CHAIN_HANDLE, be64toh(*chain->handle));
+
+ if (chain->hooknum || chain->prio || chain->dev || chain->dev_array)
+ nest = mnl_attr_nest_start(nlh, NFTA_CHAIN_HOOK);
+
+ if (chain->hooknum)
+ mnl_attr_put_u32(nlh, NFTA_HOOK_HOOKNUM, htonl(*chain->hooknum));
+ if (chain->prio)
+ mnl_attr_put_u32(nlh, NFTA_HOOK_PRIORITY, htonl(*chain->prio));
+ if (chain->dev)
+ mnl_attr_put_strz(nlh, NFTA_HOOK_DEV, chain->dev);
+ if (chain->dev_array) {
+ struct nlattr *nest_dev;
+ int i;
+
+ nest_dev = mnl_attr_nest_start(nlh, NFTA_HOOK_DEVS);
+ for (i = 0; i < chain->dev_array->num; i++)
+ mnl_attr_put_strz(nlh, NFTA_DEVICE_NAME,
+ chain->dev_array->data[i]);
+ mnl_attr_nest_end(nlh, nest_dev);
+ }
+
+ if (chain->hooknum || chain->prio || chain->dev || chain->dev_array)
+ mnl_attr_nest_end(nlh, nest);
+
+ if (chain->policy)
+ mnl_attr_put_u32(nlh, NFTA_CHAIN_POLICY, htonl(*chain->policy));
+ if (chain->type)
+ mnl_attr_put_strz(nlh, NFTA_CHAIN_TYPE, chain->type);
+
+ if (chain->bytes || chain->pkts)
+ nest = mnl_attr_nest_start(nlh, NFTA_CHAIN_COUNTERS);
+
+ if (chain->bytes)
+ mnl_attr_put_u64(nlh, NFTA_COUNTER_BYTES, be64toh(*chain->bytes));
+ if (chain->pkts)
+ mnl_attr_put_u64(nlh, NFTA_COUNTER_PACKETS, be64toh(*chain->pkts));
+
+ if (chain->bytes || chain->pkts)
+ mnl_attr_nest_end(nlh, nest);
+}
+
+static void build_chain(struct test_batch *batch, struct test_batch_cmd *cmd)
+{
+ struct chain *chain = cmd->obj;
+ struct nlmsghdr *nlh;
+
+ nlh = nftnl_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch->batch),
+ NFT_MSG_NEWCHAIN, get_nfproto(chain->family),
+ NLM_F_CREATE, cmd->lineno);
+ __build_chain(nlh, chain);
+ mnl_nlmsg_batch_next(batch->batch);
+}
+
+static void build_bitwise(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct bitwise *bitwise = (struct bitwise *)expr;
+
+ if (bitwise->sreg)
+ mnl_attr_put_u32(nlh, NFTA_BITWISE_SREG, htonl(*bitwise->sreg));
+ if (bitwise->dreg)
+ mnl_attr_put_u32(nlh, NFTA_BITWISE_DREG, htonl(*bitwise->dreg));
+ if (bitwise->op)
+ mnl_attr_put_u32(nlh, NFTA_BITWISE_OP, htonl(*bitwise->op));
+ if (bitwise->len)
+ mnl_attr_put_u32(nlh, NFTA_BITWISE_LEN, htonl(*bitwise->len));
+ if (bitwise->mask_len) {
+ struct nlattr *nest;
+
+ nest = mnl_attr_nest_start(nlh, NFTA_BITWISE_MASK);
+ mnl_attr_put(nlh, NFTA_DATA_VALUE, bitwise->mask_len,
+ bitwise->mask);
+ mnl_attr_nest_end(nlh, nest);
+ }
+ if (bitwise->xor) {
+ struct nlattr *nest;
+
+ nest = mnl_attr_nest_start(nlh, NFTA_BITWISE_XOR);
+ mnl_attr_put(nlh, NFTA_DATA_VALUE, bitwise->xor_len,
+ bitwise->xor);
+ mnl_attr_nest_end(nlh, nest);
+ }
+ if (bitwise->data) {
+ struct nlattr *nest;
+
+ nest = mnl_attr_nest_start(nlh, NFTA_BITWISE_DATA);
+ mnl_attr_put(nlh, NFTA_DATA_VALUE, bitwise->data_len,
+ bitwise->data);
+ mnl_attr_nest_end(nlh, nest);
+ }
+}
+
+static void build_byteorder(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct byteorder *byteorder = (struct byteorder *)expr;
+
+ if (byteorder->sreg)
+ mnl_attr_put_u32(nlh, NFTA_BYTEORDER_SREG, htonl(*byteorder->sreg));
+ if (byteorder->dreg)
+ mnl_attr_put_u32(nlh, NFTA_BYTEORDER_DREG, htonl(*byteorder->dreg));
+ if (byteorder->op)
+ mnl_attr_put_u32(nlh, NFTA_BYTEORDER_OP, htonl(*byteorder->op));
+ if (byteorder->len)
+ mnl_attr_put_u32(nlh, NFTA_BYTEORDER_LEN, htonl(*byteorder->len));
+ if (byteorder->size)
+ mnl_attr_put_u32(nlh, NFTA_BYTEORDER_SIZE, htonl(*byteorder->size));
+}
+
+static void build_cmp(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct cmp *cmp = (struct cmp *)expr;
+
+ if (cmp->sreg)
+ mnl_attr_put_u32(nlh, NFTA_CMP_SREG, htonl(*cmp->sreg));
+ if (cmp->op)
+ mnl_attr_put_u32(nlh, NFTA_CMP_OP, htonl(*cmp->op));
+ if (cmp->data_len) {
+ struct nlattr *nest;
+
+ nest = mnl_attr_nest_start(nlh, NFTA_CMP_DATA);
+ mnl_attr_put(nlh, NFTA_DATA_VALUE, cmp->data_len, cmp->data);
+ mnl_attr_nest_end(nlh, nest);
+ }
+}
+
+static void build_connlimit(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct connlimit *connlimit = (struct connlimit *)expr;
+
+ if (connlimit->count) {
+ mnl_attr_put_u32(nlh, NFTA_CONNLIMIT_COUNT,
+ htonl(*connlimit->count));
+ }
+ if (connlimit->flags) {
+ mnl_attr_put_u32(nlh, NFTA_CONNLIMIT_FLAGS,
+ htonl(*connlimit->flags));
+ }
+}
+
+static void build_counter(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct counter *ctr = (struct counter *)expr;
+
+ if (ctr->bytes)
+ mnl_attr_put_u64(nlh, NFTA_COUNTER_BYTES, htobe64(*ctr->bytes));
+ if (ctr->pkts)
+ mnl_attr_put_u64(nlh, NFTA_COUNTER_PACKETS, htobe64(*ctr->pkts));
+}
+
+static void build_ct(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct ct *ct = (struct ct *)expr;
+
+ if (ct->key)
+ mnl_attr_put_u32(nlh, NFTA_CT_KEY, htonl(*ct->key));
+ if (ct->dreg)
+ mnl_attr_put_u32(nlh, NFTA_CT_DREG, htonl(*ct->dreg));
+ if (ct->dir)
+ mnl_attr_put_u8(nlh, NFTA_CT_DIRECTION, *ct->dir);
+ if (ct->sreg)
+ mnl_attr_put_u32(nlh, NFTA_CT_SREG, htonl(*ct->sreg));
+}
+
+static void build_dup(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct dup *dup = (struct dup *)expr;
+
+ if (dup->sreg_addr)
+ mnl_attr_put_u32(nlh, NFTA_DUP_SREG_ADDR, htonl(*dup->sreg_addr));
+ if (dup->sreg_dev)
+ mnl_attr_put_u32(nlh, NFTA_DUP_SREG_DEV, htonl(*dup->sreg_dev));
+}
+
+static void build_dynset(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct dynset *dynset = (struct dynset *)expr;
+ uint32_t num_exprs = 0;
+
+ if (dynset->sreg_key)
+ mnl_attr_put_u32(nlh, NFTA_DYNSET_SREG_KEY, htonl(*dynset->sreg_key));
+ if (dynset->sreg_data)
+ mnl_attr_put_u32(nlh, NFTA_DYNSET_SREG_DATA, htonl(*dynset->sreg_data));
+ if (dynset->op)
+ mnl_attr_put_u32(nlh, NFTA_DYNSET_OP, htonl(*dynset->op));
+ if (dynset->timeout)
+ mnl_attr_put_u64(nlh, NFTA_DYNSET_TIMEOUT, htobe64(*dynset->timeout));
+ if (dynset->set)
+ mnl_attr_put_strz(nlh, NFTA_DYNSET_SET_NAME, dynset->set);
+ if (dynset->set_id)
+ mnl_attr_put_u32(nlh, NFTA_DYNSET_SET_ID, htonl(*dynset->set_id));
+ if (dynset->flags)
+ mnl_attr_put_u32(nlh, NFTA_DYNSET_FLAGS, htonl(*dynset->flags));
+ if (!list_empty(&dynset->expr_list))
+ ; // TODO
+}
+
+static void build_exthdr(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct exthdr *exthdr = (struct exthdr *)expr;
+
+ if (exthdr->dreg)
+ mnl_attr_put_u32(nlh, NFTA_EXTHDR_DREG, htonl(*exthdr->dreg));
+ if (exthdr->sreg)
+ mnl_attr_put_u32(nlh, NFTA_EXTHDR_SREG, htonl(*exthdr->sreg));
+ if (exthdr->type)
+ mnl_attr_put_u8(nlh, NFTA_EXTHDR_TYPE, *exthdr->type);
+ if (exthdr->offset)
+ mnl_attr_put_u32(nlh, NFTA_EXTHDR_OFFSET, htonl(*exthdr->offset));
+ if (exthdr->len)
+ mnl_attr_put_u32(nlh, NFTA_EXTHDR_LEN, htonl(*exthdr->len));
+ if (exthdr->op)
+ mnl_attr_put_u32(nlh, NFTA_EXTHDR_OP, htonl(*exthdr->op));
+ if (exthdr->flags)
+ mnl_attr_put_u32(nlh, NFTA_EXTHDR_FLAGS, htonl(*exthdr->flags));
+}
+
+static void build_fib(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct fib *fib = (struct fib *)expr;
+
+ if (fib->flags)
+ mnl_attr_put_u32(nlh, NFTA_FIB_FLAGS, htonl(*fib->flags));
+ if (fib->result)
+ mnl_attr_put_u32(nlh, NFTA_FIB_RESULT, htonl(*fib->result));
+ if (fib->dreg)
+ mnl_attr_put_u32(nlh, NFTA_FIB_DREG, htonl(*fib->dreg));
+}
+
+static void build_fwd(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct fwd *fwd = (struct fwd *)expr;
+
+ if (fwd->sreg_dev)
+ mnl_attr_put_u32(nlh, NFTA_FWD_SREG_DEV, htonl(*fwd->sreg_dev));
+ if (fwd->sreg_addr)
+ mnl_attr_put_u32(nlh, NFTA_FWD_SREG_ADDR, htonl(*fwd->sreg_addr));
+ if (fwd->nfproto)
+ mnl_attr_put_u32(nlh, NFTA_FWD_NFPROTO, htonl(*fwd->nfproto));
+}
+
+static void build_hash(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct hash *hash = (struct hash *)expr;
+
+ if (hash->sreg)
+ mnl_attr_put_u32(nlh, NFTA_HASH_SREG, htonl(*hash->sreg));
+ if (hash->dreg)
+ mnl_attr_put_u32(nlh, NFTA_HASH_DREG, htonl(*hash->dreg));
+ if (hash->len)
+ mnl_attr_put_u32(nlh, NFTA_HASH_LEN, htonl(*hash->len));
+ if (hash->modulus)
+ mnl_attr_put_u32(nlh, NFTA_HASH_MODULUS, htonl(*hash->modulus));
+ if (hash->seed)
+ mnl_attr_put_u32(nlh, NFTA_HASH_SEED, htonl(*hash->seed));
+ if (hash->offset)
+ mnl_attr_put_u32(nlh, NFTA_HASH_OFFSET, htonl(*hash->offset));
+ if (hash->type)
+ mnl_attr_put_u32(nlh, NFTA_HASH_TYPE, htonl(*hash->type));
+}
+
+static void build_immediate(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct immediate *imm = (struct immediate *)expr;
+ struct nlattr *nest1, *nest2;
+
+ if (imm->dreg)
+ mnl_attr_put_u32(nlh, NFTA_IMMEDIATE_DREG, htonl(*imm->dreg));
+
+ /* Sane configurations allows you to set ONLY one of these two below */
+ if (imm->data_len) {
+ struct nlattr *nest;
+
+ nest = mnl_attr_nest_start(nlh, NFTA_IMMEDIATE_DATA);
+ mnl_attr_put(nlh, NFTA_DATA_VALUE, imm->data_len, imm->data);
+ mnl_attr_nest_end(nlh, nest);
+ }
+
+ if (imm->verdict || imm->chain) {
+ struct nlattr *nest1, *nest2;
+
+ nest1 = mnl_attr_nest_start(nlh, NFTA_IMMEDIATE_DATA);
+ nest2 = mnl_attr_nest_start(nlh, NFTA_DATA_VERDICT);
+ if (imm->verdict)
+ mnl_attr_put_u32(nlh, NFTA_VERDICT_CODE, htonl(*imm->verdict));
+ if (imm->chain)
+ mnl_attr_put_strz(nlh, NFTA_VERDICT_CHAIN, imm->chain);
+ if (imm->chain_id)
+ mnl_attr_put_u32(nlh, NFTA_VERDICT_CHAIN_ID, htonl(*imm->chain_id));
+
+ mnl_attr_nest_end(nlh, nest1);
+ mnl_attr_nest_end(nlh, nest2);
+ }
+}
+
+static void __build_expr(struct nlmsghdr *nlh, struct expr *expr);
+
+static void build_inner(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct inner *inner = (struct inner *)expr;
+ struct nlattr *nest;
+ struct expr *inner_expr;
+
+ if (inner->num)
+ mnl_attr_put_u32(nlh, NFTA_INNER_NUM, htonl(*inner->num));
+ if (inner->type)
+ mnl_attr_put_u32(nlh, NFTA_INNER_TYPE, htonl(*inner->type));
+ if (inner->flags)
+ mnl_attr_put_u32(nlh, NFTA_INNER_FLAGS, htonl(*inner->flags));
+ if (inner->hdrsize)
+ mnl_attr_put_u32(nlh, NFTA_INNER_HDRSIZE, htonl(*inner->hdrsize));
+ if (!list_empty(&inner->expr_list)) {
+ nest = mnl_attr_nest_start(nlh, NFTA_INNER_EXPR);
+ inner_expr = list_first_entry(&inner->expr_list, struct expr, list);
+ __build_expr(nlh, inner_expr);
+ mnl_attr_nest_end(nlh, nest);
+ }
+}
+
+static void build_last(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct last *last = (struct last *)expr;
+
+ if (last->msecs)
+ mnl_attr_put_u64(nlh, NFTA_LAST_MSECS, htobe64(*last->msecs));
+ if (last->set)
+ mnl_attr_put_u32(nlh, NFTA_LAST_SET, htonl(*last->set));
+}
+
+static void build_limit(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct limit *limit = (struct limit *)expr;
+
+ if (limit->rate)
+ mnl_attr_put_u64(nlh, NFTA_LIMIT_RATE, htobe64(*limit->rate));
+ if (limit->unit)
+ mnl_attr_put_u64(nlh, NFTA_LIMIT_UNIT, htobe64(*limit->unit));
+ if (limit->burst)
+ mnl_attr_put_u32(nlh, NFTA_LIMIT_BURST, htonl(*limit->burst));
+ if (limit->type)
+ mnl_attr_put_u32(nlh, NFTA_LIMIT_TYPE, htonl(*limit->type));
+ if (limit->flags)
+ mnl_attr_put_u32(nlh, NFTA_LIMIT_FLAGS, htonl(*limit->flags));
+}
+
+static void build_log(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct log *log = (struct log *)expr;
+
+ if (log->prefix)
+ mnl_attr_put_strz(nlh, NFTA_LOG_PREFIX, log->prefix);
+ if (log->group)
+ mnl_attr_put_u16(nlh, NFTA_LOG_GROUP, htons(*log->group));
+ if (log->snaplen)
+ mnl_attr_put_u32(nlh, NFTA_LOG_SNAPLEN, htonl(*log->snaplen));
+ if (log->qthreshold)
+ mnl_attr_put_u16(nlh, NFTA_LOG_QTHRESHOLD, htons(*log->qthreshold));
+ if (log->level)
+ mnl_attr_put_u32(nlh, NFTA_LOG_LEVEL, htonl(*log->level));
+ if (log->flags)
+ mnl_attr_put_u32(nlh, NFTA_LOG_FLAGS, htonl(*log->flags));
+}
+
+static void build_lookup(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct lookup *lookup = (struct lookup *)expr;
+
+ if (lookup->sreg)
+ mnl_attr_put_u32(nlh, NFTA_LOOKUP_SREG, htonl(*lookup->sreg));
+ if (lookup->dreg)
+ mnl_attr_put_u32(nlh, NFTA_LOOKUP_DREG, htonl(*lookup->dreg));
+ if (lookup->set)
+ mnl_attr_put_strz(nlh, NFTA_LOOKUP_SET, lookup->set);
+ if (lookup->set_id)
+ mnl_attr_put_u32(nlh, NFTA_LOOKUP_SET_ID, htonl(*lookup->set_id));
+ if (lookup->flags)
+ mnl_attr_put_u32(nlh, NFTA_LOOKUP_FLAGS, htonl(*lookup->flags));
+}
+
+static void build_masq(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct masq *masq = (struct masq *)expr;
+
+ if (masq->flags)
+ mnl_attr_put_u32(nlh, NFTA_MASQ_FLAGS, htobe32(*masq->flags));
+ if (masq->sreg_proto_min)
+ mnl_attr_put_u32(nlh, NFTA_MASQ_REG_PROTO_MIN,
+ htobe32(*masq->sreg_proto_min));
+ if (masq->sreg_proto_max)
+ mnl_attr_put_u32(nlh, NFTA_MASQ_REG_PROTO_MAX,
+ htobe32(*masq->sreg_proto_max));
+}
+
+static void build_match(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct match *mt = (struct match *)expr;
+
+ if (mt->name)
+ mnl_attr_put_strz(nlh, NFTA_MATCH_NAME, mt->name);
+ if (mt->rev)
+ mnl_attr_put_u32(nlh, NFTA_MATCH_REV, htonl(*mt->rev));
+ if (mt->data_len)
+ mnl_attr_put(nlh, NFTA_MATCH_INFO, mt->data_len, mt->data);
+}
+
+static void build_meta(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct meta *meta = (struct meta *)expr;
+
+ if (meta->key)
+ mnl_attr_put_u32(nlh, NFTA_META_KEY, htonl(*meta->key));
+ if (meta->dreg)
+ mnl_attr_put_u32(nlh, NFTA_META_DREG, htonl(*meta->dreg));
+ if (meta->sreg)
+ mnl_attr_put_u32(nlh, NFTA_META_SREG, htonl(*meta->sreg));
+}
+
+static void build_nat(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct nat *nat = (struct nat *)expr;
+
+ if (nat->type)
+ mnl_attr_put_u32(nlh, NFTA_NAT_TYPE, htonl(*nat->type));
+ if (nat->nfproto)
+ mnl_attr_put_u32(nlh, NFTA_NAT_FAMILY, htonl(*nat->nfproto));
+ if (nat->sreg_addr_min)
+ mnl_attr_put_u32(nlh, NFTA_NAT_REG_ADDR_MIN,
+ htonl(*nat->sreg_addr_min));
+ if (nat->sreg_addr_max)
+ mnl_attr_put_u32(nlh, NFTA_NAT_REG_ADDR_MAX,
+ htonl(*nat->sreg_addr_max));
+ if (nat->sreg_proto_min)
+ mnl_attr_put_u32(nlh, NFTA_NAT_REG_PROTO_MIN,
+ htonl(*nat->sreg_proto_min));
+ if (nat->sreg_proto_max)
+ mnl_attr_put_u32(nlh, NFTA_NAT_REG_PROTO_MAX,
+ htonl(*nat->sreg_proto_max));
+ if (nat->flags)
+ mnl_attr_put_u32(nlh, NFTA_NAT_FLAGS, htonl(*nat->flags));
+}
+
+static void build_numgen(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct numgen *ng = (struct numgen *)expr;
+
+ if (ng->dreg)
+ mnl_attr_put_u32(nlh, NFTA_NG_DREG, htonl(*ng->dreg));
+ if (ng->modulus)
+ mnl_attr_put_u32(nlh, NFTA_NG_MODULUS, htonl(*ng->modulus));
+ if (ng->type)
+ mnl_attr_put_u32(nlh, NFTA_NG_TYPE, htonl(*ng->type));
+ if (ng->offset)
+ mnl_attr_put_u32(nlh, NFTA_NG_OFFSET, htonl(*ng->offset));
+}
+
+static void build_objref(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct objref *objref = (struct objref *)expr;
+
+ if (objref->type)
+ mnl_attr_put_u32(nlh, NFTA_OBJREF_IMM_TYPE,
+ htonl(*objref->type));
+ if (objref->name)
+ mnl_attr_put_str(nlh, NFTA_OBJREF_IMM_NAME, objref->name);
+ if (objref->sreg)
+ mnl_attr_put_u32(nlh, NFTA_OBJREF_SET_SREG,
+ htonl(*objref->sreg));
+ if (objref->set_name)
+ mnl_attr_put_str(nlh, NFTA_OBJREF_SET_NAME, objref->set_name);
+ if (objref->set_id)
+ mnl_attr_put_u32(nlh, NFTA_OBJREF_SET_ID,
+ htonl(*objref->set_id));
+}
+
+static void build_osf(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct osf *osf = (struct osf *)expr;
+
+ if (osf->dreg)
+ mnl_attr_put_u32(nlh, NFTA_OSF_DREG, htonl(*osf->dreg));
+ if (osf->ttl)
+ mnl_attr_put_u8(nlh, NFTA_OSF_TTL, *osf->ttl);
+ if (osf->flags)
+ mnl_attr_put_u32(nlh, NFTA_OSF_FLAGS, htonl(*osf->flags));
+}
+
+static void build_payload(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct payload *payload = (struct payload *)expr;
+
+ if (payload->sreg)
+ mnl_attr_put_u32(nlh, NFTA_PAYLOAD_SREG, htonl(*payload->sreg));
+ if (payload->dreg)
+ mnl_attr_put_u32(nlh, NFTA_PAYLOAD_DREG, htonl(*payload->dreg));
+ if (payload->base)
+ mnl_attr_put_u32(nlh, NFTA_PAYLOAD_BASE, htonl(*payload->base));
+ if (payload->offset)
+ mnl_attr_put_u32(nlh, NFTA_PAYLOAD_OFFSET, htonl(*payload->offset));
+ if (payload->len)
+ mnl_attr_put_u32(nlh, NFTA_PAYLOAD_LEN, htonl(*payload->len));
+ if (payload->csum_type)
+ mnl_attr_put_u32(nlh, NFTA_PAYLOAD_CSUM_TYPE,
+ htonl(*payload->csum_type));
+ if (payload->csum_offset)
+ mnl_attr_put_u32(nlh, NFTA_PAYLOAD_CSUM_OFFSET,
+ htonl(*payload->csum_offset));
+ if (payload->csum_flags)
+ mnl_attr_put_u32(nlh, NFTA_PAYLOAD_CSUM_FLAGS,
+ htonl(*payload->csum_flags));
+}
+
+static void build_queue(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct queue *queue = (struct queue *)expr;
+
+ if (queue->queue_num)
+ mnl_attr_put_u16(nlh, NFTA_QUEUE_NUM, htons(*queue->queue_num));
+ if (queue->queue_total)
+ mnl_attr_put_u16(nlh, NFTA_QUEUE_TOTAL, htons(*queue->queue_total));
+ if (queue->flags)
+ mnl_attr_put_u16(nlh, NFTA_QUEUE_FLAGS, htons(*queue->flags));
+ if (queue->sreg_qnum)
+ mnl_attr_put_u32(nlh, NFTA_QUEUE_SREG_QNUM, htonl(*queue->sreg_qnum));
+}
+
+static void build_quota(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct quota *quota = (struct quota *)expr;
+
+ if (quota->bytes)
+ mnl_attr_put_u64(nlh, NFTA_QUOTA_BYTES, htobe64(*quota->bytes));
+ if (quota->consumed)
+ mnl_attr_put_u64(nlh, NFTA_QUOTA_CONSUMED, htobe64(*quota->consumed));
+ if (quota->flags)
+ mnl_attr_put_u32(nlh, NFTA_QUOTA_FLAGS, htonl(*quota->flags));
+}
+
+static void build_range(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct range *range = (struct range *)expr;
+
+ if (range->sreg)
+ mnl_attr_put_u32(nlh, NFTA_RANGE_SREG, htonl(*range->sreg));
+ if (range->op)
+ mnl_attr_put_u32(nlh, NFTA_RANGE_OP, htonl(*range->op));
+ if (range->data_from_len) {
+ struct nlattr *nest;
+
+ nest = mnl_attr_nest_start(nlh, NFTA_RANGE_FROM_DATA);
+ mnl_attr_put(nlh, NFTA_DATA_VALUE, range->data_from_len,
+ range->data_from);
+ mnl_attr_nest_end(nlh, nest);
+ }
+ if (range->data_to_len) {
+ struct nlattr *nest;
+
+ nest = mnl_attr_nest_start(nlh, NFTA_RANGE_TO_DATA);
+ mnl_attr_put(nlh, NFTA_DATA_VALUE, range->data_to_len,
+ range->data_to);
+ mnl_attr_nest_end(nlh, nest);
+ }
+}
+
+static void build_redir(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct redir *redir = (struct redir *)expr;
+
+ if (redir->sreg_proto_min)
+ mnl_attr_put_u32(nlh, NFTA_REDIR_REG_PROTO_MIN,
+ htobe32(*redir->sreg_proto_min));
+ if (redir->sreg_proto_max)
+ mnl_attr_put_u32(nlh, NFTA_REDIR_REG_PROTO_MAX,
+ htobe32(*redir->sreg_proto_max));
+ if (redir->flags)
+ mnl_attr_put_u32(nlh, NFTA_REDIR_FLAGS, htobe32(*redir->flags));
+}
+
+static void build_reject(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct reject *reject = (struct reject *)expr;
+
+ if (reject->type)
+ mnl_attr_put_u32(nlh, NFTA_REJECT_TYPE, htonl(*reject->type));
+ if (reject->icmp_code)
+ mnl_attr_put_u8(nlh, NFTA_REJECT_ICMP_CODE, *reject->icmp_code);
+}
+
+static void build_rt(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct rt *rt = (struct rt *)expr;
+
+ if (rt->key)
+ mnl_attr_put_u32(nlh, NFTA_RT_KEY, htonl(*rt->key));
+ if (rt->dreg)
+ mnl_attr_put_u32(nlh, NFTA_RT_DREG, htonl(*rt->dreg));
+}
+
+static void build_socket(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct socket *socket = (struct socket *)expr;
+
+ if (socket->key)
+ mnl_attr_put_u32(nlh, NFTA_SOCKET_KEY, htonl(*socket->key));
+ if (socket->dreg)
+ mnl_attr_put_u32(nlh, NFTA_SOCKET_DREG, htonl(*socket->dreg));
+ if (socket->level)
+ mnl_attr_put_u32(nlh, NFTA_SOCKET_LEVEL, htonl(*socket->level));
+}
+
+static void build_synproxy(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct synproxy *synproxy = (struct synproxy *)expr;
+
+ if (synproxy->mss)
+ mnl_attr_put_u16(nlh, NFTA_SYNPROXY_MSS, htons(*synproxy->mss));
+ if (synproxy->wscale)
+ mnl_attr_put_u8(nlh, NFTA_SYNPROXY_WSCALE, *synproxy->wscale);
+ if (synproxy->flags)
+ mnl_attr_put_u32(nlh, NFTA_SYNPROXY_FLAGS,
+ htonl(*synproxy->flags));
+}
+
+static void build_target(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct target *tg = (struct target *)expr;
+
+ if (tg->name)
+ mnl_attr_put_strz(nlh, NFTA_TARGET_NAME, tg->name);
+ if (tg->rev)
+ mnl_attr_put_u32(nlh, NFTA_TARGET_REV, htonl(*tg->rev));
+ if (tg->data_len)
+ mnl_attr_put(nlh, NFTA_TARGET_INFO, tg->data_len, tg->data);
+}
+
+static void build_tproxy(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct tproxy *tproxy = (struct tproxy *)expr;
+
+ if (tproxy->nfproto)
+ mnl_attr_put_u32(nlh, NFTA_TPROXY_FAMILY, htonl(*tproxy->nfproto));
+
+ if (tproxy->sreg_addr)
+ mnl_attr_put_u32(nlh, NFTA_TPROXY_REG_ADDR,
+ htonl(*tproxy->sreg_addr));
+
+ if (tproxy->sreg_port)
+ mnl_attr_put_u32(nlh, NFTA_TPROXY_REG_PORT,
+ htonl(*tproxy->sreg_port));
+}
+
+static void build_tunnel(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct tunnel *tunnel = (struct tunnel *)expr;
+
+ if (tunnel->key)
+ mnl_attr_put_u32(nlh, NFTA_TUNNEL_KEY, htonl(*tunnel->key));
+ if (tunnel->dreg)
+ mnl_attr_put_u32(nlh, NFTA_TUNNEL_DREG, htonl(*tunnel->dreg));
+}
+
+static void build_xfrm(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct xfrm *x = (struct xfrm *)expr;
+
+ if (x->key)
+ mnl_attr_put_u32(nlh, NFTA_XFRM_KEY, htonl(*x->key));
+ if (x->dir)
+ mnl_attr_put_u8(nlh, NFTA_XFRM_DIR, *x->dir);
+ if (x->spnum)
+ mnl_attr_put_u32(nlh, NFTA_XFRM_SPNUM, htonl(*x->spnum));
+ if (x->dreg)
+ mnl_attr_put_u32(nlh, NFTA_XFRM_DREG, htonl(*x->dreg));
+}
+
+struct {
+ const char *name;
+ void (*build)(struct nlmsghdr *nlh, struct expr *expr);
+} handler[] = {
+ [BITWISE] = { .name = "bitwise", .build = build_bitwise, },
+ [BYTEORDER] = { .name = "byteorder", .build = build_byteorder, },
+ [CMP] = { .name = "cmp", .build = build_cmp, },
+ [CONNLIMIT] = { .name = "connlimit", .build = build_connlimit, },
+ [COUNTER] = { .name = "counter", .build = build_counter, },
+ [CT] = { .name = "ct", .build = build_ct, },
+ [DUP] = { .name = "dup", .build = build_dup, },
+ [DYNSET] = { .name = "dynset", .build = build_dynset, },
+ [EXTHDR] = { .name = "exthdr", .build = build_exthdr, },
+ [FIB] = { .name = "fib", .build = build_fib, },
+ [FWD] = { .name = "fwd", .build = build_fwd, },
+ [HASH] = { .name = "hash", .build = build_hash, },
+ [INNER] = { .name = "inner", .build = build_inner, },
+ [IMMEDIATE] = { .name = "immediate", .build = build_immediate, },
+ [LAST] = { .name = "last", .build = build_last, },
+ [LIMIT] = { .name = "limit", .build = build_limit, },
+ [LOG] = { .name = "log", .build = build_log, },
+ [LOOKUP] = { .name = "lookup", .build = build_lookup, },
+ [MASQ] = { .name = "masq", .build = build_masq, },
+ [MATCH] = { .name = "match", .build = build_match, },
+ [META] = { .name = "meta", .build = build_meta, },
+ [NAT] = { .name = "nat", .build = build_nat, },
+ [NUMGEN] = { .name = "numgen", .build = build_numgen, },
+ [OBJREF] = { .name = "objref", .build = build_objref, },
+ [OSF] = { .name = "osf", .build = build_osf, },
+ [PAYLOAD] = { .name = "payload", .build = build_payload, },
+ [QUEUE] = { .name = "queue", .build = build_queue, },
+ [QUOTA] = { .name = "quota", .build = build_quota, },
+ [RANGE] = { .name = "range", .build = build_range, },
+ [REDIR] = { .name = "redir", .build = build_redir, },
+ [REJECT] = { .name = "reject", .build = build_reject, },
+ [RT] = { .name = "rt", .build = build_rt, },
+ [SOCKET] = { .name = "socket", .build = build_socket, },
+ [SYNPROXY] = { .name = "synproxy", .build = build_synproxy, },
+ [TARGET] = { .name = "target", .build = build_target, },
+ [TPROXY] = { .name = "tproxy", .build = build_tproxy, },
+ [TUNNEL] = { .name = "tunnel", .build = build_tunnel, },
+ [XFRM] = { .name = "xfrm", .build = build_xfrm, },
+};
+
+static void __build_expr(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct nlattr *nest;
+
+ mnl_attr_put_strz(nlh, NFTA_EXPR_NAME, handler[expr->type].name);
+
+ if (!handler[expr->type].build)
+ return;
+
+ nest = mnl_attr_nest_start(nlh, NFTA_EXPR_DATA);
+ handler[expr->type].build(nlh, expr);
+ mnl_attr_nest_end(nlh, nest);
+}
+
+static void __build_rule(struct nlmsghdr *nlh, struct rule *rule)
+{
+ struct nlattr *nest, *nest2;
+ struct expr *expr;
+
+ if (rule->table)
+ mnl_attr_put_strz(nlh, NFTA_RULE_TABLE, rule->table);
+ if (rule->chain)
+ mnl_attr_put_strz(nlh, NFTA_RULE_CHAIN, rule->chain);
+/* FIXME
+ if (rule->handle) {
+ mnl_attr_put_u64(nlh, NFTA_RULE_HANDLE, htobe64(*rule->handle));
+ } */
+ if (rule->pos)
+ mnl_attr_put_u64(nlh, NFTA_RULE_POSITION, htobe64(*rule->pos));
+ if (rule->rule_id)
+ mnl_attr_put_u32(nlh, NFTA_RULE_ID, htonl(*rule->rule_id));
+ if (rule->userdata)
+ mnl_attr_put(nlh, NFTA_RULE_USERDATA, strlen(rule->userdata),
+ rule->userdata);
+ if (rule->pos_id)
+ mnl_attr_put_u32(nlh, NFTA_RULE_POSITION_ID, htonl(*rule->pos_id));
+
+ if (!list_empty(&rule->expr_list)) {
+ nest = mnl_attr_nest_start(nlh, NFTA_RULE_EXPRESSIONS);
+ list_for_each_entry(expr, &rule->expr_list, list) {
+ nest2 = mnl_attr_nest_start(nlh, NFTA_LIST_ELEM);
+ __build_expr(nlh, expr);
+ mnl_attr_nest_end(nlh, nest2);
+ }
+ mnl_attr_nest_end(nlh, nest);
+ }
+ if (rule->compat.l4proto || rule->compat.flags) {
+ nest = mnl_attr_nest_start(nlh, NFTA_RULE_COMPAT);
+ if (rule->compat.l4proto)
+ mnl_attr_put_u32(nlh, NFTA_RULE_COMPAT_PROTO,
+ htonl(*rule->compat.l4proto));
+ if (rule->compat.flags)
+ mnl_attr_put_u32(nlh, NFTA_RULE_COMPAT_FLAGS,
+ htonl(*rule->compat.flags));
+ mnl_attr_nest_end(nlh, nest);
+ }
+}
+
+static void build_rule(struct test_batch *batch, struct test_batch_cmd *cmd)
+{
+ struct rule *rule = cmd->obj;
+ struct nlmsghdr *nlh;
+
+ nlh = nftnl_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch->batch),
+ NFT_MSG_NEWRULE, *rule->family,
+ NLM_F_APPEND | NLM_F_CREATE,
+ cmd->lineno);
+ __build_rule(nlh, rule);
+ mnl_nlmsg_batch_next(batch->batch);
+}
+
+static void __build_set_field(struct nlmsghdr *nlh, struct set *set)
+{
+ struct nlattr *nest;
+ int i;
+
+ nest = mnl_attr_nest_start(nlh, NFTA_SET_DESC_CONCAT);
+ for (i = 0; i < set->field_array->num; i++) {
+ struct nlattr *nest_elem;
+
+ nest_elem = mnl_attr_nest_start(nlh, NFTA_LIST_ELEM);
+ mnl_attr_put_u32(nlh, NFTA_SET_FIELD_LEN,
+ htonl(set->field_array->data[i]));
+ mnl_attr_nest_end(nlh, nest_elem);
+ }
+ mnl_attr_nest_end(nlh, nest);
+}
+
+static void __build_set_desc(struct nlmsghdr *nlh, struct set *set)
+{
+ struct nlattr *nest;
+
+ nest = mnl_attr_nest_start(nlh, NFTA_SET_DESC);
+ if (set->size)
+ mnl_attr_put_u32(nlh, NFTA_SET_DESC_SIZE, htonl(*set->size));
+ if (set->field_array)
+ __build_set_field(nlh, set);
+ mnl_attr_nest_end(nlh, nest);
+}
+
+static void __build_set(struct nlmsghdr *nlh, struct set *set,
+ struct test_batch *batch)
+{
+ uint32_t num_exprs = 0;
+
+ if (set->table)
+ mnl_attr_put_strz(nlh, NFTA_SET_TABLE, set->table);
+ if (set->name)
+ mnl_attr_put_strz(nlh, NFTA_SET_NAME, set->name);
+ if (set->handle)
+ mnl_attr_put_u64(nlh, NFTA_SET_HANDLE, htobe64(*set->handle));
+ if (set->flags)
+ mnl_attr_put_u32(nlh, NFTA_SET_FLAGS, htonl(*set->flags));
+ if (set->key_type)
+ mnl_attr_put_u32(nlh, NFTA_SET_KEY_TYPE, htonl(*set->key_type));
+ if (set->key_len)
+ mnl_attr_put_u32(nlh, NFTA_SET_KEY_LEN, htonl(*set->key_len));
+ if (set->data_type)
+ mnl_attr_put_u32(nlh, NFTA_SET_DATA_TYPE, htonl(*set->data_type));
+ if (set->data_len)
+ mnl_attr_put_u32(nlh, NFTA_SET_DATA_LEN, htonl(*set->data_len));
+ if (set->obj_type)
+ mnl_attr_put_u32(nlh, NFTA_SET_OBJ_TYPE, htonl(*set->obj_type));
+ if (set->set_id)
+ mnl_attr_put_u32(nlh, NFTA_SET_ID, htonl(*set->set_id));
+ if (set->policy)
+ mnl_attr_put_u32(nlh, NFTA_SET_POLICY, htonl(*set->policy));
+ if (set->field_array || set->size)
+ __build_set_desc(nlh, set);
+ if (set->timeout)
+ mnl_attr_put_u64(nlh, NFTA_SET_TIMEOUT, htobe64(*set->timeout));
+ if (set->gc_interval)
+ mnl_attr_put_u32(nlh, NFTA_SET_GC_INTERVAL, htonl(*set->gc_interval));
+ if (set->userdata)
+ mnl_attr_put(nlh, NFTA_SET_USERDATA, strlen(set->userdata), set->userdata);
+ if (!list_empty(&set->expr_list)) {
+ struct expr *expr;
+
+ list_for_each_entry(expr, &set->expr_list, list)
+ num_exprs++;
+
+ if (num_exprs == 1) {
+ struct nlattr *nest1;
+
+ nest1 = mnl_attr_nest_start(nlh, NFTA_SET_EXPR);
+ list_for_each_entry(expr, &set->expr_list, list)
+ __build_expr(nlh, expr);
+
+ mnl_attr_nest_end(nlh, nest1);
+ } else if (num_exprs > 1) {
+ struct nlattr *nest1, *nest2;
+
+ nest1 = mnl_attr_nest_start(nlh, NFTA_SET_EXPRESSIONS);
+ list_for_each_entry(expr, &set->expr_list, list) {
+ nest2 = mnl_attr_nest_start(nlh, NFTA_LIST_ELEM);
+ __build_expr(nlh, expr);
+ mnl_attr_nest_end(nlh, nest2);
+ }
+ mnl_attr_nest_end(nlh, nest1);
+ }
+ }
+}
+
+static void build_set(struct test_batch *batch, struct test_batch_cmd *cmd)
+{
+ struct set *set = cmd->obj;
+ struct nlmsghdr *nlh;
+
+ nlh = nftnl_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch->batch),
+ NFT_MSG_NEWSET, *set->family,
+ NLM_F_CREATE, cmd->lineno);
+ __build_set(nlh, set, batch);
+ mnl_nlmsg_batch_next(batch->batch);
+}
+
+static void __build_one_setelem(struct nlmsghdr *nlh, struct elem *elem,
+ struct test_batch *batch)
+{
+ uint32_t num_exprs = 0;
+ struct nlattr *nest;
+ struct expr *expr;
+
+ if (elem->flags) {
+ mnl_attr_put_u32(nlh, NFTA_SET_ELEM_FLAGS, htonl(*elem->flags));
+ }
+ if (elem->timeout) {
+ mnl_attr_put_u64(nlh, NFTA_SET_ELEM_TIMEOUT, htobe64(*elem->timeout));
+ }
+ if (elem->expiration) {
+ mnl_attr_put_u64(nlh, NFTA_SET_ELEM_EXPIRATION, htobe64(*elem->expiration));
+ }
+ if (elem->key) {
+ struct nlattr *nest1;
+
+ nest1 = mnl_attr_nest_start(nlh, NFTA_SET_ELEM_KEY);
+ mnl_attr_put(nlh, NFTA_DATA_VALUE, elem->key_len, elem->key);
+ mnl_attr_nest_end(nlh, nest1);
+ }
+ if (elem->key_end) {
+ struct nlattr *nest1;
+
+ nest1 = mnl_attr_nest_start(nlh, NFTA_SET_ELEM_KEY_END);
+ mnl_attr_put(nlh, NFTA_DATA_VALUE, elem->key_end_len,
+ elem->key_end);
+ mnl_attr_nest_end(nlh, nest1);
+ }
+ if (elem->verdict) {
+ struct nlattr *nest1, *nest2;
+
+ nest1 = mnl_attr_nest_start(nlh, NFTA_SET_ELEM_DATA);
+ nest2 = mnl_attr_nest_start(nlh, NFTA_DATA_VERDICT);
+ mnl_attr_put_u32(nlh, NFTA_VERDICT_CODE, htonl(*elem->verdict));
+ if (elem->chain)
+ mnl_attr_put_strz(nlh, NFTA_VERDICT_CHAIN, elem->chain);
+
+ mnl_attr_nest_end(nlh, nest1);
+ mnl_attr_nest_end(nlh, nest2);
+ }
+ if (elem->data_len) {
+ struct nlattr *nest1;
+
+ nest1 = mnl_attr_nest_start(nlh, NFTA_SET_ELEM_DATA);
+ mnl_attr_put(nlh, NFTA_DATA_VALUE, elem->data_len, elem->data);
+ mnl_attr_nest_end(nlh, nest1);
+ }
+ if (elem->userdata) {
+ mnl_attr_put(nlh, NFTA_SET_ELEM_USERDATA, strlen(elem->userdata), elem->userdata);
+ }
+
+ if (elem->objname)
+ mnl_attr_put_strz(nlh, NFTA_SET_ELEM_OBJREF, elem->objname);
+
+ if (!list_empty(&elem->expr_list)) {
+ list_for_each_entry(expr, &elem->expr_list, list)
+ num_exprs++;
+
+ if (num_exprs == 1) {
+ struct nlattr *nest1;
+
+ nest1 = mnl_attr_nest_start(nlh, NFTA_SET_ELEM_EXPR);
+ list_for_each_entry(expr, &elem->expr_list, list)
+ __build_expr(nlh, expr);
+
+ mnl_attr_nest_end(nlh, nest1);
+ } else if (num_exprs > 1) {
+ struct nlattr *nest1, *nest2;
+
+ nest1 = mnl_attr_nest_start(nlh, NFTA_SET_ELEM_EXPRESSIONS);
+ list_for_each_entry(expr, &elem->expr_list, list) {
+ nest2 = mnl_attr_nest_start(nlh, NFTA_LIST_ELEM);
+ __build_expr(nlh, expr);
+ mnl_attr_nest_end(nlh, nest2);
+ }
+ mnl_attr_nest_end(nlh, nest1);
+ }
+ }
+}
+
+
+static void __build_setelem(struct nlmsghdr *nlh, struct elem *elem,
+ struct test_batch *batch)
+{
+ struct nlattr *nest1, *nest2;
+
+ if (elem->set)
+ mnl_attr_put_strz(nlh, NFTA_SET_ELEM_LIST_SET, elem->set);
+ if (elem->set_id)
+ mnl_attr_put_u32(nlh, NFTA_SET_ELEM_LIST_SET_ID, htonl(elem->set_id));
+ if (elem->table)
+ mnl_attr_put_strz(nlh, NFTA_SET_ELEM_LIST_TABLE, elem->table);
+
+ nest1 = mnl_attr_nest_start(nlh, NFTA_SET_ELEM_LIST_ELEMENTS);
+ nest2 = mnl_attr_nest_start(nlh, 0);
+ __build_one_setelem(nlh, elem, batch);
+ mnl_attr_nest_end(nlh, nest2);
+ mnl_attr_nest_end(nlh, nest1);
+}
+
+static void build_setelem(struct test_batch *batch, struct test_batch_cmd *cmd)
+{
+ struct elem *elem = cmd->obj;
+ struct nlmsghdr *nlh;
+
+ nlh = nftnl_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch->batch),
+ NFT_MSG_NEWSETELEM, *elem->family,
+ 0, cmd->lineno);
+ __build_setelem(nlh, elem, batch);
+ mnl_nlmsg_batch_next(batch->batch);
+}
+
+static void build_del_setelem(struct test_batch *batch, struct test_batch_cmd *cmd)
+{
+ struct elem *elem = cmd->obj;
+ struct nlmsghdr *nlh;
+
+ nlh = nftnl_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch->batch),
+ NFT_MSG_DELSETELEM, *elem->family,
+ 0, cmd->lineno);
+ __build_setelem(nlh, elem, batch);
+ mnl_nlmsg_batch_next(batch->batch);
+}
+
+static void __build_flowtable(struct nlmsghdr *nlh, struct flowtable *ft)
+{
+ struct nlattr *nest = NULL;
+
+ if (ft->table)
+ mnl_attr_put_strz(nlh, NFTA_FLOWTABLE_TABLE, ft->table);
+ if (ft->name)
+ mnl_attr_put_strz(nlh, NFTA_FLOWTABLE_NAME, ft->name);
+
+ if (ft->hooknum || ft->prio || ft->dev_array)
+ nest = mnl_attr_nest_start(nlh, NFTA_FLOWTABLE_HOOK);
+
+ if (ft->hooknum)
+ mnl_attr_put_u32(nlh, NFTA_FLOWTABLE_HOOK_NUM, htonl(*ft->hooknum));
+ if (ft->prio)
+ mnl_attr_put_u32(nlh, NFTA_FLOWTABLE_HOOK_PRIORITY, htonl(*ft->prio));
+
+ if (ft->dev_array) {
+ struct nlattr *nest_dev;
+ unsigned int i;
+
+ nest_dev = mnl_attr_nest_start(nlh, NFTA_FLOWTABLE_HOOK_DEVS);
+ for (i = 0; i < ft->dev_array->num; i++) {
+ mnl_attr_put_strz(nlh, NFTA_DEVICE_NAME, ft->dev_array->data[i]);
+ }
+ mnl_attr_nest_end(nlh, nest_dev);
+ }
+
+ if (nest)
+ mnl_attr_nest_end(nlh, nest);
+
+/* if (ft->flags)
+ mnl_attr_put_u32(nlh, NFTA_FLOWTABLE_FLAGS, htonl(ft->flags)); */
+/* if (ft->handle)
+ mnl_attr_put_u64(nlh, NFTA_FLOWTABLE_HANDLE, htobe64(ft->handle)); */
+}
+
+static void build_add_flowtable(struct test_batch *batch, struct test_batch_cmd *cmd)
+{
+ struct flowtable *ft = cmd->obj;
+ struct nlmsghdr *nlh;
+
+ nlh = nftnl_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch->batch),
+ NFT_MSG_NEWFLOWTABLE, *ft->family,
+ NLM_F_CREATE, cmd->lineno);
+ __build_flowtable(nlh, ft);
+ mnl_nlmsg_batch_next(batch->batch);
+}
+
+static void build_del_flowtable(struct test_batch *batch, struct test_batch_cmd *cmd)
+{
+ struct flowtable *ft = cmd->obj;
+ struct nlmsghdr *nlh;
+
+ nlh = nftnl_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch->batch),
+ NFT_MSG_DELFLOWTABLE, *ft->family,
+ NLM_F_CREATE, cmd->lineno);
+ __build_flowtable(nlh, ft);
+ mnl_nlmsg_batch_next(batch->batch);
+}
+
+static void __build_obj_type(struct nlmsghdr *nlh, struct obj *obj)
+{
+ switch (*obj->type) {
+ case NFT_OBJECT_COUNTER:
+ if (obj->u.counter->bytes)
+ mnl_attr_put_u64(nlh, NFTA_COUNTER_BYTES, htobe64(*obj->u.counter->bytes));
+ if (obj->u.counter->pkts)
+ mnl_attr_put_u64(nlh, NFTA_COUNTER_PACKETS, htobe64(*obj->u.counter->pkts));
+ break;
+ case NFT_OBJECT_QUOTA:
+ if (obj->u.quota->bytes)
+ mnl_attr_put_u64(nlh, NFTA_QUOTA_BYTES, htobe64(*obj->u.quota->bytes));
+ if (obj->u.quota->consumed)
+ mnl_attr_put_u64(nlh, NFTA_QUOTA_CONSUMED, htobe64(*obj->u.quota->consumed));
+ if (obj->u.quota->flags)
+ mnl_attr_put_u32(nlh, NFTA_QUOTA_FLAGS, htonl(*obj->u.quota->flags));
+ break;
+ case NFT_OBJECT_LIMIT:
+ if (obj->u.limit->rate)
+ mnl_attr_put_u64(nlh, NFTA_LIMIT_RATE, htobe64(*obj->u.limit->rate));
+ if (obj->u.limit->unit)
+ mnl_attr_put_u64(nlh, NFTA_LIMIT_UNIT, htobe64(*obj->u.limit->unit));
+ if (obj->u.limit->burst)
+ mnl_attr_put_u32(nlh, NFTA_LIMIT_BURST, htonl(*obj->u.limit->burst));
+ if (obj->u.limit->type)
+ mnl_attr_put_u32(nlh, NFTA_LIMIT_TYPE, htonl(*obj->u.limit->type));
+ if (obj->u.limit->flags)
+ mnl_attr_put_u32(nlh, NFTA_LIMIT_FLAGS, htonl(*obj->u.limit->flags));
+ break;
+ case NFT_OBJECT_CONNLIMIT:
+ if (obj->u.connlimit->count)
+ mnl_attr_put_u32(nlh, NFTA_CONNLIMIT_COUNT,
+ htonl(*obj->u.connlimit->count));
+ if (obj->u.connlimit->flags)
+ mnl_attr_put_u32(nlh, NFTA_CONNLIMIT_FLAGS,
+ htonl(*obj->u.connlimit->flags));
+ break;
+ case NFT_OBJECT_TUNNEL:
+ if (obj->u.tun->id)
+ mnl_attr_put_u32(nlh, NFTA_TUNNEL_KEY_ID,
+ htonl(*obj->u.tun->id));
+ if (obj->u.tun->src_v4 ||
+ obj->u.tun->dst_v4) {
+ struct nlattr *nest;
+
+ nest = mnl_attr_nest_start(nlh, NFTA_TUNNEL_KEY_IP);
+ if (obj->u.tun->src_v4)
+ mnl_attr_put_u32(nlh, NFTA_TUNNEL_KEY_IP_SRC, *obj->u.tun->src_v4);
+ if (obj->u.tun->dst_v4)
+ mnl_attr_put_u32(nlh, NFTA_TUNNEL_KEY_IP_DST, *obj->u.tun->dst_v4);
+ mnl_attr_nest_end(nlh, nest);
+ }
+ if (obj->u.tun->src_v6 ||
+ obj->u.tun->dst_v6) {
+ struct nlattr *nest;
+
+ nest = mnl_attr_nest_start(nlh, NFTA_TUNNEL_KEY_IP6);
+ if (obj->u.tun->src_v6)
+ mnl_attr_put(nlh, NFTA_TUNNEL_KEY_IP6_SRC,
+ sizeof(obj->u.tun->_src_v6),
+ obj->u.tun->src_v6);
+ if (obj->u.tun->dst_v6)
+ mnl_attr_put(nlh, NFTA_TUNNEL_KEY_IP6_DST,
+ sizeof(obj->u.tun->_dst_v6),
+ obj->u.tun->dst_v6);
+ if (obj->u.tun->flowlabel)
+ mnl_attr_put_u32(nlh, NFTA_TUNNEL_KEY_IP6_FLOWLABEL,
+ htonl(*obj->u.tun->flowlabel));
+ mnl_attr_nest_end(nlh, nest);
+ }
+ if (obj->u.tun->sport)
+ mnl_attr_put_u16(nlh, NFTA_TUNNEL_KEY_SPORT, htons(*obj->u.tun->sport));
+ if (obj->u.tun->dport)
+ mnl_attr_put_u16(nlh, NFTA_TUNNEL_KEY_DPORT, htons(*obj->u.tun->dport));
+ if (obj->u.tun->tos)
+ mnl_attr_put_u8(nlh, NFTA_TUNNEL_KEY_TOS, *obj->u.tun->tos);
+ if (obj->u.tun->ttl)
+ mnl_attr_put_u8(nlh, NFTA_TUNNEL_KEY_TTL, *obj->u.tun->ttl);
+ if (obj->u.tun->flags)
+ mnl_attr_put_u32(nlh, NFTA_TUNNEL_KEY_FLAGS, htonl(*obj->u.tun->flags));
+ break;
+ case NFT_OBJECT_CT_EXPECT:
+ if (obj->u.ct_expect->l3proto)
+ mnl_attr_put_u16(nlh, NFTA_CT_EXPECT_L3PROTO, htons(*obj->u.ct_expect->l3proto));
+ if (obj->u.ct_expect->l4proto)
+ mnl_attr_put_u8(nlh, NFTA_CT_EXPECT_L4PROTO, *obj->u.ct_expect->l4proto);
+ if (obj->u.ct_expect->dport)
+ mnl_attr_put_u16(nlh, NFTA_CT_EXPECT_DPORT, htons(*obj->u.ct_expect->dport));
+ if (obj->u.ct_expect->timeout)
+ mnl_attr_put_u32(nlh, NFTA_CT_EXPECT_TIMEOUT, htonl(*obj->u.ct_expect->timeout));
+ if (obj->u.ct_expect->size)
+ mnl_attr_put_u8(nlh, NFTA_CT_EXPECT_SIZE, *obj->u.ct_expect->size);
+ break;
+ case NFT_OBJECT_SYNPROXY:
+ if (obj->u.synproxy->mss)
+ mnl_attr_put_u16(nlh, NFTA_SYNPROXY_MSS, htons(*obj->u.synproxy->mss));
+ if (obj->u.synproxy->wscale)
+ mnl_attr_put_u8(nlh, NFTA_SYNPROXY_WSCALE, *obj->u.synproxy->wscale);
+ if (obj->u.synproxy->flags)
+ mnl_attr_put_u32(nlh, NFTA_SYNPROXY_FLAGS, htonl(*obj->u.synproxy->flags));
+ break;
+ case NFT_OBJECT_CT_HELPER:
+ if (obj->u.ct_helper->name)
+ mnl_attr_put_str(nlh, NFTA_CT_HELPER_NAME, obj->u.ct_helper->name);
+ if (obj->u.ct_helper->l3proto)
+ mnl_attr_put_u16(nlh, NFTA_CT_HELPER_L3PROTO, htons(*obj->u.ct_helper->l3proto));
+ if (obj->u.ct_helper->l4proto)
+ mnl_attr_put_u8(nlh, NFTA_CT_HELPER_L4PROTO, *obj->u.ct_helper->l4proto);
+ break;
+ case NFT_OBJECT_CT_TIMEOUT:
+ if (obj->u.ct_timeout->l3proto)
+ mnl_attr_put_u16(nlh, NFTA_CT_TIMEOUT_L3PROTO, htons(*obj->u.ct_timeout->l3proto));
+ if (obj->u.ct_timeout->l4proto)
+ mnl_attr_put_u8(nlh, NFTA_CT_TIMEOUT_L4PROTO, *obj->u.ct_timeout->l4proto);
+ if (obj->u.ct_timeout->timeout_array) {
+ struct nlattr *nest;
+ int i;
+
+ nest = mnl_attr_nest_start(nlh, NFTA_CT_TIMEOUT_DATA);
+ for (i = 0; i < obj->u.ct_timeout->timeout_array->num; i++)
+ mnl_attr_put_u32(nlh, i+1, htonl(obj->u.ct_timeout->timeout_array->data[i]));
+
+ mnl_attr_nest_end(nlh, nest);
+ }
+ break;
+ case NFT_OBJECT_SECMARK:
+ if (obj->u.secmark->ctx)
+ mnl_attr_put_str(nlh, NFTA_SECMARK_CTX, obj->u.secmark->ctx);
+ break;
+ }
+}
+
+static void __build_obj_def(struct nlmsghdr *nlh, struct obj *obj)
+{
+ if (obj->table)
+ mnl_attr_put_strz(nlh, NFTA_OBJ_TABLE, obj->table);
+ if (obj->name)
+ mnl_attr_put_strz(nlh, NFTA_OBJ_NAME, obj->name);
+ if (obj->type)
+ mnl_attr_put_u32(nlh, NFTA_OBJ_TYPE, htonl(*obj->type));
+ if (obj->handle)
+ mnl_attr_put_u64(nlh, NFTA_OBJ_HANDLE, htobe64(*obj->handle));
+ if (obj->userdata)
+ mnl_attr_put(nlh, NFTA_OBJ_USERDATA, strlen(obj->userdata), obj->userdata);
+}
+
+static void __build_obj(struct nlmsghdr *nlh, struct obj *obj)
+{
+ __build_obj_def(nlh, obj);
+
+ if (obj->type &&
+ *obj->type > NFT_OBJECT_UNSPEC &&
+ *obj->type <= NFT_OBJECT_MAX) {
+ struct nlattr *nest = mnl_attr_nest_start(nlh, NFTA_OBJ_DATA);
+ __build_obj_type(nlh, obj);
+ mnl_attr_nest_end(nlh, nest);
+ }
+}
+
+static void build_obj(struct test_batch *batch, struct test_batch_cmd *cmd)
+{
+ struct obj *obj = cmd->obj;
+ struct nlmsghdr *nlh;
+
+ nlh = nftnl_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch->batch),
+ NFT_MSG_NEWOBJ, *obj->family,
+ NLM_F_CREATE, cmd->lineno);
+ __build_obj(nlh, obj);
+ mnl_nlmsg_batch_next(batch->batch);
+}
+
+static void build_del_obj(struct test_batch *batch, struct test_batch_cmd *cmd)
+{
+ struct obj *obj = cmd->obj;
+ struct nlmsghdr *nlh;
+
+ nlh = nftnl_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch->batch),
+ NFT_MSG_DELOBJ, *obj->family,
+ 0, cmd->lineno);
+ __build_obj_def(nlh, obj);
+ mnl_nlmsg_batch_next(batch->batch);
+}
+
+static void build_del_chain(struct test_batch *batch, struct test_batch_cmd *cmd)
+{
+ struct chain *chain = cmd->obj;
+ struct nlmsghdr *nlh;
+
+ nlh = nftnl_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch->batch),
+ NFT_MSG_DELCHAIN, get_nfproto(chain->family),
+ 0, cmd->lineno);
+ __build_chain(nlh, chain);
+ mnl_nlmsg_batch_next(batch->batch);
+}
+
+void del_chain(struct test_batch *batch, struct chain *chain)
+{
+ if (batch->ctx.table) {
+ chain->table = strdup(batch->ctx.table);
+ *chain->family = batch->ctx.family;
+ }
+
+ add_cmd(batch, DEL_CHAIN, chain);
+}
+
+static void build_del_rule(struct test_batch *batch, struct test_batch_cmd *cmd)
+{
+ struct rule *rule = cmd->obj;
+ struct nlmsghdr *nlh;
+
+ nlh = nftnl_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch->batch),
+ NFT_MSG_DELRULE, *rule->family,
+ 0, cmd->lineno);
+ __build_rule(nlh, rule);
+ mnl_nlmsg_batch_next(batch->batch);
+}
+
+void del_rule(struct test_batch *batch, struct rule *rule)
+{
+ if (batch->ctx.table) {
+ rule->table = strdup(batch->ctx.table);
+ *rule->family = batch->ctx.family;
+ }
+
+ add_cmd(batch, DEL_RULE, rule);
+}
+
+static void build_del_set(struct test_batch *batch, struct test_batch_cmd *cmd)
+{
+ struct set *set = cmd->obj;
+ struct nlmsghdr *nlh;
+
+ nlh = nftnl_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch->batch),
+ NFT_MSG_DELSET, *set->family,
+ 0, cmd->lineno);
+ __build_set(nlh, set, batch);
+ mnl_nlmsg_batch_next(batch->batch);
+}
+
+void del_set(struct test_batch *batch, struct set *set)
+{
+ if (batch->ctx.table) {
+ set->table = strdup(batch->ctx.table);
+ *set->family = batch->ctx.family;
+ }
+
+ add_cmd(batch, DEL_SET, set);
+}
+
+static void build_flush_set(struct test_batch *batch, struct test_batch_cmd *cmd)
+{
+ struct nlattr *nest1, *nest2;
+ struct set *set = cmd->obj;
+ struct nlmsghdr *nlh;
+
+ nlh = nftnl_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch->batch),
+ NFT_MSG_DELSETELEM, *set->family,
+ 0, cmd->lineno);
+ if (set->name)
+ mnl_attr_put_strz(nlh, NFTA_SET_ELEM_LIST_SET, set->name);
+ if (set->set_id)
+ mnl_attr_put_u32(nlh, NFTA_SET_ELEM_LIST_SET_ID, htonl(*set->set_id));
+ if (set->table)
+ mnl_attr_put_strz(nlh, NFTA_SET_ELEM_LIST_TABLE, set->table);
+ mnl_nlmsg_batch_next(batch->batch);
+}
+
+void flush_set(struct test_batch *batch, struct set *set)
+{
+ if (batch->ctx.table) {
+ set->table = strdup(batch->ctx.table);
+ *set->family = batch->ctx.family;
+ }
+
+ add_cmd(batch, FLUSH_SET, set);
+}
+
+static int mnl_batch_extack_cb(const struct nlmsghdr *nlh, void *data)
+{
+ const struct nlmsgerr *err = mnl_nlmsg_get_payload(nlh);
+ int errval;
+
+ if (nlh->nlmsg_len < mnl_nlmsg_size(sizeof(struct nlmsgerr)))
+ return MNL_CB_ERROR;
+
+ if (err->error < 0)
+ errval = -err->error;
+ else
+ errval = err->error;
+
+ if (errval) {
+ errno = errval;
+ if (errout)
+ printf("Error: %s, line %u\n", strerror(errno), nlh->nlmsg_seq);
+
+ return MNL_CB_ERROR;
+ }
+
+ return MNL_CB_OK;
+}
+
+static int nft_test_recv(struct mnl_socket *nl)
+{
+ static mnl_cb_t cb_ctl_array[NLMSG_MIN_TYPE] = {
+ [NLMSG_ERROR] = mnl_batch_extack_cb,
+ };
+ char buf[MNL_SOCKET_BUFFER_SIZE * 16];
+ bool mnl_error = false;
+ struct timeval tv;
+ fd_set readfds;
+ int ret;
+
+ FD_ZERO(&readfds);
+ FD_SET(mnl_socket_get_fd(nl), &readfds);
+
+ while (1) {
+ struct timeval tv = {};
+
+ ret = select(mnl_socket_get_fd(nl) + 1, &readfds, NULL, NULL, &tv);
+ if (ret == -1) {
+ perror("unexpected select() fails");
+ return -1;
+ }
+
+ if (!FD_ISSET(mnl_socket_get_fd(nl), &readfds))
+ break;
+
+ ret = mnl_socket_recvfrom(nl, buf, sizeof(buf));
+ if (ret == -1) {
+ /* enobufs means too many errors in batch. */
+ if (errno != ENOBUFS)
+ perror("unexpected recv() fails");
+ return -1;
+ }
+
+ ret = mnl_cb_run2(buf, ret, 0, mnl_socket_get_portid(nl), NULL, NULL,
+ cb_ctl_array, MNL_ARRAY_SIZE(cb_ctl_array));
+ if (ret < 0)
+ mnl_error = true;
+ }
+
+ return mnl_error ? -1 : 0;
+}
+
+static void setup_commit(struct mnl_nlmsg_batch *batch, unsigned int seq)
+{
+ nftnl_batch_end(mnl_nlmsg_batch_current(batch), seq);
+ mnl_nlmsg_batch_next(batch);
+}
+
+static int __send(struct mnl_socket *nl, struct mnl_nlmsg_batch *batch)
+{
+ int ret;
+
+ ret = mnl_socket_sendto(nl, mnl_nlmsg_batch_head(batch),
+ mnl_nlmsg_batch_size(batch));
+ if (ret == -1) {
+ perror("unexpected send() fails");
+ exit(EXIT_FAILURE);
+ }
+
+ return nft_test_recv(nl);
+}
+
+static void batch_cmd_free(struct test_batch_cmd *cmd)
+{
+ switch (cmd->type) {
+ case ADD_TABLE:
+ case SET_TABLE:
+ case DEL_TABLE:
+ free_table(cmd->obj);
+ break;
+ case ADD_BASECHAIN:
+ case ADD_CHAIN:
+ case DEL_BASECHAIN:
+ case DEL_CHAIN:
+ free_chain(cmd->obj);
+ break;
+ case ADD_RULE:
+ case DEL_RULE:
+ free_rule(cmd->obj);
+ break;
+ case ADD_SET:
+ case SET_SET:
+ case DEL_SET:
+ case FLUSH_SET:
+ free_set(cmd->obj);
+ break;
+ case ADD_SETELEM:
+ case DEL_SETELEM:
+ free_elem(cmd->obj);
+ break;
+ case ADD_FLOWTABLE:
+ case DEL_FLOWTABLE:
+ free_flowtable(cmd->obj);
+ break;
+ case ADD_OBJECT:
+ case DEL_OBJECT:
+ free_obj(cmd->obj);
+ break;
+ }
+
+ list_del(&cmd->list);
+ free(cmd);
+}
+
+static int __build(struct test_batch *batch)
+{
+ struct test_batch_cmd *cmd, *next;
+
+ list_for_each_entry_safe(cmd, next, &batch->cmd, list) {
+ switch (cmd->type) {
+ case ADD_TABLE:
+ build_table(batch, cmd);
+ break;
+ case DEL_TABLE:
+ build_del_table(batch, cmd);
+ break;
+ case SET_TABLE:
+ case SET_SET:
+ /* release table in context. */
+ break;
+ case ADD_BASECHAIN:
+ case ADD_CHAIN:
+ build_chain(batch, cmd);
+ break;
+ case DEL_BASECHAIN:
+ case DEL_CHAIN:
+ build_del_chain(batch, cmd);
+ break;
+ case ADD_RULE:
+ build_rule(batch, cmd);
+ break;
+ case DEL_RULE:
+ build_del_rule(batch, cmd);
+ break;
+ case ADD_SET:
+ build_set(batch, cmd);
+ break;
+ case DEL_SET:
+ build_del_set(batch, cmd);
+ break;
+ case FLUSH_SET:
+ build_flush_set(batch, cmd);
+ break;
+ case ADD_SETELEM:
+ build_setelem(batch, cmd);
+ break;
+ case DEL_SETELEM:
+ build_del_setelem(batch, cmd);
+ break;
+ case ADD_OBJECT:
+ build_obj(batch, cmd);
+ break;
+ case DEL_OBJECT:
+ build_del_obj(batch, cmd);
+ break;
+ case ADD_FLOWTABLE:
+ build_add_flowtable(batch, cmd);
+ break;
+ case DEL_FLOWTABLE:
+ build_del_flowtable(batch, cmd);
+ break;
+ }
+
+ batch_cmd_free(cmd);
+ }
+}
+
+int batch_abort(struct test_batch *batch)
+{
+ int ret;
+
+ __build(batch);
+
+ ret = __send(batch->nl, batch->batch);
+
+ batch_reset(batch);
+
+ return ret;
+}
+
+int batch_commit(struct test_batch *batch)
+{
+ int ret;
+
+ __build(batch);
+
+ setup_commit(batch->batch, UINT32_MAX);
+
+ ret = __send(batch->nl, batch->batch);
+
+ batch_reset(batch);
+
+ return ret;
+}
+
+void batch_stop(struct test_batch *batch)
+{
+ mnl_nlmsg_batch_stop(batch->batch);
+ mnl_socket_close(batch->nl);
+}
+
+void batch_reset(struct test_batch *batch)
+{
+ memset(&batch->ctx, 0, sizeof(batch->ctx));
+ mnl_nlmsg_batch_stop(batch->batch);
+ batch->batch = __setup_batch(batch->buf, sizeof(batch->buf));
+}
+
+int flush_ruleset(struct mnl_socket *nl)
+{
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+ struct mnl_nlmsg_batch *batch;
+ uint32_t seq, portid;
+ struct nlmsghdr *nlh;
+ int ret;
+
+ seq = time(NULL);
+ batch = mnl_nlmsg_batch_start(buf, sizeof(buf));
+
+ nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++);
+ mnl_nlmsg_batch_next(batch);
+
+ nlh = nftnl_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch),
+ NFT_MSG_DELTABLE, AF_UNSPEC,
+ NLM_F_ACK, seq++);
+ mnl_nlmsg_batch_next(batch);
+
+ nftnl_batch_end(mnl_nlmsg_batch_current(batch), seq++);
+ mnl_nlmsg_batch_next(batch);
+
+ portid = mnl_socket_get_portid(nl);
+
+ if (mnl_socket_sendto(nl, mnl_nlmsg_batch_head(batch),
+ mnl_nlmsg_batch_size(batch)) < 0) {
+ perror("mnl_socket_send");
+ return -1;
+ }
+
+ mnl_nlmsg_batch_stop(batch);
+
+ nft_test_recv(nl);
+
+ return 0;
+}
+
+bool kernel_is_tainted(void)
+{
+ unsigned int taint;
+ bool ret = false;
+ FILE *fp;
+
+ fp = fopen("/proc/sys/kernel/tainted", "r");
+ if (!fp)
+ return false;
+
+ if (fscanf(fp, "%u", &taint) == 1 && taint) {
+ fprintf(stderr, "kernel is tainted: 0x%x\n", taint);
+ ret = true;
+ }
+
+ fclose(fp);
+
+ return ret;
+}
+
+#define NFT_NLMSG_MAXSIZE (UINT16_MAX + getpagesize())
+
+static int
+nft_mnl_recv(struct mnl_socket *nl, uint32_t portid,
+ int (*cb)(const struct nlmsghdr *nlh, void *data), void *cb_data)
+{
+ char buf[NFT_NLMSG_MAXSIZE];
+ int ret;
+
+ ret = mnl_socket_recvfrom(nl, buf, sizeof(buf));
+ while (ret > 0) {
+ ret = mnl_cb_run(buf, ret, 0, portid, cb, cb_data);
+ if (ret == 0)
+ break;
+ if (ret < 0) {
+ if (errno == EAGAIN) {
+ ret = 0;
+ break;
+ }
+ if (errno != EINTR)
+ break;
+ }
+ ret = mnl_socket_recvfrom(nl, buf, sizeof(buf));
+ }
+
+ return ret;
+}
+
+static int
+nft_mnl_talk(struct mnl_socket *nl, const void *data, unsigned int len,
+ int (*cb)(const struct nlmsghdr *nlh, void *data), void *cb_data)
+{
+ uint32_t portid = mnl_socket_get_portid(nl);
+
+ if (mnl_socket_sendto(nl, data, len) < 0)
+ return -1;
+
+ return nft_mnl_recv(nl, portid, cb, cb_data);
+}
+
+static int dump_hooks_cb(const struct nlmsghdr *nlh, void *_data)
+{
+ /* exercise hook dump path, no real parsing in userspace. */
+ return MNL_CB_OK;
+}
+
+static int dump_hooks(struct mnl_socket *nl, uint8_t family,
+ const char *devname, uint32_t hooknum)
+{
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+ struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf);
+ struct nfgenmsg *nfg;
+
+ nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
+ nlh->nlmsg_type = NFNL_SUBSYS_HOOK << 8;
+ nlh->nlmsg_seq = time(NULL);
+
+ nfg = mnl_nlmsg_put_extra_header(nlh, sizeof(*nfg));
+ nfg->nfgen_family = family;
+ nfg->version = NFNETLINK_V0;
+
+ if (devname)
+ mnl_attr_put_strz(nlh, NFNLA_HOOK_DEV, devname);
+
+ mnl_attr_put_u32(nlh, NFNLA_HOOK_HOOKNUM, htonl(hooknum));
+
+ return nft_mnl_talk(nl, nlh, nlh->nlmsg_len, dump_hooks_cb, NULL);
+}
+
+static const char *devname[] = {
+ "dummy0", "dummy1", "dummy2", "dummy3", "lo", NULL,
+};
+
+/* detect UaF in hooks */
+int list_hooks(struct mnl_socket *nl)
+{
+ int i;
+
+ for (i = 0; i <= NF_INET_POST_ROUTING; i++) {
+ dump_hooks(nl, NFPROTO_IPV4, NULL, i);
+
+ if (kernel_is_tainted()) {
+ fprintf(stderr, "FATAL: list ip hooks taints kernel\n");
+ return -1;
+ }
+ }
+
+ for (i = 0; devname[i] != NULL; i++) {
+ dump_hooks(nl, NFPROTO_NETDEV, devname[i], NF_NETDEV_INGRESS);
+
+ if (kernel_is_tainted()) {
+ fprintf(stderr, "FATAL: list netdev hooks %s taints kernel\n", devname[i]);
+ return -1;
+ }
+ }
+
+ return 0;
+}
diff --git a/src/lib.h b/src/lib.h
new file mode 100644
index 0000000..a5d4362
--- /dev/null
+++ b/src/lib.h
@@ -0,0 +1,1899 @@
+#ifndef _LIB_H
+#define _LIB_H
+
+#include <libmnl/libmnl.h>
+#include <stdlib.h>
+#include "linux_list.h"
+
+#define ARRAY_MAX 4096
+
+struct array {
+ void *data[ARRAY_MAX];
+ int num;
+};
+
+struct array_u8 {
+ uint8_t data[ARRAY_MAX];
+ int num;
+};
+
+struct array_u32 {
+ uint32_t data[ARRAY_MAX];
+ int num;
+};
+
+enum test_batch_type {
+ ADD_TABLE = 0,
+ SET_TABLE,
+ DEL_TABLE,
+ ADD_BASECHAIN,
+ DEL_BASECHAIN,
+ ADD_CHAIN,
+ DEL_CHAIN,
+ ADD_SET,
+ SET_SET,
+ FLUSH_SET,
+ ADD_SETELEM,
+ DEL_SETELEM,
+ DEL_SET,
+ ADD_FLOWTABLE,
+ DEL_FLOWTABLE,
+ ADD_RULE,
+ DEL_RULE,
+ ADD_OBJECT,
+ DEL_OBJECT,
+};
+
+struct test_batch_cmd {
+ struct list_head list;
+ enum test_batch_type type;
+ int lineno;
+ void *obj;
+};
+
+struct test_batch {
+ struct list_head cmd;
+ uint32_t lineno;
+ char buf[1 << 21];
+ struct mnl_socket *nl;
+ struct mnl_nlmsg_batch *batch;
+ struct {
+ int family;
+ const char *table;
+ struct rule *rule;
+ struct list_head *expr_list; /* for inner */
+ struct set *set;
+ } ctx;
+};
+
+void setup_batch(struct test_batch *batch);
+
+struct table {
+ const char *name;
+ uint32_t *nfproto;
+ uint32_t *flags;
+ uint64_t *handle;
+ const char *userdata;
+
+ /* storage */
+ uint32_t _nfproto;
+ uint32_t _flags;
+ uint64_t _handle;
+};
+
+static inline struct table *table_init(void)
+{
+ struct table *table = calloc(1, sizeof(*table));
+
+ table->nfproto = &table->_nfproto;
+ table->flags = &table->_flags;
+ table->handle = &table->_handle;
+
+ return table;
+}
+
+void free_table(struct table *table);
+
+void set_table(struct test_batch *batch, struct table *table);
+void add_table(struct test_batch *batch, struct table *table);
+void del_table(struct test_batch *batch, struct table *table);
+
+struct chain {
+ const char *table;
+ const char *name;
+ const char *type;
+ const char *dev;
+ struct array *dev_array;
+ const char *userdata;
+
+ uint32_t *family;
+ uint32_t *flags;
+ uint32_t *chain_id;
+ uint32_t *hooknum;
+ uint64_t *handle;
+ uint32_t *policy;
+ uint64_t *bytes;
+ uint64_t *pkts;
+ uint32_t *prio;
+
+ /* storage */
+ uint32_t _family;
+ uint32_t _flags;
+ uint32_t _chain_id;
+ uint32_t _hooknum;
+ uint64_t _handle;
+ uint32_t _policy;
+ uint64_t _bytes;
+ uint64_t _pkts;
+ uint32_t _prio;
+};
+
+static inline struct chain *chain_init(void)
+{
+ struct chain *c = calloc(1, sizeof(*c));
+
+ c->family = &c->_family;
+ c->flags = &c->_flags;
+ c->chain_id = &c->_chain_id;
+ c->hooknum = &c->_hooknum;
+ c->handle = &c->_handle;
+ c->policy = &c->_policy;
+ c->bytes = &c->_bytes;
+ c->pkts = &c->_pkts;
+ c->prio = &c->_prio;
+
+ return c;
+}
+
+void free_chain(struct chain *chain);
+
+void add_chain(struct test_batch *batch, struct chain *chain);
+void del_chain(struct test_batch *batch, struct chain *chain);
+void add_basechain(struct test_batch *batch, struct chain *chain);
+void del_basechain(struct test_batch *batch, struct chain *chain);
+
+struct set {
+ const char *table;
+ const char *name;
+ struct list_head expr_list;
+
+ uint32_t *family;
+ uint32_t *flags;
+ uint32_t *key_len;
+ uint32_t *key_type;
+ uint32_t *data_len;
+ uint32_t *data_type;
+ uint32_t *policy;
+ struct array_u8 *field_array;
+ uint32_t *size;
+ uint32_t *set_id;
+ uint64_t *timeout;
+ uint32_t *gc_interval;
+ const char *userdata;
+ uint32_t *obj_type;
+ uint64_t *handle;
+
+ /* storage */
+ uint32_t _family;
+ uint32_t _flags;
+ uint32_t _key_len;
+ uint32_t _key_type;
+ uint32_t _data_len;
+ uint32_t _data_type;
+ uint32_t _policy;
+ uint32_t _size;
+ uint32_t _set_id;
+ uint64_t _timeout;
+ uint32_t _gc_interval;
+ uint32_t _obj_type;
+ uint64_t _handle;
+};
+
+static inline struct set *set_init(void)
+{
+ struct set *set = calloc(1, sizeof(*set));
+
+ INIT_LIST_HEAD(&set->expr_list);
+
+ set->family = &set->_family;
+ set->flags = &set->_flags;
+ set->key_len = &set->_key_len;
+ set->key_type = &set->_key_type;
+ set->data_len = &set->_data_len;
+ set->data_type = &set->_data_type;
+ set->policy = &set->_policy;
+ set->size = &set->_size;
+ set->set_id = &set->_set_id;
+ set->timeout = &set->_timeout;
+ set->gc_interval = &set->_gc_interval;
+ set->obj_type = &set->_obj_type;
+ set->handle = &set->_handle;
+
+ return set;
+}
+
+void add_set(struct test_batch *batch, struct set *set);
+void del_set(struct test_batch *batch, struct set *set);
+void flush_set(struct test_batch *batch, struct set *set);
+void set_set(struct test_batch *batch, struct set *set);
+void free_set(struct set *set);
+
+struct elem {
+ const char *table;
+ const char *set;
+ uint32_t set_id;
+
+ const char *chain;
+ const char *objname;
+ uint8_t *userdata;
+
+ struct list_head expr_list;
+
+ uint32_t *family;
+ uint8_t *key;
+ int key_len;
+ uint8_t *key_end;
+ int key_end_len;
+ uint8_t *data;
+ int data_len;
+ uint32_t *flags;
+ uint32_t *verdict;
+ uint64_t *timeout;
+ uint64_t *expiration;
+
+ /* storage */
+ uint32_t _family;
+ uint8_t _key[16];
+ uint8_t _key_end[16];
+ uint8_t _data[64];
+ uint32_t _flags;
+ uint32_t _verdict;
+ uint64_t _timeout;
+ uint64_t _expiration;
+};
+
+static inline struct elem *elem_init(void)
+{
+ struct elem *elem = calloc(1, sizeof(*elem));
+
+ INIT_LIST_HEAD(&elem->expr_list);
+
+ elem->family = &elem->_family;
+ elem->key = elem->_key;
+ elem->key_end = elem->_key_end;
+ elem->data = elem->_data;
+ elem->flags = &elem->_flags;
+ elem->verdict = &elem->_verdict;
+ elem->timeout = &elem->_timeout;
+ elem->expiration = &elem->_expiration;
+
+ return elem;
+}
+
+void free_elem(struct elem *elem);
+
+void add_elem(struct test_batch *batch, struct elem *elem);
+void del_elem(struct test_batch *batch, struct elem *elem);
+
+struct flowtable {
+ const char *table;
+ const char *name;
+ struct array *dev_array;
+
+ uint32_t *family;
+ uint32_t *hooknum;
+ uint32_t *prio;
+ uint64_t *handle;
+
+ /* storage */
+ uint32_t _family;
+ uint32_t _hooknum;
+ uint32_t _prio;
+ uint64_t _handle;
+};
+
+static inline struct flowtable *flowtable_init(void)
+{
+ struct flowtable *f = calloc(1, sizeof(*f));
+
+ f->family = &f->_family;
+ f->hooknum = &f->_hooknum;
+ f->handle = &f->_handle;
+ f->prio = &f->_prio;
+
+ return f;
+}
+
+void add_flowtable(struct test_batch *batch, struct flowtable *flowtable);
+void del_flowtable(struct test_batch *batch, struct flowtable *flowtable);
+void free_flowtable(struct flowtable *flowtable);
+
+struct rule {
+ const char *table;
+ const char *chain;
+ uint32_t *family;
+ uint32_t *rule_id;
+ uint32_t *pos_id;
+ uint64_t *handle;
+ uint64_t *pos;
+ const char *userdata;
+
+ /* set up when parsing match/target. */
+ struct {
+ uint32_t *l4proto;
+ uint32_t *flags;
+ } compat;
+
+ struct list_head expr_list;
+
+ /* storage */
+ uint32_t _family;
+ uint32_t _rule_id;
+ uint32_t _pos_id;
+ uint64_t _handle;
+ uint64_t _pos;
+};
+
+static inline struct rule *rule_init(void)
+{
+ struct rule *rule = calloc(1, sizeof(*rule));
+
+ rule->family = &rule->_family;
+ rule->rule_id = &rule->_rule_id;
+ rule->pos_id = &rule->_pos_id;
+ rule->handle = &rule->_handle;
+ rule->pos = &rule->_pos;
+
+ INIT_LIST_HEAD(&rule->expr_list);
+
+ return rule;
+}
+
+void free_rule(struct rule *rule);
+
+void add_rule(struct test_batch *batch, struct rule *rule);
+void del_rule(struct test_batch *batch, struct rule *rule);
+
+enum expr_type {
+ INVALID = 0,
+ BITWISE,
+ BYTEORDER,
+ CMP,
+ COUNTER,
+ CONNLIMIT,
+ CT,
+ DUP,
+ DYNSET,
+ EXTHDR,
+ FIB,
+ FWD,
+ HASH,
+ INNER,
+ IMMEDIATE,
+ LAST,
+ LIMIT,
+ LOG,
+ LOOKUP,
+ MASQ,
+ MATCH,
+ META,
+ NAT,
+ NUMGEN,
+ OBJREF,
+ OSF,
+ PAYLOAD,
+ QUEUE,
+ QUOTA,
+ RANGE,
+ REDIR,
+ REJECT,
+ RT,
+ SOCKET,
+ SYNPROXY,
+ TARGET,
+ TPROXY,
+ TUNNEL,
+ XFRM,
+};
+
+struct expr {
+ struct list_head list;
+ enum expr_type type;
+};
+
+int add_expr(struct test_batch *batch, struct expr *expr);
+
+struct bitwise {
+ struct expr expr;
+
+ uint32_t *sreg;
+ uint32_t *dreg;
+ uint32_t *op;
+ uint32_t *len;
+ uint8_t *mask;
+ int mask_len;
+ uint8_t *xor;
+ int xor_len;
+ uint8_t *data;
+ int data_len;
+
+ uint32_t _sreg;
+ uint32_t _dreg;
+ uint32_t _op;
+ uint32_t _len;
+ uint8_t _mask[16];
+ uint8_t _xor[16];
+ uint8_t _data[16];
+};
+
+static inline struct bitwise *bitwise_init(void)
+{
+ struct bitwise *bitwise = calloc(1, sizeof(*bitwise));
+
+ bitwise->expr.type = BITWISE;
+
+ bitwise->sreg = &bitwise->_sreg;
+ bitwise->dreg = &bitwise->_dreg;
+ bitwise->op = &bitwise->_op;
+ bitwise->len = &bitwise->_len;
+ bitwise->mask = bitwise->_mask;
+ bitwise->xor = bitwise->_xor;
+ bitwise->data = bitwise->_data;
+
+ return bitwise;
+}
+
+static inline void free_bitwise(struct bitwise *bitwise)
+{
+ free(bitwise);
+}
+
+struct byteorder {
+ struct expr expr;
+
+ uint32_t *sreg;
+ uint32_t *dreg;
+ uint32_t *op;
+ uint32_t *len;
+ uint32_t *size;
+
+ uint32_t _sreg;
+ uint32_t _dreg;
+ uint32_t _op;
+ uint32_t _len;
+ uint32_t _size;
+};
+
+static inline struct byteorder *byteorder_init(void)
+{
+ struct byteorder *byteorder = calloc(1, sizeof(*byteorder));
+
+ byteorder->expr.type = BYTEORDER;
+
+ byteorder->sreg = &byteorder->_sreg;
+ byteorder->dreg = &byteorder->_dreg;
+ byteorder->op = &byteorder->_op;
+ byteorder->len = &byteorder->_len;
+ byteorder->size = &byteorder->_size;
+
+ return byteorder;
+}
+
+static inline void free_byteorder(struct byteorder *byteorder)
+{
+ free(byteorder);
+}
+
+struct cmp {
+ struct expr expr;
+
+ uint32_t *sreg;
+ uint32_t *op;
+ uint8_t *data;
+ int data_len;
+
+ uint32_t _sreg;
+ uint32_t _op;
+ uint8_t _data[16];
+};
+
+static inline struct cmp *cmp_init(void)
+{
+ struct cmp *cmp = calloc(1, sizeof(*cmp));
+
+ cmp->expr.type = CMP;
+
+ cmp->sreg = &cmp->_sreg;
+ cmp->op = &cmp->_op;
+ cmp->data = cmp->_data;
+
+ return cmp;
+}
+
+static inline void free_cmp(struct cmp *cmp)
+{
+ free(cmp);
+}
+
+struct connlimit {
+ struct expr expr;
+
+ uint32_t *count;
+ uint32_t *flags;
+
+ uint32_t _count;
+ uint32_t _flags;
+};
+
+static inline struct connlimit *connlimit_init(void)
+{
+ struct connlimit *connlimit = calloc(1, sizeof(*connlimit));
+
+ connlimit->expr.type = CONNLIMIT;
+
+ connlimit->count = &connlimit->_count;
+ connlimit->flags = &connlimit->_flags;
+
+ return connlimit;
+}
+
+static inline void free_connlimit(struct connlimit *connlimit)
+{
+ free(connlimit);
+}
+
+struct counter {
+ struct expr expr;
+
+ uint64_t *pkts;
+ uint64_t *bytes;
+
+ /* storage */
+ uint64_t _pkts;
+ uint64_t _bytes;
+};
+
+static inline struct counter *counter_init(void)
+{
+ struct counter *counter = calloc(1, sizeof(*counter));
+
+ counter->expr.type = COUNTER;
+
+ counter->pkts = &counter->_pkts;
+ counter->bytes = &counter->_bytes;
+
+ return counter;
+}
+
+static inline void free_counter(struct counter *counter)
+{
+ free(counter);
+}
+
+struct ct {
+ struct expr expr;
+
+ uint32_t *sreg;
+ uint32_t *dreg;
+ uint32_t *key;
+ uint8_t *dir;
+
+ uint32_t _sreg;
+ uint32_t _dreg;
+ uint32_t _key;
+ uint8_t _dir;
+};
+
+static inline struct ct *ct_init(void)
+{
+ struct ct *ct = calloc(1, sizeof(*ct));
+
+ ct->expr.type = CT;
+
+ ct->sreg = &ct->_sreg;
+ ct->dreg = &ct->_dreg;
+ ct->key = &ct->_key;
+ ct->dir = &ct->_dir;
+
+ return ct;
+}
+
+static inline void free_ct(struct ct *ct)
+{
+ free(ct);
+}
+
+struct dup {
+ struct expr expr;
+
+ uint32_t *sreg_addr;
+ uint32_t *sreg_dev;
+
+ uint32_t _sreg_addr;
+ uint32_t _sreg_dev;
+};
+
+static inline struct dup *dup_init(void)
+{
+ struct dup *dup = calloc(1, sizeof(*dup));
+
+ dup->expr.type = DUP;
+
+ dup->sreg_addr = &dup->_sreg_addr;
+ dup->sreg_dev = &dup->_sreg_dev;
+
+ return dup;
+}
+
+static inline void free_dup(struct dup *dup)
+{
+ free(dup);
+}
+
+struct dynset {
+ struct expr expr;
+
+ const char *set;
+ struct list_head expr_list;
+
+ uint32_t *set_id;
+ uint32_t *op;
+ uint32_t *sreg_key;
+ uint32_t *sreg_data;
+ uint64_t *timeout;
+ uint32_t *flags;
+ /* exprs */
+
+ /* storage */
+ uint32_t _set_id;
+ uint32_t _op;
+ uint32_t _sreg_key;
+ uint32_t _sreg_data;
+ uint64_t _timeout;
+ uint32_t _flags;
+};
+
+static inline struct dynset *dynset_init(void)
+{
+ struct dynset *dynset = calloc(1, sizeof(*dynset));
+
+ dynset->expr.type = DYNSET;
+
+ INIT_LIST_HEAD(&dynset->expr_list);
+
+ dynset->set_id = &dynset->_set_id;
+ dynset->op = &dynset->_op;
+ dynset->sreg_key= &dynset->_sreg_key;
+ dynset->sreg_data = &dynset->_sreg_data;
+ dynset->timeout = &dynset->_timeout;
+ dynset->flags = &dynset->_flags;
+
+ return dynset;
+}
+
+static inline void free_dynset(struct dynset *dynset)
+{
+ free((void *)dynset->set);
+ free(dynset);
+}
+
+struct exthdr {
+ struct expr expr;
+
+ uint32_t *sreg;
+ uint32_t *dreg;
+ uint32_t *offset;
+ uint32_t *len;
+ uint32_t *type;
+ uint32_t *op;
+ uint32_t *flags;
+
+ uint32_t _sreg;
+ uint32_t _dreg;
+ uint32_t _offset;
+ uint32_t _len;
+ uint32_t _type;
+ uint32_t _op;
+ uint32_t _flags;
+};
+
+static inline struct exthdr *exthdr_init(void)
+{
+ struct exthdr *exthdr = calloc(1, sizeof(*exthdr));
+
+ exthdr->expr.type = EXTHDR;
+
+ exthdr->sreg = &exthdr->_sreg;
+ exthdr->dreg = &exthdr->_dreg;
+ exthdr->offset = &exthdr->_offset;
+ exthdr->len = &exthdr->_len;
+ exthdr->type = &exthdr->_type;
+ exthdr->op = &exthdr->_op;
+ exthdr->flags = &exthdr->_flags;
+
+ return exthdr;
+}
+
+static inline void free_exthdr(struct exthdr *exthdr)
+{
+ free(exthdr);
+}
+
+struct fib {
+ struct expr expr;
+
+ uint32_t *flags;
+ uint32_t *result;
+ uint32_t *dreg;
+
+ /* storage */
+ uint32_t _flags;
+ uint32_t _result;
+ uint32_t _dreg;
+};
+
+static inline struct fib *fib_init(void)
+{
+ struct fib *fib = calloc(1, sizeof(*fib));
+
+ fib->expr.type = FIB;
+
+ fib->flags = &fib->_flags;
+ fib->result = &fib->_result;
+ fib->dreg = &fib->_dreg;
+
+ return fib;
+}
+
+static inline void free_fib(struct fib *fib)
+{
+ free(fib);
+}
+
+struct fwd {
+ struct expr expr;
+
+ uint32_t *sreg_addr;
+ uint32_t *sreg_dev;
+ uint32_t *nfproto;
+
+ uint32_t _sreg_addr;
+ uint32_t _sreg_dev;
+ uint32_t _nfproto;
+};
+
+static inline struct fwd *fwd_init(void)
+{
+ struct fwd *fwd = calloc(1, sizeof(*fwd));
+
+ fwd->expr.type = FWD;
+
+ fwd->sreg_addr = &fwd->_sreg_addr;
+ fwd->sreg_dev = &fwd->_sreg_dev;
+ fwd->nfproto = &fwd->_nfproto;
+
+ return fwd;
+}
+
+static inline void free_fwd(struct fwd *fwd)
+{
+ free(fwd);
+}
+
+struct hash {
+ struct expr expr;
+
+ uint32_t *type;
+ uint32_t *sreg;
+ uint32_t *dreg;
+ uint32_t *len;
+ uint32_t *modulus;
+ uint32_t *seed;
+ uint32_t *offset;
+
+ uint32_t _type;
+ uint32_t _sreg;
+ uint32_t _dreg;
+ uint32_t _len;
+ uint32_t _modulus;
+ uint32_t _seed;
+ uint32_t _offset;
+};
+
+static inline struct hash *hash_init(void)
+{
+ struct hash *hash = calloc(1, sizeof(*hash));
+
+ hash->expr.type = HASH;
+
+ hash->type = &hash->_type;
+ hash->sreg = &hash->_sreg;
+ hash->dreg = &hash->_dreg;
+ hash->len = &hash->_len;
+ hash->modulus = &hash->_modulus;
+ hash->seed = &hash->_seed;
+ hash->offset = &hash->_offset;
+
+ return hash;
+}
+
+static inline void free_hash(struct hash *hash)
+{
+ free(hash);
+}
+
+struct inner {
+ struct expr expr;
+
+ uint32_t _num;
+ uint32_t _type;
+ uint32_t _flags;
+ uint32_t _hdrsize;
+
+ uint32_t *num;
+ uint32_t *type;
+ uint32_t *flags;
+ uint32_t *hdrsize;
+
+ struct list_head expr_list;
+};
+
+static inline struct inner *inner_init(void)
+{
+ struct inner *inner = calloc(1, sizeof(*inner));
+
+ inner->expr.type = INNER;
+
+ inner->num = &inner->_num;
+ inner->type = &inner->_type;
+ inner->flags = &inner->_flags;
+ inner->hdrsize = &inner->_hdrsize;
+
+ INIT_LIST_HEAD(&inner->expr_list);
+
+ return inner;
+}
+
+void free_expr_list(struct list_head *expr_list);
+
+static inline void free_inner(struct inner *inner)
+{
+ free_expr_list(&inner->expr_list);
+ free(inner);
+}
+
+struct immediate {
+ struct expr expr;
+
+ const char *chain;
+
+ uint32_t *dreg;
+ uint32_t *chain_id;
+ uint32_t *verdict;
+ uint8_t *data;
+ int data_len;
+
+ uint32_t _dreg;
+ uint32_t _chain_id;
+ uint32_t _verdict;
+ uint8_t _data[16];
+};
+
+static inline struct immediate *immediate_init(void)
+{
+ struct immediate *imm = calloc(1, sizeof(*imm));
+
+ imm->expr.type = IMMEDIATE;
+
+ imm->dreg = &imm->_dreg;
+ imm->chain_id = &imm->_chain_id;
+ imm->verdict = &imm->_verdict;
+ imm->data = imm->_data;
+
+ return imm;
+}
+
+static inline void free_immediate(struct immediate *immediate)
+{
+ free((void *)immediate->chain);
+ free(immediate);
+}
+
+struct last {
+ struct expr expr;
+
+ uint64_t *msecs;
+ uint32_t *set;
+
+ /* storage */
+ uint64_t _msecs;
+ uint32_t _set;
+};
+
+static inline struct last *last_init(void)
+{
+ struct last *last = calloc(1, sizeof(*last));
+
+ last->expr.type = LAST;
+
+ last->msecs = &last->_msecs;
+ last->set = &last->_set;
+
+ return last;
+}
+
+static inline void free_last(struct last *last)
+{
+ free(last);
+}
+
+struct limit {
+ struct expr expr;
+
+ uint64_t *rate;
+ uint64_t *unit;
+ uint32_t *burst;
+ uint32_t *type;
+ uint32_t *flags;
+
+ /* storage */
+ uint64_t _rate;
+ uint64_t _unit;
+ uint32_t _burst;
+ uint32_t _type;
+ uint32_t _flags;
+};
+
+static inline struct limit *limit_init(void)
+{
+ struct limit *limit = calloc(1, sizeof(*limit));
+
+ limit->expr.type = LIMIT;
+
+ limit->rate = &limit->_rate;
+ limit->unit = &limit->_unit;
+ limit->burst = &limit->_burst;
+ limit->type = &limit->_type;
+ limit->flags = &limit->_flags;
+
+ return limit;
+}
+
+static inline void free_limit(struct limit *limit)
+{
+ free(limit);
+}
+
+struct log {
+ struct expr expr;
+
+ const char *prefix;
+
+ uint32_t *snaplen;
+ uint16_t *group;
+ uint16_t *qthreshold;
+ uint32_t *level;
+ uint32_t *flags;
+
+ /* storage */
+ uint32_t _snaplen;
+ uint16_t _group;
+ uint16_t _qthreshold;
+ uint32_t _level;
+ uint32_t _flags;
+};
+
+static inline struct log *log_init(void)
+{
+ struct log *log = calloc(1, sizeof(*log));
+
+ log->expr.type = LOG;
+
+ log->snaplen = &log->_snaplen;
+ log->group = &log->_group;
+ log->qthreshold = &log->_qthreshold;
+ log->level = &log->_level;
+ log->flags = &log->_flags;
+
+ return log;
+}
+
+static inline void free_log(struct log *log)
+{
+ free((void *)log->prefix);
+ free(log);
+}
+
+struct lookup {
+ struct expr expr;
+
+ const char *set;
+ uint32_t *set_id;
+ uint32_t *sreg;
+ uint32_t *dreg;
+ uint32_t *flags;
+
+ /* storage */
+ uint32_t _set_id;
+ uint32_t _sreg;
+ uint32_t _dreg;
+};
+
+static inline struct lookup *lookup_init(void)
+{
+ struct lookup *lookup = calloc(1, sizeof(*lookup));
+
+ lookup->expr.type = LOOKUP;
+
+ lookup->set_id = &lookup->_set_id;
+ lookup->sreg = &lookup->_sreg;
+ lookup->dreg = &lookup->_dreg;
+ lookup->flags = NULL; // TODO
+
+ return lookup;
+}
+
+static inline void free_lookup(struct lookup *lookup)
+{
+ free((void *)lookup->set);
+ free(lookup);
+}
+
+struct masq {
+ struct expr expr;
+
+ uint32_t *sreg_proto_min;
+ uint32_t *sreg_proto_max;
+ uint32_t *flags;
+
+ uint32_t _sreg_proto_min;
+ uint32_t _sreg_proto_max;
+ uint32_t _flags;
+};
+
+static inline struct masq *masq_init(void)
+{
+ struct masq *masq = calloc(1, sizeof(*masq));
+
+ masq->expr.type = MASQ;
+
+ masq->sreg_proto_min = &masq->_sreg_proto_min;
+ masq->sreg_proto_max = &masq->_sreg_proto_max;
+ masq->flags = &masq->_flags;
+
+ return masq;
+}
+
+static inline void free_masq(struct masq *masq)
+{
+ free(masq);
+}
+
+struct match {
+ struct expr expr;
+
+ const char *name;
+
+ uint32_t *rev;
+ uint8_t *data;
+ int data_len;
+ uint32_t *l4proto;
+ uint32_t *flags;
+
+ /* storage */
+ uint32_t _rev;
+ uint8_t _data[65536];
+ uint32_t _l4proto;
+ uint32_t _flags;
+};
+
+static inline struct match *match_init(void)
+{
+ struct match *match = calloc(1, sizeof(*match));
+
+ match->expr.type = MATCH;
+
+ match->rev = &match->_rev;
+ match->data = match->_data;
+ match->l4proto = &match->_l4proto;
+ match->flags = &match->_flags;
+
+ return match;
+}
+
+static inline void free_match(struct match *match)
+{
+ free((void *)match->name);
+ free(match);
+}
+
+struct meta {
+ struct expr expr;
+
+ uint32_t *sreg;
+ uint32_t *dreg;
+ uint32_t *key;
+
+ uint32_t _sreg;
+ uint32_t _dreg;
+ uint32_t _key;
+};
+
+static inline struct meta *meta_init(void)
+{
+ struct meta *meta = calloc(1, sizeof(*meta));
+
+ meta->expr.type = META;
+
+ meta->sreg = &meta->_sreg;
+ meta->dreg = &meta->_dreg;
+ meta->key = &meta->_key;
+
+ return meta;
+}
+
+static inline void free_meta(struct meta *meta)
+{
+ free(meta);
+}
+
+struct nat {
+ struct expr expr;
+
+ uint32_t *sreg_addr_min;
+ uint32_t *sreg_addr_max;
+ uint32_t *sreg_proto_min;
+ uint32_t *sreg_proto_max;
+ uint32_t *nfproto;
+ uint32_t *type;
+ uint32_t *flags;
+
+ uint32_t _sreg_addr_min;
+ uint32_t _sreg_addr_max;
+ uint32_t _sreg_proto_min;
+ uint32_t _sreg_proto_max;
+ uint32_t _nfproto;
+ uint32_t _type;
+ uint32_t _flags;
+};
+
+static inline struct nat *nat_init(void)
+{
+ struct nat *nat = calloc(1, sizeof(*nat));
+
+ nat->expr.type = NAT;
+
+ nat->sreg_addr_min = &nat->_sreg_addr_min;
+ nat->sreg_addr_max = &nat->_sreg_addr_max;
+ nat->sreg_proto_min = &nat->_sreg_proto_min;
+ nat->sreg_proto_max = &nat->_sreg_proto_max;
+ nat->nfproto = &nat->_nfproto;
+ nat->type = &nat->_type;
+ nat->flags = &nat->_flags;
+
+ return nat;
+}
+
+static inline void free_nat(struct nat *nat)
+{
+ free(nat);
+}
+
+struct numgen {
+ struct expr expr;
+
+ uint32_t *dreg;
+ uint32_t *modulus;
+ uint32_t *type;
+ uint32_t *offset;
+
+ uint32_t _dreg;
+ uint32_t _modulus;
+ uint32_t _type;
+ uint32_t _offset;
+};
+
+static inline struct numgen *numgen_init(void)
+{
+ struct numgen *numgen = calloc(1, sizeof(*numgen));
+
+ numgen->expr.type = NUMGEN;
+
+ numgen->dreg = &numgen->_dreg;
+ numgen->modulus = &numgen->_modulus;
+ numgen->type = &numgen->_type;
+ numgen->offset = &numgen->_offset;
+
+ return numgen;
+}
+
+static inline void free_numgen(struct numgen *numgen)
+{
+ free(numgen);
+}
+
+struct objref {
+ struct expr expr;
+
+ const char *set_name;
+ const char *name;
+
+ uint32_t *type;
+ uint32_t *sreg;
+ uint32_t *set_id;
+
+ uint32_t _type;
+ uint32_t _sreg;
+ uint32_t _set_id;
+};
+
+static inline struct objref *objref_init(void)
+{
+ struct objref *objref = calloc(1, sizeof(*objref));
+
+ objref->expr.type = OBJREF;
+
+ objref->type = &objref->_type;
+ objref->sreg = &objref->_sreg;
+ objref->set_id = &objref->_set_id;
+
+ return objref;
+}
+
+static inline void free_objref(struct objref *objref)
+{
+ free((void *)objref->set_name);
+ free((void *)objref->name);
+ free(objref);
+}
+
+struct osf {
+ struct expr expr;
+
+ uint32_t *dreg;
+ uint32_t *ttl;
+ uint32_t *flags;
+
+ uint32_t _dreg;
+ uint32_t _ttl;
+ uint32_t _flags;
+};
+
+static inline struct osf *osf_init(void)
+{
+ struct osf *osf = calloc(1, sizeof(*osf));
+
+ osf->expr.type = OSF;
+
+ osf->dreg = &osf->_dreg;
+ osf->ttl = &osf->_ttl;
+ osf->flags = &osf->_flags;
+
+ return osf;
+}
+
+static inline void free_osf(struct osf *osf)
+{
+ free(osf);
+}
+
+struct payload {
+ struct expr expr;
+
+ uint32_t *sreg;
+ uint32_t *dreg;
+ uint32_t *offset;
+ uint32_t *base;
+ uint32_t *len;
+ uint32_t *csum_type;
+ uint32_t *csum_offset;
+ uint32_t *csum_flags;
+
+ /* storage */
+ uint32_t _sreg;
+ uint32_t _dreg;
+ uint32_t _offset;
+ uint32_t _base;
+ uint32_t _len;
+ uint32_t _csum_type;
+ uint32_t _csum_offset;
+ uint32_t _csum_flags;
+};
+
+static inline struct payload *payload_init(void)
+{
+ struct payload *payload = calloc(1, sizeof(*payload));
+
+ payload->expr.type = PAYLOAD;
+
+ payload->sreg = &payload->_sreg;
+ payload->dreg = &payload->_dreg;
+ payload->offset = &payload->_offset;
+ payload->base = &payload->_base;
+ payload->len = &payload->_len;
+ payload->csum_type = &payload->_csum_type;
+ payload->csum_offset = &payload->_csum_offset;
+ payload->csum_flags = &payload->_csum_flags;
+
+ return payload;
+}
+
+static inline void free_payload(struct payload *payload)
+{
+ free(payload);
+}
+
+struct queue {
+ struct expr expr;
+
+ uint32_t *sreg_qnum;
+ uint16_t *queue_num;
+ uint16_t *queue_total;
+ uint16_t *flags;
+
+ uint32_t _sreg_qnum;
+ uint16_t _queue_num;
+ uint16_t _queue_total;
+ uint16_t _flags;
+};
+
+static inline struct queue *queue_init(void)
+{
+ struct queue *queue = calloc(1, sizeof(*queue));
+
+ queue->expr.type = QUEUE;
+
+ queue->sreg_qnum = &queue->_sreg_qnum;
+ queue->queue_num = &queue->_queue_num;
+ queue->queue_total = &queue->_queue_total;
+ queue->flags = &queue->_flags;
+
+ return queue;
+}
+
+static inline void free_queue(struct queue *queue)
+{
+ free(queue);
+}
+
+struct quota {
+ struct expr expr;
+
+ uint64_t *bytes;
+ uint64_t *consumed;
+ uint32_t *flags;
+
+ /* storage */
+ uint64_t _bytes;
+ uint64_t _consumed;
+ uint32_t _flags;
+};
+
+static inline struct quota *quota_init(void)
+{
+ struct quota *quota = calloc(1, sizeof(*quota));
+
+ quota->expr.type = QUOTA;
+
+ quota->bytes = &quota->_bytes;
+ quota->consumed = &quota->_consumed;
+ quota->flags = &quota->_flags;
+
+ return quota;
+}
+
+static inline void free_quota(struct quota *quota)
+{
+ free(quota);
+}
+
+struct range {
+ struct expr expr;
+
+ uint32_t *sreg;
+ uint32_t *op;
+ uint8_t *data_from;
+ int data_from_len;
+ uint8_t *data_to;
+ int data_to_len;
+
+ uint32_t _sreg;
+ uint32_t _op;
+ uint8_t _data_from[16];
+ uint8_t _data_to[16];
+};
+
+static inline struct range *range_init(void)
+{
+ struct range *range = calloc(1, sizeof(*range));
+
+ range->expr.type = RANGE;
+
+ range->sreg = &range->_sreg;
+ range->op = &range->_op;
+ range->data_from = range->_data_from;
+ range->data_to = range->_data_to;
+
+ return range;
+}
+
+static inline void free_range(struct range *range)
+{
+ free(range);
+}
+
+struct redir {
+ struct expr expr;
+
+ uint32_t *sreg_proto_min;
+ uint32_t *sreg_proto_max;
+ uint32_t *flags;
+
+ uint32_t _sreg_proto_min;
+ uint32_t _sreg_proto_max;
+ uint32_t _flags;
+};
+
+static inline struct redir *redir_init(void)
+{
+ struct redir *redir = calloc(1, sizeof(*redir));
+
+ redir->expr.type = REDIR;
+
+ redir->sreg_proto_min = &redir->_sreg_proto_min;
+ redir->sreg_proto_max = &redir->_sreg_proto_max;
+ redir->flags = &redir->_flags;
+
+ return redir;
+}
+
+static inline void free_redir(struct redir *redir)
+{
+ free(redir);
+}
+
+struct reject {
+ struct expr expr;
+
+ uint32_t *type;
+ uint8_t *icmp_code;
+
+ /* storage */
+ uint32_t _type;
+ uint8_t _icmp_code;
+};
+
+static inline struct reject *reject_init(void)
+{
+ struct reject *reject = calloc(1, sizeof(*reject));
+
+ reject->expr.type = REJECT;
+
+ reject->type = &reject->_type;
+ reject->icmp_code = &reject->_icmp_code;
+
+ return reject;
+}
+
+static inline void free_reject(struct reject *reject)
+{
+ free(reject);
+}
+
+struct rt {
+ struct expr expr;
+
+ uint32_t *dreg;
+ uint32_t *key;
+
+ uint32_t _dreg;
+ uint32_t _key;
+};
+
+static inline struct rt *rt_init(void)
+{
+ struct rt *rt = calloc(1, sizeof(*rt));
+
+ rt->expr.type = RT;
+
+ rt->dreg = &rt->_dreg;
+ rt->key = &rt->_key;
+
+ return rt;
+}
+
+static inline void free_rt(struct rt *rt)
+{
+ free(rt);
+}
+
+struct socket {
+ struct expr expr;
+
+ uint32_t *dreg;
+ uint32_t *key;
+ uint32_t *level;
+
+ uint32_t _dreg;
+ uint32_t _key;
+ uint32_t _level;
+};
+
+static inline struct socket *socket_init(void)
+{
+ struct socket *socket = calloc(1, sizeof(*socket));
+
+ socket->expr.type = SOCKET;
+
+ socket->dreg = &socket->_dreg;
+ socket->key = &socket->_key;
+ socket->level = &socket->_level;
+
+ return socket;
+}
+
+static inline void free_socket(struct socket *socket)
+{
+ free(socket);
+}
+
+struct synproxy {
+ struct expr expr;
+
+ uint16_t *mss;
+ uint8_t *wscale;
+ uint32_t *flags;
+
+ /* storage */
+ uint16_t _mss;
+ uint8_t _wscale;
+ uint32_t _flags;
+};
+
+static inline struct synproxy *synproxy_init(void)
+{
+ struct synproxy *synproxy = calloc(1, sizeof(*synproxy));
+
+ synproxy->expr.type = SYNPROXY;
+
+ synproxy->mss = &synproxy->_mss;
+ synproxy->wscale = &synproxy->_wscale;
+ synproxy->flags = &synproxy->_flags;
+
+ return synproxy;
+}
+
+static inline void free_synproxy(struct synproxy *synproxy)
+{
+ free(synproxy);
+}
+
+struct target {
+ struct expr expr;
+
+ const char *name;
+
+ uint32_t *rev;
+ uint8_t *data;
+ int data_len;
+ uint32_t *l4proto;
+ uint32_t *flags;
+
+ /* storage */
+ uint32_t _rev;
+ uint8_t _data[65536];
+ uint32_t _l4proto;
+ uint32_t _flags;
+};
+
+static inline struct target *target_init(void)
+{
+ struct target *target = calloc(1, sizeof(*target));
+
+ target->expr.type = TARGET;
+
+ target->rev = &target->_rev;
+ target->data = target->_data;
+ target->l4proto = &target->_l4proto;
+ target->flags = &target->_flags;
+
+ return target;
+}
+
+static void free_target(struct target *target)
+{
+ free((void *)target->name);
+ free(target);
+}
+
+struct tproxy {
+ struct expr expr;
+
+ uint32_t *nfproto;
+ uint32_t *sreg_addr;
+ uint32_t *sreg_port;
+
+ uint32_t _nfproto;
+ uint32_t _sreg_addr;
+ uint32_t _sreg_port;
+};
+
+static inline struct tproxy *tproxy_init(void)
+{
+ struct tproxy *tproxy = calloc(1, sizeof(*tproxy));
+
+ tproxy->expr.type = TPROXY;
+
+ tproxy->nfproto = &tproxy->_nfproto;
+ tproxy->sreg_addr = &tproxy->_sreg_addr;
+ tproxy->sreg_port = &tproxy->_sreg_port;
+
+ return tproxy;
+}
+
+static inline void free_tproxy(struct tproxy *tproxy)
+{
+ free(tproxy);
+}
+
+struct tunnel {
+ struct expr expr;
+
+ uint32_t *dreg;
+ uint32_t *key;
+// uint32_t *mode;
+
+ uint32_t _dreg;
+ uint32_t _key;
+//XXX uint32_t _mode;
+};
+
+static inline struct tunnel *tunnel_init(void)
+{
+ struct tunnel *tunnel = calloc(1, sizeof(*tunnel));
+
+ tunnel->expr.type = TUNNEL;
+
+ tunnel->dreg = &tunnel->_dreg;
+ tunnel->key = &tunnel->_key;
+//XXX tunnel->mode = &tunnel->_mode;
+
+ return tunnel;
+}
+
+static inline void free_tunnel(struct tunnel *tunnel)
+{
+ free(tunnel);
+}
+
+struct xfrm {
+ struct expr expr;
+
+ uint32_t *dreg;
+ uint32_t *key;
+ uint32_t *spnum;
+ uint8_t *dir;
+
+ uint32_t _dreg;
+ uint32_t _key;
+ uint32_t _spnum;
+ uint8_t _dir;
+};
+
+static inline struct xfrm *xfrm_init(void)
+{
+ struct xfrm *xfrm = calloc(1, sizeof(*xfrm));
+
+ xfrm->expr.type = XFRM;
+
+ xfrm->dreg = &xfrm->_dreg;
+ xfrm->key = &xfrm->_key;
+ xfrm->spnum = &xfrm->_spnum;
+ xfrm->dir = &xfrm->_dir;
+
+ return xfrm;
+}
+
+static inline void free_xfrm(struct xfrm *xfrm)
+{
+ free(xfrm);
+}
+
+struct ct_helper {
+ const char *name;
+ uint8_t *l4proto;
+ uint16_t *l3proto;
+
+ /* storage */
+ uint8_t _l4proto;
+ uint16_t _l3proto;
+};
+
+static inline struct ct_helper *ct_helper_init(void)
+{
+ struct ct_helper *ct_helper = calloc(1, sizeof(*ct_helper));
+
+ ct_helper->l4proto = &ct_helper->_l4proto;
+ ct_helper->l3proto = &ct_helper->_l3proto;
+
+ return ct_helper;
+}
+
+struct ct_timeout {
+ const char *name;
+ uint8_t *l4proto;
+ uint16_t *l3proto;
+ struct array_u32 *timeout_array;
+
+ /* storage */
+ uint8_t _l4proto;
+ uint16_t _l3proto;
+};
+
+static inline struct ct_timeout *ct_timeout_init(void)
+{
+ struct ct_timeout *ct_timeout = calloc(1, sizeof(*ct_timeout));
+
+ ct_timeout->l4proto = &ct_timeout->_l4proto;
+ ct_timeout->l3proto = &ct_timeout->_l3proto;
+
+ return ct_timeout;
+}
+
+struct ct_expect {
+ const char *name;
+ uint8_t *l4proto;
+ uint16_t *l3proto;
+ uint16_t *dport;
+ uint32_t *timeout;
+ uint8_t *size;
+
+ /* storage */
+ uint8_t _l4proto;
+ uint16_t _l3proto;
+ uint16_t _dport;
+ uint32_t _timeout;
+ uint8_t _size;
+};
+
+static inline struct ct_expect *ct_expect_init(void)
+{
+ struct ct_expect *ct_expect = calloc(1, sizeof(*ct_expect));
+
+ ct_expect->l4proto = &ct_expect->_l4proto;
+ ct_expect->l3proto = &ct_expect->_l3proto;
+ ct_expect->dport = &ct_expect->_dport;
+ ct_expect->timeout = &ct_expect->_timeout;
+ ct_expect->size = &ct_expect->_size;
+
+ return ct_expect;
+}
+
+struct tunnel_tmpl {
+ uint32_t *id;
+ uint32_t *flags;
+ uint16_t *sport;
+ uint16_t *dport;
+ uint8_t *tos;
+ uint8_t *ttl;
+ uint32_t *src_v4;
+ uint8_t *src_v6;
+ uint32_t *dst_v4;
+ uint8_t *dst_v6;
+ uint32_t *flowlabel;
+
+ /* storage */
+ uint32_t _id;
+ uint32_t _flags;
+ uint16_t _sport;
+ uint16_t _dport;
+ uint8_t _tos;
+ uint8_t _ttl;
+ uint32_t _src_v4;
+ uint8_t _src_v6[16];
+ uint32_t _dst_v4;
+ uint8_t _dst_v6[16];
+ uint32_t _flowlabel;
+};
+
+static inline struct tunnel_tmpl *tunnel_tmpl_init(void)
+{
+ struct tunnel_tmpl *tunnel = calloc(1, sizeof(*tunnel));
+
+ tunnel->id = &tunnel->_id;
+ tunnel->flags = &tunnel->_flags;
+ tunnel->sport = &tunnel->_sport;
+ tunnel->dport = &tunnel->_dport;
+ tunnel->tos = &tunnel->_tos;
+ tunnel->ttl = &tunnel->_ttl;
+ tunnel->src_v4 = &tunnel->_src_v4;
+ tunnel->src_v6 = tunnel->_src_v6;
+ tunnel->dst_v4 = &tunnel->_dst_v4;
+ tunnel->dst_v6 = tunnel->_dst_v6;
+ tunnel->flowlabel = &tunnel->_flowlabel;
+
+ return tunnel;
+}
+
+struct secmark {
+ char *ctx;
+};
+
+static inline struct secmark *secmark_init(void)
+{
+ struct secmark *secmark = calloc(1, sizeof(*secmark));
+
+ return secmark;
+}
+
+struct obj {
+ const char *table;
+ const char *name;
+ uint64_t *handle;
+ const char *userdata;
+ uint32_t *type;
+ uint32_t *family;
+
+ union {
+ struct counter *counter;
+ struct quota *quota;
+ struct ct_helper *ct_helper;
+ struct limit *limit;
+ struct connlimit *connlimit;
+ struct tunnel_tmpl *tun;
+ struct ct_timeout *ct_timeout;
+ struct ct_expect *ct_expect;
+ struct secmark *secmark;
+ struct synproxy *synproxy;
+ } u;
+
+ /* storage */
+ uint32_t _type;
+ uint32_t _family;
+ uint64_t _handle;
+};
+
+static inline struct obj *obj_init(void)
+{
+ struct obj *obj = calloc(1, sizeof(*obj));
+
+ obj->type = &obj->_type;
+ obj->family = &obj->_family;
+ obj->handle = &obj->_handle;
+
+ return obj;
+}
+
+void add_obj(struct test_batch *batch, struct obj *obj);
+void del_obj(struct test_batch *batch, struct obj *obj);
+void free_obj(struct obj *obj);
+
+int batch_commit(struct test_batch *batch);
+int batch_abort(struct test_batch *batch);
+
+static inline bool batch_empty(struct test_batch *batch)
+{
+ return list_empty(&batch->cmd);
+}
+
+void batch_reset(struct test_batch *batch);
+void batch_stop(struct test_batch *batch);
+
+int flush_ruleset(struct mnl_socket *nl);
+
+struct array *array_alloc(void);
+bool array_add(struct array *array, void *data);
+void array_free(struct array *array);
+
+struct array_u8 *array_u8_alloc(void);
+bool array_u8_add(struct array_u8 *array, const char *data);
+void array_u8_free(struct array_u8 *array);
+
+struct array_u32 *array_u32_alloc(void);
+bool array_u32_add(struct array_u32 *array, const char *value);
+void array_u32_free(struct array_u32 *array);
+
+bool kernel_is_tainted(void);
+int list_hooks(struct mnl_socket *nl);
+
+#endif
diff --git a/src/linux_list.h b/src/linux_list.h
new file mode 100644
index 0000000..642b990
--- /dev/null
+++ b/src/linux_list.h
@@ -0,0 +1,738 @@
+#ifndef _LINUX_LIST_H
+#define _LINUX_LIST_H
+
+#include <stddef.h>
+
+#undef offsetof
+#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
+
+/**
+ * container_of - cast a member of a structure out to the containing structure
+ *
+ * @ptr: the pointer to the member.
+ * @type: the type of the container struct this is embedded in.
+ * @member: the name of the member within the struct.
+ *
+ */
+#define container_of(ptr, type, member) ({ \
+ typeof( ((type *)0)->member ) *__mptr = (ptr); \
+ (type *)( (char *)__mptr - offsetof(type,member) );})
+
+/*
+ * Check at compile time that something is of a particular type.
+ * Always evaluates to 1 so you may use it easily in comparisons.
+ */
+#define typecheck(type,x) \
+({ type __dummy; \
+ typeof(x) __dummy2; \
+ (void)(&__dummy == &__dummy2); \
+ 1; \
+})
+
+#define prefetch(x) ((void)0)
+
+/* empty define to make this work in userspace -HW */
+#ifndef smp_wmb
+#define smp_wmb()
+#endif
+
+/*
+ * These are non-NULL pointers that will result in page faults
+ * under normal circumstances, used to verify that nobody uses
+ * non-initialized list entries.
+ */
+#define LIST_POISON1 ((void *) 0x00100100)
+#define LIST_POISON2 ((void *) 0x00200200)
+
+/*
+ * Simple doubly linked list implementation.
+ *
+ * Some of the internal functions ("__xxx") are useful when
+ * manipulating whole lists rather than single entries, as
+ * sometimes we already know the next/prev entries and we can
+ * generate better code by using them directly rather than
+ * using the generic single-entry routines.
+ */
+
+struct list_head {
+ struct list_head *next, *prev;
+};
+
+#define LIST_HEAD_INIT(name) { &(name), &(name) }
+
+#define LIST_HEAD(name) \
+ struct list_head name = LIST_HEAD_INIT(name)
+
+#define INIT_LIST_HEAD(ptr) do { \
+ (ptr)->next = (ptr); (ptr)->prev = (ptr); \
+} while (0)
+
+/*
+ * Insert a new entry between two known consecutive entries.
+ *
+ * This is only for internal list manipulation where we know
+ * the prev/next entries already!
+ */
+static inline void __list_add(struct list_head *new,
+ struct list_head *prev,
+ struct list_head *next)
+{
+ next->prev = new;
+ new->next = next;
+ new->prev = prev;
+ prev->next = new;
+}
+
+/**
+ * list_add - add a new entry
+ * @new: new entry to be added
+ * @head: list head to add it after
+ *
+ * Insert a new entry after the specified head.
+ * This is good for implementing stacks.
+ */
+static inline void list_add(struct list_head *new, struct list_head *head)
+{
+ __list_add(new, head, head->next);
+}
+
+/**
+ * list_add_tail - add a new entry
+ * @new: new entry to be added
+ * @head: list head to add it before
+ *
+ * Insert a new entry before the specified head.
+ * This is useful for implementing queues.
+ */
+static inline void list_add_tail(struct list_head *new, struct list_head *head)
+{
+ __list_add(new, head->prev, head);
+}
+
+/*
+ * Insert a new entry between two known consecutive entries.
+ *
+ * This is only for internal list manipulation where we know
+ * the prev/next entries already!
+ */
+static inline void __list_add_rcu(struct list_head * new,
+ struct list_head * prev, struct list_head * next)
+{
+ new->next = next;
+ new->prev = prev;
+ smp_wmb();
+ next->prev = new;
+ prev->next = new;
+}
+
+/**
+ * list_add_rcu - add a new entry to rcu-protected list
+ * @new: new entry to be added
+ * @head: list head to add it after
+ *
+ * Insert a new entry after the specified head.
+ * This is good for implementing stacks.
+ *
+ * The caller must take whatever precautions are necessary
+ * (such as holding appropriate locks) to avoid racing
+ * with another list-mutation primitive, such as list_add_rcu()
+ * or list_del_rcu(), running on this same list.
+ * However, it is perfectly legal to run concurrently with
+ * the _rcu list-traversal primitives, such as
+ * list_for_each_entry_rcu().
+ */
+static inline void list_add_rcu(struct list_head *new, struct list_head *head)
+{
+ __list_add_rcu(new, head, head->next);
+}
+
+/**
+ * list_add_tail_rcu - add a new entry to rcu-protected list
+ * @new: new entry to be added
+ * @head: list head to add it before
+ *
+ * Insert a new entry before the specified head.
+ * This is useful for implementing queues.
+ *
+ * The caller must take whatever precautions are necessary
+ * (such as holding appropriate locks) to avoid racing
+ * with another list-mutation primitive, such as list_add_tail_rcu()
+ * or list_del_rcu(), running on this same list.
+ * However, it is perfectly legal to run concurrently with
+ * the _rcu list-traversal primitives, such as
+ * list_for_each_entry_rcu().
+ */
+static inline void list_add_tail_rcu(struct list_head *new,
+ struct list_head *head)
+{
+ __list_add_rcu(new, head->prev, head);
+}
+
+/*
+ * Delete a list entry by making the prev/next entries
+ * point to each other.
+ *
+ * This is only for internal list manipulation where we know
+ * the prev/next entries already!
+ */
+static inline void __list_del(struct list_head * prev, struct list_head * next)
+{
+ next->prev = prev;
+ prev->next = next;
+}
+
+/**
+ * list_del - deletes entry from list.
+ * @entry: the element to delete from the list.
+ * Note: list_empty on entry does not return true after this, the entry is
+ * in an undefined state.
+ */
+static inline void list_del(struct list_head *entry)
+{
+ __list_del(entry->prev, entry->next);
+ entry->next = LIST_POISON1;
+ entry->prev = LIST_POISON2;
+}
+
+/**
+ * list_del_rcu - deletes entry from list without re-initialization
+ * @entry: the element to delete from the list.
+ *
+ * Note: list_empty on entry does not return true after this,
+ * the entry is in an undefined state. It is useful for RCU based
+ * lockfree traversal.
+ *
+ * In particular, it means that we can not poison the forward
+ * pointers that may still be used for walking the list.
+ *
+ * The caller must take whatever precautions are necessary
+ * (such as holding appropriate locks) to avoid racing
+ * with another list-mutation primitive, such as list_del_rcu()
+ * or list_add_rcu(), running on this same list.
+ * However, it is perfectly legal to run concurrently with
+ * the _rcu list-traversal primitives, such as
+ * list_for_each_entry_rcu().
+ *
+ * Note that the caller is not permitted to immediately free
+ * the newly deleted entry. Instead, either synchronize_kernel()
+ * or call_rcu() must be used to defer freeing until an RCU
+ * grace period has elapsed.
+ */
+static inline void list_del_rcu(struct list_head *entry)
+{
+ __list_del(entry->prev, entry->next);
+ entry->prev = LIST_POISON2;
+}
+
+/**
+ * list_del_init - deletes entry from list and reinitialize it.
+ * @entry: the element to delete from the list.
+ */
+static inline void list_del_init(struct list_head *entry)
+{
+ __list_del(entry->prev, entry->next);
+ INIT_LIST_HEAD(entry);
+}
+
+/**
+ * list_move - delete from one list and add as another's head
+ * @list: the entry to move
+ * @head: the head that will precede our entry
+ */
+static inline void list_move(struct list_head *list, struct list_head *head)
+{
+ __list_del(list->prev, list->next);
+ list_add(list, head);
+}
+
+/**
+ * list_move_tail - delete from one list and add as another's tail
+ * @list: the entry to move
+ * @head: the head that will follow our entry
+ */
+static inline void list_move_tail(struct list_head *list,
+ struct list_head *head)
+{
+ __list_del(list->prev, list->next);
+ list_add_tail(list, head);
+}
+
+/**
+ * list_empty - tests whether a list is empty
+ * @head: the list to test.
+ */
+static inline int list_empty(const struct list_head *head)
+{
+ return head->next == head;
+}
+
+/**
+ * list_empty_careful - tests whether a list is
+ * empty _and_ checks that no other CPU might be
+ * in the process of still modifying either member
+ *
+ * NOTE: using list_empty_careful() without synchronization
+ * can only be safe if the only activity that can happen
+ * to the list entry is list_del_init(). Eg. it cannot be used
+ * if another CPU could re-list_add() it.
+ *
+ * @head: the list to test.
+ */
+static inline int list_empty_careful(const struct list_head *head)
+{
+ struct list_head *next = head->next;
+ return (next == head) && (next == head->prev);
+}
+
+static inline void __list_splice(struct list_head *list,
+ struct list_head *head)
+{
+ struct list_head *first = list->next;
+ struct list_head *last = list->prev;
+ struct list_head *at = head->next;
+
+ first->prev = head;
+ head->next = first;
+
+ last->next = at;
+ at->prev = last;
+}
+
+/**
+ * list_splice - join two lists
+ * @list: the new list to add.
+ * @head: the place to add it in the first list.
+ */
+static inline void list_splice(struct list_head *list, struct list_head *head)
+{
+ if (!list_empty(list))
+ __list_splice(list, head);
+}
+
+/**
+ * list_splice_init - join two lists and reinitialise the emptied list.
+ * @list: the new list to add.
+ * @head: the place to add it in the first list.
+ *
+ * The list at @list is reinitialised
+ */
+static inline void list_splice_init(struct list_head *list,
+ struct list_head *head)
+{
+ if (!list_empty(list)) {
+ __list_splice(list, head);
+ INIT_LIST_HEAD(list);
+ }
+}
+
+/**
+ * list_entry - get the struct for this entry
+ * @ptr: the &struct list_head pointer.
+ * @type: the type of the struct this is embedded in.
+ * @member: the name of the list_struct within the struct.
+ */
+#define list_entry(ptr, type, member) \
+ container_of(ptr, type, member)
+
+/**
+ * list_first_entry - get the first element from a list
+ * @ptr: the list head to take the element from.
+ * @type: the type of the struct this is embedded in.
+ * @member: the name of the list_struct within the struct.
+ *
+ * Note, that list is expected to be not empty.
+ */
+#define list_first_entry(ptr, type, member) \
+ list_entry((ptr)->next, type, member)
+
+/**
+ * list_for_each - iterate over a list
+ * @pos: the &struct list_head to use as a loop counter.
+ * @head: the head for your list.
+ */
+#define list_for_each(pos, head) \
+ for (pos = (head)->next, prefetch(pos->next); pos != (head); \
+ pos = pos->next, prefetch(pos->next))
+
+/**
+ * __list_for_each - iterate over a list
+ * @pos: the &struct list_head to use as a loop counter.
+ * @head: the head for your list.
+ *
+ * This variant differs from list_for_each() in that it's the
+ * simplest possible list iteration code, no prefetching is done.
+ * Use this for code that knows the list to be very short (empty
+ * or 1 entry) most of the time.
+ */
+#define __list_for_each(pos, head) \
+ for (pos = (head)->next; pos != (head); pos = pos->next)
+
+/**
+ * list_for_each_prev - iterate over a list backwards
+ * @pos: the &struct list_head to use as a loop counter.
+ * @head: the head for your list.
+ */
+#define list_for_each_prev(pos, head) \
+ for (pos = (head)->prev, prefetch(pos->prev); pos != (head); \
+ pos = pos->prev, prefetch(pos->prev))
+
+/**
+ * list_for_each_safe - iterate over a list safe against removal of list entry
+ * @pos: the &struct list_head to use as a loop counter.
+ * @n: another &struct list_head to use as temporary storage
+ * @head: the head for your list.
+ */
+#define list_for_each_safe(pos, n, head) \
+ for (pos = (head)->next, n = pos->next; pos != (head); \
+ pos = n, n = pos->next)
+
+/**
+ * list_for_each_entry - iterate over list of given type
+ * @pos: the type * to use as a loop counter.
+ * @head: the head for your list.
+ * @member: the name of the list_struct within the struct.
+ */
+#define list_for_each_entry(pos, head, member) \
+ for (pos = list_entry((head)->next, typeof(*pos), member), \
+ prefetch(pos->member.next); \
+ &pos->member != (head); \
+ pos = list_entry(pos->member.next, typeof(*pos), member), \
+ prefetch(pos->member.next))
+
+/**
+ * list_for_each_entry_reverse - iterate backwards over list of given type.
+ * @pos: the type * to use as a loop counter.
+ * @head: the head for your list.
+ * @member: the name of the list_struct within the struct.
+ */
+#define list_for_each_entry_reverse(pos, head, member) \
+ for (pos = list_entry((head)->prev, typeof(*pos), member), \
+ prefetch(pos->member.prev); \
+ &pos->member != (head); \
+ pos = list_entry(pos->member.prev, typeof(*pos), member), \
+ prefetch(pos->member.prev))
+
+/**
+ * list_prepare_entry - prepare a pos entry for use as a start point in
+ * list_for_each_entry_continue
+ * @pos: the type * to use as a start point
+ * @head: the head of the list
+ * @member: the name of the list_struct within the struct.
+ */
+#define list_prepare_entry(pos, head, member) \
+ ((pos) ? : list_entry(head, typeof(*pos), member))
+
+/**
+ * list_for_each_entry_continue - iterate over list of given type
+ * continuing after existing point
+ * @pos: the type * to use as a loop counter.
+ * @head: the head for your list.
+ * @member: the name of the list_struct within the struct.
+ */
+#define list_for_each_entry_continue(pos, head, member) \
+ for (pos = list_entry(pos->member.next, typeof(*pos), member), \
+ prefetch(pos->member.next); \
+ &pos->member != (head); \
+ pos = list_entry(pos->member.next, typeof(*pos), member), \
+ prefetch(pos->member.next))
+
+/**
+ * list_for_each_entry_safe - iterate over list of given type safe against removal of list entry
+ * @pos: the type * to use as a loop counter.
+ * @n: another type * to use as temporary storage
+ * @head: the head for your list.
+ * @member: the name of the list_struct within the struct.
+ */
+#define list_for_each_entry_safe(pos, n, head, member) \
+ for (pos = list_entry((head)->next, typeof(*pos), member), \
+ n = list_entry(pos->member.next, typeof(*pos), member); \
+ &pos->member != (head); \
+ pos = n, n = list_entry(n->member.next, typeof(*n), member))
+
+/**
+ * list_for_each_rcu - iterate over an rcu-protected list
+ * @pos: the &struct list_head to use as a loop counter.
+ * @head: the head for your list.
+ *
+ * This list-traversal primitive may safely run concurrently with
+ * the _rcu list-mutation primitives such as list_add_rcu()
+ * as long as the traversal is guarded by rcu_read_lock().
+ */
+#define list_for_each_rcu(pos, head) \
+ for (pos = (head)->next, prefetch(pos->next); pos != (head); \
+ pos = pos->next, ({ smp_read_barrier_depends(); 0;}), prefetch(pos->next))
+
+#define __list_for_each_rcu(pos, head) \
+ for (pos = (head)->next; pos != (head); \
+ pos = pos->next, ({ smp_read_barrier_depends(); 0;}))
+
+/**
+ * list_for_each_safe_rcu - iterate over an rcu-protected list safe
+ * against removal of list entry
+ * @pos: the &struct list_head to use as a loop counter.
+ * @n: another &struct list_head to use as temporary storage
+ * @head: the head for your list.
+ *
+ * This list-traversal primitive may safely run concurrently with
+ * the _rcu list-mutation primitives such as list_add_rcu()
+ * as long as the traversal is guarded by rcu_read_lock().
+ */
+#define list_for_each_safe_rcu(pos, n, head) \
+ for (pos = (head)->next, n = pos->next; pos != (head); \
+ pos = n, ({ smp_read_barrier_depends(); 0;}), n = pos->next)
+
+/**
+ * list_for_each_entry_rcu - iterate over rcu list of given type
+ * @pos: the type * to use as a loop counter.
+ * @head: the head for your list.
+ * @member: the name of the list_struct within the struct.
+ *
+ * This list-traversal primitive may safely run concurrently with
+ * the _rcu list-mutation primitives such as list_add_rcu()
+ * as long as the traversal is guarded by rcu_read_lock().
+ */
+#define list_for_each_entry_rcu(pos, head, member) \
+ for (pos = list_entry((head)->next, typeof(*pos), member), \
+ prefetch(pos->member.next); \
+ &pos->member != (head); \
+ pos = list_entry(pos->member.next, typeof(*pos), member), \
+ ({ smp_read_barrier_depends(); 0;}), \
+ prefetch(pos->member.next))
+
+
+/**
+ * list_for_each_continue_rcu - iterate over an rcu-protected list
+ * continuing after existing point.
+ * @pos: the &struct list_head to use as a loop counter.
+ * @head: the head for your list.
+ *
+ * This list-traversal primitive may safely run concurrently with
+ * the _rcu list-mutation primitives such as list_add_rcu()
+ * as long as the traversal is guarded by rcu_read_lock().
+ */
+#define list_for_each_continue_rcu(pos, head) \
+ for ((pos) = (pos)->next, prefetch((pos)->next); (pos) != (head); \
+ (pos) = (pos)->next, ({ smp_read_barrier_depends(); 0;}), prefetch((pos)->next))
+
+/*
+ * Double linked lists with a single pointer list head.
+ * Mostly useful for hash tables where the two pointer list head is
+ * too wasteful.
+ * You lose the ability to access the tail in O(1).
+ */
+
+struct hlist_head {
+ struct hlist_node *first;
+};
+
+struct hlist_node {
+ struct hlist_node *next, **pprev;
+};
+
+#define HLIST_HEAD_INIT { .first = NULL }
+#define HLIST_HEAD(name) struct hlist_head name = { .first = NULL }
+#define INIT_HLIST_HEAD(ptr) ((ptr)->first = NULL)
+#define INIT_HLIST_NODE(ptr) ((ptr)->next = NULL, (ptr)->pprev = NULL)
+
+static inline int hlist_unhashed(const struct hlist_node *h)
+{
+ return !h->pprev;
+}
+
+static inline int hlist_empty(const struct hlist_head *h)
+{
+ return !h->first;
+}
+
+static inline void __hlist_del(struct hlist_node *n)
+{
+ struct hlist_node *next = n->next;
+ struct hlist_node **pprev = n->pprev;
+ *pprev = next;
+ if (next)
+ next->pprev = pprev;
+}
+
+static inline void hlist_del(struct hlist_node *n)
+{
+ __hlist_del(n);
+ n->next = LIST_POISON1;
+ n->pprev = LIST_POISON2;
+}
+
+/**
+ * hlist_del_rcu - deletes entry from hash list without re-initialization
+ * @n: the element to delete from the hash list.
+ *
+ * Note: list_unhashed() on entry does not return true after this,
+ * the entry is in an undefined state. It is useful for RCU based
+ * lockfree traversal.
+ *
+ * In particular, it means that we can not poison the forward
+ * pointers that may still be used for walking the hash list.
+ *
+ * The caller must take whatever precautions are necessary
+ * (such as holding appropriate locks) to avoid racing
+ * with another list-mutation primitive, such as hlist_add_head_rcu()
+ * or hlist_del_rcu(), running on this same list.
+ * However, it is perfectly legal to run concurrently with
+ * the _rcu list-traversal primitives, such as
+ * hlist_for_each_entry().
+ */
+static inline void hlist_del_rcu(struct hlist_node *n)
+{
+ __hlist_del(n);
+ n->pprev = LIST_POISON2;
+}
+
+static inline void hlist_del_init(struct hlist_node *n)
+{
+ if (n->pprev) {
+ __hlist_del(n);
+ INIT_HLIST_NODE(n);
+ }
+}
+
+#define hlist_del_rcu_init hlist_del_init
+
+static inline void hlist_add_head(struct hlist_node *n, struct hlist_head *h)
+{
+ struct hlist_node *first = h->first;
+ n->next = first;
+ if (first)
+ first->pprev = &n->next;
+ h->first = n;
+ n->pprev = &h->first;
+}
+
+
+/**
+ * hlist_add_head_rcu - adds the specified element to the specified hlist,
+ * while permitting racing traversals.
+ * @n: the element to add to the hash list.
+ * @h: the list to add to.
+ *
+ * The caller must take whatever precautions are necessary
+ * (such as holding appropriate locks) to avoid racing
+ * with another list-mutation primitive, such as hlist_add_head_rcu()
+ * or hlist_del_rcu(), running on this same list.
+ * However, it is perfectly legal to run concurrently with
+ * the _rcu list-traversal primitives, such as
+ * hlist_for_each_entry(), but only if smp_read_barrier_depends()
+ * is used to prevent memory-consistency problems on Alpha CPUs.
+ * Regardless of the type of CPU, the list-traversal primitive
+ * must be guarded by rcu_read_lock().
+ *
+ * OK, so why don't we have an hlist_for_each_entry_rcu()???
+ */
+static inline void hlist_add_head_rcu(struct hlist_node *n,
+ struct hlist_head *h)
+{
+ struct hlist_node *first = h->first;
+ n->next = first;
+ n->pprev = &h->first;
+ smp_wmb();
+ if (first)
+ first->pprev = &n->next;
+ h->first = n;
+}
+
+/* next must be != NULL */
+static inline void hlist_add_before(struct hlist_node *n,
+ struct hlist_node *next)
+{
+ n->pprev = next->pprev;
+ n->next = next;
+ next->pprev = &n->next;
+ *(n->pprev) = n;
+}
+
+static inline void hlist_add_after(struct hlist_node *n,
+ struct hlist_node *next)
+{
+ next->next = n->next;
+ n->next = next;
+ next->pprev = &n->next;
+
+ if(next->next)
+ next->next->pprev = &next->next;
+}
+
+#define hlist_entry(ptr, type, member) container_of(ptr,type,member)
+
+#define hlist_for_each(pos, head) \
+ for (pos = (head)->first; pos && ({ prefetch(pos->next); 1; }); \
+ pos = pos->next)
+
+#define hlist_for_each_safe(pos, n, head) \
+ for (pos = (head)->first; pos && ({ n = pos->next; 1; }); \
+ pos = n)
+
+/**
+ * hlist_for_each_entry - iterate over list of given type
+ * @tpos: the type * to use as a loop counter.
+ * @pos: the &struct hlist_node to use as a loop counter.
+ * @head: the head for your list.
+ * @member: the name of the hlist_node within the struct.
+ */
+#define hlist_for_each_entry(tpos, pos, head, member) \
+ for (pos = (head)->first; \
+ pos && ({ prefetch(pos->next); 1;}) && \
+ ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
+ pos = pos->next)
+
+/**
+ * hlist_for_each_entry_continue - iterate over a hlist continuing after existing point
+ * @tpos: the type * to use as a loop counter.
+ * @pos: the &struct hlist_node to use as a loop counter.
+ * @member: the name of the hlist_node within the struct.
+ */
+#define hlist_for_each_entry_continue(tpos, pos, member) \
+ for (pos = (pos)->next; \
+ pos && ({ prefetch(pos->next); 1;}) && \
+ ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
+ pos = pos->next)
+
+/**
+ * hlist_for_each_entry_from - iterate over a hlist continuing from existing point
+ * @tpos: the type * to use as a loop counter.
+ * @pos: the &struct hlist_node to use as a loop counter.
+ * @member: the name of the hlist_node within the struct.
+ */
+#define hlist_for_each_entry_from(tpos, pos, member) \
+ for (; pos && ({ prefetch(pos->next); 1;}) && \
+ ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
+ pos = pos->next)
+
+/**
+ * hlist_for_each_entry_safe - iterate over list of given type safe against removal of list entry
+ * @tpos: the type * to use as a loop counter.
+ * @pos: the &struct hlist_node to use as a loop counter.
+ * @n: another &struct hlist_node to use as temporary storage
+ * @head: the head for your list.
+ * @member: the name of the hlist_node within the struct.
+ */
+#define hlist_for_each_entry_safe(tpos, pos, n, head, member) \
+ for (pos = (head)->first; \
+ pos && ({ n = pos->next; 1; }) && \
+ ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
+ pos = n)
+
+/**
+ * hlist_for_each_entry_rcu - iterate over rcu list of given type
+ * @pos: the type * to use as a loop counter.
+ * @pos: the &struct hlist_node to use as a loop counter.
+ * @head: the head for your list.
+ * @member: the name of the hlist_node within the struct.
+ *
+ * This list-traversal primitive may safely run concurrently with
+ * the _rcu list-mutation primitives such as hlist_add_rcu()
+ * as long as the traversal is guarded by rcu_read_lock().
+ */
+#define hlist_for_each_entry_rcu(tpos, pos, head, member) \
+ for (pos = (head)->first; \
+ pos && ({ prefetch(pos->next); 1;}) && \
+ ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
+ pos = pos->next, ({ smp_read_barrier_depends(); 0; }) )
+
+#endif
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 0000000..8075592
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,220 @@
+/*
+ * Copyright (c) 2024-2025 Pablo Neira Ayuso <pablo@netfilter.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 (or any
+ * later) as published by the Free Software Foundation.
+ */
+
+/* Funded through the NGI0 Entrust established by NLnet (https://nlnet.nl)
+ * with support from the European Commission's Next Generation Internet
+ * programme.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+#include <dirent.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <getopt.h>
+
+#include "test.h"
+
+static int run_test_dir(const char *filename, struct nft_test_ctx *ctx,
+ struct nft_test_stats *stats)
+{
+ struct dirent *dent;
+ char path[PATH_MAX];
+ struct stat st;
+ int ret = 0;
+ DIR *d;
+
+ d = opendir(filename);
+ if (!d) {
+ perror("cannot open directory");
+ return -1;
+ }
+
+ dent = readdir(d);
+ while (dent) {
+ if (!strcmp(dent->d_name, ".") || !strcmp(dent->d_name, "..")) {
+ dent = readdir(d);
+ continue;
+ }
+ snprintf(path, sizeof(path), "%s/%s", filename, dent->d_name);
+ if (stat(path, &st) < 0) {
+ fprintf(stderr, "cannot open file %s: %s\n",
+ path, strerror(errno));
+ return -1;
+ }
+ if (S_ISDIR(st.st_mode)) {
+ if (run_test_dir(path, ctx, stats) < 0) {
+ ret = -1;
+ break;
+ }
+ dent = readdir(d);
+ continue;
+ } else if (!S_ISREG(st.st_mode)) {
+ dent = readdir(d);
+ continue;
+ }
+ if (run_test(path, ctx) < 0)
+ stats->failed++;
+ else
+ stats->ok++;
+
+ if (ctx->fatal) {
+ ret = -1;
+ break;
+ }
+
+ dent = readdir(d);
+ }
+ closedir(d);
+
+ return ret;
+}
+
+static int parse_fuzz_mode(char *str, uint32_t *type, uint32_t *num_types)
+{
+ char *x, *token;
+ int a, idx = 0;
+
+ x = strchr(str, ',');
+
+ do {
+ if (idx >= FUZZ_STACK_MAX) {
+ printf("too many modes\n");
+ return -1;
+ }
+
+ if (!x) {
+ token = str;
+ a = fuzz_type_to_value(token);
+ if (a < 0) {
+ printf("unknown fuzzing mode %s\n", token);
+ printf("Expected:\n", token);
+ print_fuzz_modes();
+ return -1;
+ }
+ type[idx++] = a;
+ break;
+ }
+
+ *x = '\0';
+ token = str;
+ a = fuzz_type_to_value(token);
+ if (a < 0) {
+ printf("unknown fuzzing mode %s\n", token);
+ printf("Expected:\n", token);
+ print_fuzz_modes();
+ return -1;
+ }
+ type[idx++] = a;
+
+ str = x + 1;
+
+ x = strchr(str, ',');
+ } while (1);
+
+ *num_types = idx;
+
+ return idx;
+}
+
+bool errout;
+bool noflush;
+
+int main(int argc, char *argv[])
+{
+ struct timeval tv_start, tv_end, tv_diff;
+ struct nft_test_stats stats = {};
+ struct nft_test_ctx ctx = {};
+ const char *filename;
+ struct stat st;
+ int opt;
+
+ while ((opt = getopt(argc, argv, "nf:hedc")) != -1) {
+ switch (opt) {
+ case 'n':
+ noflush = true;
+ break;
+ case 'f':
+ ctx.fuzz = true;
+ if (parse_fuzz_mode(optarg, ctx.fuzz_ctx.type, &ctx.fuzz_ctx.num_types) < 0)
+ exit(EXIT_FAILURE);
+ break;
+ case 'd':
+ ctx.fuzz_ctx.debug = true;
+ break;
+ case 'e':
+ errout = true;
+ break;
+ case 'c':
+ ctx.dryrun = true;
+ break;
+ case 'h':
+ default:
+ fprintf(stderr, "Usage: %s [-e] [-d] [-n] [-c] [-f <mode,...>] <input>\n",
+ argv[0]);
+ printf("Options:\n");
+ printf("\t-e\tdisplay error reported by kernel\n");
+ printf("\t-n\tdo not flush previous ruleset\n");
+ printf("\t-d\tdisplay debugging information\n");
+ printf("\t-c\tdry run\n");
+ printf("\t-f <mode>\tfuzz test, see fuzz modes\n");
+ printf("Available fuzz modes:\n");
+ print_fuzz_modes();
+ exit(EXIT_FAILURE);
+ break;
+ }
+ }
+
+ if (optind >= argc) {
+ fprintf(stderr, "ERROR: specify path to file or folder with tests\n");
+ exit(EXIT_FAILURE);
+ }
+
+ filename = argv[optind];
+
+ /* TODO: add dummy device with netlink. */
+ system("sudo modprobe xt_cpu");
+ system("sudo ip link add dummy0 type dummy > /dev/null 2>&1");
+ system("sudo ip link add dummy1 type dummy > /dev/null 2>&1");
+ system("sudo ip link add dummy2 type dummy > /dev/null 2>&1");
+ system("sudo ip link add dummy3 type dummy > /dev/null 2>&1");
+
+ gettimeofday(&tv_start, NULL);
+
+ if (!strcmp(filename, "-"))
+ filename = "/dev/stdin";
+
+ if (stat(filename, &st) < 0) {
+ fprintf(stderr, "cannot open test file %s: %s\n",
+ filename, strerror(errno));
+ return EXIT_FAILURE;
+ }
+ if (S_ISDIR(st.st_mode)) {
+ run_test_dir(filename, &ctx, &stats);
+ printf("results: [\x1b[32mOK\x1b[37m] %d [\x1b[31mFAILED\x1b[37m] %d\n",
+ stats.ok, stats.failed);
+ } else if (S_ISREG(st.st_mode)) {
+ printf("%s...\n", filename);
+ run_test(filename, &ctx);
+ }
+
+ gettimeofday(&tv_end, NULL);
+ timersub(&tv_end, &tv_start, &tv_diff);
+
+ if (ctx.fuzz)
+ printf("total fuzz steps: %u in %u.%06u seconds\n", ctx.fuzz_ctx.total_fuzz_steps, tv_diff.tv_sec, tv_diff.tv_usec);
+ else
+ printf("done in %u.%06u seconds\n", tv_diff.tv_sec, tv_diff.tv_usec);
+
+ return EXIT_SUCCESS;
+}
diff --git a/src/parser_yy.y b/src/parser_yy.y
new file mode 100644
index 0000000..84c0d37
--- /dev/null
+++ b/src/parser_yy.y
@@ -0,0 +1,2990 @@
+%{
+/*
+ * (C) 2024-2025 by Pablo Neira Ayuso <pablo@netfilter.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+/* Funded through the NGI0 Entrust established by NLnet (https://nlnet.nl)
+ * with support from the European Commission's Next Generation Internet
+ * programme.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+#include <limits.h>
+#include <libgen.h>
+
+#include <linux/netfilter.h>
+#include <linux/netfilter/nf_tables.h>
+#include "lib.h"
+#include "test.h"
+#include "fuzz.h"
+
+extern char *yytext;
+extern int yylineno;
+
+int yylex(void);
+static int yyerror(const char *msg);
+void yyrestart(FILE *input_file);
+
+static struct test_batch batch;
+static int test_fails;
+
+#define NFT_EXTRA 16
+
+struct keyword_map {
+ const char *name;
+ uint32_t value;
+};
+
+static const struct keyword_map nfproto_map[] = {
+ { "NFPROTO_UNSPEC", NFPROTO_UNSPEC },
+ { "NFPROTO_IPV4", NFPROTO_IPV4 },
+ { "NFPROTO_IPV6", NFPROTO_IPV6 },
+ { "NFPROTO_NETDEV", NFPROTO_NETDEV },
+ { "NFPROTO_BRIDGE", NFPROTO_BRIDGE },
+ { "NFPROTO_INET", NFPROTO_INET },
+ { "NFPROTO_ARP", NFPROTO_ARP },
+ { NULL, 0 },
+};
+
+static const struct keyword_map verdict_map[] = {
+ { "NFT_JUMP", NFT_JUMP },
+ { "NFT_GOTO", NFT_GOTO },
+ { "NFT_RETURN", NFT_RETURN },
+ { "NF_ACCEPT", NF_ACCEPT },
+ { "NF_DROP", NF_DROP },
+ { "NF_QUEUE", NF_QUEUE },
+ { NULL, 0 },
+};
+
+static const struct keyword_map datatype_map[] = {
+ { "NFT_DATA_VERDICT", NFT_DATA_VERDICT },
+ { "NFT_DATA_VALUE", NFT_DATA_VALUE },
+ { NULL, 0 },
+};
+
+static const struct keyword_map base_map[] = {
+ { "NFT_PAYLOAD_LL_HEADER", NFT_PAYLOAD_LL_HEADER },
+ { "NFT_PAYLOAD_NETWORK_HEADER", NFT_PAYLOAD_NETWORK_HEADER },
+ { "NFT_PAYLOAD_TRANSPORT_HEADER", NFT_PAYLOAD_TRANSPORT_HEADER },
+ { "NFT_PAYLOAD_INNER_HEADER", NFT_PAYLOAD_INNER_HEADER },
+ { "NFT_PAYLOAD_TUN_HEADER", NFT_PAYLOAD_TUN_HEADER },
+ { NULL, 0 },
+};
+
+static const struct keyword_map reg_map[] = {
+ { "NFT_REG_VERDICT", NFT_REG_VERDICT },
+ { "NFT_REG_1", NFT_REG_1 },
+ { "NFT_REG_2", NFT_REG_2 },
+ { "NFT_REG_3", NFT_REG_3 },
+ { "NFT_REG_4", NFT_REG_4 },
+ { "NFT_REG32_00", NFT_REG32_00 },
+ { "NFT_REG32_01", NFT_REG32_01 },
+ { "NFT_REG32_02", NFT_REG32_02 },
+ { "NFT_REG32_03", NFT_REG32_03 },
+ { "NFT_REG32_04", NFT_REG32_04 },
+ { "NFT_REG32_05", NFT_REG32_05 },
+ { "NFT_REG32_06", NFT_REG32_06 },
+ { "NFT_REG32_07", NFT_REG32_07 },
+ { "NFT_REG32_08", NFT_REG32_08 },
+ { "NFT_REG32_09", NFT_REG32_09 },
+ { "NFT_REG32_10", NFT_REG32_10 },
+ { "NFT_REG32_11", NFT_REG32_11 },
+ { "NFT_REG32_12", NFT_REG32_12 },
+ { "NFT_REG32_13", NFT_REG32_13 },
+ { "NFT_REG32_14", NFT_REG32_14 },
+ { "NFT_REG32_15", NFT_REG32_15 },
+ { NULL, 0 },
+};
+
+static int keyword_to_value(const char *keyword,
+ const struct keyword_map *map,
+ uint32_t **value)
+{
+ char *endptr = NULL;
+ int i;
+
+ if (!keyword) {
+ free((void *)keyword);
+ *value = NULL;
+ return 0;
+ }
+
+ for (i = 0; map[i].name != NULL; i++) {
+ if (!strcmp(map[i].name, keyword)) {
+ free((void *)keyword);
+ **value = map[i].value;
+ return 1;
+ }
+ }
+
+ **value = strtoul(keyword, &endptr, 0);
+ if (!*endptr) {
+ free((void *)keyword);
+ return 1;
+ }
+
+ return -1;
+}
+
+static bool parse_uint8(const char *str, uint8_t **value)
+{
+ char *endptr = NULL;
+
+ if (!str) {
+ free((void *)str);
+ *value = NULL;
+ return true;
+ }
+
+ **value = strtoul(str, &endptr, 0);
+ if (*endptr)
+ return false;
+
+ if (**value > UINT8_MAX)
+ return false;
+
+ free((void *)str);
+
+ return true;
+}
+
+static bool parse_uint16(const char *str, uint16_t **value)
+{
+ char *endptr = NULL;
+
+ if (!str) {
+ free((void *)str);
+ *value = NULL;
+ return true;
+ }
+
+ **value = strtoul(str, &endptr, 0);
+ if (*endptr)
+ return false;
+
+ if (**value > UINT16_MAX)
+ return false;
+
+ free((void *)str);
+
+ return true;
+}
+
+static bool parse_uint32(const char *str, uint32_t **value)
+{
+ char *endptr = NULL;
+
+ if (!str) {
+ *value = NULL;
+ free((void *)str);
+ return true;
+ }
+
+ **value = strtoul(str, &endptr, 0);
+ if (*endptr)
+ return false;
+
+ if (**value > UINT32_MAX)
+ return false;
+
+ free((void *)str);
+
+ return true;
+}
+
+static bool parse_uint64(const char *str, uint64_t **value)
+{
+ char *endptr = NULL;
+
+ if (!str) {
+ *value = NULL;
+ free((void *)str);
+ return true;
+ }
+
+ **value = strtoull(str, &endptr, 0);
+ if (*endptr)
+ return false;
+
+ free((void *)str);
+
+ return true;
+}
+
+static const uint32_t hexmap[] = {
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, /* 01234567 */
+ 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 89:;<=>? */
+ 0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00, /* @ABCDEFG */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* HIJKLMNO */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* PQRSTUVW */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* XYZ[\]^_ */
+ 0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00, /* `abcdefg */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* hijklmno */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* pqrstuvw */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* xyz{|}~. */
+};
+
+static int hexmap_nibble(char x)
+{
+ if ((x >= 48 && x < 58) ||
+ (x >= 97 && x < 103))
+ return hexmap[x - 48];
+
+ return -1;
+}
+
+static int hex_parse(uint8_t *x, uint8_t **out, int *len, int maxlen)
+{
+ uint32_t byte_len;
+ int a, b, i, j;
+
+ if (!x) {
+ *out = NULL;
+ return 0;
+ }
+ byte_len = strlen(x);
+ *len = byte_len / 2;
+ if (byte_len % 2 || *len > maxlen)
+ return -1;
+
+ for (i = 0, j = 0; i < strlen(x); i += 2, j++) {
+ a = hexmap_nibble(x[i]);
+ b = hexmap_nibble(x[i + 1]);
+ if (a < 0 || b < 0)
+ return -1;
+
+ /* FIXME: this does not work with big-endian. */
+ (*out)[j] = (a << 4) | b;
+ }
+ free((void *)x);
+
+ return 0;
+}
+
+static const char *_filename;
+
+static void error(const char *token)
+{
+ fprintf(stderr, "ERROR: cannot parse `%s' in %s line %d\n",
+ token, _filename, yylineno);
+}
+
+%}
+
+%union {
+ int val;
+ char *string;
+ struct array *array;
+ struct array_u8 *array_u8;
+ struct array_u32 *array_u32;
+}
+
+%token T_ADD_TABLE
+%token T_SET_TABLE
+%token T_DEL_TABLE
+%token T_ADD_BASECHAIN
+%token T_DEL_BASECHAIN
+%token T_ADD_CHAIN
+%token T_DEL_CHAIN
+%token T_ADD_RULE
+%token T_DEL_RULE
+%token T_ADD_SET
+%token T_SET_SET
+%token T_DEL_SET
+%token T_FLUSH_SET
+%token T_ADD_FLOWTABLE
+%token T_DEL_FLOWTABLE
+%token T_ADD_ELEM
+%token T_DEL_ELEM
+%token T_ADD_BITWISE
+%token T_ADD_BYTEORDER
+%token T_ADD_CMP
+%token T_ADD_CONNLIMIT
+%token T_ADD_COUNTER
+%token T_ADD_CT
+%token T_ADD_DUP
+%token T_ADD_DYNSET
+%token T_ADD_EXTHDR
+%token T_ADD_FIB
+%token T_ADD_FLOW_OFFLOAD
+%token T_ADD_FWD
+%token T_ADD_HASH
+%token T_ADD_IMMEDIATE
+%token T_ADD_INNER
+%token T_ADD_LAST
+%token T_ADD_LIMIT
+%token T_ADD_LOG
+%token T_ADD_LOOKUP
+%token T_ADD_MASQ
+%token T_ADD_MATCH
+%token T_ADD_META
+%token T_ADD_NAT
+%token T_ADD_NUMGEN
+%token T_ADD_OBJREF
+%token T_ADD_OSF
+%token T_ADD_PAYLOAD
+%token T_ADD_QUEUE
+%token T_ADD_QUOTA
+%token T_ADD_RANGE
+%token T_ADD_REDIR
+%token T_ADD_REJECT
+%token T_ADD_RT
+%token T_ADD_SOCKET
+%token T_ADD_SYNPROXY
+%token T_ADD_TARGET
+%token T_ADD_TPROXY
+%token T_ADD_TUNNEL
+%token T_ADD_XFRM
+%token T_ADD_OBJ_COUNTER
+%token T_ADD_OBJ_QUOTA
+%token T_ADD_OBJ_CT_HELPER
+%token T_ADD_OBJ_LIMIT
+%token T_ADD_OBJ_CONNLIMIT
+%token T_ADD_OBJ_TUNNEL
+%token T_ADD_OBJ_CT_TIMEOUT
+%token T_ADD_OBJ_CT_EXPECT
+%token T_ADD_OBJ_SECMARK
+%token T_ADD_OBJ_SYNPROXY
+%token T_ADD_OBJ_UNKNOWN
+%token T_DEL_OBJ_UNKNOWN
+%token T_COMMIT
+%token T_ABORT
+
+%token <string> STRING
+%token <string> QSTRING
+%token <string> NULLSTR
+%destructor { free((void *)$$); } STRING QSTRING
+
+%type <string> qstring
+%destructor { free((void *)$$); } qstring
+%type <array> string_array
+%type <array> string_array_items
+%destructor { array_free($$); } string_array string_array_items
+%type <array_u8> u8_array
+%type <array_u8> u8_array_items
+%destructor { array_u8_free($$); } u8_array u8_array_items
+%type <array_u32> u32_array
+%type <array_u32> u32_array_items
+%destructor { array_u32_free($$); } u32_array u32_array_items
+
+%%
+
+testfile :
+ | lines
+ ;
+
+lines : line
+ | lines line
+ ;
+
+line : add_table
+ | set_table
+ | del_table
+ | add_basechain
+ | del_basechain
+ | add_chain
+ | del_chain
+ | add_set
+ | set_set
+ | del_set
+ | flush_set
+ | add_elem
+ | del_elem
+ | add_flowtable
+ | del_flowtable
+ | add_rule
+ | del_rule
+ | add_bitwise
+ | add_byteorder
+ | add_cmp
+ | add_connlimit
+ | add_counter
+ | add_ct
+ | add_dup
+ | add_dynset
+ | add_exthdr
+ | add_fib
+ | add_fwd
+ | add_hash
+ | add_inner
+ | add_immediate
+ | add_last
+ | add_limit
+ | add_log
+ | add_lookup
+ | add_masq
+ | add_match
+ | add_meta
+ | add_nat
+ | add_numgen
+ | add_objref
+ | add_osf
+ | add_payload
+ | add_queue
+ | add_quota
+ | add_range
+ | add_redir
+ | add_reject
+ | add_rt
+ | add_socket
+ | add_synproxy
+ | add_target
+ | add_tproxy
+ | add_tunnel
+ | add_xfrm
+ | add_obj_counter
+ | add_obj_limit
+ | add_obj_ct_helper
+ | add_obj_ct_timeout
+ | add_obj_quota
+ | add_obj_connlimit
+ | add_obj_secmark
+ | add_obj_synproxy
+ | add_obj_tunnel
+ | add_obj_ct_expect
+ | add_obj_unknown
+ | del_obj_unknown
+ | commit
+ | abort
+ ;
+
+qstring : QSTRING { $$ = $1; }
+ | NULLSTR { $$ = NULL; }
+ ;
+
+add_table : T_ADD_TABLE '(' STRING ',' qstring ',' qstring ',' qstring ',' qstring ')' ';'
+ {
+ struct table *table = table_init();
+
+ if (keyword_to_value($3, nfproto_map, &table->nfproto) <= 0) {
+ error("nfproto");
+ free_table(table);
+ YYERROR;
+ }
+
+ table->name = $5;
+
+ if (!parse_uint32($7, &table->flags)) {
+ error("flags");
+ free_table(table);
+ YYERROR;
+ }
+ if (!parse_uint64($9, &table->handle)) {
+ error("handle");
+ free_table(table);
+ YYERROR;
+ }
+
+ table->userdata = $11;
+
+ batch.lineno = yylineno;
+ add_table(&batch, table);
+ }
+ ;
+
+set_table : T_SET_TABLE '(' STRING ',' qstring ')' ';'
+ {
+ struct table *table = table_init();
+
+ if (keyword_to_value($3, nfproto_map, &table->nfproto) <= 0) {
+ error("nfproto");
+ free_table(table);
+ YYERROR;
+ }
+
+ table->name = $5;
+
+ set_table(&batch, table);
+ }
+ ;
+
+del_table : T_DEL_TABLE '(' STRING ',' qstring ',' qstring ')' ';'
+ {
+ struct table *table = table_init();
+
+ if (keyword_to_value($3, nfproto_map, &table->nfproto) <= 0) {
+ error("nfproto");
+ free_table(table);
+ YYERROR;
+ }
+
+ table->name = $5;
+
+ if (!parse_uint64($7, &table->handle)) {
+ error("handle");
+ free_table(table);
+ yyerror;
+ }
+
+ batch.lineno = yylineno;
+ del_table(&batch, table);
+ }
+ ;
+
+add_chain : T_ADD_CHAIN '(' qstring ',' qstring ',' qstring ',' qstring ',' qstring ')' ';'
+ {
+ struct chain *chain = chain_init();
+
+ chain->name = $3;
+
+ if (!parse_uint32($5, &chain->chain_id)) {
+ error("chain_id");
+ free_chain(chain);
+ YYERROR;
+ }
+ if (!parse_uint32($7, &chain->flags)) {
+ error("flags");
+ free_chain(chain);
+ YYERROR;
+ }
+
+ if (!parse_uint64($9, &chain->handle)) {
+ error("handle");
+ free_chain(chain);
+ yyerror;
+ }
+
+ chain->userdata = $11;
+
+ chain->hooknum = NULL;
+ chain->prio = NULL;
+ chain->policy = NULL;
+ chain->bytes = NULL;
+ chain->pkts = NULL;
+
+ batch.lineno = yylineno;
+ add_chain(&batch, chain);
+ }
+ ;
+
+del_chain : T_DEL_CHAIN '(' qstring ')' ';'
+ {
+ struct chain *chain = chain_init();
+
+ chain->name = $3;
+
+ batch.lineno = yylineno;
+ del_chain(&batch, chain);
+ }
+ ;
+
+string_array_items: QSTRING
+ {
+ $$ = array_alloc();
+ if (!array_add($$, $1)) {
+ error("array");
+ YYERROR;
+ }
+ }
+ | string_array_items ',' QSTRING
+ {
+ if (!array_add($1, $3)) {
+ error("array");
+ YYERROR;
+ }
+ $$ = $1;
+ }
+ ;
+
+string_array : '{' string_array_items '}' { $$ = $2; }
+ | NULLSTR { $$ = NULL; }
+ ;
+
+add_basechain : T_ADD_BASECHAIN '(' qstring ',' qstring ',' qstring ',' qstring ',' qstring ',' string_array ',' qstring ',' qstring ',' qstring ',' qstring ',' qstring ',' qstring ',' qstring ')' ';'
+ {
+ struct chain *chain = chain_init();
+
+ chain->name = $3;
+ chain->type = $5;
+
+ if (!parse_uint32($7, &chain->hooknum)) {
+ error("hooknum");
+ free_chain(chain);
+ YYERROR;
+ }
+ if (!parse_uint32($9, &chain->prio)) {
+ error("priority");
+ free_chain(chain);
+ YYERROR;
+ }
+
+ chain->dev = $11;
+ chain->dev_array = $13;
+
+ if (keyword_to_value($15, verdict_map, &chain->policy) < 0) {
+ error("policy");
+ free_chain(chain);
+ YYERROR;
+ }
+ if (!parse_uint32($17, &chain->chain_id)) {
+ error("chain_id");
+ free_chain(chain);
+ YYERROR;
+ }
+ if (!parse_uint32($19, &chain->flags)) {
+ error("flags");
+ free_chain(chain);
+ YYERROR;
+ }
+ if (!parse_uint64($21, &chain->bytes)) {
+ error("bytes");
+ free_chain(chain);
+ YYERROR;
+ }
+ if (!parse_uint64($23, &chain->pkts)) {
+ error("packets");
+ free_chain(chain);
+ YYERROR;
+ }
+ if (!parse_uint64($25, &chain->handle)) {
+ error("handle");
+ free_chain(chain);
+ YYERROR;
+ }
+
+ chain->userdata = $27;
+
+ batch.lineno = yylineno;
+ add_basechain(&batch, chain);
+ }
+ ;
+
+del_basechain : T_DEL_BASECHAIN '(' qstring ',' qstring ',' qstring ',' qstring ',' qstring ',' string_array ',' qstring ',' qstring ')' ';'
+ {
+ struct chain *chain = chain_init();
+
+ chain->name = $3;
+ chain->type = $5;
+
+ if (!parse_uint32($7, &chain->hooknum)) {
+ error("hooknum");
+ free_chain(chain);
+ YYERROR;
+ }
+ if (!parse_uint32($9, &chain->prio)) {
+ error("priority");
+ free_chain(chain);
+ YYERROR;
+ }
+
+ chain->dev = $11;
+ chain->dev_array = $13;
+
+ if (!parse_uint32($15, &chain->chain_id)) {
+ error("chain_id");
+ free_chain(chain);
+ YYERROR;
+ }
+ if (!parse_uint64($17, &chain->handle)) {
+ error("handle");
+ free_chain(chain);
+ YYERROR;
+ }
+
+ batch.lineno = yylineno;
+ del_basechain(&batch, chain);
+ }
+ ;
+
+add_rule : T_ADD_RULE '(' qstring ',' qstring ',' qstring ',' qstring ',' qstring ')' ';'
+ {
+ struct rule *rule = rule_init();
+
+ rule->chain = $3;
+
+ if (!parse_uint32($5, &rule->rule_id)) {
+ error("rule_id");
+ free_rule(rule);
+ YYERROR;
+ }
+ if (!parse_uint64($7, &rule->pos)) {
+ error("position");
+ free_rule(rule);
+ YYERROR;
+ }
+
+ if (!parse_uint32($9, &rule->pos_id)) {
+ error("position_id");
+ free_rule(rule);
+ YYERROR;
+ }
+
+ rule->userdata = $11;
+
+ batch.lineno = yylineno;
+ add_rule(&batch, rule);
+ }
+ ;
+
+del_rule : T_DEL_RULE '(' qstring ',' qstring ',' qstring ')' ';'
+ {
+ struct rule *rule = rule_init();
+
+ rule->chain = $3;
+
+ if (!parse_uint32($5, &rule->rule_id)) {
+ error("rule_id");
+ free_rule(rule);
+ YYERROR;
+ }
+ if (!parse_uint64($7, &rule->handle)) {
+ error("handle");
+ free_rule(rule);
+ YYERROR;
+ }
+
+ batch.lineno = yylineno;
+ del_rule(&batch, rule);
+ }
+ ;
+
+add_bitwise : T_ADD_BITWISE '(' qstring ',' qstring ',' qstring ',' qstring ',' qstring ',' qstring ',' qstring ')' ';'
+ {
+ struct bitwise *bitwise = bitwise_init();
+
+ if (keyword_to_value($3, reg_map, &bitwise->sreg) < 0) {
+ error("sreg");
+ free_bitwise(bitwise);
+ YYERROR;
+ }
+ if (keyword_to_value($5, reg_map, &bitwise->dreg) < 0) {
+ error("dreg");
+ free_bitwise(bitwise);
+ YYERROR;
+ }
+ if (!parse_uint32($7, &bitwise->op)) {
+ error("op");
+ free_bitwise(bitwise);
+ YYERROR;
+ }
+ if (!parse_uint32($9, &bitwise->len)) {
+ error("len");
+ free_bitwise(bitwise);
+ YYERROR;
+ }
+ if (hex_parse($11, &bitwise->mask, &bitwise->mask_len, 16 + NFT_EXTRA) < 0) {
+ error("mask");
+ free_bitwise(bitwise);
+ YYERROR;
+ }
+ if (hex_parse($13, &bitwise->xor, &bitwise->xor_len, 16 + NFT_EXTRA) < 0) {
+ error("xor");
+ free_bitwise(bitwise);
+ YYERROR;
+ }
+ if (hex_parse($15, &bitwise->data, &bitwise->data_len, 16 + NFT_EXTRA) < 0) {
+ error("bitwise");
+ free_bitwise(bitwise);
+ YYERROR;
+ }
+
+ if (add_expr(&batch, &bitwise->expr) < 0) {
+ error("missing context rule or set for expression");
+ free_bitwise(bitwise);
+ YYERROR;
+ }
+ }
+ ;
+
+add_byteorder : T_ADD_BYTEORDER '(' qstring ',' qstring ',' qstring ',' qstring ',' qstring ')' ';'
+ {
+ struct byteorder *byteorder = byteorder_init();
+
+ if (keyword_to_value($3, reg_map, &byteorder->sreg) < 0) {
+ error("sreg");
+ free_byteorder(byteorder);
+ YYERROR;
+ }
+ if (keyword_to_value($5, reg_map, &byteorder->dreg) < 0) {
+ error("dreg");
+ free_byteorder(byteorder);
+ YYERROR;
+ }
+ if (!parse_uint32($7, &byteorder->op)) {
+ error("op");
+ free_byteorder(byteorder);
+ YYERROR;
+ }
+ if (!parse_uint32($9, &byteorder->len)) {
+ error("len");
+ free_byteorder(byteorder);
+ YYERROR;
+ }
+ if (!parse_uint32($11, &byteorder->size)) {
+ error("size");
+ free_byteorder(byteorder);
+ YYERROR;
+ }
+
+ if (add_expr(&batch, &byteorder->expr) < 0) {
+ error("missing context rule or set for expression");
+ free_byteorder(byteorder);
+ YYERROR;
+ }
+ }
+ ;
+
+add_cmp : T_ADD_CMP '(' qstring ',' qstring ',' qstring ')' ';'
+ {
+ struct cmp *cmp = cmp_init();
+
+ if (keyword_to_value($3, reg_map, &cmp->sreg) < 0) {
+ error("sreg");
+ free_cmp(cmp);
+ YYERROR;
+ }
+ if (!parse_uint32($5, &cmp->op)) {
+ error("op");
+ free_cmp(cmp);
+ YYERROR;
+ }
+ if (hex_parse($7, &cmp->data, &cmp->data_len, 16 + NFT_EXTRA) < 0) {
+ error("data");
+ free_cmp(cmp);
+ YYERROR;
+ }
+
+ if (add_expr(&batch, &cmp->expr) < 0) {
+ error("missing context rule or set for expression");
+ free_cmp(cmp);
+ YYERROR;
+ }
+ }
+ ;
+
+add_connlimit : T_ADD_CONNLIMIT '(' qstring ',' qstring ')' ';'
+ {
+ struct connlimit *connlimit = connlimit_init();
+
+ if (!parse_uint32($3, &connlimit->count)) {
+ error("count");
+ free_connlimit(connlimit);
+ YYERROR;
+ }
+ if (!parse_uint32($5, &connlimit->flags)) {
+ error("flags");
+ free_connlimit(connlimit);
+ YYERROR;
+ }
+
+ if (add_expr(&batch, &connlimit->expr) < 0) {
+ error("missing context rule or set for expression");
+ free_connlimit(connlimit);
+ YYERROR;
+ }
+ }
+ ;
+
+add_counter : T_ADD_COUNTER '(' qstring ',' qstring')' ';'
+ {
+ struct counter *counter = counter_init();
+
+ if (!parse_uint64($3, &counter->pkts)) {
+ error("packets");
+ free_counter(counter);
+ YYERROR;
+ }
+ if (!parse_uint64($5, &counter->bytes)) {
+ error("bytes");
+ free_counter(counter);
+ YYERROR;
+ }
+
+ if (add_expr(&batch, &counter->expr) < 0) {
+ error("missing context rule or set for expression");
+ free_counter(counter);
+ YYERROR;
+ }
+ }
+ ;
+
+add_ct : T_ADD_CT '(' qstring ',' qstring ',' qstring ',' qstring ')' ';'
+ {
+ struct ct *ct = ct_init();
+
+ if (keyword_to_value($3, reg_map, &ct->sreg) < 0) {
+ free_ct(ct);
+ error("sreg");
+ YYERROR;
+ }
+ if (keyword_to_value($5, reg_map, &ct->dreg) < 0) {
+ error("dreg");
+ free_ct(ct);
+ YYERROR;
+ }
+ if (!parse_uint32($7, &ct->key)) {
+ error("key");
+ free_ct(ct);
+ YYERROR;
+ }
+ if (!parse_uint8($9, &ct->dir)) {
+ error("dir");
+ free_ct(ct);
+ YYERROR;
+ }
+
+ if (add_expr(&batch, &ct->expr) < 0) {
+ error("missing context rule or set for expression");
+ free_ct(ct);
+ YYERROR;
+ }
+ }
+ ;
+
+add_dup : T_ADD_DUP '(' qstring ',' qstring ')' ';'
+ {
+ struct dup *dup = dup_init();
+
+ if (keyword_to_value($3, reg_map, &dup->sreg_addr) < 0) {
+ error("sreg_addr");
+ free_dup(dup);
+ YYERROR;
+ }
+ if (keyword_to_value($5, reg_map, &dup->sreg_dev) < 0) {
+ error("sreg_dev");
+ free_dup(dup);
+ YYERROR;
+ }
+
+ if (add_expr(&batch, &dup->expr) < 0) {
+ error("missing context rule or set for expression");
+ free_dup(dup);
+ YYERROR;
+ }
+ }
+ ;
+
+add_dynset : T_ADD_DYNSET '(' qstring ',' qstring ',' qstring ',' qstring ',' qstring ',' qstring ',' qstring ')' ';'
+ {
+ struct dynset *dynset = dynset_init();
+
+ dynset->set = $3;
+
+ if (!parse_uint32($5, &dynset->set_id)) {
+ error("set_id");
+ free_dynset(dynset);
+ YYERROR;
+ }
+ if (!parse_uint32($7, &dynset->op)) {
+ error("op");
+ free_dynset(dynset);
+ YYERROR;
+ }
+ if (keyword_to_value($9, reg_map, &dynset->sreg_key) < 0) {
+ error("sreg_key");
+ free_dynset(dynset);
+ YYERROR;
+ }
+ if (keyword_to_value($11, reg_map, &dynset->sreg_data) < 0) {
+ error("sreg_data");
+ free_dynset(dynset);
+ YYERROR;
+ }
+ if (!parse_uint64($13, &dynset->timeout)) {
+ error("timeout");
+ free_dynset(dynset);
+ YYERROR;
+ }
+
+ if (!parse_uint32($15, &dynset->flags)) {
+ error("flags");
+ free_dynset(dynset);
+ YYERROR;
+ }
+
+ if (add_expr(&batch, &dynset->expr) < 0) {
+ error("missing context rule or set for expression");
+ free_dynset(dynset);
+ YYERROR;
+ }
+ }
+ ;
+
+add_exthdr : T_ADD_EXTHDR '(' qstring ',' qstring ',' qstring ',' qstring ',' qstring ',' qstring ',' qstring ')' ';'
+ {
+ struct exthdr *exthdr = exthdr_init();
+
+ if (keyword_to_value($3, reg_map, &exthdr->sreg) < 0) {
+ error("sreg");
+ free_exthdr(exthdr);
+ YYERROR;
+ }
+ if (keyword_to_value($5, reg_map, &exthdr->dreg) < 0) {
+ error("dreg");
+ free_exthdr(exthdr);
+ YYERROR;
+ }
+ if (!parse_uint32($7, &exthdr->offset)) {
+ error("offset");
+ free_exthdr(exthdr);
+ YYERROR;
+ }
+ if (!parse_uint32($9, &exthdr->len)) {
+ error("len");
+ free_exthdr(exthdr);
+ YYERROR;
+ }
+ if (!parse_uint32($11, &exthdr->type)) {
+ error("type");
+ free_exthdr(exthdr);
+ YYERROR;
+ }
+ if (!parse_uint32($13, &exthdr->op)) {
+ error("op");
+ free_exthdr(exthdr);
+ YYERROR;
+ }
+ if (!parse_uint32($15, &exthdr->flags)) {
+ error("flags");
+ free_exthdr(exthdr);
+ YYERROR;
+ }
+
+ if (add_expr(&batch, &exthdr->expr) < 0) {
+ error("missing context rule or set for expression");
+ free_exthdr(exthdr);
+ YYERROR;
+ }
+ }
+ ;
+
+add_fib : T_ADD_FIB '(' qstring ',' qstring ',' qstring ')' ';'
+ {
+ struct fib *fib = fib_init();
+
+ if (!parse_uint32($3, &fib->flags)) {
+ error("flags");
+ free_fib(fib);
+ YYERROR;
+ }
+ if (!parse_uint32($5, &fib->result)) {
+ error("result");
+ free_fib(fib);
+ YYERROR;
+ }
+ if (keyword_to_value($7, reg_map, &fib->dreg) < 0) {
+ error("dreg");
+ free_fib(fib);
+ YYERROR;
+ }
+
+ if (add_expr(&batch, &fib->expr) < 0) {
+ error("missing context rule or set for expression");
+ free_fib(fib);
+ YYERROR;
+ }
+ }
+ ;
+
+add_fwd : T_ADD_FWD '(' qstring ',' qstring ',' qstring ')' ';'
+ {
+ struct fwd *fwd = fwd_init();
+
+ if (keyword_to_value($3, reg_map, &fwd->sreg_addr) < 0) {
+ error("sreg_addr");
+ free_fwd(fwd);
+ YYERROR;
+ }
+ if (keyword_to_value($5, reg_map, &fwd->sreg_dev) < 0) {
+ error("sreg_dev");
+ free_fwd(fwd);
+ YYERROR;
+ }
+ if (keyword_to_value($7, nfproto_map, &fwd->nfproto) <= 0) {
+ error("nfproto");
+ free_fwd(fwd);
+ YYERROR;
+ }
+
+ if (add_expr(&batch, &fwd->expr) < 0) {
+ error("missing context rule or set for expression");
+ free_fwd(fwd);
+ YYERROR;
+ }
+ }
+ ;
+
+add_hash : T_ADD_HASH '(' qstring ',' qstring ',' qstring ',' qstring ',' qstring ',' qstring ',' qstring ')' ';'
+ {
+ struct hash *hash = hash_init();
+
+ if (!parse_uint32($3, &hash->type)) {
+ error("type");
+ free_hash(hash);
+ YYERROR;
+ }
+ if (keyword_to_value($5, reg_map, &hash->sreg) < 0) {
+ error("sreg");
+ free_hash(hash);
+ YYERROR;
+ }
+ if (keyword_to_value($7, reg_map, &hash->dreg) < 0) {
+ error("dreg");
+ free_hash(hash);
+ YYERROR;
+ }
+ if (!parse_uint32($9, &hash->len)) {
+ error("len");
+ free_hash(hash);
+ YYERROR;
+ }
+ if (!parse_uint32($11, &hash->modulus)) {
+ error("modulus");
+ free_hash(hash);
+ YYERROR;
+ }
+ if (!parse_uint32($13, &hash->seed)) {
+ error("seed");
+ free_hash(hash);
+ YYERROR;
+ }
+ if (!parse_uint32($15, &hash->offset)) {
+ error("offset");
+ free_hash(hash);
+ YYERROR;
+ }
+
+ if (add_expr(&batch, &hash->expr) < 0) {
+ error("missing context rule or set for expression");
+ free_hash(hash);
+ YYERROR;
+ }
+ }
+ ;
+
+add_inner : T_ADD_INNER '(' qstring ',' qstring ',' qstring ',' qstring ')' ';'
+ {
+ struct inner *inner = inner_init();
+
+ if (!parse_uint32($3, &inner->num)) {
+ error("num");
+ free_inner(inner);
+ YYERROR;
+ }
+ if (!parse_uint32($5, &inner->type)) {
+ error("type");
+ free_inner(inner);
+ YYERROR;
+ }
+ if (!parse_uint32($7, &inner->flags)) {
+ error("flags");
+ free_inner(inner);
+ YYERROR;
+ }
+ if (!parse_uint32($9, &inner->hdrsize)) {
+ error("hdrsize");
+ free_inner(inner);
+ YYERROR;
+ }
+
+ if (add_expr(&batch, &inner->expr) < 0) {
+ error("missing context rule or set for expression");
+ free_inner(inner);
+ YYERROR;
+ }
+
+ batch.ctx.expr_list = &inner->expr_list;
+ }
+ ;
+
+add_immediate : T_ADD_IMMEDIATE '(' qstring ',' qstring ',' qstring ',' qstring ',' qstring ')' ';'
+ {
+ struct immediate *imm = immediate_init();
+
+ if (keyword_to_value($3, reg_map, &imm->dreg) < 0) {
+ error("dreg");
+ free_immediate(imm);
+ YYERROR;
+ }
+ if (keyword_to_value($5, verdict_map, &imm->verdict) < 0) {
+ error("verdict");
+ free_immediate(imm);
+ YYERROR;
+ }
+ if (!parse_uint32($7, &imm->chain_id)) {
+ error("chain_id");
+ free_immediate(imm);
+ YYERROR;
+ }
+
+ imm->chain = $9;
+
+ if (hex_parse($11, &imm->data, &imm->data_len, 16 + NFT_EXTRA) < 0) {
+ error("data");
+ free_immediate(imm);
+ YYERROR;
+ }
+
+ if (add_expr(&batch, &imm->expr) < 0) {
+ error("missing context rule or set for expression");
+ free_immediate(imm);
+ YYERROR;
+ }
+ }
+ ;
+
+add_last : T_ADD_LAST '(' qstring ',' qstring')' ';'
+ {
+ struct last *last = last_init();
+
+ if (!parse_uint64($3, &last->msecs)) {
+ error("msecs");
+ free_last(last);
+ YYERROR;
+ }
+ if (!parse_uint32($5, &last->set)) {
+ error("set");
+ free_last(last);
+ YYERROR;
+ }
+
+ if (add_expr(&batch, &last->expr) < 0) {
+ error("missing context rule or set for expression");
+ free_last(last);
+ YYERROR;
+ }
+ }
+ ;
+
+add_limit : T_ADD_LIMIT '(' qstring ',' qstring ',' qstring ',' qstring ',' qstring ')' ';'
+ {
+ struct limit *limit = limit_init();
+
+ if (!parse_uint64($3, &limit->rate)) {
+ error("rate");
+ free_limit(limit);
+ YYERROR;
+ }
+ if (!parse_uint64($5, &limit->unit)) {
+ error("unit");
+ free_limit(limit);
+ YYERROR;
+ }
+ if (!parse_uint32($7, &limit->burst)) {
+ error("burst");
+ free_limit(limit);
+ YYERROR;
+ }
+ if (!parse_uint32($9, &limit->type)) {
+ error("type");
+ free_limit(limit);
+ YYERROR;
+ }
+ if (!parse_uint32($11, &limit->flags)) {
+ error("flags");
+ free_limit(limit);
+ YYERROR;
+ }
+
+ if (add_expr(&batch, &limit->expr) < 0) {
+ error("missing context rule or set for expression");
+ free_limit(limit);
+ YYERROR;
+ }
+ }
+ ;
+
+add_log : T_ADD_LOG '(' qstring ',' qstring ',' qstring ',' qstring ',' qstring ',' qstring ')' ';'
+ {
+ struct log *log = log_init();
+
+ if (!parse_uint32($3, &log->snaplen)) {
+ error("snaplen");
+ free_log(log);
+ YYERROR;
+ }
+ if (!parse_uint16($5, &log->group)) {
+ error("group");
+ free_log(log);
+ YYERROR;
+ }
+ if (!parse_uint16($7, &log->qthreshold)) {
+ error("qthreshold");
+ free_log(log);
+ YYERROR;
+ }
+ if (!parse_uint32($9, &log->level)) {
+ error("level");
+ free_log(log);
+ YYERROR;
+ }
+ if (!parse_uint32($11, &log->flags)) {
+ error("flags");
+ free_log(log);
+ YYERROR;
+ }
+
+ log->prefix = $13;
+
+ if (add_expr(&batch, &log->expr) < 0) {
+ error("missing context rule or set for expression");
+ free_log(log);
+ YYERROR;
+ }
+ }
+ ;
+
+add_lookup : T_ADD_LOOKUP '(' qstring ',' qstring ',' qstring ',' qstring ')' ';'
+ {
+ struct lookup *lookup = lookup_init();
+
+ if (keyword_to_value($3, reg_map, &lookup->sreg) < 0) {
+ error("sreg");
+ free_lookup(lookup);
+ YYERROR;
+ }
+ if (keyword_to_value($5, reg_map, &lookup->dreg) < 0) {
+ error("dreg");
+ free_lookup(lookup);
+ YYERROR;
+ }
+
+ lookup->set = $7;
+
+ if (!parse_uint32($9, &lookup->set_id)) {
+ error("set_id");
+ free_lookup(lookup);
+ YYERROR;
+ }
+
+ if (add_expr(&batch, &lookup->expr) < 0) {
+ error("missing context rule or set for expression");
+ free_lookup(lookup);
+ YYERROR;
+ }
+ }
+ ;
+
+add_masq : T_ADD_MASQ '(' qstring ',' qstring ',' qstring ')' ';'
+ {
+ struct masq *masq = masq_init();
+
+ if (!parse_uint32($3, &masq->flags)) {
+ error("flags");
+ free_masq(masq);
+ YYERROR;
+ }
+ if (keyword_to_value($5, reg_map, &masq->sreg_proto_min) < 0) {
+ error("sreg_proto_min");
+ free_masq(masq);
+ YYERROR;
+ }
+ if (keyword_to_value($7, reg_map, &masq->sreg_proto_max) < 0) {
+ error("sreg_proto_max");
+ free_masq(masq);
+ YYERROR;
+ }
+
+ if (add_expr(&batch, &masq->expr) < 0) {
+ error("missing context rule or set for expression");
+ free_masq(masq);
+ YYERROR;
+ }
+ }
+ ;
+
+add_match : T_ADD_MATCH '(' qstring ',' qstring ',' qstring ',' qstring ',' qstring ')' ';'
+ {
+ struct match *match = match_init();
+
+ match->name = $3;
+
+ if (!parse_uint32($5, &match->rev)) {
+ error("rev");
+ free_match(match);
+ YYERROR;
+ }
+ if (hex_parse($7, &match->data, &match->data_len, 65536) < 0) {
+ error("data");
+ free_match(match);
+ YYERROR;
+ }
+ if (!parse_uint32($9, &match->l4proto)) {
+ error("l4proto");
+ free_match(match);
+ YYERROR;
+ }
+ if (!parse_uint32($11, &match->flags)) {
+ error("flags");
+ free_match(match);
+ YYERROR;
+ }
+
+ /* special compat handling. */
+ if (batch.ctx.rule) {
+ batch.ctx.rule->compat.l4proto = match->l4proto;
+ batch.ctx.rule->compat.flags = match->flags;
+ }
+
+ if (add_expr(&batch, &match->expr) < 0) {
+ error("missing context rule or set for expression");
+ free_match(match);
+ YYERROR;
+ }
+ }
+ ;
+
+add_meta : T_ADD_META '(' qstring ',' qstring ',' qstring ')' ';'
+ {
+ struct meta *meta = meta_init();
+
+ if (keyword_to_value($3, reg_map, &meta->sreg) < 0) {
+ error("sreg");
+ free_meta(meta);
+ YYERROR;
+ }
+ if (keyword_to_value($5, reg_map, &meta->dreg) < 0) {
+ error("dreg");
+ free_meta(meta);
+ YYERROR;
+ }
+ if (!parse_uint32($7, &meta->key)) {
+ error("key");
+ free_meta(meta);
+ YYERROR;
+ }
+
+ if (add_expr(&batch, &meta->expr) < 0) {
+ error("missing context rule or set for expression");
+ free_meta(meta);
+ YYERROR;
+ }
+ }
+ ;
+
+add_nat : T_ADD_NAT '(' STRING ',' qstring ',' qstring ',' qstring ',' qstring ',' qstring ',' qstring ')' ';'
+ {
+ struct nat *nat = nat_init();
+
+ if (keyword_to_value($3, nfproto_map, &nat->nfproto) <= 0) {
+ error("nfproto");
+ free_nat(nat);
+ YYERROR;
+ }
+ if (keyword_to_value($5, reg_map, &nat->sreg_addr_min) < 0) {
+ error("sreg_addr_min");
+ free_nat(nat);
+ YYERROR;
+ }
+ if (keyword_to_value($7, reg_map, &nat->sreg_addr_max) < 0) {
+ error("sreg_addr_max");
+ free_nat(nat);
+ YYERROR;
+ }
+ if (keyword_to_value($9, reg_map, &nat->sreg_proto_min) < 0) {
+ error("sreg_proto_min");
+ free_nat(nat);
+ YYERROR;
+ }
+ if (keyword_to_value($11, reg_map, &nat->sreg_proto_max) < 0) {
+ error("sreg_proto_max");
+ free_nat(nat);
+ YYERROR;
+ }
+ if (!parse_uint32($13, &nat->type)) {
+ error("type");
+ free_nat(nat);
+ YYERROR;
+ }
+ if (!parse_uint32($15, &nat->flags)) {
+ error("flags");
+ free_nat(nat);
+ YYERROR;
+ }
+
+ if (add_expr(&batch, &nat->expr) < 0) {
+ error("missing context rule or set for expression");
+ free_nat(nat);
+ YYERROR;
+ }
+ }
+ ;
+
+add_numgen : T_ADD_NUMGEN '(' qstring ',' qstring ',' qstring ',' qstring ')' ';'
+ {
+ struct numgen *numgen = numgen_init();
+
+ if (keyword_to_value($3, reg_map, &numgen->dreg) < 0) {
+ error("dreg");
+ free_numgen(numgen);
+ YYERROR;
+ }
+ if (!parse_uint32($5, &numgen->modulus)) {
+ error("modulus");
+ free_numgen(numgen);
+ YYERROR;
+ }
+ if (!parse_uint32($7, &numgen->type)) {
+ error("type");
+ free_numgen(numgen);
+ YYERROR;
+ }
+ if (!parse_uint32($9, &numgen->offset)) {
+ error("offset");
+ free_numgen(numgen);
+ YYERROR;
+ }
+
+ if (add_expr(&batch, &numgen->expr) < 0) {
+ error("missing context rule or set for expression");
+ free_numgen(numgen);
+ YYERROR;
+ }
+ }
+ ;
+
+add_objref : T_ADD_OBJREF '(' qstring ',' qstring ',' qstring ',' qstring ',' qstring ')' ';'
+ {
+ struct objref *objref = objref_init();
+
+ if (!parse_uint32($3, &objref->type)) {
+ error("type");
+ free_objref(objref);
+ YYERROR;
+ }
+
+ objref->name = $5;
+
+ if (keyword_to_value($7, reg_map, &objref->sreg) < 0) {
+ error("sreg");
+ free_objref(objref);
+ YYERROR;
+ }
+
+ objref->set_name = $9;
+
+ if (!parse_uint32($11, &objref->set_id)) {
+ error("set_id");
+ free_objref(objref);
+ YYERROR;
+ }
+
+ if (add_expr(&batch, &objref->expr) < 0) {
+ error("missing context rule or set for expression");
+ free_objref(objref);
+ YYERROR;
+ }
+ }
+ ;
+
+add_osf : T_ADD_OSF '(' qstring ',' qstring ',' qstring ')' ';'
+ {
+ struct osf *osf = osf_init();
+
+ if (keyword_to_value($3, reg_map, &osf->dreg) < 0) {
+ error("dreg");
+ free_osf(osf);
+ YYERROR;
+ }
+ if (!parse_uint32($5, &osf->ttl)) {
+ error("ttl");
+ free_osf(osf);
+ YYERROR;
+ }
+ if (!parse_uint32($7, &osf->flags)) {
+ error("flags");
+ free_osf(osf);
+ YYERROR;
+ }
+
+ if (add_expr(&batch, &osf->expr) < 0) {
+ error("missing context rule or set for expression");
+ free_osf(osf);
+ YYERROR;
+ }
+ }
+ ;
+
+add_payload : T_ADD_PAYLOAD '(' qstring ',' qstring ',' qstring ',' qstring ',' qstring ',' qstring ',' qstring ',' qstring ')' ';'
+ {
+ struct payload *payload = payload_init();
+
+ if (keyword_to_value($3, reg_map, &payload->sreg) < 0) {
+ error("sreg");
+ free_payload(payload);
+ YYERROR;
+ }
+ if (keyword_to_value($5, reg_map, &payload->dreg) < 0) {
+ error("dreg");
+ free_payload(payload);
+ YYERROR;
+ }
+ if (keyword_to_value($7, base_map, &payload->base) < 0) {
+ error("base");
+ free_payload(payload);
+ YYERROR;
+ }
+ if (!parse_uint32($9, &payload->offset)) {
+ error("offset");
+ free_payload(payload);
+ YYERROR;
+ }
+ if (!parse_uint32($11, &payload->len)) {
+ error("len");
+ free_payload(payload);
+ YYERROR;
+ }
+ if (!parse_uint32($13, &payload->csum_type)) {
+ error("csum_type");
+ free_payload(payload);
+ YYERROR;
+ }
+ if (!parse_uint32($15, &payload->csum_offset)) {
+ error("csum_offset");
+ free_payload(payload);
+ YYERROR;
+ }
+ if (!parse_uint32($17, &payload->csum_flags)) {
+ error("flags");
+ free_payload(payload);
+ YYERROR;
+ }
+
+ if (add_expr(&batch, &payload->expr) < 0) {
+ error("missing context rule or set for expression");
+ free_payload(payload);
+ YYERROR;
+ }
+ }
+ ;
+
+add_queue : T_ADD_QUEUE '(' qstring ',' qstring ',' qstring ',' qstring ')' ';'
+ {
+ struct queue *queue = queue_init();
+
+ if (keyword_to_value($3, reg_map, &queue->sreg_qnum) < 0) {
+ error("sreg_qnum");
+ free_queue(queue);
+ YYERROR;
+ }
+ if (!parse_uint16($5, &queue->queue_num)) {
+ error("queue_num");
+ free_queue(queue);
+ YYERROR;
+ }
+ if (!parse_uint16($7, &queue->queue_total)) {
+ error("queue_total");
+ free_queue(queue);
+ YYERROR;
+ }
+ if (!parse_uint16($9, &queue->flags)) {
+ error("flags");
+ free_queue(queue);
+ YYERROR;
+ }
+
+ if (add_expr(&batch, &queue->expr) < 0) {
+ error("missing context rule or set for expression");
+ free_queue(queue);
+ YYERROR;
+ }
+ }
+ ;
+
+add_quota : T_ADD_QUOTA '(' qstring ',' qstring ',' qstring ')' ';'
+ {
+ struct quota *quota = quota_init();
+
+ if (!parse_uint64($3, &quota->bytes)) {
+ error("bytes");
+ free_quota(quota);
+ YYERROR;
+ }
+ if (!parse_uint64($5, &quota->consumed)) {
+ error("consumed");
+ free_quota(quota);
+ YYERROR;
+ }
+ if (!parse_uint32($7, &quota->flags)) {
+ error("flags");
+ free_quota(quota);
+ YYERROR;
+ }
+
+ if (add_expr(&batch, &quota->expr) < 0) {
+ error("missing context rule or set for expression");
+ free_quota(quota);
+ YYERROR;
+ }
+ }
+ ;
+
+add_range : T_ADD_RANGE '(' qstring ',' qstring ',' qstring ',' qstring ')' ';'
+ {
+ struct range *range = range_init();
+
+ if (keyword_to_value($3, reg_map, &range->sreg) < 0) {
+ error("sreg");
+ free_range(range);
+ YYERROR;
+ }
+ if (!parse_uint32($5, &range->op)) {
+ error("op");
+ free_range(range);
+ YYERROR;
+ }
+ if (hex_parse($7, &range->data_from, &range->data_from_len, 16 + NFT_EXTRA) < 0) {
+ error("data_from");
+ free_range(range);
+ YYERROR;
+ }
+ if (hex_parse($9, &range->data_to, &range->data_to_len, 16 + NFT_EXTRA) < 0) {
+ error("data_to");
+ free_range(range);
+ YYERROR;
+ }
+
+ if (add_expr(&batch, &range->expr) < 0) {
+ error("missing context rule or set for expression");
+ free_range(range);
+ YYERROR;
+ }
+ }
+ ;
+
+add_redir : T_ADD_REDIR '(' qstring ',' qstring ',' qstring ')' ';'
+ {
+ struct redir *redir = redir_init();
+
+ if (!parse_uint32($3, &redir->flags)) {
+ error("flags");
+ free_redir(redir);
+ YYERROR;
+ }
+ if (keyword_to_value($5, reg_map, &redir->sreg_proto_min) < 0) {
+ error("sreg_proto_min");
+ free_redir(redir);
+ YYERROR;
+ }
+ if (keyword_to_value($7, reg_map, &redir->sreg_proto_max) < 0) {
+ error("sreg_proto_max");
+ free_redir(redir);
+ YYERROR;
+ }
+
+ if (add_expr(&batch, &redir->expr) < 0) {
+ error("missing context rule or set for expression");
+ free_redir(redir);
+ YYERROR;
+ }
+ }
+ ;
+
+add_reject : T_ADD_REJECT '(' qstring ',' qstring')' ';'
+ {
+ struct reject *reject = reject_init();
+
+ if (!parse_uint32($3, &reject->type)) {
+ error("type");
+ free_reject(reject);
+ YYERROR;
+ }
+ if (!parse_uint8($5, &reject->icmp_code)) {
+ error("icmp_code");
+ free_reject(reject);
+ YYERROR;
+ }
+
+ if (add_expr(&batch, &reject->expr) < 0) {
+ error("missing context rule or set for expression");
+ free_reject(reject);
+ YYERROR;
+ }
+ }
+ ;
+
+add_rt : T_ADD_RT '(' qstring ',' qstring ')' ';'
+ {
+ struct rt *rt = rt_init();
+
+ if (keyword_to_value($3, reg_map, &rt->dreg) < 0) {
+ error("dreg");
+ free_rt(rt);
+ YYERROR;
+ }
+ if (!parse_uint32($5, &rt->key)) {
+ error("key");
+ free_rt(rt);
+ YYERROR;
+ }
+
+ if (add_expr(&batch, &rt->expr) < 0) {
+ error("missing context rule or set for expression");
+ free_rt(rt);
+ YYERROR;
+ }
+ }
+ ;
+
+add_socket : T_ADD_SOCKET '(' qstring ',' qstring ',' qstring ')' ';'
+ {
+ struct socket *socket = socket_init();
+
+ if (keyword_to_value($3, reg_map, &socket->dreg) < 0) {
+ error("dreg");
+ free_socket(socket);
+ YYERROR;
+ }
+ if (!parse_uint32($5, &socket->key)) {
+ error("key");
+ free_socket(socket);
+ YYERROR;
+ }
+ if (!parse_uint32($7, &socket->level)) {
+ error("level");
+ free_socket(socket);
+ YYERROR;
+ }
+
+ if (add_expr(&batch, &socket->expr) < 0) {
+ error("missing context rule or set for expression");
+ free_socket(socket);
+ YYERROR;
+ }
+ }
+ ;
+
+add_synproxy : T_ADD_SYNPROXY '(' qstring ',' qstring ',' qstring ')' ';'
+ {
+ struct synproxy *synproxy = synproxy_init();
+
+ if (!parse_uint16($3, &synproxy->mss)) {
+ error("mss");
+ free_synproxy(synproxy);
+ YYERROR;
+ }
+ if (!parse_uint8($5, &synproxy->wscale)) {
+ error("wscale");
+ free_synproxy(synproxy);
+ YYERROR;
+ }
+ if (!parse_uint32($7, &synproxy->flags)) {
+ error("flags");
+ free_synproxy(synproxy);
+ YYERROR;
+ }
+
+ if (add_expr(&batch, &synproxy->expr) < 0) {
+ error("missing context rule or set for expression");
+ free_synproxy(synproxy);
+ YYERROR;
+ }
+ }
+ ;
+
+add_target : T_ADD_TARGET '(' qstring ',' qstring ',' qstring ',' qstring ',' qstring ')' ';'
+ {
+ struct target *target = target_init();
+
+ target->name = $3;
+
+ if (!parse_uint32($5, &target->rev)) {
+ error("rev");
+ free_target(target);
+ YYERROR;
+ }
+ if (hex_parse($7, &target->data, &target->data_len, 65536) < 0) {
+ error("data");
+ free_target(target);
+ YYERROR;
+ }
+ if (!parse_uint32($9, &target->l4proto)) {
+ error("l4proto");
+ free_target(target);
+ YYERROR;
+ }
+ if (!parse_uint32($11, &target->flags)) {
+ error("flags");
+ free_target(target);
+ YYERROR;
+ }
+
+ /* special compat handling. */
+ if (batch.ctx.rule) {
+ batch.ctx.rule->compat.l4proto = target->l4proto;
+ batch.ctx.rule->compat.flags = target->flags;
+ }
+
+ if (add_expr(&batch, &target->expr) < 0) {
+ error("missing context rule or set for expression");
+ free_target(target);
+ YYERROR;
+ }
+ }
+ ;
+
+add_tproxy : T_ADD_TPROXY '(' STRING ',' qstring ',' qstring ')' ';'
+ {
+ struct tproxy *tproxy = tproxy_init();
+
+ if (keyword_to_value($3, nfproto_map, &tproxy->nfproto) <= 0) {
+ error("nfproto");
+ free_tproxy(tproxy);
+ YYERROR;
+ }
+ if (keyword_to_value($5, reg_map, &tproxy->sreg_addr) < 0) {
+ error("sreg_addr");
+ free_tproxy(tproxy);
+ YYERROR;
+ }
+ if (keyword_to_value($7, reg_map, &tproxy->sreg_port) < 0) {
+ error("sreg_port");
+ free_tproxy(tproxy);
+ YYERROR;
+ }
+
+ if (add_expr(&batch, &tproxy->expr) < 0) {
+ error("missing context rule or set for expression");
+ free_tproxy(tproxy);
+ YYERROR;
+ }
+ }
+ ;
+
+add_tunnel : T_ADD_TUNNEL '(' qstring ',' qstring ')' ';'
+ {
+ struct tunnel *tunnel = tunnel_init();
+
+ if (keyword_to_value($3, reg_map, &tunnel->dreg) < 0) {
+ error("dreg");
+ free_tunnel(tunnel);
+ YYERROR;
+ }
+ if (!parse_uint32($5, &tunnel->key)) {
+ error("key");
+ free_tunnel(tunnel);
+ YYERROR;
+ }
+//XXX if (!parse_uint32($7, &tunnel->mode)) {
+// free_tunnel(tunnel);
+// YYERROR;
+// }
+
+ if (add_expr(&batch, &tunnel->expr) < 0) {
+ error("missing context rule or set for expression");
+ free_tunnel(tunnel);
+ YYERROR;
+ }
+ }
+ ;
+
+add_xfrm : T_ADD_XFRM '(' qstring ',' qstring ',' qstring ',' qstring ')' ';'
+ {
+ struct xfrm *xfrm = xfrm_init();
+
+ if (keyword_to_value($3, reg_map, &xfrm->dreg) < 0) {
+ error("dreg");
+ free_xfrm(xfrm);
+ YYERROR;
+ }
+ if (!parse_uint32($5, &xfrm->key)) {
+ error("key");
+ free_xfrm(xfrm);
+ YYERROR;
+ }
+ if (!parse_uint32($7, &xfrm->spnum)) {
+ error("spnum");
+ free_xfrm(xfrm);
+ YYERROR;
+ }
+ if (!parse_uint8($9, &xfrm->dir)) {
+ error("dir");
+ free_xfrm(xfrm);
+ YYERROR;
+ }
+
+ if (add_expr(&batch, &xfrm->expr) < 0) {
+ error("missing context rule or set for expression");
+ free_xfrm(xfrm);
+ YYERROR;
+ }
+ }
+ ;
+
+add_obj_counter : T_ADD_OBJ_COUNTER '(' qstring ',' qstring ',' qstring ',' qstring ',' qstring ')' ';'
+ {
+ struct obj *obj = obj_init();
+ struct counter *counter;
+
+ obj->name = $3;
+
+ if (!parse_uint64($5, &obj->handle)) {
+ error("handle");
+ free_obj(obj);
+ YYERROR;
+ }
+
+ obj->userdata = $7;
+
+ *obj->type = NFT_OBJECT_COUNTER;
+
+ counter = counter_init();
+ obj->u.counter = counter;
+
+ if (!parse_uint64($9, &counter->bytes)) {
+ error("bytes");
+ free_obj(obj);
+ YYERROR;
+ }
+
+ if (!parse_uint64($11, &counter->pkts)) {
+ error("pkts");
+ free_obj(obj);
+ YYERROR;
+ }
+
+ add_obj(&batch, obj);
+ }
+ ;
+
+add_obj_quota : T_ADD_OBJ_QUOTA '(' qstring ',' qstring ',' qstring ',' qstring ',' qstring ',' qstring ')' ';'
+ {
+ struct obj *obj = obj_init();
+ struct quota *quota;
+
+ obj->name = $3;
+
+ if (!parse_uint64($5, &obj->handle)) {
+ error("handle");
+ free_obj(obj);
+ YYERROR;
+ }
+
+ obj->userdata = $7;
+
+ *obj->type = NFT_OBJECT_QUOTA;
+
+ quota = quota_init();
+ obj->u.quota = quota;
+
+ if (!parse_uint64($9, &quota->bytes)) {
+ error("bytes");
+ free_obj(obj);
+ YYERROR;
+ }
+
+ if (!parse_uint64($11, &quota->consumed)) {
+ error("consumed");
+ free_obj(obj);
+ YYERROR;
+ }
+
+ if (!parse_uint32($13, &quota->flags)) {
+ error("flags");
+ free_obj(obj);
+ YYERROR;
+ }
+
+ add_obj(&batch, obj);
+ }
+ ;
+
+add_obj_connlimit: T_ADD_OBJ_CONNLIMIT '(' qstring ',' qstring ',' qstring ',' qstring ',' qstring ')' ';'
+ {
+ struct obj *obj = obj_init();
+ struct connlimit *connlimit;
+
+ obj->name = $3;
+
+ if (!parse_uint64($5, &obj->handle)) {
+ error("handle");
+ free_obj(obj);
+ YYERROR;
+ }
+
+ obj->userdata = $7;
+
+ *obj->type = NFT_OBJECT_CONNLIMIT;
+
+ connlimit = connlimit_init();
+ obj->u.connlimit = connlimit;
+
+ if (!parse_uint32($9, &connlimit->count)) {
+ error("count");
+ free_obj(obj);
+ YYERROR;
+ }
+
+ if (!parse_uint32($11, &connlimit->flags)) {
+ error("flags");
+ free_obj(obj);
+ YYERROR;
+ }
+
+ add_obj(&batch, obj);
+ }
+ ;
+
+add_obj_secmark : T_ADD_OBJ_SECMARK '(' qstring ',' qstring ',' qstring ',' qstring ')' ';'
+ {
+ struct obj *obj = obj_init();
+ struct secmark *secmark;
+
+ obj->name = $3;
+
+ if (!parse_uint64($5, &obj->handle)) {
+ error("handle");
+ free_obj(obj);
+ YYERROR;
+ }
+
+ obj->userdata = $7;
+
+ *obj->type = NFT_OBJECT_SECMARK;
+
+ secmark = secmark_init();
+ obj->u.secmark = secmark;
+ secmark->ctx = $9;
+
+ add_obj(&batch, obj);
+ }
+ ;
+
+add_obj_synproxy: T_ADD_OBJ_SYNPROXY '(' qstring ',' qstring ',' qstring ',' qstring ',' qstring ',' qstring ')' ';'
+ {
+ struct obj *obj = obj_init();
+ struct synproxy *synproxy;
+
+ obj->name = $3;
+
+ if (!parse_uint64($5, &obj->handle)) {
+ error("handle");
+ free_obj(obj);
+ YYERROR;
+ }
+
+ obj->userdata = $7;
+
+ *obj->type = NFT_OBJECT_SYNPROXY;
+
+ synproxy = synproxy_init();
+ obj->u.synproxy = synproxy;
+
+ if (!parse_uint16($9, &synproxy->mss)) {
+ error("mss");
+ free_obj(obj);
+ YYERROR;
+ }
+ if (!parse_uint8($11, &synproxy->wscale)) {
+ error("wscale");
+ free_obj(obj);
+ YYERROR;
+ }
+ if (!parse_uint32($13, &synproxy->flags)) {
+ error("flags");
+ free_obj(obj);
+ YYERROR;
+ }
+
+ add_obj(&batch, obj);
+ }
+ ;
+
+del_obj_unknown : T_DEL_OBJ_UNKNOWN '(' qstring ',' qstring ',' qstring ',' qstring ')' ';'
+ {
+ struct obj *obj = obj_init();
+
+ obj->name = $3;
+
+ if (!parse_uint64($5, &obj->handle)) {
+ error("handle");
+ free_obj(obj);
+ YYERROR;
+ }
+
+ obj->userdata = $7;
+
+ if (!parse_uint32($9, &obj->type)) {
+ error("type");
+ free_obj(obj);
+ YYERROR;
+ }
+
+ del_obj(&batch, obj);
+ }
+ ;
+
+u32_array_items : QSTRING
+ {
+ $$ = array_u32_alloc();
+ if (!array_u32_add($$, $1)) {
+ error("array");
+ YYERROR;
+ }
+ }
+ | u32_array_items ',' QSTRING
+ {
+ if (!array_u32_add($1, $3)) {
+ error("array");
+ YYERROR;
+ }
+ $$ = $1;
+ }
+ ;
+
+u32_array : '{' u32_array_items '}' { $$ = $2; }
+ | NULLSTR { $$ = NULL; }
+ ;
+
+
+add_obj_ct_timeout : T_ADD_OBJ_CT_TIMEOUT '(' qstring ',' qstring ',' qstring ',' qstring ',' qstring ',' u32_array ')' ';'
+ {
+ struct obj *obj = obj_init();
+ struct ct_timeout *ct_timeout;
+
+ obj->name = $3;
+
+ if (!parse_uint64($5, &obj->handle)) {
+ error("handle");
+ free_obj(obj);
+ YYERROR;
+ }
+
+ obj->userdata = $7;
+
+ *obj->type = NFT_OBJECT_CT_TIMEOUT;
+
+ ct_timeout = ct_timeout_init();
+ obj->u.ct_timeout = ct_timeout;
+
+ if (!parse_uint16($9, &ct_timeout->l3proto)) {
+ error("l3proto");
+ free_obj(obj);
+ YYERROR;
+ }
+
+ if (!parse_uint8($11, &ct_timeout->l4proto)) {
+ error("l4proto");
+ free_obj(obj);
+ YYERROR;
+ }
+
+ ct_timeout->timeout_array = $13;
+
+ batch.lineno = yylineno;
+ add_obj(&batch, obj);
+ }
+ ;
+
+add_obj_ct_helper : T_ADD_OBJ_CT_HELPER '(' qstring ',' qstring ',' qstring ',' qstring ',' qstring ',' qstring ')' ';'
+ {
+ struct ct_helper *ct_helper;
+ struct obj *obj = obj_init();
+
+ obj->name = $3;
+
+ if (!parse_uint64($5, &obj->handle)) {
+ error("handle");
+ free_obj(obj);
+ YYERROR;
+ }
+
+ obj->userdata = $7;
+
+ *obj->type = NFT_OBJECT_CT_HELPER;
+
+ ct_helper = ct_helper_init();
+ obj->u.ct_helper = ct_helper;
+
+ ct_helper->name = $9;
+
+ if (!parse_uint16($11, &ct_helper->l3proto)) {
+ error("l3proto");
+ free_obj(obj);
+ YYERROR;
+ }
+
+ if (!parse_uint8($13, &ct_helper->l4proto)) {
+ error("l4proto");
+ free_obj(obj);
+ YYERROR;
+ }
+
+ batch.lineno = yylineno;
+ add_obj(&batch, obj);
+ }
+ ;
+
+add_obj_ct_expect : T_ADD_OBJ_CT_EXPECT '(' qstring ',' qstring ',' qstring ',' qstring ',' qstring ',' qstring ',' qstring ',' qstring ')' ';'
+ {
+ struct obj *obj = obj_init();
+ struct ct_expect *ct_expect;
+
+ obj->name = $3;
+
+ if (!parse_uint64($5, &obj->handle)) {
+ error("handle");
+ free_obj(obj);
+ YYERROR;
+ }
+
+ obj->userdata = $7;
+
+ *obj->type = NFT_OBJECT_CT_EXPECT;
+
+ ct_expect = ct_expect_init();
+ obj->u.ct_expect = ct_expect;
+
+ if (!parse_uint16($9, &ct_expect->l3proto)) {
+ error("l3proto");
+ free_obj(obj);
+ YYERROR;
+ }
+
+ if (!parse_uint8($11, &ct_expect->l4proto)) {
+ error("l4proto");
+ free_obj(obj);
+ YYERROR;
+ }
+
+ if (!parse_uint16($13, &ct_expect->dport)) {
+ error("dport");
+ free_obj(obj);
+ YYERROR;
+ }
+
+ if (!parse_uint32($15, &ct_expect->timeout)) {
+ error("timeout");
+ free_obj(obj);
+ YYERROR;
+ }
+
+ if (!parse_uint8($17, &ct_expect->size)) {
+ error("size");
+ free_obj(obj);
+ YYERROR;
+ }
+
+ batch.lineno = yylineno;
+ add_obj(&batch, obj);
+ }
+ ;
+
+add_obj_limit : T_ADD_OBJ_LIMIT '(' qstring ',' qstring ',' qstring ',' qstring ',' qstring ',' qstring ',' qstring ',' qstring ')' ';'
+ {
+ struct obj *obj = obj_init();
+ struct limit *limit;
+
+ obj->name = $3;
+
+ if (!parse_uint64($5, &obj->handle)) {
+ error("handle");
+ YYERROR;
+ }
+
+ obj->userdata = $7;
+
+ *obj->type = NFT_OBJECT_LIMIT;
+
+ limit = limit_init();
+ obj->u.limit = limit;
+
+ if (!parse_uint64($9, &limit->rate)) {
+ error("rate");
+ free_obj(obj);
+ YYERROR;
+ }
+ if (!parse_uint64($11, &limit->unit)) {
+ error("unit");
+ free_obj(obj);
+ YYERROR;
+ }
+ if (!parse_uint32($13, &limit->burst)) {
+ error("burst");
+ free_obj(obj);
+ YYERROR;
+ }
+ if (!parse_uint32($15, &limit->type)) {
+ error("type");
+ free_obj(obj);
+ YYERROR;
+ }
+ if (!parse_uint32($17, &limit->flags)) {
+ error("flags");
+ free_obj(obj);
+ YYERROR;
+ }
+
+ batch.lineno = yylineno;
+ add_obj(&batch, obj);
+ }
+ ;
+
+add_obj_tunnel : T_ADD_OBJ_TUNNEL '(' qstring ',' qstring ',' qstring ',' qstring ',' qstring ',' qstring ',' qstring ',' qstring ',' qstring ',' qstring ',' qstring ',' qstring ',' qstring ',' qstring ')' ';'
+ {
+ struct obj *obj = obj_init();
+ struct tunnel_tmpl *tunnel;
+ int len;
+
+ obj->name = $3;
+
+ if (!parse_uint64($5, &obj->handle)) {
+ error("handle");
+ free_obj(obj);
+ YYERROR;
+ }
+
+ obj->userdata = $7;
+
+ *obj->type = NFT_OBJECT_TUNNEL;
+
+ tunnel = tunnel_tmpl_init();
+ obj->u.tun = tunnel;
+
+ if (!parse_uint32($9, &tunnel->id)) {
+ error("flags");
+ free_obj(obj);
+ YYERROR;
+ }
+ if (!parse_uint32($11, &tunnel->src_v4)) {
+ error("src_v4");
+ free_obj(obj);
+ YYERROR;
+ }
+ if (!parse_uint32($13, &tunnel->dst_v4)) {
+ error("dst_v4");
+ free_obj(obj);
+ YYERROR;
+ }
+ len = 16;
+ if (hex_parse($15, &tunnel->src_v6, &len, 16) < 0 || len != 16) {
+ error("src_v6");
+ free_obj(obj);
+ YYERROR;
+ }
+ len = 16;
+ if (hex_parse($17, &tunnel->dst_v6, &len, 16) < 0 || len != 16) {
+ error("dst_v6");
+ free_obj(obj);
+ YYERROR;
+ }
+ if (!parse_uint32($19, &tunnel->flowlabel)) {
+ error("flowlabel");
+ free_obj(obj);
+ YYERROR;
+ }
+ if (!parse_uint16($21, &tunnel->sport)) {
+ error("sport");
+ free_obj(obj);
+ YYERROR;
+ }
+ if (!parse_uint16($23, &tunnel->dport)) {
+ error("dport");
+ free_obj(obj);
+ YYERROR;
+ }
+ if (!parse_uint32($25, &tunnel->flags)) {
+ error("flags");
+ free_obj(obj);
+ YYERROR;
+ }
+ if (!parse_uint8($27, &tunnel->tos)) {
+ error("tos");
+ free_obj(obj);
+ YYERROR;
+ }
+ if (!parse_uint8($29, &tunnel->ttl)) {
+ error("ttl");
+ free_obj(obj);
+ YYERROR;
+ }
+
+ batch.lineno = yylineno;
+ add_obj(&batch, obj);
+ }
+ ;
+
+
+add_obj_unknown : T_ADD_OBJ_UNKNOWN '(' qstring ',' qstring ',' qstring ',' qstring ')' ';'
+ {
+ struct obj *obj = obj_init();
+
+ obj->name = $3;
+
+ if (!parse_uint64($5, &obj->handle)) {
+ error("handle");
+ free_obj(obj);
+ YYERROR;
+ }
+
+ obj->userdata = $7;
+
+ if (!parse_uint32($9, &obj->type)) {
+ error("type");
+ free_obj(obj);
+ YYERROR;
+ }
+
+ add_obj(&batch, obj);
+ }
+ ;
+
+u8_array_items : QSTRING
+ {
+ $$ = array_u8_alloc();
+ if (!array_u8_add($$, $1)) {
+ error("array");
+ YYERROR;
+ }
+ }
+ | u8_array_items ',' QSTRING
+ {
+ if (!array_u8_add($1, $3)) {
+ error("array");
+ YYERROR;
+ }
+ $$ = $1;
+ }
+ ;
+
+u8_array : '{' u8_array_items '}' { $$ = $2; }
+ | NULLSTR { $$ = NULL; }
+ ;
+
+ /* name, flags, key_len, key_type, data_len, data_type, policy, size, u8_array, set_id, timeout, gc_interval,obj_type, handle, userdata */
+add_set : T_ADD_SET '(' qstring ',' qstring ',' qstring ',' qstring ',' qstring ',' qstring ',' qstring ',' qstring ',' u8_array ',' qstring ',' qstring ',' qstring ',' qstring ',' qstring ',' qstring ')' ';'
+ {
+ struct set *set = set_init();
+
+ set->name = $3;
+ if (!parse_uint32($5, &set->flags)) {
+ error("flags");
+ free_set(set);
+ YYERROR;
+ }
+ if (!parse_uint32($7, &set->key_len)) {
+ error("key_len");
+ free_set(set);
+ YYERROR;
+ }
+ if (!parse_uint32($9, &set->key_type)) {
+ error("key_type");
+ free_set(set);
+ YYERROR;
+ }
+ if (!parse_uint32($11, &set->data_len)) {
+ error("data_len");
+ free_set(set);
+ YYERROR;
+ }
+ if (keyword_to_value($13, datatype_map, &set->data_type) < 0) {
+ error("data_type");
+ free_set(set);
+ YYERROR;
+ }
+ if (!parse_uint32($15, &set->policy)) {
+ error("policy");
+ free_set(set);
+ YYERROR;
+ }
+ if (!parse_uint32($17, &set->size)) {
+ error("size");
+ free_set(set);
+ YYERROR;
+ }
+
+ set->field_array = $19;
+
+ if (!parse_uint32($21, &set->set_id)) {
+ error("set_id");
+ free_set(set);
+ YYERROR;
+ }
+ if (!parse_uint64($23, &set->timeout)) {
+ error("timeout");
+ free_set(set);
+ YYERROR;
+ }
+ if (!parse_uint32($25, &set->gc_interval)) {
+ error("gc_interval");
+ free_set(set);
+ YYERROR;
+ }
+ if (!parse_uint32($27, &set->obj_type)) {
+ error("obj_type");
+ free_set(set);
+ YYERROR;
+ }
+ if (!parse_uint64($29, &set->handle)) {
+ error("handle");
+ free_set(set);
+ YYERROR;
+ }
+
+ set->userdata = $31;
+
+ batch.lineno = yylineno;
+ add_set(&batch, set);
+ }
+ ;
+
+set_set : T_SET_SET '(' qstring ',' qstring ')' ';'
+ {
+ struct set *set = set_init();
+
+ set->name = $3;
+
+ if (!parse_uint32($5, &set->set_id)) {
+ error("set_id");
+ YYERROR;
+ }
+
+ set_set(&batch, set);
+ }
+ ;
+
+del_set : T_DEL_SET '(' qstring ',' qstring ')' ';'
+ {
+ struct set *set = set_init();
+
+ set->name = $3;
+
+ if (!parse_uint64($5, &set->handle)) {
+ error("handle");
+ YYERROR;
+ }
+
+ batch.lineno = yylineno;
+ del_set(&batch, set);
+ }
+ ;
+
+flush_set : T_FLUSH_SET '(' qstring ',' qstring ')' ';'
+ {
+ struct set *set = set_init();
+
+ set->name = $3;
+
+ if (!parse_uint64($5, &set->handle)) {
+ error("handle");
+ YYERROR;
+ }
+
+ batch.lineno = yylineno;
+ flush_set(&batch, set);
+ }
+ ;
+
+ /* key, key_end, flags, verdict, chain, data, timeout, expiration, userdata */
+add_elem : T_ADD_ELEM '(' qstring ',' qstring ',' qstring ',' qstring ',' qstring ',' qstring ',' qstring ',' qstring ',' qstring ',' qstring ')' ';'
+ {
+ struct elem *elem = elem_init();
+
+ if (hex_parse($3, &elem->key, &elem->key_len, 64 + NFT_EXTRA) < 0) {
+ error("key");
+ free_elem(elem);
+ YYERROR;
+ }
+ if (hex_parse($5, &elem->key_end, &elem->key_end_len, 64 + NFT_EXTRA) < 0) {
+ error("key_end");
+ free_elem(elem);
+ YYERROR;
+ }
+ if (!parse_uint32($7, &elem->flags)) {
+ error("flags");
+ free_elem(elem);
+ YYERROR;
+ }
+ if (keyword_to_value($9, verdict_map, &elem->verdict) < 0) {
+ error("verdict");
+ free_elem(elem);
+ YYERROR;
+ }
+
+ elem->chain = $11;
+
+ if (hex_parse($13, &elem->data, &elem->data_len, 64 + NFT_EXTRA) < 0) {
+ error("data");
+ free_elem(elem);
+ YYERROR;
+ }
+ if (!parse_uint64($15, &elem->timeout)) {
+ error("timeout");
+ free_elem(elem);
+ YYERROR;
+ }
+ if (!parse_uint64($17, &elem->expiration)) {
+ error("expiration");
+ free_elem(elem);
+ YYERROR;
+ }
+
+ elem->objname = $19;
+
+ elem->userdata = $21;
+
+ batch.lineno = yylineno;
+ add_elem(&batch, elem);
+ }
+ ;
+
+del_elem : T_DEL_ELEM '(' qstring ',' qstring ',' qstring ')' ';'
+ {
+ struct elem *elem = elem_init();
+
+ if (hex_parse($3, &elem->key, &elem->key_len, 64 + NFT_EXTRA) < 0) {
+ error("key_len");
+ YYERROR;
+ }
+ if (hex_parse($5, &elem->key_end, &elem->key_end_len, 64 + NFT_EXTRA) < 0) {
+ error("key_len");
+ YYERROR;
+ }
+ if (!parse_uint32($7, &elem->flags)) {
+ error("flags");
+ YYERROR;
+ }
+
+ batch.lineno = yylineno;
+ del_elem(&batch, elem);
+ }
+ ;
+
+add_flowtable : T_ADD_FLOWTABLE '(' qstring ',' qstring ',' qstring ',' string_array ',' qstring ')' ';'
+ {
+ struct flowtable *flowtable = flowtable_init();
+
+ flowtable->name = $3;
+
+ if (!parse_uint32($5, &flowtable->hooknum)) {
+ error("hooknum");
+ free_flowtable(flowtable);
+ YYERROR;
+ }
+ if (!parse_uint32($7, &flowtable->prio)) {
+ error("priority");
+ free_flowtable(flowtable);
+ YYERROR;
+ }
+
+ flowtable->dev_array = $9;
+
+ if (!parse_uint64($11, &flowtable->handle)) {
+ error("handle");
+ free_flowtable(flowtable);
+ YYERROR;
+ }
+
+ batch.lineno = yylineno;
+ add_flowtable(&batch, flowtable);
+ }
+ ;
+
+del_flowtable : T_DEL_FLOWTABLE '(' qstring ',' qstring ',' qstring ',' string_array ',' qstring ')' ';'
+ {
+ struct flowtable *flowtable = flowtable_init();
+
+ flowtable->name = $3;
+
+ if (!parse_uint32($5, &flowtable->hooknum)) {
+ error("hooknum");
+ free_flowtable(flowtable);
+ YYERROR;
+ }
+ if (!parse_uint32($7, &flowtable->prio)) {
+ error("priority");
+ free_flowtable(flowtable);
+ YYERROR;
+ }
+
+ flowtable->dev_array = $9;
+
+ if (!parse_uint64($11, &flowtable->handle)) {
+ error("handle");
+ free_flowtable(flowtable);
+ YYERROR;
+ }
+
+ batch.lineno = yylineno;
+ del_flowtable(&batch, flowtable);
+ }
+ ;
+
+commit : T_COMMIT '(' ')' ';'
+ {
+ if (batch_commit(&batch) < 0)
+ test_fails = true;
+ }
+ ;
+
+abort : T_ABORT '(' ')' ';'
+ {
+ if (batch_abort(&batch) < 0)
+ test_fails = true;
+ }
+ ;
+
+%%
+
+static int yyerror(const char *msg)
+{
+ fprintf(stderr, "ERROR: parsing test file in line: %d, symbol '%s': %s\n",
+ yylineno, yytext, msg);
+ return -1;
+}
+
+/* -1 fatal, 0 success, 1 failure */
+static int run_one_test(FILE *fp)
+{
+ int ret = 0;
+
+ yylineno = 1;
+ memset(&batch, 0, sizeof(batch));
+ setup_batch(&batch);
+
+ yyrestart(fp);
+ if (yyparse() < 0) {
+ ret = 1;
+ fprintf(stderr, "error parsing test file in line: %d, invalid value\n",
+ yylineno);
+ goto err_close;
+ }
+
+ if (!batch_empty(&batch)) {
+ ret = 1;
+ fprintf(stderr, "no commit/abort in test\n");
+ goto err_close;
+ }
+
+ if (kernel_is_tainted()) {
+ ret = -1;
+ fprintf(stderr, "FATAL: taints kernel\n");
+ goto err_close;
+ }
+
+ /* walk over hooks to check for uaf */
+ list_hooks(batch.nl);
+
+ if (!noflush)
+ flush_ruleset(batch.nl);
+
+ if (kernel_is_tainted()) {
+ ret = -1;
+ fprintf(stderr, "FATAL: taints kernel\n");
+ goto err_close;
+ }
+
+err_close:
+ batch_stop(&batch);
+
+ return ret;
+}
+
+int run_test_interpreter(const char *filename, struct nft_test_ctx *ctx)
+{
+ bool expected_failure = false;
+ int ret = 0;
+ FILE *fp;
+
+ //only useful if flush_ruleset() is commented out
+ //to know previous incremental ruleset
+ //system("nft list ruleset > /tmp/ruleset");
+
+ test_fails = false;
+ _filename = filename;
+
+ fp = fopen(filename, "r");
+ if (!fp) {
+ fprintf(stderr, "cannot open file %s\n", filename);
+ return -1;
+ }
+
+ if (strstr(filename, "_err.t"))
+ expected_failure = true;
+
+ ret = run_one_test(fp);
+
+ fclose(fp);
+
+ if (ret < 0)
+ goto err_tainted_kernel;
+ else if (ret == 1)
+ test_fails = true;
+
+ if (expected_failure ^ test_fails) {
+ printf("[\x1b[31mFAILED\x1b[37m] %s\n", filename);
+ ret = -1;
+ } else {
+ printf("[\x1b[32mOK\x1b[37m] %s\n", filename);
+ }
+
+ return ret;
+
+err_tainted_kernel:
+ ctx->fatal = true;
+ printf("[\x1b[31mTAINTED\x1b[37m] %s\n", filename);
+
+ return -1;
+}
+
+static int dry_run(FILE *fp)
+{
+ return 0;
+}
+
+int fuzz_test_interpreter(const char *filename, struct nft_test_ctx *ctx)
+{
+ int ret = 0, i;
+ FILE *fp;
+
+ _filename = filename;
+
+ fp = fopen(filename, "r");
+ if (!fp) {
+ fprintf(stderr, "cannot open file %s\n", filename);
+ return -1;
+ }
+
+ printf("[\x1b[36mFUZZING\x1b[37m] %s (", filename);
+ for (i = 0; i < ctx->fuzz_ctx.num_types; i++) {
+ printf("%s", fuzz_type_to_name(ctx->fuzz_ctx.type[i]));
+ if (i + 1 != ctx->fuzz_ctx.num_types)
+ printf(",");
+ }
+ printf(")\n");
+
+ if (ctx->dryrun)
+ ret = fuzz(&ctx->fuzz_ctx, fp, dry_run);
+ else
+ ret = fuzz(&ctx->fuzz_ctx, fp, run_one_test);
+
+ fclose(fp);
+
+ if (ret < 0)
+ goto err_tainted_kernel;
+ else if (ret == 1)
+ test_fails = true;
+
+ printf("[\x1b[32mOK\x1b[37m] %s (%u in %u.%06us)\n",
+ filename,
+ ctx->fuzz_ctx.fuzz_steps,
+ ctx->fuzz_ctx.tv_delta.tv_sec, ctx->fuzz_ctx.tv_delta.tv_usec);
+
+ return ret;
+
+err_tainted_kernel:
+ ctx->fatal = true;
+ printf("[\x1b[31mTAINTED\x1b[37m] %s\n", filename);
+
+ return -1;
+}
+
+int run_test(const char *filename, struct nft_test_ctx *ctx)
+{
+ int ret;
+
+ if (ctx->fuzz)
+ ret = fuzz_test_interpreter(filename, ctx);
+ else
+ ret = run_test_interpreter(filename, ctx);
+
+ return ret;
+}