diff options
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; +} |