diff options
author | Pablo Neira Ayuso <pablo@netfilter.org> | 2025-06-04 23:12:30 +0200 |
---|---|---|
committer | Pablo Neira Ayuso <pablo@netfilter.org> | 2025-06-05 00:03:59 +0200 |
commit | 009cabe369b71cf5452286fd338eae382718789a (patch) | |
tree | 9f14eb626cee252ddf79d34cda9b98b57f5a9454 /src |
knft is a tool to improve test coverage for the low-level nftables
kernel API by providing a relatively simple way to define a transaction
batch with nftables objects without having to mingle with netlink.
A set of tests 612 test (.t) files are included.
knft also provides a rudimentary deterministic fuzzer (via -f option)
along with several fuzzing modes that mangle existing tests in different
ways to improve coverage for error unwinding paths:
deltable \
delbasechain \
delchain \
delrule | - delete object in this batch
delset /
delelem /
delobj /
flushset - flush set
dup - duplicate object
reverse-commit - turn commit into abort
reverse-abort - turn abort into commit
table-dormant - inject table dormant flag
table-wakeup - inject table wake-up flag
swap - swap objects
bogus - inject bogus object to make the transaction fail
To inspect how the selected fuzzing mode mangles the test, you can use
the -d option to enable debugging along with -c to run it in dry-run
mode, eg.
# src/./knft -c -f deltable -d tests/expr/meta/03-mark_ok.t
tests/expr/meta/03-mark_ok.t...
[FUZZING] tests/expr/meta/03-mark_ok.t (deltable)
>>>> fuzz_loop at index 0 in state=0
add_table(NFPROTO_IPV4, "test", NULL, NULL, NULL);
del_table(NFPROTO_IPV4, "test", NULL);
add_chain("test", NULL, NULL, NULL, NULL);
add_rule("test", "0x1", NULL, NULL, NULL);
meta(NULL, "NFT_REG32_15", "3");
cmp("NFT_REG32_15", "0", "ffffffff");
commit();
<<<< fuzz_loop backtrack STACK limit reached
==== still more tries at index 0 in state=0
add_table(NFPROTO_IPV4, "test", NULL, NULL, NULL);
add_chain("test", NULL, NULL, NULL, NULL);
del_table(NFPROTO_IPV4, "test", NULL);
add_rule("test", "0x1", NULL, NULL, NULL);
meta(NULL, "NFT_REG32_15", "3");
cmp("NFT_REG32_15", "0", "ffffffff");
commit();
<<<< fuzz_loop backtrack STACK limit reached
...
knft provides a few more options:
-e to display the error reported by the kernel.
-n to perform test runs without flushing the existing ruleset.
This tool requires libmnl to build and to parse the netlink messages
that are sent and received by the kernel.
This tool is released under the GPLv2 (or any later).
This project is funded through the NGI0 Entrust established by NLnet
(https://nlnet.nl) with support from the European Commission's Next
Generation Internet programme.
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile.am | 14 | ||||
-rw-r--r-- | src/fuzz.c | 541 | ||||
-rw-r--r-- | src/lexer.l | 133 | ||||
-rw-r--r-- | src/lib.c | 2404 | ||||
-rw-r--r-- | src/lib.h | 1899 | ||||
-rw-r--r-- | src/linux_list.h | 738 | ||||
-rw-r--r-- | src/main.c | 220 | ||||
-rw-r--r-- | src/parser_yy.y | 2990 |
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 = "a->_bytes; + quota->consumed = "a->_consumed; + quota->flags = "a->_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, "a->bytes)) { + error("bytes"); + free_quota(quota); + YYERROR; + } + if (!parse_uint64($5, "a->consumed)) { + error("consumed"); + free_quota(quota); + YYERROR; + } + if (!parse_uint32($7, "a->flags)) { + error("flags"); + free_quota(quota); + YYERROR; + } + + if (add_expr(&batch, "a->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, "a->bytes)) { + error("bytes"); + free_obj(obj); + YYERROR; + } + + if (!parse_uint64($11, "a->consumed)) { + error("consumed"); + free_obj(obj); + YYERROR; + } + + if (!parse_uint32($13, "a->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; +} |