diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile.am | 108 | ||||
-rw-r--r-- | src/cache.c | 1283 | ||||
-rw-r--r-- | src/cli.c | 133 | ||||
-rw-r--r-- | src/cmd.c | 488 | ||||
-rw-r--r-- | src/ct.c | 54 | ||||
-rw-r--r-- | src/datatype.c | 697 | ||||
-rw-r--r-- | src/dccpopt.c | 277 | ||||
-rw-r--r-- | src/erec.c | 104 | ||||
-rw-r--r-- | src/evaluate.c | 3557 | ||||
-rw-r--r-- | src/expression.c | 668 | ||||
-rw-r--r-- | src/exthdr.c | 173 | ||||
-rw-r--r-- | src/fib.c | 21 | ||||
-rw-r--r-- | src/gmputil.c | 27 | ||||
-rw-r--r-- | src/hash.c | 6 | ||||
-rw-r--r-- | src/iface.c | 75 | ||||
-rw-r--r-- | src/intervals.c | 840 | ||||
-rw-r--r-- | src/ipopt.c | 36 | ||||
-rw-r--r-- | src/json.c | 882 | ||||
-rw-r--r-- | src/libnftables.c | 460 | ||||
-rw-r--r-- | src/libnftables.map | 17 | ||||
-rw-r--r-- | src/main.c | 440 | ||||
-rw-r--r-- | src/mergesort.c | 82 | ||||
-rw-r--r-- | src/meta.c | 255 | ||||
-rw-r--r-- | src/mini-gmp.c | 4 | ||||
-rw-r--r-- | src/misspell.c | 14 | ||||
-rw-r--r-- | src/mnl.c | 1658 | ||||
-rw-r--r-- | src/monitor.c | 246 | ||||
-rw-r--r-- | src/netlink.c | 1598 | ||||
-rw-r--r-- | src/netlink_delinearize.c | 1683 | ||||
-rw-r--r-- | src/netlink_linearize.c | 682 | ||||
-rw-r--r-- | src/nfnl_osf.c | 4 | ||||
-rw-r--r-- | src/nftutils.c | 100 | ||||
-rw-r--r-- | src/nftutils.h | 20 | ||||
-rw-r--r-- | src/numgen.c | 6 | ||||
-rw-r--r-- | src/optimize.c | 1504 | ||||
-rw-r--r-- | src/osf.c | 11 | ||||
-rw-r--r-- | src/owner.c | 182 | ||||
-rw-r--r-- | src/parser_bison.y | 2694 | ||||
-rw-r--r-- | src/parser_json.c | 1563 | ||||
-rw-r--r-- | src/payload.c | 943 | ||||
-rw-r--r-- | src/preprocess.c | 168 | ||||
-rw-r--r-- | src/print.c | 7 | ||||
-rw-r--r-- | src/proto.c | 290 | ||||
-rw-r--r-- | src/rbtree.c | 388 | ||||
-rw-r--r-- | src/rt.c | 15 | ||||
-rw-r--r-- | src/rule.c | 1224 | ||||
-rw-r--r-- | src/scanner.l | 1027 | ||||
-rw-r--r-- | src/sctp_chunk.c | 262 | ||||
-rw-r--r-- | src/segtree.c | 1192 | ||||
-rw-r--r-- | src/socket.c | 30 | ||||
-rw-r--r-- | src/statement.c | 273 | ||||
-rw-r--r-- | src/tcpopt.c | 329 | ||||
-rw-r--r-- | src/trace.c | 462 | ||||
-rw-r--r-- | src/utils.c | 24 | ||||
-rw-r--r-- | src/xfrm.c | 9 | ||||
-rw-r--r-- | src/xt.c | 252 |
56 files changed, 22502 insertions, 7045 deletions
diff --git a/src/Makefile.am b/src/Makefile.am deleted file mode 100644 index 740c21f2..00000000 --- a/src/Makefile.am +++ /dev/null @@ -1,108 +0,0 @@ -include $(top_srcdir)/Make_global.am - -sbin_PROGRAMS = nft - -CLEANFILES = scanner.c parser_bison.c - -AM_CPPFLAGS = -I$(top_srcdir)/include -AM_CPPFLAGS += -DDEFAULT_INCLUDE_PATH="\"${sysconfdir}\"" \ - ${LIBMNL_CFLAGS} ${LIBNFTNL_CFLAGS} -if BUILD_DEBUG -AM_CPPFLAGS += -g -DDEBUG -endif -if BUILD_XTABLES -AM_CPPFLAGS += ${XTABLES_CFLAGS} -endif - -AM_CFLAGS = -Wall \ - -Wstrict-prototypes -Wmissing-prototypes -Wmissing-declarations \ - -Wdeclaration-after-statement -Wsign-compare -Winit-self \ - -Wformat-nonliteral -Wformat-security -Wmissing-format-attribute \ - -Wcast-align -Wundef -Wbad-function-cast \ - -Waggregate-return -Wunused -Wwrite-strings ${GCC_FVISIBILITY_HIDDEN} - - -AM_YFLAGS = -d -Wno-yacc - -BUILT_SOURCES = parser_bison.h - -lib_LTLIBRARIES = libnftables.la - -libnftables_la_SOURCES = \ - rule.c \ - statement.c \ - cache.c \ - datatype.c \ - expression.c \ - evaluate.c \ - proto.c \ - payload.c \ - exthdr.c \ - fib.c \ - hash.c \ - ipopt.c \ - meta.c \ - rt.c \ - numgen.c \ - ct.c \ - xfrm.c \ - netlink.c \ - netlink_linearize.c \ - netlink_delinearize.c \ - misspell.c \ - monitor.c \ - segtree.c \ - rbtree.c \ - gmputil.c \ - utils.c \ - erec.c \ - mnl.c \ - iface.c \ - mergesort.c \ - osf.c \ - nfnl_osf.c \ - tcpopt.c \ - socket.c \ - print.c \ - libnftables.c \ - libnftables.map - -# yacc and lex generate dirty code -noinst_LTLIBRARIES = libparser.la -libparser_la_SOURCES = parser_bison.y scanner.l -libparser_la_CFLAGS = ${AM_CFLAGS} \ - -Wno-missing-prototypes \ - -Wno-missing-declarations \ - -Wno-implicit-function-declaration \ - -Wno-nested-externs \ - -Wno-undef \ - -Wno-redundant-decls - -libnftables_la_LIBADD = ${LIBMNL_LIBS} ${LIBNFTNL_LIBS} libparser.la -libnftables_la_LDFLAGS = -version-info ${libnftables_LIBVERSION} \ - --version-script=$(srcdir)/libnftables.map - -if BUILD_MINIGMP -noinst_LTLIBRARIES += libminigmp.la -libminigmp_la_SOURCES = mini-gmp.c -libminigmp_la_CFLAGS = ${AM_CFLAGS} -Wno-sign-compare -libnftables_la_LIBADD += libminigmp.la -endif - -libnftables_la_SOURCES += xt.c -if BUILD_XTABLES -libnftables_la_LIBADD += ${XTABLES_LIBS} -endif - -nft_SOURCES = main.c - -if BUILD_CLI -nft_SOURCES += cli.c -endif - -if BUILD_JSON -libnftables_la_SOURCES += json.c parser_json.c -libnftables_la_LIBADD += ${JANSSON_LIBS} -endif - -nft_LDADD = libnftables.la diff --git a/src/cache.c b/src/cache.c index 05f0d68e..d58fb59f 100644 --- a/src/cache.c +++ b/src/cache.c @@ -6,42 +6,60 @@ * later) as published by the Free Software Foundation. */ +#include <nft.h> + #include <expression.h> #include <statement.h> #include <rule.h> #include <erec.h> #include <utils.h> #include <cache.h> +#include <netlink.h> +#include <mnl.h> +#include <libnftnl/chain.h> +#include <linux/netfilter.h> +#include <linux/netfilter/nf_tables.h> static unsigned int evaluate_cache_add(struct cmd *cmd, unsigned int flags) { + struct set *set; + switch (cmd->obj) { + case CMD_OBJ_TABLE: + if (!cmd->table) + break; + + flags |= NFT_CACHE_TABLE | + NFT_CACHE_SET; + list_for_each_entry(set, &cmd->table->sets, list) { + if (set->automerge) + flags |= NFT_CACHE_SETELEM_MAYBE; + } + break; case CMD_OBJ_CHAIN: case CMD_OBJ_SET: case CMD_OBJ_COUNTER: case CMD_OBJ_QUOTA: case CMD_OBJ_LIMIT: case CMD_OBJ_SECMARK: + case CMD_OBJ_CT_HELPER: + case CMD_OBJ_CT_TIMEOUT: + case CMD_OBJ_CT_EXPECT: + case CMD_OBJ_SYNPROXY: case CMD_OBJ_FLOWTABLE: flags |= NFT_CACHE_TABLE; break; - case CMD_OBJ_SETELEM: + case CMD_OBJ_ELEMENTS: flags |= NFT_CACHE_TABLE | - NFT_CACHE_CHAIN | NFT_CACHE_SET | - NFT_CACHE_OBJECT | - NFT_CACHE_SETELEM; + NFT_CACHE_SETELEM_MAYBE; break; case CMD_OBJ_RULE: flags |= NFT_CACHE_TABLE | - NFT_CACHE_CHAIN | - NFT_CACHE_SET | - NFT_CACHE_OBJECT | - NFT_CACHE_FLOWTABLE; + NFT_CACHE_SET; - if (cmd->handle.index.id || - cmd->handle.position.id) - flags |= NFT_CACHE_RULE | NFT_CACHE_UPDATE; + if (cmd->handle.index.id) + flags |= NFT_CACHE_FULL | NFT_CACHE_UPDATE; break; default: break; @@ -53,10 +71,12 @@ static unsigned int evaluate_cache_add(struct cmd *cmd, unsigned int flags) static unsigned int evaluate_cache_del(struct cmd *cmd, unsigned int flags) { switch (cmd->obj) { - case CMD_OBJ_SETELEM: - flags |= NFT_CACHE_SETELEM; + case CMD_OBJ_ELEMENTS: + flags |= NFT_CACHE_SET | + NFT_CACHE_SETELEM_MAYBE; break; default: + flags = NFT_CACHE_TABLE; break; } @@ -66,7 +86,7 @@ static unsigned int evaluate_cache_del(struct cmd *cmd, unsigned int flags) static unsigned int evaluate_cache_get(struct cmd *cmd, unsigned int flags) { switch (cmd->obj) { - case CMD_OBJ_SETELEM: + case CMD_OBJ_ELEMENTS: flags |= NFT_CACHE_TABLE | NFT_CACHE_SET | NFT_CACHE_SETELEM; @@ -78,13 +98,74 @@ static unsigned int evaluate_cache_get(struct cmd *cmd, unsigned int flags) return flags; } -static unsigned int evaluate_cache_flush(struct cmd *cmd, unsigned int flags) +struct nft_cache_filter *nft_cache_filter_init(void) +{ + struct nft_cache_filter *filter; + int i; + + filter = xzalloc(sizeof(struct nft_cache_filter)); + memset(&filter->list, 0, sizeof(filter->list)); + for (i = 0; i < NFT_CACHE_HSIZE; i++) + init_list_head(&filter->obj[i].head); + + return filter; +} + +void nft_cache_filter_fini(struct nft_cache_filter *filter) +{ + int i; + + for (i = 0; i < NFT_CACHE_HSIZE; i++) { + struct nft_filter_obj *obj, *next; + + list_for_each_entry_safe(obj, next, &filter->obj[i].head, list) + free(obj); + } + free(filter); +} + +static void cache_filter_add(struct nft_cache_filter *filter, + const struct cmd *cmd) +{ + struct nft_filter_obj *obj; + uint32_t hash; + + obj = xmalloc(sizeof(struct nft_filter_obj)); + obj->family = cmd->handle.family; + obj->table = cmd->handle.table.name; + obj->set = cmd->handle.set.name; + + hash = djb_hash(cmd->handle.set.name) % NFT_CACHE_HSIZE; + list_add_tail(&obj->list, &filter->obj[hash].head); +} + +static bool cache_filter_find(const struct nft_cache_filter *filter, + const struct handle *handle) +{ + struct nft_filter_obj *obj; + uint32_t hash; + + hash = djb_hash(handle->set.name) % NFT_CACHE_HSIZE; + + list_for_each_entry(obj, &filter->obj[hash].head, list) { + if (obj->family == handle->family && + !strcmp(obj->table, handle->table.name) && + !strcmp(obj->set, handle->set.name)) + return true; + } + + return false; +} + +static unsigned int evaluate_cache_flush(struct cmd *cmd, unsigned int flags, + struct nft_cache_filter *filter) { switch (cmd->obj) { case CMD_OBJ_SET: case CMD_OBJ_MAP: case CMD_OBJ_METER: flags |= NFT_CACHE_SET; + cache_filter_add(filter, cmd); break; case CMD_OBJ_RULESET: flags |= NFT_CACHE_FLUSHED; @@ -109,56 +190,1194 @@ static unsigned int evaluate_cache_rename(struct cmd *cmd, unsigned int flags) return flags; } -unsigned int cache_evaluate(struct nft_ctx *nft, struct list_head *cmds) +static void obj_filter_setup(const struct cmd *cmd, unsigned int *flags, + struct nft_cache_filter *filter, int type) +{ + assert(filter); + + if (cmd->handle.family) + filter->list.family = cmd->handle.family; + if (cmd->handle.table.name) + filter->list.table = cmd->handle.table.name; + if (cmd->handle.obj.name) + filter->list.obj = cmd->handle.obj.name; + + filter->list.obj_type = type; + *flags |= NFT_CACHE_TABLE | NFT_CACHE_OBJECT; +} + +static unsigned int evaluate_cache_list(struct nft_ctx *nft, struct cmd *cmd, + unsigned int flags, + struct nft_cache_filter *filter) +{ + switch (cmd->obj) { + case CMD_OBJ_TABLE: + filter->list.family = cmd->handle.family; + if (!cmd->handle.table.name) { + flags |= NFT_CACHE_TABLE; + break; + } else { + filter->list.table = cmd->handle.table.name; + } + flags |= NFT_CACHE_FULL; + break; + case CMD_OBJ_CHAIN: + if (cmd->handle.chain.name) { + filter->list.family = cmd->handle.family; + filter->list.table = cmd->handle.table.name; + filter->list.chain = cmd->handle.chain.name; + /* implicit terse listing to fetch content of anonymous + * sets only when chain name is specified. + */ + flags |= NFT_CACHE_TERSE; + } + flags |= NFT_CACHE_FULL; + break; + case CMD_OBJ_SET: + case CMD_OBJ_MAP: + if (cmd->handle.table.name && cmd->handle.set.name) { + filter->list.family = cmd->handle.family; + filter->list.table = cmd->handle.table.name; + filter->list.set = cmd->handle.set.name; + } + if (filter->list.table && filter->list.set) + flags |= NFT_CACHE_TABLE | NFT_CACHE_SET | NFT_CACHE_SETELEM; + else + flags |= NFT_CACHE_FULL; + break; + case CMD_OBJ_CHAINS: + flags |= NFT_CACHE_TABLE | NFT_CACHE_CHAIN; + break; + case CMD_OBJ_SETS: + case CMD_OBJ_MAPS: + flags |= NFT_CACHE_TABLE | NFT_CACHE_SET; + if (!nft_output_terse(&nft->output)) + flags |= NFT_CACHE_SETELEM; + break; + case CMD_OBJ_FLOWTABLE: + if (cmd->handle.table.name && + cmd->handle.flowtable.name) { + filter->list.family = cmd->handle.family; + filter->list.table = cmd->handle.table.name; + filter->list.ft = cmd->handle.flowtable.name; + } + /* fall through */ + case CMD_OBJ_FLOWTABLES: + flags |= NFT_CACHE_TABLE | NFT_CACHE_FLOWTABLE; + break; + case CMD_OBJ_COUNTER: + case CMD_OBJ_COUNTERS: + obj_filter_setup(cmd, &flags, filter, NFT_OBJECT_COUNTER); + break; + case CMD_OBJ_QUOTA: + case CMD_OBJ_QUOTAS: + obj_filter_setup(cmd, &flags, filter, NFT_OBJECT_QUOTA); + break; + case CMD_OBJ_CT_HELPER: + case CMD_OBJ_CT_HELPERS: + obj_filter_setup(cmd, &flags, filter, NFT_OBJECT_CT_HELPER); + break; + case CMD_OBJ_LIMIT: + case CMD_OBJ_LIMITS: + obj_filter_setup(cmd, &flags, filter, NFT_OBJECT_LIMIT); + break; + case CMD_OBJ_CT_TIMEOUT: + case CMD_OBJ_CT_TIMEOUTS: + obj_filter_setup(cmd, &flags, filter, NFT_OBJECT_CT_TIMEOUT); + break; + case CMD_OBJ_SECMARK: + case CMD_OBJ_SECMARKS: + obj_filter_setup(cmd, &flags, filter, NFT_OBJECT_SECMARK); + break; + case CMD_OBJ_CT_EXPECT: + obj_filter_setup(cmd, &flags, filter, NFT_OBJECT_CT_EXPECT); + break; + case CMD_OBJ_SYNPROXY: + case CMD_OBJ_SYNPROXYS: + obj_filter_setup(cmd, &flags, filter, NFT_OBJECT_SYNPROXY); + break; + case CMD_OBJ_RULESET: + default: + flags |= NFT_CACHE_FULL; + break; + } + flags |= NFT_CACHE_REFRESH; + + if (nft_output_terse(&nft->output)) + flags |= NFT_CACHE_TERSE; + + return flags; +} + +static unsigned int evaluate_cache_reset(struct cmd *cmd, unsigned int flags, + struct nft_cache_filter *filter) +{ + switch (cmd->obj) { + case CMD_OBJ_TABLE: + case CMD_OBJ_CHAIN: + case CMD_OBJ_RULES: + case CMD_OBJ_RULE: + if (cmd->handle.table.name) { + filter->list.family = cmd->handle.family; + filter->list.table = cmd->handle.table.name; + } + if (cmd->handle.chain.name) + filter->list.chain = cmd->handle.chain.name; + if (cmd->handle.family) + filter->list.family = cmd->handle.family; + if (cmd->handle.handle.id) + filter->list.rule_handle = cmd->handle.handle.id; + + filter->reset.rule = true; + flags |= NFT_CACHE_FULL; + break; + case CMD_OBJ_COUNTER: + case CMD_OBJ_COUNTERS: + obj_filter_setup(cmd, &flags, filter, NFT_OBJECT_COUNTER); + filter->reset.obj = true; + break; + case CMD_OBJ_QUOTA: + case CMD_OBJ_QUOTAS: + obj_filter_setup(cmd, &flags, filter, NFT_OBJECT_QUOTA); + filter->reset.obj = true; + break; + case CMD_OBJ_SET: + case CMD_OBJ_MAP: + if (cmd->handle.table.name && cmd->handle.set.name) { + filter->list.family = cmd->handle.family; + filter->list.table = cmd->handle.table.name; + filter->list.set = cmd->handle.set.name; + } + flags |= NFT_CACHE_SETELEM; + filter->reset.elem = true; + break; + default: + flags |= NFT_CACHE_FULL; + break; + } + flags |= NFT_CACHE_REFRESH; + + return flags; +} + +static int nft_handle_validate(const struct cmd *cmd, struct list_head *msgs) +{ + const struct handle *h = &cmd->handle; + const struct location *loc; + + switch (cmd->obj) { + case CMD_OBJ_TABLE: + if (h->table.name && + strlen(h->table.name) > NFT_NAME_MAXLEN) { + loc = &h->table.location; + goto err_name_too_long; + } + break; + case CMD_OBJ_RULE: + case CMD_OBJ_RULES: + case CMD_OBJ_CHAIN: + case CMD_OBJ_CHAINS: + if (h->table.name && + strlen(h->table.name) > NFT_NAME_MAXLEN) { + loc = &h->table.location; + goto err_name_too_long; + } + if (h->chain.name && + strlen(h->chain.name) > NFT_NAME_MAXLEN) { + loc = &h->chain.location; + goto err_name_too_long; + } + break; + case CMD_OBJ_ELEMENTS: + case CMD_OBJ_SET: + case CMD_OBJ_SETS: + case CMD_OBJ_MAP: + case CMD_OBJ_MAPS: + case CMD_OBJ_METER: + case CMD_OBJ_METERS: + if (h->table.name && + strlen(h->table.name) > NFT_NAME_MAXLEN) { + loc = &h->table.location; + goto err_name_too_long; + } + if (h->set.name && + strlen(h->set.name) > NFT_NAME_MAXLEN) { + loc = &h->set.location; + goto err_name_too_long; + } + break; + case CMD_OBJ_FLOWTABLE: + case CMD_OBJ_FLOWTABLES: + if (h->table.name && + strlen(h->table.name) > NFT_NAME_MAXLEN) { + loc = &h->table.location; + goto err_name_too_long; + } + if (h->flowtable.name && + strlen(h->flowtable.name) > NFT_NAME_MAXLEN) { + loc = &h->flowtable.location; + goto err_name_too_long; + } + break; + case CMD_OBJ_INVALID: + case CMD_OBJ_EXPR: + case CMD_OBJ_RULESET: + case CMD_OBJ_MARKUP: + case CMD_OBJ_MONITOR: + case CMD_OBJ_SETELEMS: + case CMD_OBJ_HOOKS: + break; + case CMD_OBJ_COUNTER: + case CMD_OBJ_COUNTERS: + case CMD_OBJ_QUOTA: + case CMD_OBJ_QUOTAS: + case CMD_OBJ_LIMIT: + case CMD_OBJ_LIMITS: + case CMD_OBJ_SECMARK: + case CMD_OBJ_SECMARKS: + case CMD_OBJ_SYNPROXY: + case CMD_OBJ_SYNPROXYS: + case CMD_OBJ_CT_HELPER: + case CMD_OBJ_CT_HELPERS: + case CMD_OBJ_CT_TIMEOUT: + case CMD_OBJ_CT_TIMEOUTS: + case CMD_OBJ_CT_EXPECT: + case CMD_OBJ_CT_EXPECTATIONS: + if (h->table.name && + strlen(h->table.name) > NFT_NAME_MAXLEN) { + loc = &h->table.location; + goto err_name_too_long; + } + if (h->obj.name && + strlen(h->obj.name) > NFT_NAME_MAXLEN) { + loc = &h->obj.location; + goto err_name_too_long; + } + break; + } + + return 0; + +err_name_too_long: + erec_queue(error(loc, "name too long, %d characters maximum allowed", + NFT_NAME_MAXLEN), + msgs); + return -1; +} + +static void reset_filter(struct nft_cache_filter *filter) +{ + memset(&filter->list, 0, sizeof(filter->list)); + memset(&filter->reset, 0, sizeof(filter->reset)); +} + +int nft_cache_evaluate(struct nft_ctx *nft, struct list_head *cmds, + struct list_head *msgs, struct nft_cache_filter *filter, + unsigned int *pflags) { - unsigned int flags = NFT_CACHE_EMPTY; + unsigned int flags, batch_flags = NFT_CACHE_EMPTY; struct cmd *cmd; + assert(filter); + list_for_each_entry(cmd, cmds, list) { + if (nft_handle_validate(cmd, msgs) < 0) + return -1; + + flags = NFT_CACHE_EMPTY; + reset_filter(filter); + switch (cmd->op) { case CMD_ADD: case CMD_INSERT: case CMD_CREATE: flags = evaluate_cache_add(cmd, flags); - if (nft_output_echo(&nft->output)) - flags |= NFT_CACHE_FULL; break; - case CMD_REPLACE: - flags = NFT_CACHE_FULL; + case CMD_REPLACE: /* only for rule */ + flags = NFT_CACHE_TABLE | NFT_CACHE_SET; break; case CMD_DELETE: - flags |= NFT_CACHE_TABLE | - NFT_CACHE_CHAIN | - NFT_CACHE_SET | - NFT_CACHE_FLOWTABLE | - NFT_CACHE_OBJECT; - + case CMD_DESTROY: flags = evaluate_cache_del(cmd, flags); break; case CMD_GET: flags = evaluate_cache_get(cmd, flags); break; case CMD_RESET: - flags |= NFT_CACHE_TABLE; + flags = evaluate_cache_reset(cmd, flags, filter); break; case CMD_LIST: - case CMD_EXPORT: + flags = evaluate_cache_list(nft, cmd, flags, filter); + break; case CMD_MONITOR: - flags |= NFT_CACHE_FULL; + flags = NFT_CACHE_FULL; break; case CMD_FLUSH: - flags = evaluate_cache_flush(cmd, flags); + flags = evaluate_cache_flush(cmd, flags, filter); break; case CMD_RENAME: flags = evaluate_cache_rename(cmd, flags); break; case CMD_DESCRIBE: case CMD_IMPORT: + case CMD_EXPORT: break; default: break; } + batch_flags |= flags; } + *pflags = batch_flags; - return flags; + return 0; +} + +void table_cache_add(struct table *table, struct nft_cache *cache) +{ + cache_add(&table->cache, &cache->table_cache, table->handle.table.name); +} + +void table_cache_del(struct table *table) +{ + cache_del(&table->cache); +} + +struct table *table_cache_find(const struct cache *cache, + const char *name, uint32_t family) +{ + struct table *table; + uint32_t hash; + + assert(name); + + hash = djb_hash(name) % NFT_CACHE_HSIZE; + list_for_each_entry(table, &cache->ht[hash], cache.hlist) { + if (table->handle.family == family && + !strcmp(table->handle.table.name, name)) + return table; + } + + return NULL; +} + +struct chain_cache_dump_ctx { + struct netlink_ctx *nlctx; + struct table *table; +}; + +static int chain_cache_cb(struct nftnl_chain *nlc, void *arg) +{ + struct chain_cache_dump_ctx *ctx = arg; + const char *chain_name, *table_name; + struct chain *chain; + uint32_t family; + + table_name = nftnl_chain_get_str(nlc, NFTNL_CHAIN_TABLE); + family = nftnl_chain_get_u32(nlc, NFTNL_CHAIN_FAMILY); + + if (family != ctx->table->handle.family || + strcmp(table_name, ctx->table->handle.table.name)) + return 0; + + chain_name = nftnl_chain_get_str(nlc, NFTNL_CHAIN_NAME); + + chain = netlink_delinearize_chain(ctx->nlctx, nlc); + if (chain->flags & CHAIN_F_BINDING) { + list_add_tail(&chain->cache.list, &ctx->table->chain_bindings); + } else { + cache_add(&chain->cache, &ctx->table->chain_cache, chain_name); + } + + nftnl_chain_list_del(nlc); + nftnl_chain_free(nlc); + + return 0; +} + +static int chain_cache_init(struct netlink_ctx *ctx, struct table *table, + struct nftnl_chain_list *chain_list) +{ + struct chain_cache_dump_ctx dump_ctx = { + .nlctx = ctx, + .table = table, + }; + nftnl_chain_list_foreach(chain_list, chain_cache_cb, &dump_ctx); + + return 0; +} + +static struct nftnl_chain_list * +chain_cache_dump(struct netlink_ctx *ctx, + const struct nft_cache_filter *filter, int *err) +{ + struct nftnl_chain_list *chain_list; + const char *table = NULL; + const char *chain = NULL; + int family = NFPROTO_UNSPEC; + + if (filter && filter->list.table && filter->list.chain) { + family = filter->list.family; + table = filter->list.table; + chain = filter->list.chain; + } + + chain_list = mnl_nft_chain_dump(ctx, family, table, chain); + if (chain_list == NULL) { + if (errno == EINTR) { + *err = -1; + return NULL; + } + *err = 0; + return NULL; + } + + return chain_list; +} + +void nft_chain_cache_update(struct netlink_ctx *ctx, struct table *table, + const char *chain) +{ + struct nftnl_chain_list *chain_list; + + chain_list = mnl_nft_chain_dump(ctx, table->handle.family, + table->handle.table.name, chain); + if (!chain_list) + return; + + chain_cache_init(ctx, table, chain_list); + + nftnl_chain_list_free(chain_list); +} + +void chain_cache_add(struct chain *chain, struct table *table) +{ + cache_add(&chain->cache, &table->chain_cache, chain->handle.chain.name); +} + +void chain_cache_del(struct chain *chain) +{ + cache_del(&chain->cache); +} + +struct chain *chain_cache_find(const struct table *table, const char *name) +{ + struct chain *chain; + uint32_t hash; + + assert(name); + + hash = djb_hash(name) % NFT_CACHE_HSIZE; + list_for_each_entry(chain, &table->chain_cache.ht[hash], cache.hlist) { + if (!strcmp(chain->handle.chain.name, name)) + return chain; + } + + return NULL; +} + +static int list_rule_cb(struct nftnl_rule *nlr, void *data) +{ + struct netlink_ctx *ctx = data; + const struct handle *h = ctx->data; + const char *table, *chain; + struct rule *rule; + uint32_t family; + + family = nftnl_rule_get_u32(nlr, NFTNL_RULE_FAMILY); + table = nftnl_rule_get_str(nlr, NFTNL_RULE_TABLE); + chain = nftnl_rule_get_str(nlr, NFTNL_RULE_CHAIN); + + if ((h->family != NFPROTO_UNSPEC && h->family != family) || + (h->table.name && strcmp(table, h->table.name) != 0) || + (h->chain.name && strcmp(chain, h->chain.name) != 0)) + return 0; + + netlink_dump_rule(nlr, ctx); + rule = netlink_delinearize_rule(ctx, nlr); + assert(rule); + list_add_tail(&rule->list, &ctx->list); + + return 0; +} + +static int rule_cache_dump(struct netlink_ctx *ctx, const struct handle *h, + const struct nft_cache_filter *filter) +{ + struct nftnl_rule_list *rule_cache; + const char *table = h->table.name; + const char *chain = NULL; + uint64_t rule_handle = 0; + int family = h->family; + bool reset = false; + bool dump = true; + + if (filter) { + if (filter->list.table) + table = filter->list.table; + if (filter->list.chain) + chain = filter->list.chain; + if (filter->list.rule_handle) { + rule_handle = filter->list.rule_handle; + dump = false; + } + if (filter->list.family) + family = filter->list.family; + + reset = filter->reset.rule; + } + + rule_cache = mnl_nft_rule_dump(ctx, family, + table, chain, rule_handle, dump, reset); + if (rule_cache == NULL) { + if (errno == EINTR) + return -1; + + return 0; + } + + ctx->data = h; + nftnl_rule_list_foreach(rule_cache, list_rule_cb, ctx); + nftnl_rule_list_free(rule_cache); + return 0; +} + +struct set_cache_dump_ctx { + struct netlink_ctx *nlctx; + struct table *table; +}; + +static int set_cache_cb(struct nftnl_set *nls, void *arg) +{ + struct set_cache_dump_ctx *ctx = arg; + const char *set_table; + const char *set_name; + uint32_t set_family; + struct set *set; + + set_table = nftnl_set_get_str(nls, NFTNL_SET_TABLE); + set_family = nftnl_set_get_u32(nls, NFTNL_SET_FAMILY); + + if (set_family != ctx->table->handle.family || + strcmp(set_table, ctx->table->handle.table.name)) + return 0; + + set = netlink_delinearize_set(ctx->nlctx, nls); + if (!set) + return -1; + + set_name = nftnl_set_get_str(nls, NFTNL_SET_NAME); + cache_add(&set->cache, &ctx->table->set_cache, set_name); + + nftnl_set_list_del(nls); + nftnl_set_free(nls); + return 0; +} + +static int set_cache_init(struct netlink_ctx *ctx, struct table *table, + struct nftnl_set_list *set_list) +{ + struct set_cache_dump_ctx dump_ctx = { + .nlctx = ctx, + .table = table, + }; + + nftnl_set_list_foreach(set_list, set_cache_cb, &dump_ctx); + + return 0; +} + +static struct nftnl_set_list * +set_cache_dump(struct netlink_ctx *ctx, + const struct nft_cache_filter *filter, int *err) +{ + struct nftnl_set_list *set_list; + int family = NFPROTO_UNSPEC; + const char *table = NULL; + const char *set = NULL; + + if (filter) { + family = filter->list.family; + table = filter->list.table; + set = filter->list.set; + } + + set_list = mnl_nft_set_dump(ctx, family, table, set); + if (!set_list) { + if (errno == EINTR) { + *err = -1; + return NULL; + } + *err = 0; + return NULL; + } + + return set_list; +} + +void set_cache_add(struct set *set, struct table *table) +{ + cache_add(&set->cache, &table->set_cache, set->handle.set.name); +} + +void set_cache_del(struct set *set) +{ + cache_del(&set->cache); +} + +struct set *set_cache_find(const struct table *table, const char *name) +{ + struct set *set; + uint32_t hash; + + assert(name); + + hash = djb_hash(name) % NFT_CACHE_HSIZE; + list_for_each_entry(set, &table->set_cache.ht[hash], cache.hlist) { + if (!strcmp(set->handle.set.name, name)) + return set; + } + + return NULL; +} + +struct obj_cache_dump_ctx { + struct netlink_ctx *nlctx; + struct table *table; +}; + +static int obj_cache_cb(struct nftnl_obj *nlo, void *arg) +{ + struct obj_cache_dump_ctx *ctx = arg; + const char *obj_table; + const char *obj_name; + uint32_t obj_family; + struct obj *obj; + + obj_table = nftnl_obj_get_str(nlo, NFTNL_OBJ_TABLE); + obj_family = nftnl_obj_get_u32(nlo, NFTNL_OBJ_FAMILY); + + if (obj_family != ctx->table->handle.family || + strcmp(obj_table, ctx->table->handle.table.name)) + return 0; + + obj = netlink_delinearize_obj(ctx->nlctx, nlo); + if (obj) { + obj_name = nftnl_obj_get_str(nlo, NFTNL_OBJ_NAME); + cache_add(&obj->cache, &ctx->table->obj_cache, obj_name); + } + + nftnl_obj_list_del(nlo); + nftnl_obj_free(nlo); + + return 0; +} + +static int obj_cache_init(struct netlink_ctx *ctx, struct table *table, + struct nftnl_obj_list *obj_list) +{ + struct obj_cache_dump_ctx dump_ctx = { + .nlctx = ctx, + .table = table, + }; + nftnl_obj_list_foreach(obj_list, obj_cache_cb, &dump_ctx); + + return 0; +} + +static struct nftnl_obj_list *obj_cache_dump(struct netlink_ctx *ctx, + const struct nft_cache_filter *filter) +{ + struct nftnl_obj_list *obj_list; + int type = NFT_OBJECT_UNSPEC; + int family = NFPROTO_UNSPEC; + const char *table = NULL; + const char *obj = NULL; + bool reset = false; + bool dump = true; + + if (filter) { + family = filter->list.family; + if (filter->list.table) + table = filter->list.table; + if (filter->list.obj) { + obj = filter->list.obj; + dump = false; + } + if (filter->list.obj_type) + type = filter->list.obj_type; + + reset = filter->reset.obj; + } + obj_list = mnl_nft_obj_dump(ctx, family, table, obj, type, dump, reset); + if (!obj_list) { + if (errno == EINTR) + return NULL; + + /* old kernels do not support this, provide an empty list. */ + obj_list = nftnl_obj_list_alloc(); + if (!obj_list) + memory_allocation_error(); + + return obj_list; + } + + return obj_list; +} + +void obj_cache_add(struct obj *obj, struct table *table) +{ + cache_add(&obj->cache, &table->obj_cache, obj->handle.obj.name); +} + +void obj_cache_del(struct obj *obj) +{ + cache_del(&obj->cache); +} + +struct obj *obj_cache_find(const struct table *table, const char *name, + uint32_t obj_type) +{ + struct obj *obj; + uint32_t hash; + + assert(name); + + hash = djb_hash(name) % NFT_CACHE_HSIZE; + list_for_each_entry(obj, &table->obj_cache.ht[hash], cache.hlist) { + if (!strcmp(obj->handle.obj.name, name) && + obj->type == obj_type) + return obj; + } + + return NULL; +} + +struct ft_cache_dump_ctx { + struct netlink_ctx *nlctx; + struct table *table; +}; + +static int ft_cache_cb(struct nftnl_flowtable *nlf, void *arg) +{ + struct ft_cache_dump_ctx *ctx = arg; + struct flowtable *ft; + const char *ft_table; + const char *ft_name; + uint32_t ft_family; + + ft_family = nftnl_flowtable_get_u32(nlf, NFTNL_FLOWTABLE_FAMILY); + ft_table = nftnl_flowtable_get_str(nlf, NFTNL_FLOWTABLE_TABLE); + + if (ft_family != ctx->table->handle.family || + strcmp(ft_table, ctx->table->handle.table.name)) + return 0; + + ft = netlink_delinearize_flowtable(ctx->nlctx, nlf); + if (!ft) + return -1; + + ft_name = nftnl_flowtable_get_str(nlf, NFTNL_FLOWTABLE_NAME); + cache_add(&ft->cache, &ctx->table->ft_cache, ft_name); + + nftnl_flowtable_list_del(nlf); + nftnl_flowtable_free(nlf); + return 0; +} + +static int ft_cache_init(struct netlink_ctx *ctx, struct table *table, + struct nftnl_flowtable_list *ft_list) +{ + struct ft_cache_dump_ctx dump_ctx = { + .nlctx = ctx, + .table = table, + }; + nftnl_flowtable_list_foreach(ft_list, ft_cache_cb, &dump_ctx); + + return 0; +} + +static struct nftnl_flowtable_list * +ft_cache_dump(struct netlink_ctx *ctx, const struct nft_cache_filter *filter) +{ + struct nftnl_flowtable_list *ft_list; + int family = NFPROTO_UNSPEC; + const char *table = NULL; + const char *ft = NULL; + + if (filter) { + family = filter->list.family; + table = filter->list.table; + ft = filter->list.ft; + } + + ft_list = mnl_nft_flowtable_dump(ctx, family, table, ft); + if (!ft_list) { + if (errno == EINTR) + return NULL; + + /* old kernels do not support this, provide an empty list. */ + ft_list = nftnl_flowtable_list_alloc(); + if (!ft_list) + memory_allocation_error(); + + return ft_list; + } + + return ft_list; +} + +void ft_cache_add(struct flowtable *ft, struct table *table) +{ + cache_add(&ft->cache, &table->ft_cache, ft->handle.flowtable.name); +} + +void ft_cache_del(struct flowtable *ft) +{ + cache_del(&ft->cache); +} + +struct flowtable *ft_cache_find(const struct table *table, const char *name) +{ + struct flowtable *ft; + uint32_t hash; + + assert(name); + + hash = djb_hash(name) % NFT_CACHE_HSIZE; + list_for_each_entry(ft, &table->ft_cache.ht[hash], cache.hlist) { + if (!strcmp(ft->handle.flowtable.name, name)) + return ft; + } + + return NULL; +} + +static int cache_init_tables(struct netlink_ctx *ctx, struct handle *h, + struct nft_cache *cache, + const struct nft_cache_filter *filter) +{ + struct table *table, *next; + int ret; + + ret = netlink_list_tables(ctx, h, filter); + if (ret < 0) + return -1; + + list_for_each_entry_safe(table, next, &ctx->list, list) { + list_del(&table->list); + table_cache_add(table, cache); + } + + return 0; +} + +static int rule_init_cache(struct netlink_ctx *ctx, struct table *table, + const struct nft_cache_filter *filter) +{ + struct rule *rule, *nrule; + struct chain *chain; + int ret; + + ret = rule_cache_dump(ctx, &table->handle, filter); + + list_for_each_entry_safe(rule, nrule, &ctx->list, list) { + chain = chain_cache_find(table, rule->handle.chain.name); + if (!chain) + chain = chain_binding_lookup(table, + rule->handle.chain.name); + if (!chain) + goto err_ctx_list; + + list_move_tail(&rule->list, &chain->rules); + } + + return ret; + +err_ctx_list: + list_for_each_entry_safe(rule, nrule, &ctx->list, list) { + list_del(&rule->list); + rule_free(rule); + } + errno = EINTR; + + return -1; +} + +static int implicit_chain_cache(struct netlink_ctx *ctx, struct table *table, + const char *chain_name) +{ + struct nft_cache_filter filter = {}; + struct chain *chain; + int ret = 0; + + list_for_each_entry(chain, &table->chain_bindings, cache.list) { + filter.list.table = table->handle.table.name; + filter.list.chain = chain->handle.chain.name; + + ret = rule_init_cache(ctx, table, &filter); + } + + return ret; +} + +static int cache_init_objects(struct netlink_ctx *ctx, unsigned int flags, + const struct nft_cache_filter *filter) +{ + struct nftnl_flowtable_list *ft_list = NULL; + struct nftnl_chain_list *chain_list = NULL; + struct nftnl_set_list *set_list = NULL; + struct nftnl_obj_list *obj_list = NULL; + struct table *table; + struct set *set; + int ret = 0; + + if (flags & NFT_CACHE_CHAIN_BIT) { + chain_list = chain_cache_dump(ctx, filter, &ret); + if (!chain_list) + return -1; + } + if (flags & NFT_CACHE_SET_BIT) { + set_list = set_cache_dump(ctx, filter, &ret); + if (!set_list) { + ret = -1; + goto cache_fails; + } + } + if (flags & NFT_CACHE_OBJECT_BIT) { + obj_list = obj_cache_dump(ctx, filter); + if (!obj_list) { + ret = -1; + goto cache_fails; + } + } + if (flags & NFT_CACHE_FLOWTABLE_BIT) { + ft_list = ft_cache_dump(ctx, filter); + if (!ft_list) { + ret = -1; + goto cache_fails; + } + } + + list_for_each_entry(table, &ctx->nft->cache.table_cache.list, cache.list) { + if (flags & NFT_CACHE_SET_BIT) { + ret = set_cache_init(ctx, table, set_list); + if (ret < 0) + goto cache_fails; + } + if (flags & NFT_CACHE_SETELEM_BIT) { + list_for_each_entry(set, &table->set_cache.list, cache.list) { + if (cache_filter_find(filter, &set->handle)) + continue; + if (!set_is_anonymous(set->flags) && + flags & NFT_CACHE_TERSE) + continue; + + ret = netlink_list_setelems(ctx, &set->handle, + set, filter->reset.elem); + if (ret < 0) + goto cache_fails; + } + } else if (flags & NFT_CACHE_SETELEM_MAYBE) { + list_for_each_entry(set, &table->set_cache.list, cache.list) { + if (cache_filter_find(filter, &set->handle)) + continue; + + if (!set_is_non_concat_range(set)) + continue; + + ret = netlink_list_setelems(ctx, &set->handle, + set, filter->reset.elem); + if (ret < 0) + goto cache_fails; + } + } + if (flags & NFT_CACHE_CHAIN_BIT) { + ret = chain_cache_init(ctx, table, chain_list); + if (ret < 0) + goto cache_fails; + } + if (flags & NFT_CACHE_FLOWTABLE_BIT) { + ret = ft_cache_init(ctx, table, ft_list); + if (ret < 0) + goto cache_fails; + } + if (flags & NFT_CACHE_OBJECT_BIT) { + ret = obj_cache_init(ctx, table, obj_list); + if (ret < 0) + goto cache_fails; + } + + if (flags & NFT_CACHE_RULE_BIT) { + ret = rule_init_cache(ctx, table, filter); + if (ret < 0) + goto cache_fails; + + if (filter && filter->list.table && filter->list.chain) { + ret = implicit_chain_cache(ctx, table, filter->list.chain); + if (ret < 0) + goto cache_fails; + } + } + } + +cache_fails: + if (set_list) + nftnl_set_list_free(set_list); + if (obj_list) + nftnl_obj_list_free(obj_list); + if (ft_list) + nftnl_flowtable_list_free(ft_list); + + if (flags & NFT_CACHE_CHAIN_BIT) + nftnl_chain_list_free(chain_list); + + return ret; +} + +static int nft_cache_init(struct netlink_ctx *ctx, unsigned int flags, + const struct nft_cache_filter *filter) +{ + struct handle handle = { + .family = NFPROTO_UNSPEC, + }; + int ret; + + if (flags == NFT_CACHE_EMPTY) + return 0; + + /* assume NFT_CACHE_TABLE is always set. */ + ret = cache_init_tables(ctx, &handle, &ctx->nft->cache, filter); + if (ret < 0) + return ret; + ret = cache_init_objects(ctx, flags, filter); + if (ret < 0) + return ret; + + return 0; +} + +static bool nft_cache_is_complete(struct nft_cache *cache, unsigned int flags) +{ + return (cache->flags & flags) == flags; +} + +static bool nft_cache_needs_refresh(struct nft_cache *cache, unsigned int flags) +{ + return (cache->flags & NFT_CACHE_REFRESH) || + (flags & NFT_CACHE_REFRESH); +} + +static bool nft_cache_is_updated(struct nft_cache *cache, uint16_t genid) +{ + return genid && genid == cache->genid; +} + +bool nft_cache_needs_update(struct nft_cache *cache) +{ + return cache->flags & NFT_CACHE_UPDATE; +} + +int nft_cache_update(struct nft_ctx *nft, unsigned int flags, + struct list_head *msgs, + const struct nft_cache_filter *filter) +{ + struct netlink_ctx ctx = { + .list = LIST_HEAD_INIT(ctx.list), + .nft = nft, + .msgs = msgs, + }; + struct nft_cache *cache = &nft->cache; + uint32_t genid, genid_stop, oldflags; + int ret; +replay: + ctx.seqnum = cache->seqnum++; + genid = mnl_genid_get(&ctx); + if (!nft_cache_needs_refresh(cache, flags) && + nft_cache_is_complete(cache, flags) && + nft_cache_is_updated(cache, genid)) + return 0; + + if (cache->genid) + nft_cache_release(cache); + + if (flags & NFT_CACHE_FLUSHED) { + oldflags = flags; + flags = NFT_CACHE_EMPTY; + if (oldflags & NFT_CACHE_UPDATE) + flags |= NFT_CACHE_UPDATE; + goto skip; + } + + ret = nft_cache_init(&ctx, flags, filter); + if (ret < 0) { + if (errno == EINTR) { + nft_cache_release(cache); + goto replay; + } + + erec_queue(error(&netlink_location, "cache initialization failed: %s", + strerror(errno)), + msgs); + nft_cache_release(cache); + + return -1; + } + + genid_stop = mnl_genid_get(&ctx); + if (genid != genid_stop) { + nft_cache_release(cache); + goto replay; + } +skip: + cache->genid = genid; + cache->flags = flags; + return 0; +} + +static void nft_cache_flush(struct cache *table_cache) +{ + struct table *table, *next; + + list_for_each_entry_safe(table, next, &table_cache->list, cache.list) { + table_cache_del(table); + table_free(table); + } +} + +void nft_cache_release(struct nft_cache *cache) +{ + nft_cache_flush(&cache->table_cache); + cache->genid = 0; + cache->flags = NFT_CACHE_EMPTY; +} + +void cache_init(struct cache *cache) +{ + int i; + + cache->ht = xmalloc(sizeof(struct list_head) * NFT_CACHE_HSIZE); + for (i = 0; i < NFT_CACHE_HSIZE; i++) + init_list_head(&cache->ht[i]); + + init_list_head(&cache->list); +} + +void cache_free(struct cache *cache) +{ + free(cache->ht); +} + +void cache_add(struct cache_item *item, struct cache *cache, const char *name) +{ + uint32_t hash; + + hash = djb_hash(name) % NFT_CACHE_HSIZE; + list_add_tail(&item->hlist, &cache->ht[hash]); + list_add_tail(&item->list, &cache->list); +} + +void cache_del(struct cache_item *item) +{ + list_del(&item->hlist); + list_del(&item->list); } @@ -12,18 +12,19 @@ * Development of this code funded by Astaro AG (http://www.astaro.com/) */ -#include <config.h> -#include <stdlib.h> +#include <nft.h> + #include <stdio.h> #include <stdarg.h> #include <unistd.h> #include <errno.h> -#include <string.h> #include <ctype.h> #include <limits.h> #ifdef HAVE_LIBREADLINE #include <readline/readline.h> #include <readline/history.h> +#elif defined(HAVE_LIBEDIT) +#include <editline/readline.h> #else #include <linenoise.h> #endif @@ -35,6 +36,15 @@ #define CMDLINE_PROMPT "nft> " #define CMDLINE_QUIT "quit" +static bool cli_quit; +static int cli_rc; + +static void __cli_exit(int rc) +{ + cli_quit = true; + cli_rc = rc; +} + static char histfile[PATH_MAX]; static void @@ -48,7 +58,26 @@ init_histfile(void) snprintf(histfile, sizeof(histfile), "%s/%s", home, CMDLINE_HISTFILE); } -#ifdef HAVE_LIBREADLINE +#if defined(HAVE_LIBREADLINE) +static void nft_rl_prompt_save(void) +{ + rl_save_prompt(); + rl_clear_message(); + rl_set_prompt(".... "); +} +#define nft_rl_prompt_restore rl_restore_prompt +#elif defined(HAVE_LIBEDIT) +static void nft_rl_prompt_save(void) +{ + rl_set_prompt(".... "); +} +static void nft_rl_prompt_restore(void) +{ + rl_set_prompt("nft> "); +} +#endif + +#if defined(HAVE_LIBREADLINE) || defined(HAVE_LIBEDIT) static struct nft_ctx *cli_nft; static char *multiline; @@ -72,17 +101,15 @@ static char *cli_append_multiline(char *line) if (multiline == NULL) { multiline = line; - rl_save_prompt(); - rl_clear_message(); - rl_set_prompt(".... "); + nft_rl_prompt_save(); } else { len += strlen(multiline); s = malloc(len + 1); if (!s) { fprintf(stderr, "%s:%u: Memory allocation failure\n", __FILE__, __LINE__); - cli_exit(); - exit(EXIT_FAILURE); + cli_exit(EXIT_FAILURE); + return NULL; } snprintf(s, len + 1, "%s%s", multiline, line); free(multiline); @@ -93,7 +120,7 @@ static char *cli_append_multiline(char *line) if (complete) { line = multiline; multiline = NULL; - rl_restore_prompt(); + nft_rl_prompt_restore(); } return line; } @@ -106,8 +133,7 @@ static void cli_complete(char *line) if (line == NULL) { printf("\n"); - cli_exit(); - exit(0); + return cli_exit(0); } line = cli_append_multiline(line); @@ -120,10 +146,8 @@ static void cli_complete(char *line) if (*c == '\0') return; - if (!strcmp(line, CMDLINE_QUIT)) { - cli_exit(); - exit(0); - } + if (!strcmp(line, CMDLINE_QUIT)) + return cli_exit(0); /* avoid duplicate history entries */ hist = history_get(history_length); @@ -133,6 +157,9 @@ static void cli_complete(char *line) nft_run_cmd_from_buffer(cli_nft, line); free(line); } +#endif + +#if defined(HAVE_LIBREADLINE) static char **cli_completion(const char *text, int start, int end) { @@ -142,7 +169,7 @@ static char **cli_completion(const char *text, int start, int end) int cli_init(struct nft_ctx *nft) { cli_nft = nft; - rl_readline_name = "nft"; + rl_readline_name = (char *)"nft"; rl_instream = stdin; rl_outstream = stdout; @@ -154,45 +181,95 @@ int cli_init(struct nft_ctx *nft) read_history(histfile); history_set_pos(history_length); - while (true) + while (!cli_quit) rl_callback_read_char(); - return 0; + + return cli_rc; } -void cli_exit(void) +void cli_exit(int rc) { rl_callback_handler_remove(); rl_deprep_terminal(); write_history(histfile); + + __cli_exit(rc); +} + +#elif defined(HAVE_LIBEDIT) + +int cli_init(struct nft_ctx *nft) +{ + char *line; + + cli_nft = nft; + rl_readline_name = (char *)"nft"; + rl_instream = stdin; + rl_outstream = stdout; + + init_histfile(); + + read_history(histfile); + history_set_pos(history_length); + + rl_set_prompt(CMDLINE_PROMPT); + while (!cli_quit) { + line = readline(rl_prompt); + if (!line) { + cli_exit(0); + break; + } + line = cli_append_multiline(line); + if (!line) + continue; + + cli_complete(line); + } + + return cli_rc; +} + +void cli_exit(int rc) +{ + rl_deprep_terminal(); + write_history(histfile); + + __cli_exit(rc); } -#else /* !HAVE_LIBREADLINE */ +#else /* HAVE_LINENOISE */ int cli_init(struct nft_ctx *nft) { - int quit = 0; char *line; init_histfile(); linenoiseHistoryLoad(histfile); linenoiseSetMultiLine(1); - while (!quit && (line = linenoise(CMDLINE_PROMPT)) != NULL) { + while (!cli_quit) { + line = linenoise(CMDLINE_PROMPT); + if (!line) { + cli_exit(0); + break; + } if (strcmp(line, CMDLINE_QUIT) == 0) { - quit = 1; + cli_exit(0); } else if (line[0] != '\0') { linenoiseHistoryAdd(line); nft_run_cmd_from_buffer(nft, line); } linenoiseFree(line); } - cli_exit(); - exit(0); + + return cli_rc; } -void cli_exit(void) +void cli_exit(int rc) { linenoiseHistorySave(histfile); + + __cli_exit(rc); } -#endif /* HAVE_LIBREADLINE */ +#endif /* HAVE_LINENOISE */ diff --git a/src/cmd.c b/src/cmd.c new file mode 100644 index 00000000..ff634af2 --- /dev/null +++ b/src/cmd.c @@ -0,0 +1,488 @@ +/* + * Copyright (c) 2020 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. + */ + +#include <nft.h> + +#include <erec.h> +#include <mnl.h> +#include <cmd.h> +#include <parser.h> +#include <utils.h> +#include <iface.h> +#include <errno.h> +#include <cache.h> + +void cmd_add_loc(struct cmd *cmd, const struct nlmsghdr *nlh, const struct location *loc) +{ + if (cmd->num_attrs >= cmd->attr_array_len) { + cmd->attr_array_len *= 2; + cmd->attr = xrealloc(cmd->attr, sizeof(struct nlerr_loc) * cmd->attr_array_len); + } + + cmd->attr[cmd->num_attrs].seqnum = nlh->nlmsg_seq; + cmd->attr[cmd->num_attrs].offset = nlh->nlmsg_len; + cmd->attr[cmd->num_attrs].location = loc; + cmd->num_attrs++; +} + +static int nft_cmd_enoent_table(struct netlink_ctx *ctx, const struct cmd *cmd, + const struct location *loc) +{ + struct table *table; + + if (!cmd->handle.table.name) + return 0; + + table = table_lookup_fuzzy(&cmd->handle, &ctx->nft->cache); + if (!table) + return 0; + + netlink_io_error(ctx, loc, "%s; did you mean table '%s' in family %s?", + strerror(ENOENT), table->handle.table.name, + family2str(table->handle.family)); + return 1; +} + +static int table_fuzzy_check(struct netlink_ctx *ctx, const struct cmd *cmd, + const struct table *table) +{ + if (table_cache_find(&ctx->nft->cache.table_cache, + cmd->handle.table.name, cmd->handle.family)) + return 0; + + if (strcmp(cmd->handle.table.name, table->handle.table.name) || + cmd->handle.family != table->handle.family) { + netlink_io_error(ctx, &cmd->handle.table.location, + "%s; did you mean table '%s' in family %s?", + strerror(ENOENT), table->handle.table.name, + family2str(table->handle.family)); + return 1; + } + + return 0; +} + +static int nft_cmd_enoent_chain(struct netlink_ctx *ctx, const struct cmd *cmd, + const struct location *loc) +{ + const struct table *table = NULL; + struct chain *chain; + + if (!cmd->handle.chain.name) + return 0; + + if (nft_cache_update(ctx->nft, NFT_CACHE_TABLE | NFT_CACHE_CHAIN, + ctx->msgs, NULL) < 0) + return 0; + + chain = chain_lookup_fuzzy(&cmd->handle, &ctx->nft->cache, &table); + /* check table first. */ + if (!table) + return 0; + + if (table_fuzzy_check(ctx, cmd, table)) + return 1; + + if (!chain) + return 0; + + netlink_io_error(ctx, loc, "%s; did you mean chain '%s' in table %s '%s'?", + strerror(ENOENT), chain->handle.chain.name, + family2str(table->handle.family), + table->handle.table.name); + return 1; +} + +static int nft_cmd_enoent_rule(struct netlink_ctx *ctx, const struct cmd *cmd, + const struct location *loc) +{ + unsigned int flags = NFT_CACHE_TABLE | + NFT_CACHE_CHAIN; + const struct table *table = NULL; + struct chain *chain; + + if (nft_cache_update(ctx->nft, flags, ctx->msgs, NULL) < 0) + return 0; + + chain = chain_lookup_fuzzy(&cmd->handle, &ctx->nft->cache, &table); + /* check table first. */ + if (!table) + return 0; + + if (table_fuzzy_check(ctx, cmd, table)) + return 1; + + if (!chain) + return 0; + + if (strcmp(cmd->handle.chain.name, chain->handle.chain.name)) { + netlink_io_error(ctx, loc, "%s; did you mean chain '%s' in table %s '%s'?", + strerror(ENOENT), + chain->handle.chain.name, + family2str(table->handle.family), + table->handle.table.name); + return 1; + } + + return 0; +} + +static int nft_cmd_enoent_set(struct netlink_ctx *ctx, const struct cmd *cmd, + const struct location *loc) +{ + const struct table *table = NULL; + struct set *set; + + if (!cmd->handle.set.name) + return 0; + + if (nft_cache_update(ctx->nft, NFT_CACHE_TABLE | NFT_CACHE_SET, + ctx->msgs, NULL) < 0) + return 0; + + set = set_lookup_fuzzy(cmd->handle.set.name, &ctx->nft->cache, &table); + /* check table first. */ + if (!table) + return 0; + + if (table_fuzzy_check(ctx, cmd, table)) + return 1; + + if (!set) + return 0; + + netlink_io_error(ctx, loc, "%s; did you mean %s '%s' in table %s '%s'?", + strerror(ENOENT), + set_is_map(set->flags) ? "map" : "set", + set->handle.set.name, + family2str(set->handle.family), + table->handle.table.name); + return 1; +} + +static int nft_cmd_enoent_obj(struct netlink_ctx *ctx, const struct cmd *cmd, + const struct location *loc) +{ + const struct table *table = NULL; + struct obj *obj; + + if (!cmd->handle.obj.name) + return 0; + + if (nft_cache_update(ctx->nft, NFT_CACHE_TABLE | NFT_CACHE_OBJECT, + ctx->msgs, NULL) < 0) + return 0; + + obj = obj_lookup_fuzzy(cmd->handle.obj.name, &ctx->nft->cache, &table); + /* check table first. */ + if (!table) + return 0; + + if (table_fuzzy_check(ctx, cmd, table)) + return 1; + + if (!obj) + return 0; + + netlink_io_error(ctx, loc, "%s; did you mean obj '%s' in table %s '%s'?", + strerror(ENOENT), obj->handle.obj.name, + family2str(obj->handle.family), + table->handle.table.name); + return 1; +} + +static int nft_cmd_enoent_flowtable(struct netlink_ctx *ctx, + const struct cmd *cmd, + const struct location *loc) +{ + const struct table *table = NULL; + struct flowtable *ft; + + if (!cmd->handle.flowtable.name) + return 0; + + if (nft_cache_update(ctx->nft, NFT_CACHE_TABLE | NFT_CACHE_FLOWTABLE, + ctx->msgs, NULL) < 0) + return 0; + + ft = flowtable_lookup_fuzzy(cmd->handle.flowtable.name, + &ctx->nft->cache, &table); + /* check table first. */ + if (!table) + return 0; + + if (table_fuzzy_check(ctx, cmd, table)) + return 1; + + if (!ft) + return 0; + + netlink_io_error(ctx, loc, "%s; did you mean flowtable '%s' in table %s '%s'?", + strerror(ENOENT), ft->handle.flowtable.name, + family2str(ft->handle.family), + table->handle.table.name); + return 1; +} + +static void nft_cmd_enoent(struct netlink_ctx *ctx, const struct cmd *cmd, + const struct location *loc, int err) +{ + int ret = 0; + + switch (cmd->obj) { + case CMD_OBJ_TABLE: + ret = nft_cmd_enoent_table(ctx, cmd, loc); + break; + case CMD_OBJ_CHAIN: + ret = nft_cmd_enoent_chain(ctx, cmd, loc); + break; + case CMD_OBJ_SET: + ret = nft_cmd_enoent_set(ctx, cmd, loc); + break; + case CMD_OBJ_RULE: + ret = nft_cmd_enoent_rule(ctx, cmd, loc); + break; + case CMD_OBJ_COUNTER: + case CMD_OBJ_QUOTA: + case CMD_OBJ_CT_HELPER: + case CMD_OBJ_CT_TIMEOUT: + case CMD_OBJ_LIMIT: + case CMD_OBJ_SECMARK: + case CMD_OBJ_CT_EXPECT: + case CMD_OBJ_SYNPROXY: + ret = nft_cmd_enoent_obj(ctx, cmd, loc); + break; + case CMD_OBJ_FLOWTABLE: + ret = nft_cmd_enoent_flowtable(ctx, cmd, loc); + break; + default: + break; + } + + if (ret) + return; + + netlink_io_error(ctx, loc, "Could not process rule: %s", strerror(err)); +} + +static int nft_cmd_chain_error(struct netlink_ctx *ctx, struct cmd *cmd, + struct mnl_err *err) +{ + struct chain *chain = cmd->chain, *existing_chain; + const struct table *table; + int priority; + + switch (err->err) { + case EOPNOTSUPP: + if (!(chain->flags & CHAIN_F_BASECHAIN)) + break; + + mpz_export_data(&priority, chain->priority.expr->value, + BYTEORDER_HOST_ENDIAN, sizeof(int)); + if (priority <= -200 && !strcmp(chain->type.str, "nat")) + return netlink_io_error(ctx, &chain->priority.loc, + "Chains of type \"nat\" must have a priority value above -200"); + + if (nft_cache_update(ctx->nft, NFT_CACHE_TABLE | NFT_CACHE_CHAIN, + ctx->msgs, NULL) < 0) { + return netlink_io_error(ctx, &chain->loc, + "Chain of type \"%s\" is not supported, perhaps kernel support is missing?", + chain->type.str); + } + + table = table_cache_find(&ctx->nft->cache.table_cache, + cmd->handle.table.name, cmd->handle.family); + if (table) { + existing_chain = chain_cache_find(table, cmd->handle.chain.name); + if (existing_chain && existing_chain != chain && + !strcmp(existing_chain->handle.chain.name, chain->handle.chain.name)) + return netlink_io_error(ctx, &chain->loc, + "Chain \"%s\" already exists in table %s '%s' with different declaration", + chain->handle.chain.name, + family2str(table->handle.family), table->handle.table.name); + } + + return netlink_io_error(ctx, &chain->loc, + "Chain of type \"%s\" is not supported, perhaps kernel support is missing?", + chain->type.str); + default: + break; + } + + return 0; +} + +void nft_cmd_error(struct netlink_ctx *ctx, struct cmd *cmd, + struct mnl_err *err) +{ + const struct location *loc = NULL; + uint32_t i; + + for (i = 0; i < cmd->num_attrs; i++) { + if (cmd->attr[i].seqnum == err->seqnum && + cmd->attr[i].offset == err->offset) + loc = cmd->attr[i].location; + } + + if (loc) { + if (err->err == ENOENT) { + nft_cmd_enoent(ctx, cmd, loc, err->err); + return; + } + } else { + loc = &cmd->location; + } + + switch (cmd->obj) { + case CMD_OBJ_CHAIN: + if (nft_cmd_chain_error(ctx, cmd, err) < 0) + return; + break; + default: + break; + } + + if (cmd->op == CMD_DESTROY && err->err == EINVAL) { + netlink_io_error(ctx, loc, + "\"destroy\" command is not supported, perhaps kernel support is missing?"); + return; + } + + netlink_io_error(ctx, loc, "Could not process rule: %s", + strerror(err->err)); +} + +static void nft_cmd_expand_chain(struct chain *chain, struct list_head *new_cmds) +{ + struct rule *rule, *next; + struct handle h; + struct cmd *new; + + list_for_each_entry_safe(rule, next, &chain->rules, list) { + list_del(&rule->list); + handle_merge(&rule->handle, &chain->handle); + memset(&h, 0, sizeof(h)); + handle_merge(&h, &chain->handle); + if (chain->flags & CHAIN_F_BINDING) { + rule->handle.chain_id = chain->handle.chain_id; + rule->handle.chain.location = chain->location; + } + new = cmd_alloc(CMD_ADD, CMD_OBJ_RULE, &h, + &rule->location, rule); + list_add_tail(&new->list, new_cmds); + } +} + +bool nft_cmd_collapse_elems(enum cmd_ops op, struct list_head *cmds, + struct handle *handle, struct expr *init) +{ + struct cmd *last_cmd; + + if (list_empty(cmds)) + return false; + + if (init->etype == EXPR_VARIABLE) + return false; + + last_cmd = list_last_entry(cmds, struct cmd, list); + if (last_cmd->op != op || + last_cmd->obj != CMD_OBJ_ELEMENTS || + last_cmd->expr->etype == EXPR_VARIABLE || + last_cmd->handle.family != handle->family || + strcmp(last_cmd->handle.table.name, handle->table.name) || + strcmp(last_cmd->handle.set.name, handle->set.name)) + return false; + + list_splice_tail_init(&expr_set(init)->expressions, + &expr_set(last_cmd->expr)->expressions); + expr_set(last_cmd->expr)->size += expr_set(init)->size; + + return true; +} + +void nft_cmd_expand(struct cmd *cmd) +{ + struct list_head new_cmds; + struct flowtable *ft; + struct table *table; + struct chain *chain; + struct set *set; + struct obj *obj; + struct cmd *new; + struct handle h; + + init_list_head(&new_cmds); + + switch (cmd->obj) { + case CMD_OBJ_TABLE: + table = cmd->table; + if (!table) + return; + + list_for_each_entry(chain, &table->chains, list) { + handle_merge(&chain->handle, &table->handle); + memset(&h, 0, sizeof(h)); + handle_merge(&h, &chain->handle); + h.chain_id = chain->handle.chain_id; + new = cmd_alloc(CMD_ADD, CMD_OBJ_CHAIN, &h, + &chain->location, chain_get(chain)); + list_add_tail(&new->list, &new_cmds); + } + list_for_each_entry(obj, &table->objs, list) { + handle_merge(&obj->handle, &table->handle); + memset(&h, 0, sizeof(h)); + handle_merge(&h, &obj->handle); + new = cmd_alloc(CMD_ADD, obj_type_to_cmd(obj->type), &h, + &obj->location, obj_get(obj)); + list_add_tail(&new->list, &new_cmds); + } + list_for_each_entry(set, &table->sets, list) { + handle_merge(&set->handle, &table->handle); + memset(&h, 0, sizeof(h)); + handle_merge(&h, &set->handle); + new = cmd_alloc(CMD_ADD, CMD_OBJ_SET, &h, + &set->location, set_get(set)); + list_add_tail(&new->list, &new_cmds); + } + list_for_each_entry(ft, &table->flowtables, list) { + handle_merge(&ft->handle, &table->handle); + memset(&h, 0, sizeof(h)); + handle_merge(&h, &ft->handle); + new = cmd_alloc(CMD_ADD, CMD_OBJ_FLOWTABLE, &h, + &ft->location, flowtable_get(ft)); + list_add_tail(&new->list, &new_cmds); + } + list_for_each_entry(chain, &table->chains, list) + nft_cmd_expand_chain(chain, &new_cmds); + + list_splice(&new_cmds, &cmd->list); + break; + case CMD_OBJ_CHAIN: + chain = cmd->chain; + if (!chain || list_empty(&chain->rules)) + break; + + nft_cmd_expand_chain(chain, &new_cmds); + list_splice(&new_cmds, &cmd->list); + break; + case CMD_OBJ_SET: + case CMD_OBJ_MAP: + set = cmd->set; + if (!set->init) + break; + + memset(&h, 0, sizeof(h)); + handle_merge(&h, &set->handle); + new = cmd_alloc(CMD_ADD, CMD_OBJ_SETELEMS, &h, + &set->location, set_get(set)); + list_add(&new->list, &cmd->list); + break; + default: + break; + } +} @@ -10,11 +10,11 @@ * Development of this code funded by Astaro AG (http://www.astaro.com/) */ +#include <nft.h> + #include <stddef.h> -#include <stdlib.h> #include <stdio.h> #include <inttypes.h> -#include <string.h> #include <netinet/ip.h> #include <linux/netfilter.h> @@ -98,7 +98,14 @@ static const struct symbol_table ct_status_tbl = { SYMBOL("confirmed", IPS_CONFIRMED), SYMBOL("snat", IPS_SRC_NAT), SYMBOL("dnat", IPS_DST_NAT), + SYMBOL("seq-adjust", IPS_SEQ_ADJUST), + SYMBOL("snat-done", IPS_SRC_NAT_DONE), + SYMBOL("dnat-done", IPS_DST_NAT_DONE), SYMBOL("dying", IPS_DYING), + SYMBOL("fixed-timeout", IPS_FIXED_TIMEOUT), + SYMBOL("helper", IPS_HELPER_BIT), + SYMBOL("offload", IPS_OFFLOAD_BIT), + SYMBOL("hw-offload", IPS_HW_OFFLOAD_BIT), SYMBOL_LIST_END }, }; @@ -131,7 +138,7 @@ static const struct symbol_table ct_events_tbl = { }, }; -static const struct datatype ct_event_type = { +const struct datatype ct_event_type = { .type = TYPE_CT_EVENTBIT, .name = "ct_event", .desc = "conntrack event bits", @@ -176,7 +183,7 @@ static struct error_record *ct_label_type_parse(struct parse_ctx *ctx, { const struct symbolic_constant *s; const struct datatype *dtype; - uint8_t data[CT_LABEL_BIT_SIZE]; + uint8_t data[CT_LABEL_BIT_SIZE / BITS_PER_BYTE]; uint64_t bit; mpz_t value; @@ -211,16 +218,22 @@ static struct error_record *ct_label_type_parse(struct parse_ctx *ctx, mpz_export_data(data, value, BYTEORDER_HOST_ENDIAN, sizeof(data)); *res = constant_expr_alloc(&sym->location, dtype, - dtype->byteorder, sizeof(data), - data); + dtype->byteorder, CT_LABEL_BIT_SIZE, data); mpz_clear(value); return NULL; } -static const struct datatype ct_label_type = { +static void ct_label_type_describe(struct output_ctx *octx) +{ + rt_symbol_table_describe(octx, CONNLABEL_CONF, + octx->tbl.ct_label, &ct_label_type); +} + +const struct datatype ct_label_type = { .type = TYPE_CT_LABEL, .name = "ct_label", .desc = "conntrack label", + .describe = ct_label_type_describe, .byteorder = BYTEORDER_HOST_ENDIAN, .size = CT_LABEL_BIT_SIZE, .basetype = &bitmask_type, @@ -272,10 +285,10 @@ const struct ct_template ct_templates[__NFT_CT_MAX] = { [NFT_CT_PROTOCOL] = CT_TEMPLATE("protocol", &inet_protocol_type, BYTEORDER_BIG_ENDIAN, BITS_PER_BYTE), - [NFT_CT_PROTO_SRC] = CT_TEMPLATE("proto-src", &invalid_type, + [NFT_CT_PROTO_SRC] = CT_TEMPLATE("proto-src", &inet_service_type, BYTEORDER_BIG_ENDIAN, 2 * BITS_PER_BYTE), - [NFT_CT_PROTO_DST] = CT_TEMPLATE("proto-dst", &invalid_type, + [NFT_CT_PROTO_DST] = CT_TEMPLATE("proto-dst", &inet_service_type, BYTEORDER_BIG_ENDIAN, 2 * BITS_PER_BYTE), [NFT_CT_LABELS] = CT_TEMPLATE("label", &ct_label_type, @@ -301,6 +314,8 @@ const struct ct_template ct_templates[__NFT_CT_MAX] = { BYTEORDER_BIG_ENDIAN, 128), [NFT_CT_SECMARK] = CT_TEMPLATE("secmark", &integer_type, BYTEORDER_HOST_ENDIAN, 32), + [NFT_CT_ID] = CT_TEMPLATE("id", &integer_type, + BYTEORDER_BIG_ENDIAN, 32), }; static void ct_print(enum nft_ct_keys key, int8_t dir, uint8_t nfproto, @@ -349,9 +364,11 @@ static void ct_expr_clone(struct expr *new, const struct expr *expr) new->ct = expr->ct; } -static void ct_expr_pctx_update(struct proto_ctx *ctx, const struct expr *expr) +static void ct_expr_pctx_update(struct proto_ctx *ctx, + const struct location *loc, + const struct expr *left, + const struct expr *right) { - const struct expr *left = expr->left, *right = expr->right; const struct proto_desc *base = NULL, *desc; uint32_t nhproto; @@ -364,7 +381,7 @@ static void ct_expr_pctx_update(struct proto_ctx *ctx, const struct expr *expr) if (!desc) return; - proto_ctx_update(ctx, left->ct.base + 1, &expr->location, desc); + proto_ctx_update(ctx, left->ct.base + 1, loc, desc); } #define NFTNL_UDATA_CT_KEY 0 @@ -446,7 +463,11 @@ struct expr *ct_expr_alloc(const struct location *loc, enum nft_ct_keys key, switch (key) { case NFT_CT_SRC: + case NFT_CT_SRC_IP: + case NFT_CT_SRC_IP6: case NFT_CT_DST: + case NFT_CT_DST_IP: + case NFT_CT_DST_IP6: expr->ct.base = PROTO_BASE_NETWORK_HDR; break; case NFT_CT_PROTO_SRC: @@ -520,7 +541,7 @@ static void ct_stmt_destroy(struct stmt *stmt) expr_free(stmt->ct.expr); } -static const struct stmt_ops ct_stmt_ops = { +const struct stmt_ops ct_stmt_ops = { .type = STMT_CT, .name = "ct", .print = ct_stmt_print, @@ -547,7 +568,7 @@ static void notrack_stmt_print(const struct stmt *stmt, struct output_ctx *octx) nft_print(octx, "notrack"); } -static const struct stmt_ops notrack_stmt_ops = { +const struct stmt_ops notrack_stmt_ops = { .type = STMT_NOTRACK, .name = "notrack", .print = notrack_stmt_print, @@ -567,14 +588,15 @@ static void flow_offload_stmt_print(const struct stmt *stmt, static void flow_offload_stmt_destroy(struct stmt *stmt) { - xfree(stmt->flow.table_name); + free_const(stmt->flow.table_name); } -static const struct stmt_ops flow_offload_stmt_ops = { +const struct stmt_ops flow_offload_stmt_ops = { .type = STMT_FLOW_OFFLOAD, .name = "flow_offload", .print = flow_offload_stmt_print, .destroy = flow_offload_stmt_destroy, + .json = flow_offload_stmt_json, }; struct stmt *flow_offload_stmt_alloc(const struct location *loc, diff --git a/src/datatype.c b/src/datatype.c index 189e1b48..f347010f 100644 --- a/src/datatype.c +++ b/src/datatype.c @@ -8,8 +8,8 @@ * Development of this code funded by Astaro AG (http://www.astaro.com/) */ -#include <stdlib.h> -#include <string.h> +#include <nft.h> + #include <inttypes.h> #include <ctype.h> /* isdigit */ #include <errno.h> @@ -18,6 +18,8 @@ #include <linux/types.h> #include <linux/netfilter.h> #include <linux/icmpv6.h> +#include <dirent.h> +#include <sys/stat.h> #include <nftables.h> #include <datatype.h> @@ -26,6 +28,8 @@ #include <erec.h> #include <netlink.h> #include <json.h> +#include <misspell.h> +#include "nftutils.h" #include <netinet/ip_icmp.h> @@ -60,6 +64,7 @@ static const struct datatype *datatypes[TYPE_MAX + 1] = { [TYPE_CT_DIR] = &ct_dir_type, [TYPE_CT_STATUS] = &ct_status_type, [TYPE_ICMP6_TYPE] = &icmp6_type_type, + [TYPE_CT_LABEL] = &ct_label_type, [TYPE_PKTTYPE] = &pkttype_type, [TYPE_ICMP_CODE] = &icmp_code_type, [TYPE_ICMPV6_CODE] = &icmpv6_code_type, @@ -69,11 +74,13 @@ static const struct datatype *datatypes[TYPE_MAX + 1] = { [TYPE_ECN] = &ecn_type, [TYPE_FIB_ADDR] = &fib_addr_type, [TYPE_BOOLEAN] = &boolean_type, + [TYPE_CT_EVENTBIT] = &ct_event_type, [TYPE_IFNAME] = &ifname_type, [TYPE_IGMP_TYPE] = &igmp_type_type, [TYPE_TIME_DATE] = &date_type, [TYPE_TIME_HOUR] = &hour_type, [TYPE_TIME_DAY] = &day_type, + [TYPE_CGROUPV2] = &cgroupv2_type, }; const struct datatype *datatype_lookup(enum datatypes type) @@ -116,10 +123,16 @@ void datatype_print(const struct expr *expr, struct output_ctx *octx) expr->dtype->name); } +bool datatype_prefix_notation(const struct datatype *dtype) +{ + return dtype->type == TYPE_IPADDR || dtype->type == TYPE_IP6ADDR; +} + struct error_record *symbol_parse(struct parse_ctx *ctx, const struct expr *sym, struct expr **res) { const struct datatype *dtype = sym->dtype; + struct error_record *erec; assert(sym->etype == EXPR_SYMBOL); @@ -133,11 +146,54 @@ struct error_record *symbol_parse(struct parse_ctx *ctx, const struct expr *sym, res); } while ((dtype = dtype->basetype)); - return error(&sym->location, - "Can't parse symbolic %s expressions", + dtype = sym->dtype; + if (dtype->err) { + erec = dtype->err(sym); + if (erec) + return erec; + } + + return error(&sym->location, "Could not parse symbolic %s expression", sym->dtype->desc); } +static struct error_record *__symbol_parse_fuzzy(const struct expr *sym, + const struct symbol_table *tbl) +{ + const struct symbolic_constant *s; + struct string_misspell_state st; + + string_misspell_init(&st); + + for (s = tbl->symbols; s->identifier != NULL; s++) { + string_misspell_update(sym->identifier, s->identifier, + (void *)s->identifier, &st); + } + + if (st.obj) { + return error(&sym->location, + "Could not parse %s expression; did you you mean `%s`?", + sym->dtype->desc, st.obj); + } + + return NULL; +} + +static struct error_record *symbol_parse_fuzzy(const struct expr *sym, + const struct symbol_table *tbl) +{ + struct error_record *erec; + + if (!tbl) + return NULL; + + erec = __symbol_parse_fuzzy(sym, tbl); + if (erec) + return erec; + + return NULL; +} + struct error_record *symbolic_constant_parse(struct parse_ctx *ctx, const struct expr *sym, const struct symbol_table *tbl, @@ -160,8 +216,16 @@ struct error_record *symbolic_constant_parse(struct parse_ctx *ctx, do { if (dtype->basetype->parse) { erec = dtype->basetype->parse(ctx, sym, res); - if (erec != NULL) - return erec; + if (erec != NULL) { + struct error_record *fuzzy_erec; + + fuzzy_erec = symbol_parse_fuzzy(sym, tbl); + if (!fuzzy_erec) + return erec; + + erec_destroy(erec); + return fuzzy_erec; + } if (*res) return NULL; goto out; @@ -247,20 +311,25 @@ const struct datatype invalid_type = { .print = invalid_type_print, }; +void expr_chain_export(const struct expr *e, char *chain_name) +{ + unsigned int len; + + len = e->len / BITS_PER_BYTE; + if (len >= NFT_CHAIN_MAXNAMELEN) + BUG("verdict expression length %u is too large (%u bits max)", + e->len, NFT_CHAIN_MAXNAMELEN * BITS_PER_BYTE); + + mpz_export_data(chain_name, e->value, BYTEORDER_HOST_ENDIAN, len); +} + static void verdict_jump_chain_print(const char *what, const struct expr *e, struct output_ctx *octx) { char chain[NFT_CHAIN_MAXNAMELEN]; - unsigned int len; memset(chain, 0, sizeof(chain)); - - len = e->len / BITS_PER_BYTE; - if (len >= sizeof(chain)) - BUG("verdict expression length %u is too large (%lu bits max)", - e->len, (unsigned long)sizeof(chain) * BITS_PER_BYTE); - - mpz_export_data(chain, e->value, BYTEORDER_HOST_ENDIAN, len); + expr_chain_export(e, chain); nft_print(octx, "%s %s", what, chain); } @@ -313,15 +382,33 @@ static void verdict_type_print(const struct expr *expr, struct output_ctx *octx) } } -static struct error_record *verdict_type_parse(struct parse_ctx *ctx, - const struct expr *sym, - struct expr **res) +static struct error_record *verdict_type_error(const struct expr *sym) { - *res = constant_expr_alloc(&sym->location, &string_type, - BYTEORDER_HOST_ENDIAN, - (strlen(sym->identifier) + 1) * BITS_PER_BYTE, - sym->identifier); - return NULL; + /* Skip jump and goto from fuzzy match to provide better error + * reporting, fall back to `jump chain' if no clue. + */ + static const char *verdict_array[] = { + "continue", "break", "return", "accept", "drop", "queue", + "stolen", NULL, + }; + struct string_misspell_state st; + int i; + + string_misspell_init(&st); + + for (i = 0; verdict_array[i] != NULL; i++) { + string_misspell_update(sym->identifier, verdict_array[i], + (void *)verdict_array[i], &st); + } + + if (st.obj) { + return error(&sym->location, "Could not parse %s; did you mean `%s'?", + sym->dtype->desc, st.obj); + } + + /* assume user would like to jump to chain as a hint. */ + return error(&sym->location, "Could not parse %s; did you mean `jump %s'?", + sym->dtype->desc, sym->identifier); } const struct datatype verdict_type = { @@ -329,7 +416,7 @@ const struct datatype verdict_type = { .name = "verdict", .desc = "netfilter verdict", .print = verdict_type_print, - .parse = verdict_type_parse, + .err = verdict_type_error, }; static const struct symbol_table nfproto_tbl = { @@ -402,6 +489,40 @@ const struct datatype integer_type = { .parse = integer_type_parse, }; +static void xinteger_type_print(const struct expr *expr, struct output_ctx *octx) +{ + nft_gmp_print(octx, "0x%Zx", expr->value); +} + +/* Alias of integer_type to print raw payload expressions in hexadecimal. */ +const struct datatype xinteger_type = { + .type = TYPE_INTEGER, + .name = "integer", + .desc = "integer", + .basetype = &integer_type, + .print = xinteger_type_print, + .json = integer_type_json, + .parse = integer_type_parse, +}; + +static void queue_type_print(const struct expr *expr, struct output_ctx *octx) +{ + nft_gmp_print(octx, "queue"); +} + +/* Dummy queue_type for set declaration with typeof, see + * constant_expr_build_udata and constant_expr_parse_udata, + * basetype is used for elements. +*/ +const struct datatype queue_type = { + .type = TYPE_INTEGER, + .is_typeof = 1, + .name = "queue", + .desc = "queue", + .basetype = &integer_type, + .print = queue_type_print, +}; + static void string_type_print(const struct expr *expr, struct output_ctx *octx) { unsigned int len = div_round_up(expr->len, BITS_PER_BYTE); @@ -504,27 +625,34 @@ static struct error_record *ipaddr_type_parse(struct parse_ctx *ctx, const struct expr *sym, struct expr **res) { - struct addrinfo *ai, hints = { .ai_family = AF_INET, - .ai_socktype = SOCK_DGRAM}; - struct in_addr *addr; - int err; + struct in_addr addr; + + if (nft_input_no_dns(ctx->input)) { + if (inet_pton(AF_INET, sym->identifier, &addr) != 1) + return error(&sym->location, "Invalid IPv4 address"); + } else { + struct addrinfo *ai, hints = { .ai_family = AF_INET, + .ai_socktype = SOCK_DGRAM}; + int err; - err = getaddrinfo(sym->identifier, NULL, &hints, &ai); - if (err != 0) - return error(&sym->location, "Could not resolve hostname: %s", - gai_strerror(err)); + err = getaddrinfo(sym->identifier, NULL, &hints, &ai); + if (err != 0) + return error(&sym->location, "Could not resolve hostname: %s", + gai_strerror(err)); - if (ai->ai_next != NULL) { + if (ai->ai_next != NULL) { + freeaddrinfo(ai); + return error(&sym->location, + "Hostname resolves to multiple addresses"); + } + assert(ai->ai_addr->sa_family == AF_INET); + addr = ((struct sockaddr_in *) (void *) ai->ai_addr)->sin_addr; freeaddrinfo(ai); - return error(&sym->location, - "Hostname resolves to multiple addresses"); } - addr = &((struct sockaddr_in *)ai->ai_addr)->sin_addr; *res = constant_expr_alloc(&sym->location, &ipaddr_type, BYTEORDER_BIG_ENDIAN, - sizeof(*addr) * BITS_PER_BYTE, addr); - freeaddrinfo(ai); + sizeof(addr) * BITS_PER_BYTE, &addr); return NULL; } @@ -537,7 +665,6 @@ const struct datatype ipaddr_type = { .basetype = &integer_type, .print = ipaddr_type_print, .parse = ipaddr_type_parse, - .flags = DTYPE_F_PREFIX, }; static void ip6addr_type_print(const struct expr *expr, struct output_ctx *octx) @@ -563,27 +690,35 @@ static struct error_record *ip6addr_type_parse(struct parse_ctx *ctx, const struct expr *sym, struct expr **res) { - struct addrinfo *ai, hints = { .ai_family = AF_INET6, - .ai_socktype = SOCK_DGRAM}; - struct in6_addr *addr; - int err; + struct in6_addr addr; + + if (nft_input_no_dns(ctx->input)) { + if (inet_pton(AF_INET6, sym->identifier, &addr) != 1) + return error(&sym->location, "Invalid IPv6 address"); + } else { + struct addrinfo *ai, hints = { .ai_family = AF_INET6, + .ai_socktype = SOCK_DGRAM}; + int err; - err = getaddrinfo(sym->identifier, NULL, &hints, &ai); - if (err != 0) - return error(&sym->location, "Could not resolve hostname: %s", - gai_strerror(err)); + err = getaddrinfo(sym->identifier, NULL, &hints, &ai); + if (err != 0) + return error(&sym->location, "Could not resolve hostname: %s", + gai_strerror(err)); + + if (ai->ai_next != NULL) { + freeaddrinfo(ai); + return error(&sym->location, + "Hostname resolves to multiple addresses"); + } - if (ai->ai_next != NULL) { + assert(ai->ai_addr->sa_family == AF_INET6); + addr = ((struct sockaddr_in6 *)(void *)ai->ai_addr)->sin6_addr; freeaddrinfo(ai); - return error(&sym->location, - "Hostname resolves to multiple addresses"); } - addr = &((struct sockaddr_in6 *)ai->ai_addr)->sin6_addr; *res = constant_expr_alloc(&sym->location, &ip6addr_type, BYTEORDER_BIG_ENDIAN, - sizeof(*addr) * BITS_PER_BYTE, addr); - freeaddrinfo(ai); + sizeof(addr) * BITS_PER_BYTE, &addr); return NULL; } @@ -596,29 +731,41 @@ const struct datatype ip6addr_type = { .basetype = &integer_type, .print = ip6addr_type_print, .parse = ip6addr_type_parse, - .flags = DTYPE_F_PREFIX, }; static void inet_protocol_type_print(const struct expr *expr, struct output_ctx *octx) { - struct protoent *p; + if (!nft_output_numeric_proto(octx) && + mpz_cmp_ui(expr->value, UINT8_MAX) <= 0) { + char name[NFT_PROTONAME_MAXSIZE]; - if (!nft_output_numeric_proto(octx)) { - p = getprotobynumber(mpz_get_uint8(expr->value)); - if (p != NULL) { - nft_print(octx, "%s", p->p_name); + if (nft_getprotobynumber(mpz_get_uint8(expr->value), name, sizeof(name))) { + nft_print(octx, "%s", name); return; } } integer_type_print(expr, octx); } +static void inet_protocol_type_describe(struct output_ctx *octx) +{ + uint8_t protonum; + + for (protonum = 0; protonum < UINT8_MAX; protonum++) { + char name[NFT_PROTONAME_MAXSIZE]; + + if (!nft_getprotobynumber(protonum, name, sizeof(name))) + continue; + + nft_print(octx, "\t%-30s\t%u\n", name, protonum); + } +} + static struct error_record *inet_protocol_type_parse(struct parse_ctx *ctx, const struct expr *sym, struct expr **res) { - struct protoent *p; uint8_t proto; uintmax_t i; char *end; @@ -631,11 +778,13 @@ static struct error_record *inet_protocol_type_parse(struct parse_ctx *ctx, proto = i; } else { - p = getprotobyname(sym->identifier); - if (p == NULL) + int r; + + r = nft_getprotobyname(sym->identifier); + if (r < 0) return error(&sym->location, "Could not resolve protocol name"); - proto = p->p_proto; + proto = r; } *res = constant_expr_alloc(&sym->location, &inet_protocol_type, @@ -653,43 +802,24 @@ const struct datatype inet_protocol_type = { .print = inet_protocol_type_print, .json = inet_protocol_type_json, .parse = inet_protocol_type_parse, + .describe = inet_protocol_type_describe, }; static void inet_service_print(const struct expr *expr, struct output_ctx *octx) { - struct sockaddr_in sin = { .sin_family = AF_INET }; - char buf[NI_MAXSERV]; - uint16_t port; - int err; - - sin.sin_port = mpz_get_be16(expr->value); - err = getnameinfo((struct sockaddr *)&sin, sizeof(sin), NULL, 0, - buf, sizeof(buf), 0); - if (err != 0) { - nft_print(octx, "%u", ntohs(sin.sin_port)); - return; - } - port = atoi(buf); - /* We got a TCP service name string, display it... */ - if (htons(port) != sin.sin_port) { - nft_print(octx, "\"%s\"", buf); - return; - } + uint16_t port = mpz_get_be16(expr->value); + char name[NFT_SERVNAME_MAXSIZE]; - /* ...otherwise, this might be a UDP service name. */ - err = getnameinfo((struct sockaddr *)&sin, sizeof(sin), NULL, 0, - buf, sizeof(buf), NI_DGRAM); - if (err != 0) { - /* No service name, display numeric value. */ - nft_print(octx, "%u", ntohs(sin.sin_port)); - return; - } - nft_print(octx, "\"%s\"", buf); + if (!nft_getservbyport(port, NULL, name, sizeof(name))) + nft_print(octx, "%hu", ntohs(port)); + else + nft_print(octx, "\"%s\"", name); } void inet_service_type_print(const struct expr *expr, struct output_ctx *octx) { - if (nft_output_service(octx)) { + if (nft_output_service(octx) && + mpz_cmp_ui(expr->value, UINT16_MAX) <= 0) { inet_service_print(expr, octx); return; } @@ -719,7 +849,12 @@ static struct error_record *inet_service_type_parse(struct parse_ctx *ctx, return error(&sym->location, "Could not resolve service: %s", gai_strerror(err)); - port = ((struct sockaddr_in *)ai->ai_addr)->sin_port; + if (ai->ai_addr->sa_family == AF_INET) { + port = ((struct sockaddr_in *)(void *)ai->ai_addr)->sin_port; + } else { + assert(ai->ai_addr->sa_family == AF_INET6); + port = ((struct sockaddr_in6 *)(void *)ai->ai_addr)->sin6_port; + } freeaddrinfo(ai); } @@ -743,19 +878,48 @@ const struct datatype inet_service_type = { #define RT_SYM_TAB_INITIAL_SIZE 16 +static FILE *open_iproute2_db(const char *filename, char **path) +{ + FILE *ret; + + if (filename[0] == '/') + return fopen(filename, "r"); + + if (asprintf(path, "/etc/iproute2/%s", filename) == -1) + goto fail; + + ret = fopen(*path, "r"); + if (ret) + return ret; + + free(*path); + if (asprintf(path, "/usr/share/iproute2/%s", filename) == -1) + goto fail; + + ret = fopen(*path, "r"); + if (ret) + return ret; + + free(*path); +fail: + *path = NULL; + return NULL; +} + struct symbol_table *rt_symbol_table_init(const char *filename) { + char buf[512], namebuf[512], *p, *path = NULL; struct symbolic_constant s; struct symbol_table *tbl; unsigned int size, nelems, val; - char buf[512], namebuf[512], *p; FILE *f; size = RT_SYM_TAB_INITIAL_SIZE; tbl = xmalloc(sizeof(*tbl) + size * sizeof(s)); + tbl->base = BASE_DECIMAL; nelems = 0; - f = fopen(filename, "r"); + f = open_iproute2_db(filename, &path); if (f == NULL) goto out; @@ -765,12 +929,15 @@ struct symbol_table *rt_symbol_table_init(const char *filename) p++; if (*p == '#' || *p == '\n' || *p == '\0') continue; - if (sscanf(p, "0x%x %511s\n", &val, namebuf) != 2 && - sscanf(p, "0x%x %511s #", &val, namebuf) != 2 && - sscanf(p, "%u %511s\n", &val, namebuf) != 2 && - sscanf(p, "%u %511s #", &val, namebuf) != 2) { + if (sscanf(p, "0x%x %511s\n", &val, namebuf) == 2 || + sscanf(p, "0x%x %511s #", &val, namebuf) == 2) { + tbl->base = BASE_HEXADECIMAL; + } else if (sscanf(p, "%u %511s\n", &val, namebuf) == 2 || + sscanf(p, "%u %511s #", &val, namebuf) == 2) { + tbl->base = BASE_DECIMAL; + } else { fprintf(stderr, "iproute database '%s' corrupted\n", - filename); + path ?: filename); break; } @@ -787,6 +954,8 @@ struct symbol_table *rt_symbol_table_init(const char *filename) fclose(f); out: + if (path) + free(path); tbl->symbols[nelems] = SYMBOL_LIST_END; return tbl; } @@ -796,13 +965,40 @@ void rt_symbol_table_free(const struct symbol_table *tbl) const struct symbolic_constant *s; for (s = tbl->symbols; s->identifier != NULL; s++) - xfree(s->identifier); - xfree(tbl); + free_const(s->identifier); + free_const(tbl); +} + +void rt_symbol_table_describe(struct output_ctx *octx, const char *name, + const struct symbol_table *tbl, + const struct datatype *type) +{ + char *path = NULL; + FILE *f; + + if (!tbl || !tbl->symbols[0].identifier) + return; + + f = open_iproute2_db(name, &path); + if (f) + fclose(f); + if (!path && asprintf(&path, "%s%s", + name[0] == '/' ? "" : "unknown location of ", + name) < 0) + return; + + nft_print(octx, "\npre-defined symbolic constants from %s ", path); + if (tbl->base == BASE_DECIMAL) + nft_print(octx, "(in decimal):\n"); + else + nft_print(octx, "(in hexadecimal):\n"); + symbol_table_print(tbl, type, type->byteorder, octx); + free(path); } void mark_table_init(struct nft_ctx *ctx) { - ctx->output.tbl.mark = rt_symbol_table_init("/etc/iproute2/rt_marks"); + ctx->output.tbl.mark = rt_symbol_table_init("rt_marks"); } void mark_table_exit(struct nft_ctx *ctx) @@ -822,10 +1018,17 @@ static struct error_record *mark_type_parse(struct parse_ctx *ctx, return symbolic_constant_parse(ctx, sym, ctx->tbl->mark, res); } +static void mark_type_describe(struct output_ctx *octx) +{ + rt_symbol_table_describe(octx, "rt_marks", + octx->tbl.mark, &mark_type); +} + const struct datatype mark_type = { .type = TYPE_MARK, .name = "mark", .desc = "packet mark", + .describe = mark_type_describe, .size = 4 * BITS_PER_BYTE, .byteorder = BYTEORDER_HOST_ENDIAN, .basetype = &integer_type, @@ -833,9 +1036,9 @@ const struct datatype mark_type = { .print = mark_type_print, .json = mark_type_json, .parse = mark_type_parse, - .flags = DTYPE_F_PREFIX, }; +/* symbol table for private datatypes for reject statement. */ static const struct symbol_table icmp_code_tbl = { .base = BASE_DECIMAL, .symbols = { @@ -846,20 +1049,22 @@ static const struct symbol_table icmp_code_tbl = { SYMBOL("net-prohibited", ICMP_NET_ANO), SYMBOL("host-prohibited", ICMP_HOST_ANO), SYMBOL("admin-prohibited", ICMP_PKT_FILTERED), + SYMBOL("frag-needed", ICMP_FRAG_NEEDED), SYMBOL_LIST_END }, }; -const struct datatype icmp_code_type = { - .type = TYPE_ICMP_CODE, +/* private datatype for reject statement. */ +const struct datatype reject_icmp_code_type = { .name = "icmp_code", - .desc = "icmp code", + .desc = "reject icmp code", .size = BITS_PER_BYTE, .byteorder = BYTEORDER_BIG_ENDIAN, .basetype = &integer_type, .sym_tbl = &icmp_code_tbl, }; +/* symbol table for private datatypes for reject statement. */ static const struct symbol_table icmpv6_code_tbl = { .base = BASE_DECIMAL, .symbols = { @@ -873,16 +1078,17 @@ static const struct symbol_table icmpv6_code_tbl = { }, }; -const struct datatype icmpv6_code_type = { - .type = TYPE_ICMPV6_CODE, +/* private datatype for reject statement. */ +const struct datatype reject_icmpv6_code_type = { .name = "icmpv6_code", - .desc = "icmpv6 code", + .desc = "reject icmpv6 code", .size = BITS_PER_BYTE, .byteorder = BYTEORDER_BIG_ENDIAN, .basetype = &integer_type, .sym_tbl = &icmpv6_code_tbl, }; +/* symbol table for private datatypes for reject statement. */ static const struct symbol_table icmpx_code_tbl = { .base = BASE_DECIMAL, .symbols = { @@ -894,6 +1100,60 @@ static const struct symbol_table icmpx_code_tbl = { }, }; +/* private datatype for reject statement. */ +const struct datatype reject_icmpx_code_type = { + .name = "icmpx_code", + .desc = "reject icmpx code", + .size = BITS_PER_BYTE, + .byteorder = BYTEORDER_BIG_ENDIAN, + .basetype = &integer_type, + .sym_tbl = &icmpx_code_tbl, +}; + +/* Backward compatible parser for the reject statement. */ +static struct error_record *icmp_code_parse(struct parse_ctx *ctx, + const struct expr *sym, + struct expr **res) +{ + return symbolic_constant_parse(ctx, sym, &icmp_code_tbl, res); +} + +const struct datatype icmp_code_type = { + .type = TYPE_ICMP_CODE, + .name = "icmp_code", + .desc = "icmp code", + .size = BITS_PER_BYTE, + .byteorder = BYTEORDER_BIG_ENDIAN, + .basetype = &integer_type, + .parse = icmp_code_parse, +}; + +/* Backward compatible parser for the reject statement. */ +static struct error_record *icmpv6_code_parse(struct parse_ctx *ctx, + const struct expr *sym, + struct expr **res) +{ + return symbolic_constant_parse(ctx, sym, &icmpv6_code_tbl, res); +} + +const struct datatype icmpv6_code_type = { + .type = TYPE_ICMPV6_CODE, + .name = "icmpv6_code", + .desc = "icmpv6 code", + .size = BITS_PER_BYTE, + .byteorder = BYTEORDER_BIG_ENDIAN, + .basetype = &integer_type, + .parse = icmpv6_code_parse, +}; + +/* Backward compatible parser for the reject statement. */ +static struct error_record *icmpx_code_parse(struct parse_ctx *ctx, + const struct expr *sym, + struct expr **res) +{ + return symbolic_constant_parse(ctx, sym, &icmpx_code_tbl, res); +} + const struct datatype icmpx_code_type = { .type = TYPE_ICMPX_CODE, .name = "icmpx_code", @@ -901,12 +1161,18 @@ const struct datatype icmpx_code_type = { .size = BITS_PER_BYTE, .byteorder = BYTEORDER_BIG_ENDIAN, .basetype = &integer_type, - .sym_tbl = &icmpx_code_tbl, + .parse = icmpx_code_parse, }; void time_print(uint64_t ms, struct output_ctx *octx) { uint64_t days, hours, minutes, seconds; + bool printed = false; + + if (nft_output_seconds(octx)) { + nft_print(octx, "%" PRIu64 "s", ms / 1000); + return; + } days = ms / 86400000; ms %= 86400000; @@ -920,16 +1186,29 @@ void time_print(uint64_t ms, struct output_ctx *octx) seconds = ms / 1000; ms %= 1000; - if (days > 0) + if (days > 0) { nft_print(octx, "%" PRIu64 "d", days); - if (hours > 0) + printed = true; + } + if (hours > 0) { nft_print(octx, "%" PRIu64 "h", hours); - if (minutes > 0) + printed = true; + } + if (minutes > 0) { nft_print(octx, "%" PRIu64 "m", minutes); - if (seconds > 0) + printed = true; + } + if (seconds > 0) { nft_print(octx, "%" PRIu64 "s", seconds); - if (ms > 0) + printed = true; + } + if (ms > 0) { nft_print(octx, "%" PRIu64 "ms", ms); + printed = true; + } + + if (!printed) + nft_print(octx, "0s"); } enum { @@ -1044,6 +1323,7 @@ static struct error_record *time_type_parse(struct parse_ctx *ctx, struct expr **res) { struct error_record *erec; + uint32_t s32; uint64_t s; erec = time_parse(&sym->location, sym->identifier, &s); @@ -1053,9 +1333,10 @@ static struct error_record *time_type_parse(struct parse_ctx *ctx, if (s > UINT32_MAX) return error(&sym->location, "value too large"); + s32 = s; *res = constant_expr_alloc(&sym->location, &time_type, BYTEORDER_HOST_ENDIAN, - sizeof(uint32_t) * BITS_PER_BYTE, &s); + sizeof(uint32_t) * BITS_PER_BYTE, &s32); return NULL; } @@ -1064,7 +1345,7 @@ const struct datatype time_type = { .name = "time", .desc = "relative time", .byteorder = BYTEORDER_HOST_ENDIAN, - .size = 8 * BITS_PER_BYTE, + .size = 4 * BITS_PER_BYTE, .basetype = &integer_type, .print = time_type_print, .json = time_type_json, @@ -1079,45 +1360,55 @@ static struct error_record *concat_type_parse(struct parse_ctx *ctx, sym->dtype->desc); } -static struct datatype *dtype_alloc(void) +static struct datatype *datatype_alloc(void) { struct datatype *dtype; dtype = xzalloc(sizeof(*dtype)); - dtype->flags = DTYPE_F_ALLOC; + dtype->alloc = 1; + dtype->refcnt = 1; return dtype; } -struct datatype *datatype_get(const struct datatype *ptr) +const struct datatype *datatype_get(const struct datatype *ptr) { struct datatype *dtype = (struct datatype *)ptr; if (!dtype) return NULL; - if (!(dtype->flags & DTYPE_F_ALLOC)) + if (!dtype->alloc) return dtype; dtype->refcnt++; return dtype; } +void __datatype_set(struct expr *expr, const struct datatype *dtype) +{ + const struct datatype *dtype_free; + + dtype_free = expr->dtype; + expr->dtype = dtype; + datatype_free(dtype_free); +} + void datatype_set(struct expr *expr, const struct datatype *dtype) { - datatype_free(expr->dtype); - expr->dtype = datatype_get(dtype); + if (dtype != expr->dtype) + __datatype_set(expr, datatype_get(dtype)); } -static struct datatype *dtype_clone(const struct datatype *orig_dtype) +struct datatype *datatype_clone(const struct datatype *orig_dtype) { struct datatype *dtype; - dtype = xzalloc(sizeof(*dtype)); + dtype = xmalloc(sizeof(*dtype)); *dtype = *orig_dtype; dtype->name = xstrdup(orig_dtype->name); dtype->desc = xstrdup(orig_dtype->desc); - dtype->flags = DTYPE_F_ALLOC | orig_dtype->flags; - dtype->refcnt = 0; + dtype->alloc = 1; + dtype->refcnt = 1; return dtype; } @@ -1128,14 +1419,17 @@ void datatype_free(const struct datatype *ptr) if (!dtype) return; - if (!(dtype->flags & DTYPE_F_ALLOC)) + if (!dtype->alloc) return; + + assert(dtype->refcnt != 0); + if (--dtype->refcnt > 0) return; - xfree(dtype->name); - xfree(dtype->desc); - xfree(dtype); + free_const(dtype->name); + free_const(dtype->desc); + free(dtype); } const struct datatype *concat_type_alloc(uint32_t type) @@ -1164,7 +1458,7 @@ const struct datatype *concat_type_alloc(uint32_t type) } strncat(desc, ")", sizeof(desc) - strlen(desc) - 1); - dtype = dtype_alloc(); + dtype = datatype_alloc(); dtype->type = type; dtype->size = size; dtype->subtypes = subtypes; @@ -1176,15 +1470,15 @@ const struct datatype *concat_type_alloc(uint32_t type) } const struct datatype *set_datatype_alloc(const struct datatype *orig_dtype, - unsigned int byteorder) + enum byteorder byteorder) { struct datatype *dtype; /* Restrict dynamic datatype allocation to generic integer datatype. */ if (orig_dtype != &integer_type) - return orig_dtype; + return datatype_get(orig_dtype); - dtype = dtype_clone(orig_dtype); + dtype = datatype_clone(orig_dtype); dtype->byteorder = byteorder; return dtype; @@ -1204,7 +1498,7 @@ static struct error_record *time_unit_parse(const struct location *loc, else if (strcmp(str, "week") == 0) *unit = 1ULL * 60 * 60 * 24 * 7; else - return error(loc, "Wrong rate format"); + return error(loc, "Wrong time format, expecting second, minute, hour, day or week"); return NULL; } @@ -1212,14 +1506,14 @@ static struct error_record *time_unit_parse(const struct location *loc, struct error_record *data_unit_parse(const struct location *loc, const char *str, uint64_t *rate) { - if (strncmp(str, "bytes", strlen("bytes")) == 0) + if (strcmp(str, "bytes") == 0) *rate = 1ULL; - else if (strncmp(str, "kbytes", strlen("kbytes")) == 0) + else if (strcmp(str, "kbytes") == 0) *rate = 1024; - else if (strncmp(str, "mbytes", strlen("mbytes")) == 0) + else if (strcmp(str, "mbytes") == 0) *rate = 1024 * 1024; else - return error(loc, "Wrong rate format"); + return error(loc, "Wrong unit format, expecting bytes, kbytes or mbytes"); return NULL; } @@ -1227,14 +1521,20 @@ struct error_record *data_unit_parse(const struct location *loc, struct error_record *rate_parse(const struct location *loc, const char *str, uint64_t *rate, uint64_t *unit) { + const char *slash, *rate_str; struct error_record *erec; - const char *slash; slash = strchr(str, '/'); if (!slash) - return error(loc, "wrong rate format"); + return error(loc, "wrong rate format, expecting {bytes,kbytes,mbytes}/{second,minute,hour,day,week}"); + + rate_str = strndup(str, slash - str); + if (!rate_str) + memory_allocation_error(); + + erec = data_unit_parse(loc, rate_str, rate); + free_const(rate_str); - erec = data_unit_parse(loc, str, rate); if (erec != NULL) return erec; @@ -1254,11 +1554,35 @@ static const struct symbol_table boolean_tbl = { }, }; +static struct error_record *boolean_type_parse(struct parse_ctx *ctx, + const struct expr *sym, + struct expr **res) +{ + struct error_record *erec; + int num; + + erec = integer_type_parse(ctx, sym, res); + if (erec) + return erec; + + if (mpz_cmp_ui((*res)->value, 0)) + num = 1; + else + num = 0; + + expr_free(*res); + + *res = constant_expr_alloc(&sym->location, &boolean_type, + BYTEORDER_HOST_ENDIAN, 1, &num); + return NULL; +} + const struct datatype boolean_type = { .type = TYPE_BOOLEAN, .name = "boolean", .desc = "boolean type", .size = 1, + .parse = boolean_type_parse, .basetype = &integer_type, .sym_tbl = &boolean_tbl, .json = boolean_type_json, @@ -1329,3 +1653,92 @@ const struct datatype policy_type = { .desc = "policy type", .parse = policy_type_parse, }; + +#define SYSFS_CGROUPSV2_PATH "/sys/fs/cgroup" + +static char *cgroupv2_get_path(const char *path, uint64_t id) +{ + char dent_name[PATH_MAX + 1]; + char *cgroup_path = NULL; + struct dirent *dent; + struct stat st; + DIR *d; + + d = opendir(path); + if (!d) + return NULL; + + while ((dent = readdir(d)) != NULL) { + if (!strcmp(dent->d_name, ".") || + !strcmp(dent->d_name, "..")) + continue; + + snprintf(dent_name, sizeof(dent_name), "%s/%s", + path, dent->d_name); + dent_name[sizeof(dent_name) - 1] = '\0'; + + if (dent->d_ino == id) { + cgroup_path = xstrdup(dent_name); + break; + } + + if (stat(dent_name, &st) >= 0 && S_ISDIR(st.st_mode)) { + cgroup_path = cgroupv2_get_path(dent_name, id); + if (cgroup_path) + break; + } + } + closedir(d); + + return cgroup_path; +} + +static void cgroupv2_type_print(const struct expr *expr, + struct output_ctx *octx) +{ + uint64_t id = mpz_get_uint64(expr->value); + char *cgroup_path; + + cgroup_path = cgroupv2_get_path(SYSFS_CGROUPSV2_PATH, id); + if (cgroup_path) + nft_print(octx, "\"%s\"", + &cgroup_path[strlen(SYSFS_CGROUPSV2_PATH) + 1]); + else + nft_print(octx, "%" PRIu64, id); + + free(cgroup_path); +} + +static struct error_record *cgroupv2_type_parse(struct parse_ctx *ctx, + const struct expr *sym, + struct expr **res) +{ + char cgroupv2_path[PATH_MAX + 1]; + struct stat st; + uint64_t ino; + + snprintf(cgroupv2_path, sizeof(cgroupv2_path), "%s/%s", + SYSFS_CGROUPSV2_PATH, sym->identifier); + cgroupv2_path[sizeof(cgroupv2_path) - 1] = '\0'; + + if (stat(cgroupv2_path, &st) < 0) + return error(&sym->location, "cgroupv2 path fails: %s", + strerror(errno)); + + ino = st.st_ino; + *res = constant_expr_alloc(&sym->location, &cgroupv2_type, + BYTEORDER_HOST_ENDIAN, + sizeof(ino) * BITS_PER_BYTE, &ino); + return NULL; +} + +const struct datatype cgroupv2_type = { + .type = TYPE_CGROUPV2, + .name = "cgroupsv2", + .desc = "cgroupsv2 path", + .byteorder = BYTEORDER_HOST_ENDIAN, + .size = 8 * BITS_PER_BYTE, + .basetype = &integer_type, + .print = cgroupv2_type_print, + .parse = cgroupv2_type_parse, +}; diff --git a/src/dccpopt.c b/src/dccpopt.c new file mode 100644 index 00000000..ebb645a9 --- /dev/null +++ b/src/dccpopt.c @@ -0,0 +1,277 @@ +#include <nft.h> + +#include <stddef.h> + +#include <datatype.h> +#include <dccpopt.h> +#include <expression.h> +#include <nftables.h> +#include <utils.h> + +#define PHT(__token, __offset, __len) \ + PROTO_HDR_TEMPLATE(__token, &integer_type, BYTEORDER_BIG_ENDIAN, \ + __offset, __len) + +static const struct proto_hdr_template dccpopt_unknown_template = + PROTO_HDR_TEMPLATE("unknown", &invalid_type, BYTEORDER_INVALID, 0, 0); + +/* + * Option DCCP- Section + * Type Length Meaning Data? Reference + * ---- ------ ------- ----- --------- + * 0 1 Padding Y 5.8.1 + * 1 1 Mandatory N 5.8.2 + * 2 1 Slow Receiver Y 11.6 + * 3-31 1 Reserved + * 32 variable Change L N 6.1 + * 33 variable Confirm L N 6.2 + * 34 variable Change R N 6.1 + * 35 variable Confirm R N 6.2 + * 36 variable Init Cookie N 8.1.4 + * 37 3-8 NDP Count Y 7.7 + * 38 variable Ack Vector [Nonce 0] N 11.4 + * 39 variable Ack Vector [Nonce 1] N 11.4 + * 40 variable Data Dropped N 11.7 + * 41 6 Timestamp Y 13.1 + * 42 6/8/10 Timestamp Echo Y 13.3 + * 43 4/6 Elapsed Time N 13.2 + * 44 6 Data Checksum Y 9.3 + * 45-127 variable Reserved + * 128-255 variable CCID-specific options - 10.3 + */ + +static const struct exthdr_desc dccpopt_padding = { + .name = "padding", + .type = DCCPOPT_PADDING, + .templates = { + [DCCPOPT_FIELD_TYPE] = PHT("type", 0, 8), + }, +}; + +static const struct exthdr_desc dccpopt_mandatory = { + .name = "mandatory", + .type = DCCPOPT_MANDATORY, + .templates = { + [DCCPOPT_FIELD_TYPE] = PHT("type", 0, 8), + }, +}; + +static const struct exthdr_desc dccpopt_slow_receiver = { + .name = "slow_receiver", + .type = DCCPOPT_SLOW_RECEIVER, + .templates = { + [DCCPOPT_FIELD_TYPE] = PHT("type", 0, 8), + }, +}; + +static const struct exthdr_desc dccpopt_reserved_short = { + .name = "reserved_short", + .type = DCCPOPT_RESERVED_SHORT, + .templates = { + [DCCPOPT_FIELD_TYPE] = PHT("type", 0, 8), + }, +}; + +static const struct exthdr_desc dccpopt_change_l = { + .name = "change_l", + .type = DCCPOPT_CHANGE_L, + .templates = { + [DCCPOPT_FIELD_TYPE] = PHT("type", 0, 8) + }, +}; + +static const struct exthdr_desc dccpopt_confirm_l = { + .name = "confirm_l", + .type = DCCPOPT_CONFIRM_L, + .templates = { + [DCCPOPT_FIELD_TYPE] = PHT("type", 0, 8), + }, +}; + +static const struct exthdr_desc dccpopt_change_r = { + .name = "change_r", + .type = DCCPOPT_CHANGE_R, + .templates = { + [DCCPOPT_FIELD_TYPE] = PHT("type", 0, 8), + }, +}; + +static const struct exthdr_desc dccpopt_confirm_r = { + .name = "confirm_r", + .type = DCCPOPT_CONFIRM_R, + .templates = { + [DCCPOPT_FIELD_TYPE] = PHT("type", 0, 8), + }, +}; + +static const struct exthdr_desc dccpopt_init_cookie = { + .name = "init_cookie", + .type = DCCPOPT_INIT_COOKIE, + .templates = { + [DCCPOPT_FIELD_TYPE] = PHT("type", 0, 8), + }, +}; + +static const struct exthdr_desc dccpopt_ndp_count = { + .name = "ndp_count", + .type = DCCPOPT_NDP_COUNT, + .templates = { + [DCCPOPT_FIELD_TYPE] = PHT("type", 0, 8), + }, +}; + +static const struct exthdr_desc dccpopt_ack_vector_nonce_0 = { + .name = "ack_vector_nonce_0", + .type = DCCPOPT_ACK_VECTOR_NONCE_0, + .templates = { + [DCCPOPT_FIELD_TYPE] = PHT("type", 0, 8), + }, +}; + +static const struct exthdr_desc dccpopt_ack_vector_nonce_1 = { + .name = "ack_vector_nonce_1", + .type = DCCPOPT_ACK_VECTOR_NONCE_1, + .templates = { + [DCCPOPT_FIELD_TYPE] = PHT("type", 0, 8), + }, +}; + +static const struct exthdr_desc dccpopt_data_dropped = { + .name = "data_dropped", + .type = DCCPOPT_DATA_DROPPED, + .templates = { + [DCCPOPT_FIELD_TYPE] = PHT("type", 0, 8), + }, +}; + +static const struct exthdr_desc dccpopt_timestamp = { + .name = "timestamp", + .type = DCCPOPT_TIMESTAMP, + .templates = { + [DCCPOPT_FIELD_TYPE] = PHT("type", 0, 8), + }, +}; + +static const struct exthdr_desc dccpopt_timestamp_echo = { + .name = "timestamp_echo", + .type = DCCPOPT_TIMESTAMP_ECHO, + .templates = { + [DCCPOPT_FIELD_TYPE] = PHT("type", 0, 8), + }, +}; + +static const struct exthdr_desc dccpopt_elapsed_time = { + .name = "elapsed_time", + .type = DCCPOPT_ELAPSED_TIME, + .templates = { + [DCCPOPT_FIELD_TYPE] = PHT("type", 0, 8), + }, +}; + +static const struct exthdr_desc dccpopt_data_checksum = { + .name = "data_checksum", + .type = DCCPOPT_DATA_CHECKSUM, + .templates = { + [DCCPOPT_FIELD_TYPE] = PHT("type", 0, 8), + }, +}; + +static const struct exthdr_desc dccpopt_reserved_long = { + .name = "reserved_long", + .type = DCCPOPT_RESERVED_LONG, + .templates = { + [DCCPOPT_FIELD_TYPE] = PHT("type", 0, 8), + }, +}; + +static const struct exthdr_desc dccpopt_ccid_specific = { + .name = "ccid_specific", + .type = DCCPOPT_CCID_SPECIFIC, + .templates = { + [DCCPOPT_FIELD_TYPE] = PHT("type", 0, 8), + }, +}; + +const struct exthdr_desc *dccpopt_protocols[1 + UINT8_MAX] = { + [DCCPOPT_PADDING] = &dccpopt_padding, + [DCCPOPT_MANDATORY] = &dccpopt_mandatory, + [DCCPOPT_SLOW_RECEIVER] = &dccpopt_slow_receiver, + [DCCPOPT_RESERVED_SHORT] = &dccpopt_reserved_short, + [DCCPOPT_CHANGE_L] = &dccpopt_change_l, + [DCCPOPT_CONFIRM_L] = &dccpopt_confirm_l, + [DCCPOPT_CHANGE_R] = &dccpopt_change_r, + [DCCPOPT_CONFIRM_R] = &dccpopt_confirm_r, + [DCCPOPT_INIT_COOKIE] = &dccpopt_init_cookie, + [DCCPOPT_NDP_COUNT] = &dccpopt_ndp_count, + [DCCPOPT_ACK_VECTOR_NONCE_0] = &dccpopt_ack_vector_nonce_0, + [DCCPOPT_ACK_VECTOR_NONCE_1] = &dccpopt_ack_vector_nonce_1, + [DCCPOPT_DATA_DROPPED] = &dccpopt_data_dropped, + [DCCPOPT_TIMESTAMP] = &dccpopt_timestamp, + [DCCPOPT_TIMESTAMP_ECHO] = &dccpopt_timestamp_echo, + [DCCPOPT_ELAPSED_TIME] = &dccpopt_elapsed_time, + [DCCPOPT_DATA_CHECKSUM] = &dccpopt_data_checksum, + [DCCPOPT_RESERVED_LONG] = &dccpopt_reserved_long, + [DCCPOPT_CCID_SPECIFIC] = &dccpopt_ccid_specific, +}; + +const struct exthdr_desc * +dccpopt_find_desc(uint8_t type) +{ + enum dccpopt_types proto_idx = + 3 <= type && type <= 31 ? DCCPOPT_RESERVED_SHORT : + 45 <= type && type <= 127 ? DCCPOPT_RESERVED_LONG : + 128 <= type ? DCCPOPT_CCID_SPECIFIC : type; + + return dccpopt_protocols[proto_idx]; +} + +struct expr * +dccpopt_expr_alloc(const struct location *loc, uint8_t type) +{ + const struct proto_hdr_template *tmpl; + const struct exthdr_desc *desc; + struct expr *expr; + + desc = dccpopt_find_desc(type); + tmpl = &desc->templates[DCCPOPT_FIELD_TYPE]; + + expr = expr_alloc(loc, EXPR_EXTHDR, tmpl->dtype, + BYTEORDER_BIG_ENDIAN, BITS_PER_BYTE); + expr->exthdr.desc = desc; + expr->exthdr.tmpl = tmpl; + expr->exthdr.offset = tmpl->offset; + expr->exthdr.raw_type = type; + expr->exthdr.flags = NFT_EXTHDR_F_PRESENT; + expr->exthdr.op = NFT_EXTHDR_OP_DCCP; + + return expr; +} + +void +dccpopt_init_raw(struct expr *expr, uint8_t type, unsigned int offset, + unsigned int len) +{ + const struct proto_hdr_template *tmpl; + const struct exthdr_desc *desc; + + assert(expr->etype == EXPR_EXTHDR); + + desc = dccpopt_find_desc(type); + tmpl = &desc->templates[DCCPOPT_FIELD_TYPE]; + + expr->len = len; + datatype_set(expr, &boolean_type); + + expr->exthdr.offset = offset; + expr->exthdr.desc = desc; + expr->exthdr.flags = NFT_EXTHDR_F_PRESENT; + expr->exthdr.op = NFT_EXTHDR_OP_DCCP; + + /* Make sure that it's the right template based on offset and + * len + */ + if (tmpl->offset != offset || tmpl->len != len) + expr->exthdr.tmpl = &dccpopt_unknown_template; + else + expr->exthdr.tmpl = tmpl; +} @@ -8,12 +8,10 @@ * Development of this code funded by Astaro AG (http://www.astaro.com/) */ -#define _GNU_SOURCE -#include <config.h> +#include <nft.h> + #include <stdio.h> -#include <string.h> #include <stdarg.h> -#include <stdlib.h> #include <netlink.h> #include <gmputil.h> @@ -38,14 +36,15 @@ void erec_add_location(struct error_record *erec, const struct location *loc) { assert(erec->num_locations < EREC_LOCATIONS_MAX); erec->locations[erec->num_locations] = *loc; - erec->locations[erec->num_locations].indesc = loc->indesc; + erec->locations[erec->num_locations].indesc = loc->indesc ? + : &internal_indesc; erec->num_locations++; } void erec_destroy(struct error_record *erec) { - xfree(erec->msg); - xfree(erec); + free(erec->msg); + free(erec); } __attribute__((format(printf, 3, 0))) @@ -80,11 +79,58 @@ struct error_record *erec_create(enum error_record_types type, return erec; } +void print_location(FILE *f, const struct input_descriptor *indesc, + const struct location *loc) +{ + const struct input_descriptor *tmp; + const struct location *iloc; + + if (indesc->location.indesc != NULL) { + const char *prefix = "In file included from"; + iloc = &indesc->location; + for (tmp = iloc->indesc; + tmp != NULL && tmp->type != INDESC_INTERNAL; + tmp = iloc->indesc) { + fprintf(f, "%s %s:%u:%u-%u:\n", prefix, + tmp->name, + iloc->first_line, iloc->first_column, + iloc->last_column); + prefix = " from"; + iloc = &tmp->location; + } + } + if (indesc->type != INDESC_BUFFER && indesc->name) { + fprintf(f, "%s:%u:%u-%u: ", indesc->name, + loc->first_line, loc->first_column, + loc->last_column); + } +} + +const char *line_location(const struct input_descriptor *indesc, + const struct location *loc, char *buf, size_t bufsiz) +{ + const char *line = NULL; + FILE *f; + + f = fopen(indesc->name, "r"); + if (!f) + return NULL; + + if (!fseek(f, loc->line_offset, SEEK_SET) && + fread(buf, 1, bufsiz - 1, f) > 0) { + *strchrnul(buf, '\n') = '\0'; + line = buf; + } + fclose(f); + + return line; +} + void erec_print(struct output_ctx *octx, const struct error_record *erec, unsigned int debug_mask) { - const struct location *loc = erec->locations, *iloc; - const struct input_descriptor *indesc = loc->indesc, *tmp; + const struct location *loc = erec->locations; + const struct input_descriptor *indesc = loc->indesc; const char *line = NULL; char buf[1024] = {}; char *pbuf = NULL; @@ -98,17 +144,13 @@ void erec_print(struct output_ctx *octx, const struct error_record *erec, line = indesc->data; *strchrnul(line, '\n') = '\0'; break; + case INDESC_STDIN: + line = indesc->data; + line += loc->line_offset; + *strchrnul(line, '\n') = '\0'; + break; case INDESC_FILE: - f = fopen(indesc->name, "r"); - if (!f) - break; - - if (!fseek(f, loc->line_offset, SEEK_SET) && - fread(buf, 1, sizeof(buf) - 1, f) > 0) { - *strchrnul(buf, '\n') = '\0'; - line = buf; - } - fclose(f); + line = line_location(indesc, loc, buf, sizeof(buf)); break; case INDESC_INTERNAL: case INDESC_NETLINK: @@ -126,29 +168,15 @@ void erec_print(struct output_ctx *octx, const struct error_record *erec, fprintf(f, "%s\n", erec->msg); for (l = 0; l < (int)erec->num_locations; l++) { loc = &erec->locations[l]; + if (!loc->nle) + continue; netlink_dump_expr(loc->nle, f, debug_mask); } return; } - if (indesc->location.indesc != NULL) { - const char *prefix = "In file included from"; - iloc = &indesc->location; - for (tmp = iloc->indesc; - tmp != NULL && tmp->type != INDESC_INTERNAL; - tmp = iloc->indesc) { - fprintf(f, "%s %s:%u:%u-%u:\n", prefix, - tmp->name, - iloc->first_line, iloc->first_column, - iloc->last_column); - prefix = " from"; - iloc = &tmp->location; - } - } - if (indesc->name != NULL) - fprintf(f, "%s:%u:%u-%u: ", indesc->name, - loc->first_line, loc->first_column, - loc->last_column); + print_location(f, indesc, loc); + if (error_record_names[erec->type]) fprintf(f, "%s: ", error_record_names[erec->type]); fprintf(f, "%s\n", erec->msg); @@ -175,7 +203,7 @@ void erec_print(struct output_ctx *octx, const struct error_record *erec, } pbuf[end] = '\0'; fprintf(f, "%s", pbuf); - xfree(pbuf); + free(pbuf); } fprintf(f, "\n"); } diff --git a/src/evaluate.c b/src/evaluate.c index e7881543..9c905908 100644 --- a/src/evaluate.c +++ b/src/evaluate.c @@ -8,16 +8,17 @@ * Development of this code funded by Astaro AG (http://www.astaro.com/) */ +#include <nft.h> + #include <stddef.h> -#include <stdlib.h> #include <stdio.h> -#include <stdint.h> -#include <string.h> #include <arpa/inet.h> #include <linux/netfilter.h> #include <linux/netfilter_arp.h> #include <linux/netfilter/nf_tables.h> #include <linux/netfilter/nf_synproxy.h> +#include <linux/netfilter/nf_nat.h> +#include <linux/netfilter/nf_log.h> #include <linux/netfilter_ipv4.h> #include <netinet/ip_icmp.h> #include <netinet/icmp6.h> @@ -27,6 +28,7 @@ #include <expression.h> #include <statement.h> +#include <intervals.h> #include <netlink.h> #include <time.h> #include <rule.h> @@ -36,6 +38,13 @@ #include <utils.h> #include <xt.h> +struct proto_ctx *eval_proto_ctx(struct eval_ctx *ctx) +{ + uint8_t idx = ctx->inner_desc ? 1 : 0; + + return &ctx->_pctx[idx]; +} + static int expr_evaluate(struct eval_ctx *ctx, struct expr **expr); static const char * const byteorder_names[] = { @@ -65,6 +74,33 @@ static int __fmtstring(3, 4) set_error(struct eval_ctx *ctx, return -1; } +const char *stmt_name(const struct stmt *stmt) +{ + switch (stmt->type) { + case STMT_NAT: + switch (stmt->nat.type) { + case NFT_NAT_SNAT: + return "snat"; + case NFT_NAT_DNAT: + return "dnat"; + case NFT_NAT_REDIR: + return "redirect"; + case NFT_NAT_MASQ: + return "masquerade"; + } + break; + default: + break; + } + + return stmt_ops(stmt)->name; +} + +static int stmt_error_range(struct eval_ctx *ctx, const struct stmt *stmt, const struct expr *e) +{ + return expr_error(ctx->msgs, e, "%s: range argument not supported", stmt_name(stmt)); +} + static void key_fix_dtype_byteorder(struct expr *key) { const struct datatype *dtype = key->dtype; @@ -72,34 +108,47 @@ static void key_fix_dtype_byteorder(struct expr *key) if (dtype->byteorder == key->byteorder) return; - datatype_set(key, set_datatype_alloc(dtype, key->byteorder)); + __datatype_set(key, set_datatype_alloc(dtype, key->byteorder)); } +static int set_evaluate(struct eval_ctx *ctx, struct set *set); static struct expr *implicit_set_declaration(struct eval_ctx *ctx, const char *name, struct expr *key, - struct expr *expr) + struct expr *data, + struct expr *expr, + uint32_t flags) { struct cmd *cmd; struct set *set; struct handle h; - if (set_is_datamap(expr->set_flags)) + if (set_is_datamap(flags)) key_fix_dtype_byteorder(key); set = set_alloc(&expr->location); - set->flags = NFT_SET_ANONYMOUS | expr->set_flags; + set->flags = flags; set->handle.set.name = xstrdup(name); set->key = key; + set->data = data; set->init = expr; - set->automerge = set->flags & NFT_SET_INTERVAL; + set->automerge = flags & NFT_SET_INTERVAL; + + handle_merge(&set->handle, &ctx->cmd->handle); + + if (set_evaluate(ctx, set) < 0) { + if (set->flags & (NFT_SET_MAP|NFT_SET_OBJECT)) + set->init = NULL; + set_free(set); + return NULL; + } if (ctx->table != NULL) list_add_tail(&set->list, &ctx->table->sets); else { - handle_merge(&set->handle, &ctx->cmd->handle); memset(&h, 0, sizeof(h)); handle_merge(&h, &set->handle); + h.set.location = expr->location; cmd = cmd_alloc(CMD_ADD, CMD_OBJ_SET, &h, &expr->location, set); cmd->location = set->location; list_add_tail(&cmd->list, &ctx->cmd->list); @@ -130,19 +179,63 @@ static enum ops byteorder_conversion_op(struct expr *expr, static int byteorder_conversion(struct eval_ctx *ctx, struct expr **expr, enum byteorder byteorder) { + enum datatypes basetype; enum ops op; assert(!expr_is_constant(*expr) || expr_is_singleton(*expr)); if ((*expr)->byteorder == byteorder) return 0; - if (expr_basetype(*expr)->type != TYPE_INTEGER) + + /* Conversion for EXPR_CONCAT is handled for single composing ranges */ + if ((*expr)->etype == EXPR_CONCAT) { + struct expr *i, *next, *unary; + + list_for_each_entry_safe(i, next, &expr_concat(*expr)->expressions, list) { + if (i->byteorder == BYTEORDER_BIG_ENDIAN) + continue; + + basetype = expr_basetype(i)->type; + if (basetype == TYPE_STRING) + continue; + + assert(basetype == TYPE_INTEGER); + + switch (i->etype) { + case EXPR_VALUE: + if (i->byteorder == BYTEORDER_HOST_ENDIAN) + mpz_switch_byteorder(i->value, div_round_up(i->len, BITS_PER_BYTE)); + break; + default: + if (div_round_up(i->len, BITS_PER_BYTE) >= 2) { + op = byteorder_conversion_op(i, byteorder); + unary = unary_expr_alloc(&i->location, op, i); + if (expr_evaluate(ctx, &unary) < 0) + return -1; + + list_replace(&i->list, &unary->list); + } + } + } + + return 0; + } + + basetype = expr_basetype(*expr)->type; + switch (basetype) { + case TYPE_INTEGER: + break; + case TYPE_STRING: + return 0; + default: return expr_error(ctx->msgs, *expr, - "Byteorder mismatch: expected %s, got %s", + "Byteorder mismatch: %s expected %s, %s got %s", byteorder_names[byteorder], + expr_name(*expr), byteorder_names[(*expr)->byteorder]); + } - if (expr_is_constant(*expr)) + if (expr_is_constant(*expr) || div_round_up((*expr)->len, BITS_PER_BYTE) < 2) (*expr)->byteorder = byteorder; else { op = byteorder_conversion_op(*expr, byteorder); @@ -153,20 +246,6 @@ static int byteorder_conversion(struct eval_ctx *ctx, struct expr **expr, return 0; } -static struct table *table_lookup_global(struct eval_ctx *ctx) -{ - struct table *table; - - if (ctx->table != NULL) - return ctx->table; - - table = table_lookup(&ctx->cmd->handle, &ctx->nft->cache); - if (table == NULL) - return NULL; - - return table; -} - static int table_not_found(struct eval_ctx *ctx) { struct table *table; @@ -177,7 +256,7 @@ static int table_not_found(struct eval_ctx *ctx) "%s", strerror(ENOENT)); return cmd_error(ctx, &ctx->cmd->handle.table.location, - "%s; did you mean table ‘%s’ in family %s?", + "%s; did you mean table '%s' in family %s?", strerror(ENOENT), table->handle.table.name, family2str(table->handle.family)); } @@ -193,7 +272,7 @@ static int chain_not_found(struct eval_ctx *ctx) "%s", strerror(ENOENT)); return cmd_error(ctx, &ctx->cmd->handle.chain.location, - "%s; did you mean chain ‘%s’ in table %s ‘%s’?", + "%s; did you mean chain '%s' in table %s '%s'?", strerror(ENOENT), chain->handle.chain.name, family2str(chain->handle.family), table->handle.table.name); @@ -210,7 +289,7 @@ static int set_not_found(struct eval_ctx *ctx, const struct location *loc, return cmd_error(ctx, loc, "%s", strerror(ENOENT)); return cmd_error(ctx, loc, - "%s; did you mean %s ‘%s’ in table %s ‘%s’?", + "%s; did you mean %s '%s' in table %s '%s'?", strerror(ENOENT), set_is_map(set->flags) ? "map" : "set", set->handle.set.name, @@ -225,11 +304,11 @@ static int flowtable_not_found(struct eval_ctx *ctx, const struct location *loc, struct flowtable *ft; ft = flowtable_lookup_fuzzy(ft_name, &ctx->nft->cache, &table); - if (ft == NULL) + if (!ft) return cmd_error(ctx, loc, "%s", strerror(ENOENT)); return cmd_error(ctx, loc, - "%s; did you mean flowtable ‘%s’ in table %s ‘%s’?", + "%s; did you mean flowtable '%s' in table %s '%s'?", strerror(ENOENT), ft->handle.flowtable.name, family2str(ft->handle.family), table->handle.table.name); @@ -240,7 +319,10 @@ static int flowtable_not_found(struct eval_ctx *ctx, const struct location *loc, */ static int expr_evaluate_symbol(struct eval_ctx *ctx, struct expr **expr) { - struct parse_ctx parse_ctx = { .tbl = &ctx->nft->output.tbl, }; + struct parse_ctx parse_ctx = { + .tbl = &ctx->nft->output.tbl, + .input = &ctx->nft->input, + }; struct error_record *erec; struct table *table; struct set *set; @@ -256,12 +338,14 @@ static int expr_evaluate_symbol(struct eval_ctx *ctx, struct expr **expr) } break; case SYMBOL_SET: - table = table_lookup_global(ctx); + table = table_cache_find(&ctx->nft->cache.table_cache, + ctx->cmd->handle.table.name, + ctx->cmd->handle.family); if (table == NULL) return table_not_found(ctx); - set = set_lookup(table, (*expr)->identifier); - if (set == NULL) + set = set_cache_find(table, (*expr)->identifier); + if (set == NULL || !set->key) return set_not_found(ctx, &(*expr)->location, (*expr)->identifier); @@ -311,8 +395,11 @@ static int expr_evaluate_string(struct eval_ctx *ctx, struct expr **exprp) return 0; } - if (datalen >= 1 && - data[datalen - 1] == '\\') { + if (datalen == 0) + return expr_error(ctx->msgs, expr, + "All-wildcard strings are not supported"); + + if (data[datalen - 1] == '\\') { char unescaped_str[data_len]; memset(unescaped_str, 0, sizeof(unescaped_str)); @@ -325,15 +412,18 @@ static int expr_evaluate_string(struct eval_ctx *ctx, struct expr **exprp) *exprp = value; return 0; } + + data[datalen] = 0; value = constant_expr_alloc(&expr->location, ctx->ectx.dtype, BYTEORDER_HOST_ENDIAN, - datalen * BITS_PER_BYTE, data); + expr->len, data); prefix = prefix_expr_alloc(&expr->location, value, datalen * BITS_PER_BYTE); datatype_set(prefix, ctx->ectx.dtype); prefix->flags |= EXPR_F_CONSTANT; prefix->byteorder = BYTEORDER_HOST_ENDIAN; + prefix->len = expr->len; expr_free(expr); *exprp = prefix; @@ -344,6 +434,7 @@ static int expr_evaluate_integer(struct eval_ctx *ctx, struct expr **exprp) { struct expr *expr = *exprp; char *valstr, *rangestr; + uint32_t masklen; mpz_t mask; if (ctx->ectx.maxval > 0 && @@ -352,24 +443,29 @@ static int expr_evaluate_integer(struct eval_ctx *ctx, struct expr **exprp) expr_error(ctx->msgs, expr, "Value %s exceeds valid range 0-%u", valstr, ctx->ectx.maxval); - free(valstr); + nft_gmp_free(valstr); return -1; } - mpz_init_bitmask(mask, ctx->ectx.len); + if (ctx->stmt_len > ctx->ectx.len) + masklen = ctx->stmt_len; + else + masklen = ctx->ectx.len; + + mpz_init_bitmask(mask, masklen); if (mpz_cmp(expr->value, mask) > 0) { valstr = mpz_get_str(NULL, 10, expr->value); rangestr = mpz_get_str(NULL, 10, mask); expr_error(ctx->msgs, expr, "Value %s exceeds valid range 0-%s", valstr, rangestr); - free(valstr); - free(rangestr); + nft_gmp_free(valstr); + nft_gmp_free(rangestr); mpz_clear(mask); return -1; } expr->byteorder = ctx->ectx.byteorder; - expr->len = ctx->ectx.len; + expr->len = masklen; mpz_clear(mask); return 0; } @@ -386,7 +482,8 @@ static int expr_evaluate_value(struct eval_ctx *ctx, struct expr **expr) return -1; break; default: - BUG("invalid basetype %s\n", expr_basetype(*expr)->name); + return expr_error(ctx->msgs, *expr, "Unexpected datatype %s", + (*expr)->dtype->name); } return 0; } @@ -401,20 +498,34 @@ static int expr_evaluate_primary(struct eval_ctx *ctx, struct expr **expr) return 0; } +int stmt_dependency_evaluate(struct eval_ctx *ctx, struct stmt *stmt) +{ + uint32_t stmt_len = ctx->stmt_len; + + if (stmt_evaluate(ctx, stmt) < 0) + return stmt_error(ctx, stmt, "dependency statement is invalid"); + + ctx->stmt_len = stmt_len; + + return 0; +} + static int -conflict_resolution_gen_dependency(struct eval_ctx *ctx, int protocol, - const struct expr *expr, - struct stmt **res) +ll_conflict_resolution_gen_dependency(struct eval_ctx *ctx, int protocol, + const struct expr *expr, + struct stmt **res) { enum proto_bases base = expr->payload.base; const struct proto_hdr_template *tmpl; const struct proto_desc *desc = NULL; struct expr *dep, *left, *right; + struct proto_ctx *pctx; struct stmt *stmt; assert(expr->payload.base == PROTO_BASE_LL_HDR); - desc = ctx->pctx.protocol[base].desc; + pctx = eval_proto_ctx(ctx); + desc = pctx->protocol[base].desc; tmpl = &desc->templates[desc->protocol_key]; left = payload_expr_alloc(&expr->location, desc, desc->protocol_key); @@ -424,10 +535,13 @@ conflict_resolution_gen_dependency(struct eval_ctx *ctx, int protocol, dep = relational_expr_alloc(&expr->location, OP_EQ, left, right); stmt = expr_stmt_alloc(&dep->location, dep); - if (stmt_evaluate(ctx, stmt) < 0) + if (stmt_dependency_evaluate(ctx, stmt) < 0) return expr_error(ctx->msgs, expr, "dependency statement is invalid"); + if (ctx->inner_desc) + left->payload.inner_desc = ctx->inner_desc; + *res = stmt; return 0; } @@ -448,20 +562,28 @@ static uint8_t expr_offset_shift(const struct expr *expr, unsigned int offset, return shift; } -static void expr_evaluate_bits(struct eval_ctx *ctx, struct expr **exprp) +static int expr_evaluate_bits(struct eval_ctx *ctx, struct expr **exprp) { - struct expr *expr = *exprp, *and, *mask, *lshift, *off; + struct expr *expr = *exprp, *and, *mask, *rshift, *off; unsigned masklen, len = expr->len, extra_len = 0; + enum byteorder byteorder; uint8_t shift; mpz_t bitmask; + /* payload statement with relational expression as a value does not + * require the transformations that are needed for payload matching, + * skip this. + */ + if (ctx->stmt && ctx->stmt->type == STMT_PAYLOAD) + return 0; + switch (expr->etype) { case EXPR_PAYLOAD: shift = expr_offset_shift(expr, expr->payload.offset, &extra_len); break; case EXPR_EXTHDR: - shift = expr_offset_shift(expr, expr->exthdr.tmpl->offset, + shift = expr_offset_shift(expr, expr->exthdr.offset, &extra_len); break; default: @@ -469,7 +591,10 @@ static void expr_evaluate_bits(struct eval_ctx *ctx, struct expr **exprp) } masklen = len + shift; - assert(masklen <= NFT_REG_SIZE * BITS_PER_BYTE); + + if (masklen > NFT_REG_SIZE * BITS_PER_BYTE) + return expr_error(ctx->msgs, expr, "mask length %u exceeds allowed maximum of %u\n", + masklen, NFT_REG_SIZE * BITS_PER_BYTE); mpz_init2(bitmask, masklen); mpz_bitmask(bitmask, len); @@ -478,6 +603,7 @@ static void expr_evaluate_bits(struct eval_ctx *ctx, struct expr **exprp) mask = constant_expr_alloc(&expr->location, expr_basetype(expr), BYTEORDER_HOST_ENDIAN, masklen, NULL); mpz_set(mask->value, bitmask); + mpz_clear(bitmask); and = binop_expr_alloc(&expr->location, OP_AND, expr, mask); and->dtype = expr->dtype; @@ -485,26 +611,39 @@ static void expr_evaluate_bits(struct eval_ctx *ctx, struct expr **exprp) and->len = masklen; if (shift) { + if ((ctx->ectx.key || ctx->stmt_len > 0) && + div_round_up(masklen, BITS_PER_BYTE) > 1) { + int op = byteorder_conversion_op(expr, BYTEORDER_HOST_ENDIAN); + and = unary_expr_alloc(&expr->location, op, and); + and->len = masklen; + byteorder = BYTEORDER_HOST_ENDIAN; + } else { + byteorder = expr->byteorder; + } + off = constant_expr_alloc(&expr->location, expr_basetype(expr), - BYTEORDER_BIG_ENDIAN, + BYTEORDER_HOST_ENDIAN, sizeof(shift), &shift); - lshift = binop_expr_alloc(&expr->location, OP_RSHIFT, and, off); - lshift->dtype = expr->dtype; - lshift->byteorder = expr->byteorder; - lshift->len = masklen; + rshift = binop_expr_alloc(&expr->location, OP_RSHIFT, and, off); + rshift->dtype = expr->dtype; + rshift->byteorder = byteorder; + rshift->len = masklen; - *exprp = lshift; + *exprp = rshift; } else *exprp = and; if (extra_len) expr->len += extra_len; + + return 0; } static int __expr_evaluate_exthdr(struct eval_ctx *ctx, struct expr **exprp) { + const struct expr *key = ctx->ectx.key; struct expr *expr = *exprp; if (expr->exthdr.flags & NFT_EXTHDR_F_PRESENT) @@ -513,18 +652,22 @@ static int __expr_evaluate_exthdr(struct eval_ctx *ctx, struct expr **exprp) if (expr_evaluate_primary(ctx, exprp) < 0) return -1; - if (expr->exthdr.tmpl->offset % BITS_PER_BYTE != 0 || - expr->len % BITS_PER_BYTE != 0) - expr_evaluate_bits(ctx, exprp); + ctx->ectx.key = key; + + if (expr->exthdr.offset % BITS_PER_BYTE != 0 || + expr->len % BITS_PER_BYTE != 0) { + int err = expr_evaluate_bits(ctx, exprp); + + if (err) + return err; + } switch (expr->exthdr.op) { case NFT_EXTHDR_OP_TCPOPT: { static const unsigned int max_tcpoptlen = (15 * 4 - 20) * BITS_PER_BYTE; - unsigned int totlen = 0; + unsigned int totlen; - totlen += expr->exthdr.tmpl->offset; - totlen += expr->exthdr.tmpl->len; - totlen += expr->exthdr.offset; + totlen = expr->exthdr.tmpl->len + expr->exthdr.offset; if (totlen > max_tcpoptlen) return expr_error(ctx->msgs, expr, @@ -534,11 +677,9 @@ static int __expr_evaluate_exthdr(struct eval_ctx *ctx, struct expr **exprp) } case NFT_EXTHDR_OP_IPV4: { static const unsigned int max_ipoptlen = 40 * BITS_PER_BYTE; - unsigned int totlen = 0; + unsigned int totlen; - totlen += expr->exthdr.tmpl->offset; - totlen += expr->exthdr.tmpl->len; - totlen += expr->exthdr.offset; + totlen = expr->exthdr.offset + expr->exthdr.tmpl->len; if (totlen > max_ipoptlen) return expr_error(ctx->msgs, expr, @@ -563,13 +704,14 @@ static int expr_evaluate_exthdr(struct eval_ctx *ctx, struct expr **exprp) const struct proto_desc *base, *dependency = NULL; enum proto_bases pb = PROTO_BASE_NETWORK_HDR; struct expr *expr = *exprp; + struct proto_ctx *pctx; struct stmt *nstmt; switch (expr->exthdr.op) { case NFT_EXTHDR_OP_TCPOPT: - dependency = &proto_tcp; - pb = PROTO_BASE_TRANSPORT_HDR; - break; + case NFT_EXTHDR_OP_SCTP: + case NFT_EXTHDR_OP_DCCP: + return __expr_evaluate_exthdr(ctx, exprp); case NFT_EXTHDR_OP_IPV4: dependency = &proto_ip; break; @@ -581,7 +723,8 @@ static int expr_evaluate_exthdr(struct eval_ctx *ctx, struct expr **exprp) assert(dependency); - base = ctx->pctx.protocol[pb].desc; + pctx = eval_proto_ctx(ctx); + base = pctx->protocol[pb].desc; if (base == dependency) return __expr_evaluate_exthdr(ctx, exprp); @@ -599,7 +742,7 @@ static int expr_evaluate_exthdr(struct eval_ctx *ctx, struct expr **exprp) /* dependency supersede. * - * 'inet' is a 'phony' l2 dependency used by NFPROTO_INET to fulfill network + * 'inet' is a 'phony' l2 dependency used by NFPROTO_INET to fulfil network * header dependency, i.e. ensure that 'ip saddr 1.2.3.4' only sees ip headers. * * If a match expression that depends on a particular L2 header, e.g. ethernet, @@ -611,7 +754,7 @@ static int expr_evaluate_exthdr(struct eval_ctx *ctx, struct expr **exprp) * Example: inet filter in ip saddr 1.2.3.4 ether saddr a:b:c:d:e:f * * ip saddr adds meta dependency on ipv4 packets - * ether saddr adds another dependeny on ethernet frames. + * ether saddr adds another dependency on ethernet frames. */ static int meta_iiftype_gen_dependency(struct eval_ctx *ctx, struct expr *payload, struct stmt **res) @@ -625,9 +768,11 @@ static int meta_iiftype_gen_dependency(struct eval_ctx *ctx, "for this family"); nstmt = meta_stmt_meta_iiftype(&payload->location, type); - if (stmt_evaluate(ctx, nstmt) < 0) - return expr_error(ctx->msgs, payload, - "dependency statement is invalid"); + if (stmt_dependency_evaluate(ctx, nstmt) < 0) + return -1; + + if (ctx->inner_desc) + nstmt->expr->left->meta.inner_desc = ctx->inner_desc; *res = nstmt; return 0; @@ -638,35 +783,90 @@ static bool proto_is_dummy(const struct proto_desc *desc) return desc == &proto_inet || desc == &proto_netdev; } -static int resolve_protocol_conflict(struct eval_ctx *ctx, - const struct proto_desc *desc, - struct expr *payload) +static int stmt_dep_conflict(struct eval_ctx *ctx, const struct stmt *nstmt) +{ + struct stmt *stmt; + + list_for_each_entry(stmt, &ctx->rule->stmts, list) { + if (stmt == nstmt) + break; + + if (stmt->type != STMT_EXPRESSION || + stmt->expr->etype != EXPR_RELATIONAL || + stmt->expr->right->etype != EXPR_VALUE || + stmt->expr->left->etype != EXPR_PAYLOAD || + stmt->expr->left->etype != nstmt->expr->left->etype || + stmt->expr->left->len != nstmt->expr->left->len) + continue; + + if (stmt->expr->left->payload.desc != nstmt->expr->left->payload.desc || + stmt->expr->left->payload.inner_desc != nstmt->expr->left->payload.inner_desc || + stmt->expr->left->payload.base != nstmt->expr->left->payload.base || + stmt->expr->left->payload.offset != nstmt->expr->left->payload.offset) + continue; + + return stmt_binary_error(ctx, stmt, nstmt, + "conflicting statements"); + } + + return 0; +} + +static int rule_stmt_dep_add(struct eval_ctx *ctx, + struct stmt *nstmt, struct stmt *stmt) +{ + rule_stmt_insert_at(ctx->rule, nstmt, ctx->stmt); + + if (stmt_dep_conflict(ctx, nstmt) < 0) + return -1; + + return 0; +} + +static int resolve_ll_protocol_conflict(struct eval_ctx *ctx, + const struct proto_desc *desc, + struct expr *payload) { enum proto_bases base = payload->payload.base; struct stmt *nstmt = NULL; + struct proto_ctx *pctx; + unsigned int i; int link, err; - if (payload->payload.base == PROTO_BASE_LL_HDR && - proto_is_dummy(desc)) { - err = meta_iiftype_gen_dependency(ctx, payload, &nstmt); - if (err < 0) - return err; + assert(base == PROTO_BASE_LL_HDR); - list_add_tail(&nstmt->list, &ctx->stmt->list); - } + pctx = eval_proto_ctx(ctx); - assert(base <= PROTO_BASE_MAX); - /* This payload and the existing context don't match, conflict. */ - if (ctx->pctx.protocol[base + 1].desc != NULL) - return 1; + if (proto_is_dummy(desc)) { + if (ctx->inner_desc) { + proto_ctx_update(pctx, PROTO_BASE_LL_HDR, &payload->location, &proto_eth); + } else { + err = meta_iiftype_gen_dependency(ctx, payload, &nstmt); + if (err < 0) + return err; + + desc = payload->payload.desc; + if (rule_stmt_dep_add(ctx, nstmt, ctx->stmt) < 0) + return -1; + } + } else { + /* payload desc stored in the L2 header stack? No conflict. */ + for (i = 0; i < pctx->stacked_ll_count; i++) { + if (pctx->stacked_ll[i] == payload->payload.desc) + return 0; + } + } link = proto_find_num(desc, payload->payload.desc); if (link < 0 || - conflict_resolution_gen_dependency(ctx, link, payload, &nstmt) < 0) + ll_conflict_resolution_gen_dependency(ctx, link, payload, &nstmt) < 0) return 1; - payload->payload.offset += ctx->pctx.protocol[base].offset; - list_add_tail(&nstmt->list, &ctx->stmt->list); + for (i = 0; i < pctx->stacked_ll_count; i++) + payload->payload.offset += pctx->stacked_ll[i]->length; + + if (rule_stmt_dep_add(ctx, nstmt, ctx->stmt) < 0) + return -1; return 0; } @@ -681,44 +881,106 @@ static int __expr_evaluate_payload(struct eval_ctx *ctx, struct expr *expr) struct expr *payload = expr; enum proto_bases base = payload->payload.base; const struct proto_desc *desc; + struct proto_ctx *pctx; struct stmt *nstmt; int err; if (expr->etype == EXPR_PAYLOAD && expr->payload.is_raw) return 0; - desc = ctx->pctx.protocol[base].desc; + pctx = eval_proto_ctx(ctx); + desc = pctx->protocol[base].desc; if (desc == NULL) { if (payload_gen_dependency(ctx, payload, &nstmt) < 0) return -1; - list_add_tail(&nstmt->list, &ctx->stmt->list); - } else { - /* No conflict: Same payload protocol as context, adjust offset - * if needed. - */ - if (desc == payload->payload.desc) { - payload->payload.offset += - ctx->pctx.protocol[base].offset; - return 0; - } - /* If we already have context and this payload is on the same - * base, try to resolve the protocol conflict. - */ - if (payload->payload.base == desc->base) { - err = resolve_protocol_conflict(ctx, desc, payload); - if (err <= 0) - return err; - desc = ctx->pctx.protocol[base].desc; - if (desc == payload->payload.desc) - return 0; + if (rule_stmt_dep_add(ctx, nstmt, ctx->stmt) < 0) + return -1; + + desc = pctx->protocol[base].desc; + + if (desc == expr->payload.desc) + goto check_icmp; + + if (base == PROTO_BASE_LL_HDR) { + int link; + + link = proto_find_num(desc, payload->payload.desc); + if (link < 0 || + ll_conflict_resolution_gen_dependency(ctx, link, payload, &nstmt) < 0) + return expr_error(ctx->msgs, payload, + "conflicting protocols specified: %s vs. %s", + desc->name, + payload->payload.desc->name); + + assert(pctx->stacked_ll_count); + payload->payload.offset += pctx->stacked_ll[0]->length; + + if (rule_stmt_dep_add(ctx, nstmt, ctx->stmt) < 0) + return -1; + + return 1; } + goto check_icmp; + } + + if (payload->payload.base == desc->base && + proto_ctx_is_ambiguous(pctx, base)) { + desc = proto_ctx_find_conflict(pctx, base, payload->payload.desc); + assert(desc); + return expr_error(ctx->msgs, payload, "conflicting protocols specified: %s vs. %s", - ctx->pctx.protocol[base].desc->name, + desc->name, payload->payload.desc->name); } - return 0; + + /* No conflict: Same payload protocol as context, adjust offset + * if needed. + */ + if (desc == payload->payload.desc) { + const struct proto_hdr_template *tmpl; + + if (desc->base == PROTO_BASE_LL_HDR) { + unsigned int i; + + for (i = 0; i < pctx->stacked_ll_count; i++) + payload->payload.offset += pctx->stacked_ll[i]->length; + } +check_icmp: + if (desc != &proto_icmp && desc != &proto_icmp6) + return 0; + + tmpl = expr->payload.tmpl; + + if (!tmpl || !tmpl->icmp_dep) + return 0; + + if (payload_gen_icmp_dependency(ctx, expr, &nstmt) < 0) + return -1; + + if (nstmt && rule_stmt_dep_add(ctx, nstmt, ctx->stmt) < 0) + return -1; + + return 0; + } + /* If we already have context and this payload is on the same + * base, try to resolve the protocol conflict. + */ + if (base == PROTO_BASE_LL_HDR) { + err = resolve_ll_protocol_conflict(ctx, desc, payload); + if (err <= 0) + return err; + + desc = pctx->protocol[base].desc; + if (desc == payload->payload.desc) + return 0; + } + return expr_error(ctx->msgs, payload, + "conflicting %s protocols specified: %s vs. %s", + proto_base_names[base], + pctx->protocol[base].desc->name, + payload->payload.desc->name); } static bool payload_needs_adjustment(const struct expr *expr) @@ -729,20 +991,94 @@ static bool payload_needs_adjustment(const struct expr *expr) static int expr_evaluate_payload(struct eval_ctx *ctx, struct expr **exprp) { + const struct expr *key = ctx->ectx.key; struct expr *expr = *exprp; + if (expr->payload.evaluated) + return 0; + if (__expr_evaluate_payload(ctx, expr) < 0) return -1; if (expr_evaluate_primary(ctx, exprp) < 0) return -1; - if (payload_needs_adjustment(expr)) - expr_evaluate_bits(ctx, exprp); + ctx->ectx.key = key; + + if (payload_needs_adjustment(expr)) { + int err = expr_evaluate_bits(ctx, exprp); + + if (err) + return err; + } + + expr->payload.evaluated = true; return 0; } +static int expr_evaluate_inner(struct eval_ctx *ctx, struct expr **exprp) +{ + struct proto_ctx *pctx = eval_proto_ctx(ctx); + const struct proto_desc *desc = NULL; + struct expr *expr = *exprp; + int ret; + + assert(expr->etype == EXPR_PAYLOAD); + + pctx = eval_proto_ctx(ctx); + desc = pctx->protocol[PROTO_BASE_TRANSPORT_HDR].desc; + + if (desc == NULL && + expr->payload.inner_desc->base < PROTO_BASE_INNER_HDR) { + struct stmt *nstmt; + + if (payload_gen_inner_dependency(ctx, expr, &nstmt) < 0) + return -1; + + if (rule_stmt_dep_add(ctx, nstmt, ctx->stmt) < 0) + return -1; + + proto_ctx_update(pctx, PROTO_BASE_TRANSPORT_HDR, &expr->location, expr->payload.inner_desc); + } + + if (expr->payload.inner_desc->base == PROTO_BASE_INNER_HDR) { + desc = pctx->protocol[expr->payload.inner_desc->base - 1].desc; + if (!desc) { + return expr_error(ctx->msgs, expr, + "no transport protocol specified"); + } + + if (proto_find_num(desc, expr->payload.inner_desc) < 0) { + return expr_error(ctx->msgs, expr, + "unexpected transport protocol %s", + desc->name); + } + + proto_ctx_update(pctx, expr->payload.inner_desc->base, &expr->location, + expr->payload.inner_desc); + } + + if (expr->payload.base != PROTO_BASE_INNER_HDR) + ctx->inner_desc = expr->payload.inner_desc; + + ret = expr_evaluate_payload(ctx, exprp); + + return ret; +} + +static int expr_evaluate_payload_inner(struct eval_ctx *ctx, struct expr **exprp) +{ + int ret; + + if ((*exprp)->payload.inner_desc) + ret = expr_evaluate_inner(ctx, exprp); + else + ret = expr_evaluate_payload(ctx, exprp); + + return ret; +} + /* * RT expression: validate protocol dependencies. */ @@ -750,20 +1086,22 @@ static int expr_evaluate_rt(struct eval_ctx *ctx, struct expr **expr) { static const char emsg[] = "cannot determine ip protocol version, use \"ip nexthop\" or \"ip6 nexthop\" instead"; struct expr *rt = *expr; + struct proto_ctx *pctx; - rt_expr_update_type(&ctx->pctx, rt); + pctx = eval_proto_ctx(ctx); + rt_expr_update_type(pctx, rt); switch (rt->rt.key) { case NFT_RT_NEXTHOP4: if (rt->dtype != &ipaddr_type) return expr_error(ctx->msgs, rt, "%s", emsg); - if (ctx->pctx.family == NFPROTO_IPV6) + if (pctx->family == NFPROTO_IPV6) return expr_error(ctx->msgs, rt, "%s nexthop will not match", "ip"); break; case NFT_RT_NEXTHOP6: if (rt->dtype != &ip6addr_type) return expr_error(ctx->msgs, rt, "%s", emsg); - if (ctx->pctx.family == NFPROTO_IPV4) + if (pctx->family == NFPROTO_IPV4) return expr_error(ctx->msgs, rt, "%s nexthop will not match", "ip6"); break; default: @@ -778,8 +1116,10 @@ static int ct_gen_nh_dependency(struct eval_ctx *ctx, struct expr *ct) const struct proto_desc *base, *base_now; struct expr *left, *right, *dep; struct stmt *nstmt = NULL; + struct proto_ctx *pctx; - base_now = ctx->pctx.protocol[PROTO_BASE_NETWORK_HDR].desc; + pctx = eval_proto_ctx(ctx); + base_now = pctx->protocol[PROTO_BASE_NETWORK_HDR].desc; switch (ct->ct.nfproto) { case NFPROTO_IPV4: @@ -789,10 +1129,10 @@ static int ct_gen_nh_dependency(struct eval_ctx *ctx, struct expr *ct) base = &proto_ip6; break; default: - base = ctx->pctx.protocol[PROTO_BASE_NETWORK_HDR].desc; + base = pctx->protocol[PROTO_BASE_NETWORK_HDR].desc; if (base == &proto_ip) ct->ct.nfproto = NFPROTO_IPV4; - else if (base == &proto_ip) + else if (base == &proto_ip6) ct->ct.nfproto = NFPROTO_IPV6; if (base) @@ -811,8 +1151,8 @@ static int ct_gen_nh_dependency(struct eval_ctx *ctx, struct expr *ct) return expr_error(ctx->msgs, ct, "conflicting dependencies: %s vs. %s\n", base->name, - ctx->pctx.protocol[PROTO_BASE_NETWORK_HDR].desc->name); - switch (ctx->pctx.family) { + pctx->protocol[PROTO_BASE_NETWORK_HDR].desc->name); + switch (pctx->family) { case NFPROTO_IPV4: case NFPROTO_IPV6: return 0; @@ -825,11 +1165,13 @@ static int ct_gen_nh_dependency(struct eval_ctx *ctx, struct expr *ct) constant_data_ptr(ct->ct.nfproto, left->len)); dep = relational_expr_alloc(&ct->location, OP_EQ, left, right); - relational_expr_pctx_update(&ctx->pctx, dep); + relational_expr_pctx_update(pctx, dep); nstmt = expr_stmt_alloc(&dep->location, dep); - list_add_tail(&nstmt->list, &ctx->stmt->list); + if (rule_stmt_dep_add(ctx, nstmt, ctx->stmt) < 0) + return -1; + return 0; } @@ -841,13 +1183,16 @@ static int expr_evaluate_ct(struct eval_ctx *ctx, struct expr **expr) { const struct proto_desc *base, *error; struct expr *ct = *expr; + struct proto_ctx *pctx; - base = ctx->pctx.protocol[PROTO_BASE_NETWORK_HDR].desc; + pctx = eval_proto_ctx(ctx); + base = pctx->protocol[PROTO_BASE_NETWORK_HDR].desc; switch (ct->ct.key) { case NFT_CT_SRC: case NFT_CT_DST: - ct_gen_nh_dependency(ctx, ct); + if (ct_gen_nh_dependency(ctx, ct) < 0) + return -1; break; case NFT_CT_SRC_IP: case NFT_CT_DST_IP: @@ -867,13 +1212,13 @@ static int expr_evaluate_ct(struct eval_ctx *ctx, struct expr **expr) break; } - ct_expr_update_type(&ctx->pctx, ct); + ct_expr_update_type(pctx, ct); return expr_evaluate_primary(ctx, expr); err_conflict: return stmt_binary_error(ctx, ct, - &ctx->pctx.protocol[PROTO_BASE_NETWORK_HDR], + &pctx->protocol[PROTO_BASE_NETWORK_HDR], "conflicting protocols specified: %s vs. %s", base->name, error->name); } @@ -920,7 +1265,6 @@ static int expr_evaluate_prefix(struct eval_ctx *ctx, struct expr **expr) mpz_prefixmask(mask->value, base->len, prefix->prefix_len); break; case TYPE_STRING: - mpz_init2(mask->value, base->len); mpz_bitmask(mask->value, prefix->prefix_len); break; } @@ -931,7 +1275,7 @@ static int expr_evaluate_prefix(struct eval_ctx *ctx, struct expr **expr) base = prefix->prefix; assert(expr_is_constant(base)); - prefix->dtype = base->dtype; + prefix->dtype = datatype_get(base->dtype); prefix->byteorder = base->byteorder; prefix->len = base->len; prefix->flags |= EXPR_F_CONSTANT; @@ -970,21 +1314,26 @@ static int __expr_evaluate_range(struct eval_ctx *ctx, struct expr **expr) return 0; } -static int expr_evaluate_range(struct eval_ctx *ctx, struct expr **expr) +static int expr_evaluate_range(struct eval_ctx *ctx, struct expr **exprp) { - struct expr *range = *expr, *left, *right; + struct expr *range = *exprp, *left, *right; int rc; - rc = __expr_evaluate_range(ctx, expr); + rc = __expr_evaluate_range(ctx, exprp); if (rc) return rc; left = range->left; right = range->right; - if (mpz_cmp(left->value, right->value) >= 0) - return expr_error(ctx->msgs, range, - "Range has zero or negative size"); + if (mpz_cmp(left->value, right->value) > 0) + return expr_error(ctx->msgs, range, "Range negative size"); + + if (mpz_cmp(left->value, right->value) == 0) { + *exprp = expr_get(left); + expr_free(range); + return 0; + } datatype_set(range, left->dtype); range->flags |= EXPR_F_CONSTANT; @@ -997,12 +1346,10 @@ static int expr_evaluate_range(struct eval_ctx *ctx, struct expr **expr) */ static int expr_evaluate_unary(struct eval_ctx *ctx, struct expr **expr) { - struct expr *unary = *expr, *arg; + struct expr *unary = *expr, *arg = unary->arg; enum byteorder byteorder; - if (expr_evaluate(ctx, &unary->arg) < 0) - return -1; - arg = unary->arg; + /* unary expression arguments has already been evaluated. */ assert(!expr_is_constant(arg)); assert(expr_basetype(arg)->type == TYPE_INTEGER); @@ -1021,7 +1368,7 @@ static int expr_evaluate_unary(struct eval_ctx *ctx, struct expr **expr) BUG("invalid unary operation %u\n", unary->op); } - unary->dtype = arg->dtype; + __datatype_set(unary, datatype_clone(arg->dtype)); unary->byteorder = byteorder; unary->len = arg->len; return 0; @@ -1088,14 +1435,24 @@ static int constant_binop_simplify(struct eval_ctx *ctx, struct expr **expr) static int expr_evaluate_shift(struct eval_ctx *ctx, struct expr **expr) { struct expr *op = *expr, *left = op->left, *right = op->right; + unsigned int shift, max_shift_len; - if (mpz_get_uint32(right->value) >= left->len) + /* mpz_get_uint32 has assert() for huge values */ + if (mpz_cmp_ui(right->value, UINT_MAX) > 0) return expr_binary_error(ctx->msgs, right, left, - "%s shift of %u bits is undefined " - "for type of %u bits width", + "shifts exceeding %u bits are not supported", UINT_MAX); + + shift = mpz_get_uint32(right->value); + if (ctx->stmt_len > left->len) + max_shift_len = ctx->stmt_len; + else + max_shift_len = left->len; + + if (shift >= max_shift_len) + return expr_binary_error(ctx->msgs, right, left, + "%s shift of %u bits is undefined for type of %u bits width", op->op == OP_LSHIFT ? "Left" : "Right", - mpz_get_uint32(right->value), - left->len); + shift, max_shift_len); /* Both sides need to be in host byte order */ if (byteorder_conversion(ctx, &op->left, BYTEORDER_HOST_ENDIAN) < 0) @@ -1104,9 +1461,9 @@ static int expr_evaluate_shift(struct eval_ctx *ctx, struct expr **expr) if (byteorder_conversion(ctx, &op->right, BYTEORDER_HOST_ENDIAN) < 0) return -1; - op->dtype = &integer_type; + datatype_set(op, &integer_type); op->byteorder = BYTEORDER_HOST_ENDIAN; - op->len = left->len; + op->len = max_shift_len; if (expr_is_constant(left)) return constant_binop_simplify(ctx, expr); @@ -1116,41 +1473,82 @@ static int expr_evaluate_shift(struct eval_ctx *ctx, struct expr **expr) static int expr_evaluate_bitwise(struct eval_ctx *ctx, struct expr **expr) { struct expr *op = *expr, *left = op->left; + const struct datatype *dtype; + enum byteorder byteorder; + unsigned int max_len; + + if (ctx->stmt_len > left->len) { + max_len = ctx->stmt_len; + byteorder = BYTEORDER_HOST_ENDIAN; + dtype = &integer_type; + + /* Both sides need to be in host byte order */ + if (byteorder_conversion(ctx, &op->left, BYTEORDER_HOST_ENDIAN) < 0) + return -1; + + left = op->left; + } else { + max_len = left->len; + byteorder = left->byteorder; + dtype = left->dtype; + } - if (byteorder_conversion(ctx, &op->right, left->byteorder) < 0) + if (byteorder_conversion(ctx, &op->right, byteorder) < 0) return -1; - op->dtype = left->dtype; - op->byteorder = left->byteorder; - op->len = left->len; + datatype_set(op, dtype); + op->byteorder = byteorder; + op->len = max_len; - if (expr_is_constant(left)) + if (expr_is_constant(left) && expr_is_constant(op->right)) return constant_binop_simplify(ctx, expr); return 0; } /* - * Binop expression: both sides must be of integer base type. The left - * hand side may be either constant or non-constant; in case its constant - * it must be a singleton. The ride hand side must always be a constant - * singleton. + * Binop expression: both sides must be of integer base type. The left-hand side + * may be either constant or non-constant; if it is constant, it must be a + * singleton. For bitwise operations, the right-hand side must be constant if + * the left-hand side is constant; the right-hand side may be constant or + * non-constant, if the left-hand side is non-constant; for shifts, the + * right-hand side must be constant; if it is constant, it must be a singleton. */ static int expr_evaluate_binop(struct eval_ctx *ctx, struct expr **expr) { struct expr *op = *expr, *left, *right; const char *sym = expr_op_symbols[op->op]; + unsigned int max_shift_len = ctx->ectx.len; + int ret = -1; + + if (ctx->recursion.binop >= USHRT_MAX) + return expr_binary_error(ctx->msgs, op, NULL, + "Binary operation limit %u reached ", + ctx->recursion.binop); + ctx->recursion.binop++; if (expr_evaluate(ctx, &op->left) < 0) return -1; left = op->left; - if (op->op == OP_LSHIFT || op->op == OP_RSHIFT) + if (op->op == OP_LSHIFT || op->op == OP_RSHIFT) { + if (left->len > max_shift_len) + max_shift_len = left->len; + __expr_set_context(&ctx->ectx, &integer_type, - left->byteorder, ctx->ectx.len, 0); + left->byteorder, max_shift_len, 0); + } + if (expr_evaluate(ctx, &op->right) < 0) return -1; right = op->right; + /* evaluation expects constant to the right hand side. */ + if (expr_is_constant(left) && !expr_is_constant(right)) { + range_expr_swap_values(op); + left = op->left; + right = op->right; + } + switch (expr_basetype(left)->type) { case TYPE_INTEGER: case TYPE_STRING: @@ -1168,31 +1566,73 @@ static int expr_evaluate_binop(struct eval_ctx *ctx, struct expr **expr) "for %s expressions", sym, expr_name(left)); - if (!expr_is_constant(right)) - return expr_binary_error(ctx->msgs, right, op, - "Right hand side of binary operation " - "(%s) must be constant", sym); - - if (!expr_is_singleton(right)) + if (!datatype_equal(expr_basetype(left), expr_basetype(right))) return expr_binary_error(ctx->msgs, left, op, - "Binary operation (%s) is undefined " - "for %s expressions", - sym, expr_name(right)); - - /* The grammar guarantees this */ - assert(expr_basetype(left) == expr_basetype(right)); + "Binary operation (%s) with different base types " + "(%s vs %s) is not supported", + sym, expr_basetype(left)->name, expr_basetype(right)->name); switch (op->op) { case OP_LSHIFT: case OP_RSHIFT: - return expr_evaluate_shift(ctx, expr); + if (!expr_is_constant(right)) + return expr_binary_error(ctx->msgs, right, op, + "Right hand side of binary operation " + "(%s) must be constant", sym); + + if (!expr_is_singleton(right)) + return expr_binary_error(ctx->msgs, left, op, + "Binary operation (%s) is undefined " + "for %s expressions", + sym, expr_name(right)); + + ret = expr_evaluate_shift(ctx, expr); + break; case OP_AND: case OP_XOR: case OP_OR: - return expr_evaluate_bitwise(ctx, expr); + if (expr_is_constant(left) && !expr_is_constant(right)) + return expr_binary_error(ctx->msgs, right, op, + "Right hand side of binary operation " + "(%s) must be constant", sym); + + if (expr_is_constant(right) && !expr_is_singleton(right)) + return expr_binary_error(ctx->msgs, left, op, + "Binary operation (%s) is undefined " + "for %s expressions", + sym, expr_name(right)); + + ret = expr_evaluate_bitwise(ctx, expr); + break; default: BUG("invalid binary operation %u\n", op->op); } + + + if (ctx->recursion.binop == 0) + BUG("recursion counter underflow"); + + /* can't check earlier: evaluate functions might do constant-merging + expr_free. + * + * So once we've evaluate everything check for remaining length of the + * binop chain. + */ + if (--ctx->recursion.binop == 0) { + unsigned int to_linearize = 0; + + op = *expr; + while (op && op->etype == EXPR_BINOP && op->left != NULL) { + to_linearize++; + op = op->left; + + if (to_linearize >= NFT_MAX_EXPR_RECURSION) + return expr_binary_error(ctx->msgs, op, NULL, + "Binary operation limit %u reached ", + NFT_MAX_EXPR_RECURSION); + } + } + + return ret; } static int list_member_evaluate(struct eval_ctx *ctx, struct expr **expr) @@ -1200,52 +1640,183 @@ static int list_member_evaluate(struct eval_ctx *ctx, struct expr **expr) struct expr *next = list_entry((*expr)->list.next, struct expr, list); int err; + /* should never be hit in practice */ + if (ctx->recursion.list >= USHRT_MAX) + return expr_binary_error(ctx->msgs, next, NULL, + "List limit %u reached ", + ctx->recursion.list); + + ctx->recursion.list++; assert(*expr != next); list_del(&(*expr)->list); err = expr_evaluate(ctx, expr); list_add_tail(&(*expr)->list, &next->list); + ctx->recursion.list--; return err; } -static int expr_evaluate_concat(struct eval_ctx *ctx, struct expr **expr, - bool eval) +static int expr_evaluate_concat(struct eval_ctx *ctx, struct expr **expr) { const struct datatype *dtype = ctx->ectx.dtype, *tmp; uint32_t type = dtype ? dtype->type : 0, ntype = 0; int off = dtype ? dtype->subtypes : 0; unsigned int flags = EXPR_F_CONSTANT | EXPR_F_SINGLETON; - struct expr *i, *next; + const struct list_head *expressions = NULL; + struct expr *i, *next, *key = NULL; + const struct expr *key_ctx = NULL; + bool runaway = false; + uint32_t size = 0; + + if (ctx->ectx.key && ctx->ectx.key->etype == EXPR_CONCAT) { + key_ctx = ctx->ectx.key; + assert(!list_empty(&expr_concat(ctx->ectx.key)->expressions)); + key = list_first_entry(&expr_concat(ctx->ectx.key)->expressions, struct expr, list); + expressions = &expr_concat(ctx->ectx.key)->expressions; + } + + list_for_each_entry_safe(i, next, &expr_concat(*expr)->expressions, list) { + enum byteorder bo = BYTEORDER_INVALID; + unsigned dsize_bytes, dsize = 0; + + if (runaway) { + return expr_binary_error(ctx->msgs, *expr, key_ctx, + "too many concatenation components"); + } + + if (i->etype == EXPR_CT && + (i->ct.key == NFT_CT_SRC || + i->ct.key == NFT_CT_DST)) + return expr_error(ctx->msgs, i, + "specify either ip or ip6 for address matching"); - list_for_each_entry_safe(i, next, &(*expr)->expressions, list) { if (expr_is_constant(*expr) && dtype && off == 0) return expr_binary_error(ctx->msgs, i, *expr, "unexpected concat component, " "expecting %s", dtype->desc); - if (dtype == NULL) + if (key) { + tmp = key->dtype; + dsize = key->len; + bo = key->byteorder; + off--; + } else if (dtype == NULL || off == 0) { tmp = datatype_lookup(TYPE_INVALID); - else + } else { tmp = concat_subtype_lookup(type, --off); - expr_set_context(&ctx->ectx, tmp, tmp->size); + dsize = tmp->size; + bo = tmp->byteorder; + } - if (eval && list_member_evaluate(ctx, &i) < 0) + __expr_set_context(&ctx->ectx, tmp, bo, dsize, 0); + ctx->ectx.key = i; + + if (list_member_evaluate(ctx, &i) < 0) return -1; + + switch (i->etype) { + case EXPR_INVALID: + case __EXPR_MAX: + BUG("Unexpected etype %d", i->etype); + break; + case EXPR_VALUE: + case EXPR_UNARY: + case EXPR_BINOP: + case EXPR_RELATIONAL: + case EXPR_CONCAT: + case EXPR_MAP: + case EXPR_PAYLOAD: + case EXPR_EXTHDR: + case EXPR_META: + case EXPR_RT: + case EXPR_CT: + case EXPR_SET_ELEM: + case EXPR_NUMGEN: + case EXPR_HASH: + case EXPR_FIB: + case EXPR_SOCKET: + case EXPR_OSF: + case EXPR_XFRM: + break; + case EXPR_RANGE: + case EXPR_PREFIX: + case EXPR_RANGE_VALUE: + /* allowed on RHS (e.g. th dport . mark { 1-65535 . 42 } + * ~~~~~~~~ allowed + * but not on LHS (e.g 1-4 . mark { ...} + * ~~~ illegal + * + * recursion.list > 0 means that the concatenation is + * part of another expression, such as EXPR_MAPPING or + * EXPR_SET_ELEM (is used as RHS). + */ + if (ctx->recursion.list > 0) + break; + + return expr_error(ctx->msgs, i, + "cannot use %s in concatenation", + expr_name(i)); + case EXPR_VERDICT: + case EXPR_SYMBOL: + case EXPR_VARIABLE: + case EXPR_LIST: + case EXPR_SET: + case EXPR_SET_REF: + case EXPR_MAPPING: + case EXPR_SET_ELEM_CATCHALL: + case EXPR_RANGE_SYMBOL: + return expr_error(ctx->msgs, i, + "cannot use %s in concatenation", + expr_name(i)); + } + + if (!i->dtype) + return expr_error(ctx->msgs, i, + "cannot use %s in concatenation, lacks datatype", + expr_name(i)); + flags &= i->flags; + if (!key && i->dtype->type == TYPE_INTEGER) { + struct datatype *clone; + + clone = datatype_clone(i->dtype); + clone->size = i->len; + clone->byteorder = i->byteorder; + __datatype_set(i, clone); + } + if (dtype == NULL && i->dtype->size == 0) return expr_binary_error(ctx->msgs, i, *expr, "can not use variable sized " "data types (%s) in concat " "expressions", i->dtype->name); + if (dsize == 0) /* reload after evaluation or clone above */ + dsize = i->dtype->size; ntype = concat_subtype_add(ntype, i->dtype->type); + + dsize_bytes = div_round_up(dsize, BITS_PER_BYTE); + expr_concat(*expr)->field_len[expr_concat(*expr)->field_count++] = dsize_bytes; + size += netlink_padded_len(dsize); + if (key && expressions) { + if (list_is_last(&key->list, expressions)) + runaway = true; + else + key = list_next_entry(key, list); + } + + ctx->inner_desc = NULL; + + if (size > NFT_MAX_EXPR_LEN_BITS) + return expr_error(ctx->msgs, i, "Concatenation of size %u exceeds maximum size of %u", + size, NFT_MAX_EXPR_LEN_BITS); } (*expr)->flags |= flags; - datatype_set(*expr, concat_type_alloc(ntype)); - (*expr)->len = (*expr)->dtype->size; + __datatype_set(*expr, concat_type_alloc(ntype)); + (*expr)->len = size; if (off > 0) return expr_error(ctx->msgs, *expr, @@ -1254,6 +1825,10 @@ static int expr_evaluate_concat(struct eval_ctx *ctx, struct expr **expr, dtype->desc, (*expr)->dtype->desc); expr_set_context(&ctx->ectx, (*expr)->dtype, (*expr)->len); + if (!key_ctx) + ctx->ectx.key = *expr; + else + ctx->ectx.key = key_ctx; return 0; } @@ -1264,17 +1839,23 @@ static int expr_evaluate_list(struct eval_ctx *ctx, struct expr **expr) mpz_t val; mpz_init_set_ui(val, 0); - list_for_each_entry_safe(i, next, &list->expressions, list) { - if (list_member_evaluate(ctx, &i) < 0) + list_for_each_entry_safe(i, next, &expr_list(list)->expressions, list) { + if (list_member_evaluate(ctx, &i) < 0) { + mpz_clear(val); return -1; - if (i->etype != EXPR_VALUE) + } + if (i->etype != EXPR_VALUE) { + mpz_clear(val); return expr_error(ctx->msgs, i, "List member must be a constant " "value"); - if (i->dtype->basetype->type != TYPE_BITMASK) + } + if (datatype_basetype(i->dtype)->type != TYPE_BITMASK) { + mpz_clear(val); return expr_error(ctx->msgs, i, "Basetype of type %s is not bitmask", i->dtype->desc); + } mpz_ior(val, val, i->value); } @@ -1288,9 +1869,84 @@ static int expr_evaluate_list(struct eval_ctx *ctx, struct expr **expr) return 0; } +static int __expr_evaluate_set_elem(struct eval_ctx *ctx, struct expr *elem) +{ + int num_elem_exprs = 0, num_set_exprs = 0; + struct set *set = ctx->set; + struct stmt *stmt; + + list_for_each_entry(stmt, &elem->stmt_list, list) + num_elem_exprs++; + list_for_each_entry(stmt, &set->stmt_list, list) + num_set_exprs++; + + if (num_elem_exprs > 0) { + struct stmt *set_stmt, *elem_stmt; + + if (num_set_exprs > 0 && num_elem_exprs != num_set_exprs) { + return expr_error(ctx->msgs, elem, + "number of statements mismatch, set expects %d " + "but element has %d", num_set_exprs, + num_elem_exprs); + } else if (num_set_exprs == 0) { + if (!(set->flags & NFT_SET_ANONYMOUS) && + !(set->flags & NFT_SET_EVAL)) { + elem_stmt = list_first_entry(&elem->stmt_list, struct stmt, list); + return stmt_error(ctx, elem_stmt, + "missing statement in %s declaration", + set_is_map(set->flags) ? "map" : "set"); + } + return 0; + } + + set_stmt = list_first_entry(&set->stmt_list, struct stmt, list); + + list_for_each_entry(elem_stmt, &elem->stmt_list, list) { + if (set_stmt->type != elem_stmt->type) { + return stmt_error(ctx, elem_stmt, + "statement mismatch, element expects %s, " + "but %s has type %s", + stmt_name(elem_stmt), + set_is_map(set->flags) ? "map" : "set", + stmt_name(set_stmt)); + } + set_stmt = list_next_entry(set_stmt, list); + } + } + + return 0; +} + +static bool datatype_compatible(const struct datatype *a, const struct datatype *b) +{ + return (a->type == TYPE_MARK && + datatype_equal(datatype_basetype(a), datatype_basetype(b))) || + datatype_equal(a, b); +} + +static bool elem_key_compatible(const struct expr *set_key, + const struct expr *elem_key) +{ + /* Catchall element is always compatible with the set key declaration */ + if (elem_key->etype == EXPR_SET_ELEM_CATCHALL) + return true; + + return datatype_compatible(set_key->dtype, elem_key->dtype); +} + static int expr_evaluate_set_elem(struct eval_ctx *ctx, struct expr **expr) { struct expr *elem = *expr; + const struct expr *key; + + if (ctx->set) { + if (__expr_evaluate_set_elem(ctx, elem) < 0) + return -1; + + key = ctx->set->key; + __expr_set_context(&ctx->ectx, key->dtype, key->byteorder, key->len, 0); + ctx->ectx.key = key; + } if (expr_evaluate(ctx, &elem->key) < 0) return -1; @@ -1300,41 +1956,149 @@ static int expr_evaluate_set_elem(struct eval_ctx *ctx, struct expr **expr) switch (elem->key->etype) { case EXPR_PREFIX: case EXPR_RANGE: - return expr_error(ctx->msgs, elem, - "You must add 'flags interval' to your %s declaration if you want to add %s elements", - set_is_map(ctx->set->flags) ? "map" : "set", expr_name(elem->key)); + case EXPR_RANGE_VALUE: + key = elem->key; + goto err_missing_flag; + case EXPR_CONCAT: + list_for_each_entry(key, &expr_concat(elem->key)->expressions, list) { + switch (key->etype) { + case EXPR_PREFIX: + case EXPR_RANGE: + case EXPR_RANGE_VALUE: + goto err_missing_flag; + default: + break; + } + } + break; default: break; } } + if (ctx->set && !elem_key_compatible(ctx->ectx.key, elem->key)) + return expr_error(ctx->msgs, elem, + "Element mismatches %s definition, expected %s, not '%s'", + set_is_map(ctx->set->flags) ? "map" : "set", + ctx->ectx.key->dtype->desc, elem->key->dtype->desc); + datatype_set(elem, elem->key->dtype); elem->len = elem->key->len; elem->flags = elem->key->flags; + + return 0; + +err_missing_flag: + return expr_error(ctx->msgs, key, + "You must add 'flags interval' to your %s declaration if you want to add %s elements", + set_is_map(ctx->set->flags) ? "map" : "set", expr_name(key)); +} + +static int expr_evaluate_set_elem_catchall(struct eval_ctx *ctx, struct expr **expr) +{ + struct expr *elem = *expr; + + if (ctx->set) + elem->len = ctx->set->key->len; + return 0; } +static const struct expr *expr_set_elem(const struct expr *expr) +{ + if (expr->etype == EXPR_MAPPING) + return expr->left; + + return expr; +} + +static int interval_set_eval(struct eval_ctx *ctx, struct set *set, + struct expr *init) +{ + int ret; + + if (!init) + return 0; + + ret = 0; + switch (ctx->cmd->op) { + case CMD_CREATE: + case CMD_ADD: + case CMD_REPLACE: + case CMD_INSERT: + if (set->automerge) { + ret = set_automerge(ctx->msgs, ctx->cmd, set, init, + ctx->nft->debug_mask); + } else { + ret = set_overlap(ctx->msgs, set, init); + } + break; + case CMD_DELETE: + case CMD_DESTROY: + ret = set_delete(ctx->msgs, ctx->cmd, set, init, + ctx->nft->debug_mask); + break; + case CMD_GET: + case CMD_RESET: + break; + default: + BUG("unhandled op %d\n", ctx->cmd->op); + break; + } + + return ret; +} + +static void expr_evaluate_set_ref(struct eval_ctx *ctx, struct expr *expr) +{ + struct set *set = expr->set; + + expr_set_context(&ctx->ectx, set->key->dtype, set->key->len); + ctx->ectx.key = set->key; +} + static int expr_evaluate_set(struct eval_ctx *ctx, struct expr **expr) { struct expr *set = *expr, *i, *next; + const struct expr *elem; - list_for_each_entry_safe(i, next, &set->expressions, list) { + list_for_each_entry_safe(i, next, &expr_set(set)->expressions, list) { if (list_member_evaluate(ctx, &i) < 0) return -1; - if (i->etype == EXPR_SET_ELEM && - i->key->etype == EXPR_SET_REF) + if (i->etype == EXPR_MAPPING && + i->left->etype == EXPR_SET_ELEM && + i->left->key->etype == EXPR_SET) { + struct expr *new, *j; + + list_for_each_entry(j, &expr_set(i->left->key)->expressions, list) { + new = mapping_expr_alloc(&i->location, + expr_get(j), + expr_get(i->right)); + list_add_tail(&new->list, &expr_set(set)->expressions); + expr_set(set)->size++; + } + list_del(&i->list); + expr_free(i); + continue; + } + + elem = expr_set_elem(i); + + if (elem->etype == EXPR_SET_ELEM && + elem->key->etype == EXPR_SET_REF) return expr_error(ctx->msgs, i, "Set reference cannot be part of another set"); - if (i->etype == EXPR_SET_ELEM && - i->key->etype == EXPR_SET) { - struct expr *new = expr_clone(i->key); + if (elem->etype == EXPR_SET_ELEM && + elem->key->etype == EXPR_SET) { + struct expr *new = expr_get(elem->key); - set->set_flags |= i->key->set_flags; + expr_set(set)->set_flags |= expr_set(elem->key)->set_flags; list_replace(&i->list, &new->list); expr_free(i); i = new; + elem = expr_set_elem(i); } if (!expr_is_constant(i)) @@ -1343,30 +2107,116 @@ static int expr_evaluate_set(struct eval_ctx *ctx, struct expr **expr) if (i->etype == EXPR_SET) { /* Merge recursive set definitions */ - list_splice_tail_init(&i->expressions, &i->list); + list_splice_tail_init(&expr_set(i)->expressions, &i->list); list_del(&i->list); - set->size += i->size - 1; - set->set_flags |= i->set_flags; + expr_set(set)->size += expr_set(i)->size - 1; + expr_set(set)->set_flags |= expr_set(i)->set_flags; expr_free(i); - } else if (!expr_is_singleton(i)) - set->set_flags |= NFT_SET_INTERVAL; + } else if (!expr_is_singleton(i)) { + expr_set(set)->set_flags |= NFT_SET_INTERVAL; + if (elem->key->etype == EXPR_CONCAT) + expr_set(set)->set_flags |= NFT_SET_CONCAT; + } + } + + if (ctx->set) { + if (ctx->set->flags & NFT_SET_CONCAT) + expr_set(set)->set_flags |= NFT_SET_CONCAT; } - set->set_flags |= NFT_SET_CONSTANT; + expr_set(set)->set_flags |= NFT_SET_CONSTANT; datatype_set(set, ctx->ectx.dtype); set->len = ctx->ectx.len; set->flags |= EXPR_F_CONSTANT; + return 0; } static int binop_transfer(struct eval_ctx *ctx, struct expr **expr); + +static void map_set_concat_info(struct expr *map) +{ + map->mappings->set->flags |= expr_set(map->mappings->set->init)->set_flags; + + if (map->mappings->set->flags & NFT_SET_INTERVAL && + map->map->etype == EXPR_CONCAT) { + memcpy(&map->mappings->set->desc.field_len, + &expr_concat(map->map)->field_len, + sizeof(map->mappings->set->desc.field_len)); + map->mappings->set->desc.field_count = + expr_concat(map->map)->field_count; + map->mappings->flags |= NFT_SET_CONCAT; + } +} + +static void __mapping_expr_expand(struct expr *i) +{ + struct expr *j, *range, *next; + + assert(i->etype == EXPR_MAPPING); + switch (i->right->etype) { + case EXPR_VALUE: + range = range_expr_alloc(&i->location, expr_get(i->right), expr_get(i->right)); + expr_free(i->right); + i->right = range; + break; + case EXPR_CONCAT: + list_for_each_entry_safe(j, next, &expr_concat(i->right)->expressions, list) { + if (j->etype != EXPR_VALUE) + continue; + + range = range_expr_alloc(&j->location, expr_get(j), expr_get(j)); + list_replace(&j->list, &range->list); + expr_free(j); + } + i->right->flags &= ~EXPR_F_SINGLETON; + break; + default: + break; + } +} + +static int mapping_expr_expand(struct eval_ctx *ctx) +{ + struct expr *i; + + if (!set_is_anonymous(ctx->set->flags)) + return 0; + + list_for_each_entry(i, &expr_set(ctx->set->init)->expressions, list) { + if (i->etype != EXPR_MAPPING) + return expr_error(ctx->msgs, i, + "expected mapping, not %s", expr_name(i)); + __mapping_expr_expand(i); + } + + return 0; +} + static int expr_evaluate_map(struct eval_ctx *ctx, struct expr **expr) { - struct expr_ctx ectx = ctx->ectx; struct expr *map = *expr, *mappings; - const struct datatype *dtype; - struct expr *key; + struct expr_ctx ectx = ctx->ectx; + uint32_t set_flags = NFT_SET_MAP; + struct expr *key, *data; + + if (map->map->etype == EXPR_CT && + (map->map->ct.key == NFT_CT_SRC || + map->map->ct.key == NFT_CT_DST)) + return expr_error(ctx->msgs, map->map, + "specify either ip or ip6 for address matching"); + else if (map->map->etype == EXPR_CONCAT) { + struct expr *i; + + list_for_each_entry(i, &expr_concat(map->map)->expressions, list) { + if (i->etype == EXPR_CT && + (i->ct.key == NFT_CT_SRC || + i->ct.key == NFT_CT_DST)) + return expr_error(ctx->msgs, i, + "specify either ip or ip6 for address matching"); + } + } expr_set_context(&ctx->ectx, NULL, 0); if (expr_evaluate(ctx, &map->map) < 0) @@ -1375,25 +2225,47 @@ static int expr_evaluate_map(struct eval_ctx *ctx, struct expr **expr) return expr_error(ctx->msgs, map->map, "Map expression can not be constant"); + ctx->stmt_len = 0; mappings = map->mappings; - mappings->set_flags |= NFT_SET_MAP; switch (map->mappings->etype) { case EXPR_SET: - key = constant_expr_alloc(&map->location, - ctx->ectx.dtype, - ctx->ectx.byteorder, - ctx->ectx.len, NULL); + set_flags |= expr_set(mappings)->set_flags; + /* fallthrough */ + case EXPR_VARIABLE: + if (ctx->ectx.key && ctx->ectx.key->etype == EXPR_CONCAT) { + key = expr_clone(ctx->ectx.key); + } else { + key = constant_expr_alloc(&map->location, + ctx->ectx.dtype, + ctx->ectx.byteorder, + ctx->ectx.len, NULL); + } - mappings = implicit_set_declaration(ctx, "__map%d", - key, - mappings); + if (!ectx.dtype) { + expr_free(key); + return expr_error(ctx->msgs, map, + "Implicit map expression without known datatype"); + } + + if (ectx.dtype->type == TYPE_VERDICT) { + data = verdict_expr_alloc(&netlink_location, 0, NULL); + } else { + const struct datatype *dtype; + + dtype = set_datatype_alloc(ectx.dtype, ectx.byteorder); + data = constant_expr_alloc(&netlink_location, dtype, + dtype->byteorder, ectx.len, NULL); + datatype_free(dtype); + } - dtype = set_datatype_alloc(ectx.dtype, ectx.byteorder); + mappings = implicit_set_declaration(ctx, "__map%d", + key, data, + mappings, + NFT_SET_ANONYMOUS | set_flags); + if (!mappings) + return -1; - mappings->set->data = constant_expr_alloc(&netlink_location, - dtype, dtype->byteorder, - ectx.len, NULL); if (ectx.len && mappings->set->data->len != ectx.len) BUG("%d vs %d\n", mappings->set->data->len, ectx.len); @@ -1402,14 +2274,33 @@ static int expr_evaluate_map(struct eval_ctx *ctx, struct expr **expr) ctx->set = mappings->set; if (expr_evaluate(ctx, &map->mappings->set->init) < 0) return -1; + + if (map->mappings->set->init->etype != EXPR_SET) { + return expr_error(ctx->msgs, map->mappings->set->init, + "Expression is not a map"); + } + + if (set_is_interval(expr_set(map->mappings->set->init)->set_flags) && + !(expr_set(map->mappings->set->init)->set_flags & NFT_SET_CONCAT) && + interval_set_eval(ctx, ctx->set, map->mappings->set->init) < 0) + return -1; + expr_set_context(&ctx->ectx, ctx->set->key->dtype, ctx->set->key->len); if (binop_transfer(ctx, expr) < 0) return -1; + if (ctx->set->data->flags & EXPR_F_INTERVAL) { + ctx->set->data->len *= 2; + + if (mapping_expr_expand(ctx)) + return -1; + } + ctx->set->key->len = ctx->ectx.len; ctx->set = NULL; map = *expr; - map->mappings->set->flags |= map->mappings->set->init->set_flags; + + map_set_concat_info(map); break; case EXPR_SYMBOL: if (expr_evaluate(ctx, &map->mappings) < 0) @@ -1419,12 +2310,18 @@ static int expr_evaluate_map(struct eval_ctx *ctx, struct expr **expr) return expr_error(ctx->msgs, map->mappings, "Expression is not a map"); break; + case EXPR_SET_REF: + /* symbol has been already evaluated to set reference */ + if (!set_is_map(mappings->set->flags)) + return expr_error(ctx->msgs, map->mappings, + "Expression is not a map"); + break; default: - BUG("invalid mapping expression %s\n", - expr_name(map->mappings)); + return expr_binary_error(ctx->msgs, map->mappings, map->map, + "invalid mapping expression %s", expr_name(map->mappings)); } - if (!datatype_equal(map->map->dtype, map->mappings->set->key->dtype)) + if (!datatype_compatible(map->mappings->set->key->dtype, map->map->dtype)) return expr_binary_error(ctx->msgs, map->mappings, map->map, "datatype mismatch, map expects %s, " "mapping expression has type %s", @@ -1442,10 +2339,33 @@ static int expr_evaluate_map(struct eval_ctx *ctx, struct expr **expr) return 0; } +static bool data_mapping_has_interval(struct expr *data) +{ + struct expr *i; + + if (data->etype == EXPR_RANGE || + data->etype == EXPR_RANGE_VALUE || + data->etype == EXPR_PREFIX) + return true; + + if (data->etype != EXPR_CONCAT) + return false; + + list_for_each_entry(i, &expr_concat(data)->expressions, list) { + if (i->etype == EXPR_RANGE || + i->etype == EXPR_RANGE_VALUE || + i->etype == EXPR_PREFIX) + return true; + } + + return false; +} + static int expr_evaluate_mapping(struct eval_ctx *ctx, struct expr **expr) { struct expr *mapping = *expr; struct set *set = ctx->set; + uint32_t datalen; if (set == NULL) return expr_error(ctx->msgs, mapping, @@ -1461,25 +2381,93 @@ static int expr_evaluate_mapping(struct eval_ctx *ctx, struct expr **expr) "Key must be a constant"); mapping->flags |= mapping->left->flags & EXPR_F_SINGLETON; - if (set->data) { - expr_set_context(&ctx->ectx, set->data->dtype, set->data->len); - } else { - assert((set->flags & NFT_SET_MAP) == 0); - } + /* This can happen for malformed map definitions */ + if (!set->data) + return set_error(ctx, set, "map has no mapping data"); + + if (!set_is_anonymous(set->flags) && + set->data->flags & EXPR_F_INTERVAL) + datalen = set->data->len / 2; + else + datalen = set->data->len; + __expr_set_context(&ctx->ectx, set->data->dtype, + set->data->byteorder, datalen, 0); if (expr_evaluate(ctx, &mapping->right) < 0) return -1; if (!expr_is_constant(mapping->right)) return expr_error(ctx->msgs, mapping->right, "Value must be a constant"); - if (!expr_is_singleton(mapping->right)) + + if (set_is_anonymous(set->flags) && + data_mapping_has_interval(mapping->right)) + set->data->flags |= EXPR_F_INTERVAL; + + if (!set_is_anonymous(set->flags) && + set->data->flags & EXPR_F_INTERVAL) + __mapping_expr_expand(mapping); + + if (!(set->data->flags & EXPR_F_INTERVAL) && + !expr_is_singleton(mapping->right)) return expr_error(ctx->msgs, mapping->right, "Value must be a singleton"); + if (set_is_objmap(set->flags) && mapping->right->etype != EXPR_VALUE) + return expr_error(ctx->msgs, mapping->right, + "Object mapping data should be a value, not %s", + expr_name(mapping->right)); + mapping->flags |= EXPR_F_CONSTANT; return 0; } +static int expr_evaluate_symbol_range(struct eval_ctx *ctx, struct expr **exprp) +{ + struct expr *left, *right, *range, *constant_range; + struct expr *expr = *exprp; + + /* expand to symbol and range expressions to consolidate evaluation. */ + left = symbol_expr_alloc(&expr->location, expr->symtype, + (struct scope *)expr->scope, + expr->identifier_range[0]); + right = symbol_expr_alloc(&expr->location, expr->symtype, + (struct scope *)expr->scope, + expr->identifier_range[1]); + range = range_expr_alloc(&expr->location, left, right); + + if (expr_evaluate(ctx, &range) < 0) { + expr_free(range); + return -1; + } + + if (range->etype != EXPR_RANGE) + goto out_done; + + left = range->left; + right = range->right; + + if (ctx->set && + left->etype == EXPR_VALUE && + right->etype == EXPR_VALUE) { + constant_range = constant_range_expr_alloc(&expr->location, + left->dtype, + left->byteorder, + left->len, + left->value, + right->value); + expr_free(range); + expr_free(expr); + *exprp = constant_range; + return 0; + } + +out_done: + expr_free(expr); + *exprp = range; + + return 0; +} + /* We got datatype context via statement. If the basetype is compatible, set * this expression datatype to the one of the statement to make it datatype * compatible. This is a more conservative approach than enabling datatype @@ -1501,17 +2489,20 @@ static void expr_dtype_integer_compatible(struct eval_ctx *ctx, static int expr_evaluate_numgen(struct eval_ctx *ctx, struct expr **exprp) { struct expr *expr = *exprp; + unsigned int maxval; expr_dtype_integer_compatible(ctx, expr); + maxval = expr->numgen.mod + expr->numgen.offset - 1; __expr_set_context(&ctx->ectx, expr->dtype, expr->byteorder, expr->len, - expr->numgen.mod - 1); + maxval); return 0; } static int expr_evaluate_hash(struct eval_ctx *ctx, struct expr **exprp) { struct expr *expr = *exprp; + unsigned int maxval; expr_dtype_integer_compatible(ctx, expr); @@ -1524,8 +2515,9 @@ static int expr_evaluate_hash(struct eval_ctx *ctx, struct expr **exprp) * expression to be hashed. Since this input is transformed to a 4 bytes * integer, restore context to the datatype that results from hashing. */ + maxval = expr->hash.mod + expr->hash.offset - 1; __expr_set_context(&ctx->ectx, expr->dtype, expr->byteorder, expr->len, - expr->hash.mod - 1); + maxval); return 0; } @@ -1594,8 +2586,6 @@ static int binop_transfer_one(struct eval_ctx *ctx, return 0; } - expr_get(*right); - switch (left->op) { case OP_LSHIFT: (*right) = binop_expr_alloc(&(*right)->location, OP_RSHIFT, @@ -1667,12 +2657,12 @@ static int __binop_transfer(struct eval_ctx *ctx, return -1; break; case EXPR_SET: - list_for_each_entry(i, &(*right)->expressions, list) { + list_for_each_entry(i, &expr_set(*right)->expressions, list) { err = binop_can_transfer(ctx, left, i); if (err <= 0) return err; } - list_for_each_entry_safe(i, next, &(*right)->expressions, list) { + list_for_each_entry_safe(i, next, &expr_set(*right)->expressions, list) { list_del(&i->list); err = binop_transfer_one(ctx, left, &i); list_add_tail(&i->list, &next->list); @@ -1708,7 +2698,7 @@ static int binop_transfer(struct eval_ctx *ctx, struct expr **expr) return 0; } -static bool lhs_is_meta_hour(const struct expr *meta) +bool lhs_is_meta_hour(const struct expr *meta) { if (meta->etype != EXPR_META) return false; @@ -1717,7 +2707,7 @@ static bool lhs_is_meta_hour(const struct expr *meta) meta->meta.key == NFT_META_TIME_DAY; } -static void swap_values(struct expr *range) +void range_expr_swap_values(struct expr *range) { struct expr *left_tmp; @@ -1734,16 +2724,54 @@ static bool range_needs_swap(const struct expr *range) return mpz_cmp(left->value, right->value) > 0; } +static void optimize_singleton_set(struct expr *rel, struct expr **expr) +{ + struct expr *set = rel->right, *i; + + i = list_first_entry(&expr_set(set)->expressions, struct expr, list); + if (i->etype == EXPR_SET_ELEM && + list_empty(&i->stmt_list)) { + + switch (i->key->etype) { + case EXPR_PREFIX: + case EXPR_RANGE: + case EXPR_VALUE: + rel->right = *expr = i->key; + i->key = NULL; + expr_free(set); + break; + default: + break; + } + } + + if (rel->op == OP_IMPLICIT && + rel->right->dtype->basetype && + rel->right->dtype->basetype->type == TYPE_BITMASK && + rel->right->dtype->type != TYPE_CT_STATE) { + rel->op = OP_EQ; + } +} + static int expr_evaluate_relational(struct eval_ctx *ctx, struct expr **expr) { struct expr *rel = *expr, *left, *right; + struct proto_ctx *pctx; struct expr *range; int ret; + right = rel->right; + if (right->etype == EXPR_SYMBOL && + right->symtype == SYMBOL_SET && + expr_evaluate(ctx, &rel->right) < 0) + return -1; + if (expr_evaluate(ctx, &rel->left) < 0) return -1; left = rel->left; + pctx = eval_proto_ctx(ctx); + if (rel->right->etype == EXPR_RANGE && lhs_is_meta_hour(rel->left)) { ret = __expr_evaluate_range(ctx, &rel->right); if (ret) @@ -1761,10 +2789,10 @@ static int expr_evaluate_relational(struct eval_ctx *ctx, struct expr **expr) "Inverting range values for cross-day hour matching\n\n"); if (rel->op == OP_EQ || rel->op == OP_IMPLICIT) { - swap_values(range); + range_expr_swap_values(range); rel->op = OP_NEQ; } else if (rel->op == OP_NEQ) { - swap_values(range); + range_expr_swap_values(range); rel->op = OP_EQ; } } @@ -1804,6 +2832,19 @@ static int expr_evaluate_relational(struct eval_ctx *ctx, struct expr **expr) return expr_binary_error(ctx->msgs, right, left, "Cannot be used with right hand side constant value"); + if (left->etype != EXPR_CONCAT) { + switch (rel->op) { + case OP_EQ: + case OP_IMPLICIT: + case OP_NEQ: + if (right->etype == EXPR_SET && expr_set(right)->size == 1) + optimize_singleton_set(rel, &right); + break; + default: + break; + } + } + switch (rel->op) { case OP_EQ: case OP_IMPLICIT: @@ -1811,11 +2852,22 @@ static int expr_evaluate_relational(struct eval_ctx *ctx, struct expr **expr) * Update protocol context for payload and meta iiftype * equality expressions. */ - if (expr_is_singleton(right)) - relational_expr_pctx_update(&ctx->pctx, rel); + relational_expr_pctx_update(pctx, rel); /* fall through */ case OP_NEQ: + case OP_NEG: + if (rel->op == OP_NEG) { + if (left->etype == EXPR_BINOP) + return expr_binary_error(ctx->msgs, left, right, + "cannot combine negation with binary expression"); + if (right->etype != EXPR_VALUE || + right->dtype->basetype == NULL || + right->dtype->basetype->type != TYPE_BITMASK) + return expr_binary_error(ctx->msgs, left, right, + "negation can only be used with singleton bitmask values. Did you mean \"!=\"?"); + } + switch (right->etype) { case EXPR_RANGE: if (byteorder_conversion(ctx, &rel->left, BYTEORDER_BIG_ENDIAN) < 0) @@ -1834,16 +2886,34 @@ static int expr_evaluate_relational(struct eval_ctx *ctx, struct expr **expr) return -1; break; case EXPR_SET: + if (expr_set(right)->size == 0) + return expr_error(ctx->msgs, right, "Set is empty"); + right = rel->right = implicit_set_declaration(ctx, "__set%d", - expr_get(left), right); + expr_get(left), NULL, + right, + expr_set(right)->set_flags | NFT_SET_ANONYMOUS); + if (!right) + return -1; + /* fall through */ case EXPR_SET_REF: + if (rel->left->etype == EXPR_CT && + (rel->left->ct.key == NFT_CT_SRC || + rel->left->ct.key == NFT_CT_DST)) + return expr_error(ctx->msgs, left, + "specify either ip or ip6 for address matching"); + /* Data for range lookups needs to be in big endian order */ if (right->set->flags & NFT_SET_INTERVAL && byteorder_conversion(ctx, &rel->left, BYTEORDER_BIG_ENDIAN) < 0) return -1; break; + case EXPR_CONCAT: + return expr_binary_error(ctx->msgs, left, right, + "Use concatenations with sets and maps, not singleton values"); + break; default: BUG("invalid expression type %s\n", expr_name(right)); } @@ -1898,11 +2968,12 @@ static int expr_evaluate_fib(struct eval_ctx *ctx, struct expr **exprp) static int expr_evaluate_meta(struct eval_ctx *ctx, struct expr **exprp) { + struct proto_ctx *pctx = eval_proto_ctx(ctx); struct expr *meta = *exprp; switch (meta->meta.key) { case NFT_META_NFPROTO: - if (ctx->pctx.family != NFPROTO_INET && + if (pctx->family != NFPROTO_INET && meta->flags & EXPR_F_PROTOCOL) return expr_error(ctx->msgs, meta, "meta nfproto is only useful in the inet family"); @@ -1920,9 +2991,11 @@ static int expr_evaluate_meta(struct eval_ctx *ctx, struct expr **exprp) static int expr_evaluate_socket(struct eval_ctx *ctx, struct expr **expr) { + enum nft_socket_keys key = (*expr)->socket.key; int maxval = 0; - if((*expr)->socket.key == NFT_SOCKET_TRANSPARENT) + if (key == NFT_SOCKET_TRANSPARENT || + key == NFT_SOCKET_WILDCARD) maxval = 1; __expr_set_context(&ctx->ectx, (*expr)->dtype, (*expr)->byteorder, (*expr)->len, maxval); @@ -1943,19 +3016,34 @@ static int expr_evaluate_osf(struct eval_ctx *ctx, struct expr **expr) static int expr_evaluate_variable(struct eval_ctx *ctx, struct expr **exprp) { - struct expr *new = expr_clone((*exprp)->sym->expr); + struct symbol *sym = (*exprp)->sym; + struct expr *new; + + /* If variable is reused from different locations in the ruleset, then + * clone expression. + */ + if (sym->refcnt > 2) + new = expr_clone(sym->expr); + else + new = expr_get(sym->expr); + + if (expr_evaluate(ctx, &new) < 0) { + expr_free(new); + return -1; + } expr_free(*exprp); *exprp = new; - return expr_evaluate(ctx, exprp); + return 0; } static int expr_evaluate_xfrm(struct eval_ctx *ctx, struct expr **exprp) { + struct proto_ctx *pctx = eval_proto_ctx(ctx); struct expr *expr = *exprp; - switch (ctx->pctx.family) { + switch (pctx->family) { case NFPROTO_IPV4: case NFPROTO_IPV6: case NFPROTO_INET: @@ -1968,6 +3056,51 @@ static int expr_evaluate_xfrm(struct eval_ctx *ctx, struct expr **exprp) return expr_evaluate_primary(ctx, exprp); } +static int verdict_validate_chain(struct eval_ctx *ctx, + struct expr *chain) +{ + char buf[NFT_CHAIN_MAXNAMELEN]; + unsigned int len; + + len = chain->len / BITS_PER_BYTE; + if (len > NFT_CHAIN_MAXNAMELEN) + return expr_error(ctx->msgs, chain, + "chain name too long (%u, max %u)", + chain->len / BITS_PER_BYTE, + NFT_CHAIN_MAXNAMELEN); + + if (!len) + return expr_error(ctx->msgs, chain, + "chain name length 0 not allowed"); + + memset(buf, 0, sizeof(buf)); + mpz_export_data(buf, chain->value, BYTEORDER_HOST_ENDIAN, len); + + if (strnlen(buf, sizeof(buf)) < sizeof(buf)) + return 0; + + return expr_error(ctx->msgs, chain, + "chain name must be smaller than %u", + NFT_CHAIN_MAXNAMELEN); +} + +static int expr_evaluate_verdict(struct eval_ctx *ctx, struct expr **exprp) +{ + struct expr *expr = *exprp; + + switch (expr->verdict) { + case NFT_GOTO: + case NFT_JUMP: + if (expr->chain->etype == EXPR_VALUE && + verdict_validate_chain(ctx, expr->chain)) + return -1; + + break; + } + + return expr_evaluate_primary(ctx, exprp); +} + static int expr_evaluate(struct eval_ctx *ctx, struct expr **expr) { if (ctx->nft->debug_mask & NFT_DEBUG_EVALUATION) { @@ -1986,13 +3119,14 @@ static int expr_evaluate(struct eval_ctx *ctx, struct expr **expr) case EXPR_VARIABLE: return expr_evaluate_variable(ctx, expr); case EXPR_SET_REF: + expr_evaluate_set_ref(ctx, *expr); return 0; case EXPR_VALUE: return expr_evaluate_value(ctx, expr); case EXPR_EXTHDR: return expr_evaluate_exthdr(ctx, expr); case EXPR_VERDICT: - return expr_evaluate_primary(ctx, expr); + return expr_evaluate_verdict(ctx, expr); case EXPR_META: return expr_evaluate_meta(ctx, expr); case EXPR_SOCKET: @@ -2002,7 +3136,7 @@ static int expr_evaluate(struct eval_ctx *ctx, struct expr **expr) case EXPR_FIB: return expr_evaluate_fib(ctx, expr); case EXPR_PAYLOAD: - return expr_evaluate_payload(ctx, expr); + return expr_evaluate_payload_inner(ctx, expr); case EXPR_RT: return expr_evaluate_rt(ctx, expr); case EXPR_CT: @@ -2016,7 +3150,7 @@ static int expr_evaluate(struct eval_ctx *ctx, struct expr **expr) case EXPR_BINOP: return expr_evaluate_binop(ctx, expr); case EXPR_CONCAT: - return expr_evaluate_concat(ctx, expr, true); + return expr_evaluate_concat(ctx, expr); case EXPR_LIST: return expr_evaluate_list(ctx, expr); case EXPR_SET: @@ -2035,6 +3169,10 @@ static int expr_evaluate(struct eval_ctx *ctx, struct expr **expr) return expr_evaluate_hash(ctx, expr); case EXPR_XFRM: return expr_evaluate_xfrm(ctx, expr); + case EXPR_SET_ELEM_CATCHALL: + return expr_evaluate_set_elem_catchall(ctx, expr); + case EXPR_RANGE_SYMBOL: + return expr_evaluate_symbol_range(ctx, expr); default: BUG("unknown expression type %s\n", expr_name(*expr)); } @@ -2092,14 +3230,10 @@ static int stmt_prefix_conversion(struct eval_ctx *ctx, struct expr **expr, return 0; } -static int stmt_evaluate_arg(struct eval_ctx *ctx, struct stmt *stmt, - const struct datatype *dtype, unsigned int len, - enum byteorder byteorder, struct expr **expr) +static int __stmt_evaluate_arg(struct eval_ctx *ctx, struct stmt *stmt, + const struct datatype *dtype, unsigned int len, + enum byteorder byteorder, struct expr **expr) { - __expr_set_context(&ctx->ectx, dtype, byteorder, len, 0); - if (expr_evaluate(ctx, expr) < 0) - return -1; - if ((*expr)->etype == EXPR_PAYLOAD && (*expr)->dtype->type == TYPE_INTEGER && ((*expr)->dtype->type != datatype_basetype(dtype)->type || @@ -2109,13 +3243,18 @@ static int stmt_evaluate_arg(struct eval_ctx *ctx, struct stmt *stmt, "expression has type %s with length %d", dtype->desc, (*expr)->dtype->desc, (*expr)->len); - else if ((*expr)->dtype->type != TYPE_INTEGER && - !datatype_equal((*expr)->dtype, dtype)) + + if (!datatype_compatible(dtype, (*expr)->dtype)) return stmt_binary_error(ctx, *expr, stmt, /* verdict vs invalid? */ "datatype mismatch: expected %s, " "expression has type %s", dtype->desc, (*expr)->dtype->desc); + if (dtype->type == TYPE_MARK && + datatype_equal(datatype_basetype(dtype), datatype_basetype((*expr)->dtype)) && + !expr_is_constant(*expr)) + return byteorder_conversion(ctx, expr, byteorder); + /* we are setting a value, we can't use a set */ switch ((*expr)->etype) { case EXPR_SET: @@ -2130,6 +3269,10 @@ static int stmt_evaluate_arg(struct eval_ctx *ctx, struct stmt *stmt, return byteorder_conversion(ctx, expr, byteorder); case EXPR_PREFIX: return stmt_prefix_conversion(ctx, expr, byteorder); + case EXPR_NUMGEN: + if (dtype->type == TYPE_IPADDR) + return byteorder_conversion(ctx, expr, byteorder); + break; default: break; } @@ -2137,6 +3280,36 @@ static int stmt_evaluate_arg(struct eval_ctx *ctx, struct stmt *stmt, return 0; } +static int stmt_evaluate_arg(struct eval_ctx *ctx, struct stmt *stmt, + const struct datatype *dtype, unsigned int len, + enum byteorder byteorder, struct expr **expr) +{ + __expr_set_context(&ctx->ectx, dtype, byteorder, len, 0); + if (expr_evaluate(ctx, expr) < 0) + return -1; + + return __stmt_evaluate_arg(ctx, stmt, dtype, len, byteorder, expr); +} + +/* like stmt_evaluate_arg, but keep existing context created + * by previous expr_evaluate(). + * + * This is needed for add/update statements: + * ctx->ectx.key has the set key, which may be needed for 'typeof' + * sets: the 'add/update' expression might contain integer data types. + * + * Without the key we cannot derive the element size. + */ +static int stmt_evaluate_key(struct eval_ctx *ctx, struct stmt *stmt, + const struct datatype *dtype, unsigned int len, + enum byteorder byteorder, struct expr **expr) +{ + if (expr_evaluate(ctx, expr) < 0) + return -1; + + return __stmt_evaluate_arg(ctx, stmt, dtype, len, byteorder, expr); +} + static int stmt_evaluate_verdict(struct eval_ctx *ctx, struct stmt *stmt) { if (stmt_evaluate_arg(ctx, stmt, &verdict_type, 0, 0, &stmt->expr) < 0) @@ -2147,12 +3320,16 @@ static int stmt_evaluate_verdict(struct eval_ctx *ctx, struct stmt *stmt) if (stmt->expr->verdict != NFT_CONTINUE) stmt->flags |= STMT_F_TERMINAL; if (stmt->expr->chain != NULL) { - if (expr_evaluate(ctx, &stmt->expr->chain) < 0) + if (stmt_evaluate_arg(ctx, stmt, &string_type, 0, 0, + &stmt->expr->chain) < 0) return -1; if (stmt->expr->chain->etype != EXPR_VALUE) { return expr_error(ctx->msgs, stmt->expr->chain, "not a value expression"); } + + if (verdict_validate_chain(ctx, stmt->expr->chain)) + return -1; } break; case EXPR_MAP: @@ -2167,33 +3344,83 @@ static bool stmt_evaluate_payload_need_csum(const struct expr *payload) { const struct proto_desc *desc; + if (payload->payload.base == PROTO_BASE_INNER_HDR) + return true; + desc = payload->payload.desc; return desc && desc->checksum_key; } +static bool stmt_evaluate_is_vlan(const struct expr *payload) +{ + return payload->payload.base == PROTO_BASE_LL_HDR && + payload->payload.desc == &proto_vlan; +} + +/** stmt_evaluate_payload_need_aligned_fetch + * + * @payload: payload expression to check + * + * Some types of stores need to round up to an even sized byte length, + * typically 1 -> 2 or 3 -> 4 bytes. + * + * This includes anything that needs inet checksum fixups and also writes + * to the vlan header. This is because of VLAN header removal in the + * kernel: nftables kernel side provides illusion of a linear packet, i.e. + * ethernet_header|vlan_header|network_header. + * + * When a write to the vlan header is performed, kernel side updates the + * pseudoheader, but only accepts 2 or 4 byte writes to vlan proto/TCI. + * + * Return true if load needs to be expanded to cover even amount of bytes + */ +static bool stmt_evaluate_payload_need_aligned_fetch(const struct expr *payload) +{ + if (stmt_evaluate_payload_need_csum(payload)) + return true; + + if (stmt_evaluate_is_vlan(payload)) + return true; + + return false; +} + static int stmt_evaluate_exthdr(struct eval_ctx *ctx, struct stmt *stmt) { struct expr *exthdr; + int ret; if (__expr_evaluate_exthdr(ctx, &stmt->exthdr.expr) < 0) return -1; exthdr = stmt->exthdr.expr; - return stmt_evaluate_arg(ctx, stmt, exthdr->dtype, exthdr->len, - BYTEORDER_BIG_ENDIAN, - &stmt->exthdr.val); + ret = stmt_evaluate_arg(ctx, stmt, exthdr->dtype, exthdr->len, + BYTEORDER_BIG_ENDIAN, + &stmt->exthdr.val); + if (ret < 0) + return ret; + + if (stmt->exthdr.val->etype == EXPR_RANGE) + return stmt_error_range(ctx, stmt, stmt->exthdr.val); + + return 0; } static int stmt_evaluate_payload(struct eval_ctx *ctx, struct stmt *stmt) { - struct expr *binop, *mask, *and, *payload_bytes; - unsigned int masklen, extra_len = 0; + struct expr *mask, *and, *xor, *expr, *payload_bytes; unsigned int payload_byte_size, payload_byte_offset; uint8_t shift_imm, data[NFT_REG_SIZE]; + unsigned int masklen, extra_len = 0; struct expr *payload; mpz_t bitmask, ff; - bool need_csum; + bool aligned_fetch; + + if (stmt->payload.expr->payload.inner_desc) { + return expr_error(ctx->msgs, stmt->payload.expr, + "payload statement for this expression is not supported"); + } if (__expr_evaluate_payload(ctx, stmt->payload.expr) < 0) return -1; @@ -2203,7 +3430,15 @@ static int stmt_evaluate_payload(struct eval_ctx *ctx, struct stmt *stmt) payload->byteorder, &stmt->payload.val) < 0) return -1; - need_csum = stmt_evaluate_payload_need_csum(payload); + if (!expr_is_constant(stmt->payload.val) && + byteorder_conversion(ctx, &stmt->payload.val, + payload->byteorder) < 0) + return -1; + + if (stmt->payload.val->etype == EXPR_RANGE) + return stmt_error_range(ctx, stmt, stmt->payload.val); + + aligned_fetch = stmt_evaluate_payload_need_aligned_fetch(payload); if (!payload_needs_adjustment(payload)) { @@ -2211,7 +3446,7 @@ static int stmt_evaluate_payload(struct eval_ctx *ctx, struct stmt *stmt) * update checksum and the length is not even because * kernel checksum functions cannot deal with odd lengths. */ - if (!need_csum || ((payload->len / BITS_PER_BYTE) & 1) == 0) + if (!aligned_fetch || ((payload->len / BITS_PER_BYTE) & 1) == 0) return 0; } @@ -2219,10 +3454,15 @@ static int stmt_evaluate_payload(struct eval_ctx *ctx, struct stmt *stmt) shift_imm = expr_offset_shift(payload, payload->payload.offset, &extra_len); - payload_byte_size = round_up(payload->len, BITS_PER_BYTE) / BITS_PER_BYTE; - payload_byte_size += (extra_len / BITS_PER_BYTE); + payload_byte_size = div_round_up(payload->len + extra_len, + BITS_PER_BYTE); + + if (payload_byte_size > sizeof(data)) + return expr_error(ctx->msgs, stmt->payload.expr, + "uneven load cannot span more than %u bytes, got %u", + sizeof(data), payload_byte_size); - if (need_csum && payload_byte_size & 1) { + if (aligned_fetch && payload_byte_size & 1) { payload_byte_size++; if (payload_byte_offset & 1) { /* prefer 16bit aligned fetch */ @@ -2233,22 +3473,69 @@ static int stmt_evaluate_payload(struct eval_ctx *ctx, struct stmt *stmt) } } - if (shift_imm) { - struct expr *off; + switch (stmt->payload.val->etype) { + case EXPR_VALUE: + if (shift_imm) + mpz_lshift_ui(stmt->payload.val->value, shift_imm); + break; + case EXPR_BINOP: + expr = stmt->payload.val; + while (expr->left->etype == EXPR_BINOP) + expr = expr->left; - off = constant_expr_alloc(&payload->location, - expr_basetype(payload), - BYTEORDER_HOST_ENDIAN, - sizeof(shift_imm), &shift_imm); + if (expr->left->etype != EXPR_PAYLOAD) + break; - binop = binop_expr_alloc(&payload->location, OP_LSHIFT, - stmt->payload.val, off); - binop->dtype = payload->dtype; - binop->byteorder = payload->byteorder; + if (!payload_expr_cmp(payload, expr->left)) + break; - stmt->payload.val = binop; - } + /* Adjust payload to fetch 16-bits. */ + expr->left->payload.offset = payload_byte_offset * BITS_PER_BYTE; + expr->left->len = payload_byte_size * BITS_PER_BYTE; + expr->left->payload.is_raw = 1; + + switch (expr->right->etype) { + case EXPR_VALUE: + if (shift_imm) + mpz_lshift_ui(expr->right->value, shift_imm); + + /* build bitmask to keep unmodified bits intact */ + if (expr->op == OP_AND) { + masklen = payload_byte_size * BITS_PER_BYTE; + mpz_init_bitmask(ff, masklen); + + mpz_init2(bitmask, masklen); + mpz_bitmask(bitmask, payload->len); + mpz_lshift_ui(bitmask, shift_imm); + mpz_xor(bitmask, ff, bitmask); + mpz_clear(ff); + + mpz_ior(bitmask, expr->right->value, bitmask); + mpz_set(expr->right->value, bitmask); + + mpz_clear(bitmask); + } + break; + default: + return expr_error(ctx->msgs, expr->right, + "payload statement for this expression is not supported"); + } + + expr_free(stmt->payload.expr); + /* statement payload is the same in expr and value, update it. */ + stmt->payload.expr = expr_clone(expr->left); + payload = stmt->payload.expr; + ctx->stmt_len = stmt->payload.expr->len; + + if (expr_evaluate(ctx, &stmt->payload.val) < 0) + return -1; + + return 0; + default: + return expr_error(ctx->msgs, stmt->payload.val, + "payload statement for this expression is not supported"); + } masklen = payload_byte_size * BITS_PER_BYTE; mpz_init_bitmask(ff, masklen); @@ -2261,15 +3548,17 @@ static int stmt_evaluate_payload(struct eval_ctx *ctx, struct stmt *stmt) mpz_clear(ff); assert(sizeof(data) * BITS_PER_BYTE >= masklen); - mpz_export_data(data, bitmask, BYTEORDER_HOST_ENDIAN, sizeof(data)); + mpz_export_data(data, bitmask, payload->byteorder, payload_byte_size); mask = constant_expr_alloc(&payload->location, expr_basetype(payload), - BYTEORDER_HOST_ENDIAN, masklen, data); + payload->byteorder, masklen, data); + mpz_clear(bitmask); payload_bytes = payload_expr_alloc(&payload->location, NULL, 0); payload_init_raw(payload_bytes, payload->payload.base, payload_byte_offset * BITS_PER_BYTE, payload_byte_size * BITS_PER_BYTE); + payload_bytes->payload.is_raw = 1; payload_bytes->payload.desc = payload->payload.desc; payload_bytes->byteorder = payload->byteorder; @@ -2278,23 +3567,55 @@ static int stmt_evaluate_payload(struct eval_ctx *ctx, struct stmt *stmt) and = binop_expr_alloc(&payload->location, OP_AND, payload_bytes, mask); - and->dtype = payload_bytes->dtype; - and->byteorder = payload_bytes->byteorder; - and->len = payload_bytes->len; + and->dtype = payload_bytes->dtype; + and->byteorder = payload_bytes->byteorder; + and->len = payload_bytes->len; - binop = binop_expr_alloc(&payload->location, OP_XOR, and, - stmt->payload.val); - binop->dtype = payload->dtype; - binop->byteorder = payload->byteorder; - binop->len = mask->len; - stmt->payload.val = binop; + xor = binop_expr_alloc(&payload->location, OP_XOR, and, + stmt->payload.val); + xor->dtype = payload->dtype; + xor->byteorder = payload->byteorder; + xor->len = mask->len; + + stmt->payload.val = xor; return expr_evaluate(ctx, &stmt->payload.val); } +static int stmt_evaluate_stateful(struct eval_ctx *ctx, struct stmt *stmt, const char *name) +{ + if (stmt_evaluate(ctx, stmt) < 0) + return -1; + + if (!(stmt->flags & STMT_F_STATEFUL)) + return stmt_error(ctx, stmt, "%s statement must be stateful", name); + + return 0; +} + static int stmt_evaluate_meter(struct eval_ctx *ctx, struct stmt *stmt) { - struct expr *key, *set, *setref; + struct expr *key, *setref; + struct set *existing_set; + struct table *table; + + table = table_cache_find(&ctx->nft->cache.table_cache, + ctx->cmd->handle.table.name, + ctx->cmd->handle.family); + if (table == NULL) + return table_not_found(ctx); + + existing_set = set_cache_find(table, stmt->meter.name); + if (existing_set && + (!set_is_meter_compat(existing_set->flags) || + set_is_map(existing_set->flags))) + return cmd_error(ctx, &stmt->location, + "%s; meter '%s' overlaps an existing %s '%s' in family %s", + strerror(EEXIST), + stmt->meter.name, + set_is_map(existing_set->flags) ? "map" : "set", + existing_set->handle.set.name, + family2str(existing_set->handle.family)); expr_set_context(&ctx->ectx, NULL, 0); if (expr_evaluate(ctx, &stmt->meter.key) < 0) @@ -2308,49 +3629,90 @@ static int stmt_evaluate_meter(struct eval_ctx *ctx, struct stmt *stmt) /* Declare an empty set */ key = stmt->meter.key; - set = set_expr_alloc(&key->location, NULL); - set->set_flags |= NFT_SET_EVAL; - if (key->timeout) - set->set_flags |= NFT_SET_TIMEOUT; + if (existing_set) { + if ((existing_set->flags & NFT_SET_TIMEOUT) && !key->timeout) + return expr_error(ctx->msgs, stmt->meter.key, + "existing set '%s' has timeout flag", + stmt->meter.name); + + if ((existing_set->flags & NFT_SET_TIMEOUT) == 0 && key->timeout) + return expr_error(ctx->msgs, stmt->meter.key, + "existing set '%s' lacks timeout flag", + stmt->meter.name); + + if (stmt->meter.size > 0 && existing_set->desc.size != stmt->meter.size) + return expr_error(ctx->msgs, stmt->meter.key, + "existing set '%s' has size %u, meter has %u", + stmt->meter.name, existing_set->desc.size, + stmt->meter.size); + setref = set_ref_expr_alloc(&key->location, existing_set); + } else { + struct expr *set; + + set = set_expr_alloc(&key->location, existing_set); + if (key->timeout) + expr_set(set)->set_flags |= NFT_SET_TIMEOUT; + + expr_set(set)->set_flags |= NFT_SET_EVAL; + setref = implicit_set_declaration(ctx, stmt->meter.name, + expr_get(key), NULL, set, + NFT_SET_EVAL | expr_set(set)->set_flags); + if (setref) + setref->set->desc.size = stmt->meter.size; + } - setref = implicit_set_declaration(ctx, stmt->meter.name, - expr_get(key), set); + if (!setref) + return -1; - setref->set->desc.size = stmt->meter.size; stmt->meter.set = setref; - if (stmt_evaluate(ctx, stmt->meter.stmt) < 0) + if (stmt_evaluate_stateful(ctx, stmt->meter.stmt, "meter") < 0) return -1; - if (!(stmt->meter.stmt->flags & STMT_F_STATEFUL)) - return stmt_binary_error(ctx, stmt->meter.stmt, stmt, - "meter statement must be stateful"); return 0; } static int stmt_evaluate_meta(struct eval_ctx *ctx, struct stmt *stmt) { - return stmt_evaluate_arg(ctx, stmt, - stmt->meta.tmpl->dtype, - stmt->meta.tmpl->len, - stmt->meta.tmpl->byteorder, - &stmt->meta.expr); + int ret; + + ctx->stmt_len = stmt->meta.tmpl->len; + + ret = stmt_evaluate_arg(ctx, stmt, + stmt->meta.tmpl->dtype, + stmt->meta.tmpl->len, + stmt->meta.tmpl->byteorder, + &stmt->meta.expr); + if (ret < 0) + return ret; + + if (stmt->meta.expr->etype == EXPR_RANGE) + return stmt_error_range(ctx, stmt, stmt->meta.expr); + + return ret; } static int stmt_evaluate_ct(struct eval_ctx *ctx, struct stmt *stmt) { - if (stmt_evaluate_arg(ctx, stmt, - stmt->ct.tmpl->dtype, - stmt->ct.tmpl->len, - stmt->ct.tmpl->byteorder, - &stmt->ct.expr) < 0) + int ret; + + ctx->stmt_len = stmt->ct.tmpl->len; + + ret = stmt_evaluate_arg(ctx, stmt, + stmt->ct.tmpl->dtype, + stmt->ct.tmpl->len, + stmt->ct.tmpl->byteorder, + &stmt->ct.expr); + if (ret < 0) return -1; - if (stmt->ct.key == NFT_CT_SECMARK && - expr_is_constant(stmt->ct.expr)) + if (stmt->ct.key == NFT_CT_SECMARK && expr_is_constant(stmt->ct.expr)) return stmt_error(ctx, stmt, "ct secmark must not be set to constant value"); + if (stmt->ct.expr->etype == EXPR_RANGE) + return stmt_error_range(ctx, stmt, stmt->ct.expr); + return 0; } @@ -2358,9 +3720,10 @@ static int reject_payload_gen_dependency_tcp(struct eval_ctx *ctx, struct stmt *stmt, struct expr **payload) { + struct proto_ctx *pctx = eval_proto_ctx(ctx); const struct proto_desc *desc; - desc = ctx->pctx.protocol[PROTO_BASE_TRANSPORT_HDR].desc; + desc = pctx->protocol[PROTO_BASE_TRANSPORT_HDR].desc; if (desc != NULL) return 0; *payload = payload_expr_alloc(&stmt->location, &proto_tcp, @@ -2372,9 +3735,10 @@ static int reject_payload_gen_dependency_family(struct eval_ctx *ctx, struct stmt *stmt, struct expr **payload) { + struct proto_ctx *pctx = eval_proto_ctx(ctx); const struct proto_desc *base; - base = ctx->pctx.protocol[PROTO_BASE_NETWORK_HDR].desc; + base = pctx->protocol[PROTO_BASE_NETWORK_HDR].desc; if (base != NULL) return 0; @@ -2397,8 +3761,7 @@ static int reject_payload_gen_dependency_family(struct eval_ctx *ctx, return 1; } -static int stmt_reject_gen_dependency(struct eval_ctx *ctx, struct stmt *stmt, - struct expr *expr) +static int stmt_reject_gen_dependency(struct eval_ctx *ctx, struct stmt *stmt) { struct expr *payload = NULL; struct stmt *nstmt; @@ -2433,7 +3796,7 @@ static int stmt_reject_gen_dependency(struct eval_ctx *ctx, struct stmt *stmt, */ list_add(&nstmt->list, &ctx->rule->stmts); out: - xfree(payload); + free(payload); return ret; } @@ -2441,6 +3804,7 @@ static int stmt_evaluate_reject_inet_family(struct eval_ctx *ctx, struct stmt *stmt, const struct proto_desc *desc) { + struct proto_ctx *pctx = eval_proto_ctx(ctx); const struct proto_desc *base; int protocol; @@ -2450,23 +3814,26 @@ static int stmt_evaluate_reject_inet_family(struct eval_ctx *ctx, case NFT_REJECT_ICMPX_UNREACH: break; case NFT_REJECT_ICMP_UNREACH: - base = ctx->pctx.protocol[PROTO_BASE_LL_HDR].desc; + base = pctx->protocol[PROTO_BASE_LL_HDR].desc; protocol = proto_find_num(base, desc); switch (protocol) { case NFPROTO_IPV4: + case __constant_htons(ETH_P_IP): if (stmt->reject.family == NFPROTO_IPV4) break; return stmt_binary_error(ctx, stmt->reject.expr, - &ctx->pctx.protocol[PROTO_BASE_NETWORK_HDR], + &pctx->protocol[PROTO_BASE_NETWORK_HDR], "conflicting protocols specified: ip vs ip6"); case NFPROTO_IPV6: + case __constant_htons(ETH_P_IPV6): if (stmt->reject.family == NFPROTO_IPV6) break; return stmt_binary_error(ctx, stmt->reject.expr, - &ctx->pctx.protocol[PROTO_BASE_NETWORK_HDR], + &pctx->protocol[PROTO_BASE_NETWORK_HDR], "conflicting protocols specified: ip vs ip6"); default: - BUG("unsupported family"); + return stmt_error(ctx, stmt, + "cannot infer ICMP reject variant to use: explicit value required.\n"); } break; } @@ -2474,18 +3841,18 @@ static int stmt_evaluate_reject_inet_family(struct eval_ctx *ctx, return 0; } -static int stmt_evaluate_reject_inet(struct eval_ctx *ctx, struct stmt *stmt, - struct expr *expr) +static int stmt_evaluate_reject_inet(struct eval_ctx *ctx, struct stmt *stmt) { + struct proto_ctx *pctx = eval_proto_ctx(ctx); const struct proto_desc *desc; - desc = ctx->pctx.protocol[PROTO_BASE_NETWORK_HDR].desc; + desc = pctx->protocol[PROTO_BASE_NETWORK_HDR].desc; if (desc != NULL && stmt_evaluate_reject_inet_family(ctx, stmt, desc) < 0) return -1; if (stmt->reject.type == NFT_REJECT_ICMPX_UNREACH) return 0; - if (stmt_reject_gen_dependency(ctx, stmt, expr) < 0) + if (stmt_reject_gen_dependency(ctx, stmt) < 0) return -1; return 0; } @@ -2494,13 +3861,14 @@ static int stmt_evaluate_reject_bridge_family(struct eval_ctx *ctx, struct stmt *stmt, const struct proto_desc *desc) { + struct proto_ctx *pctx = eval_proto_ctx(ctx); const struct proto_desc *base; int protocol; switch (stmt->reject.type) { case NFT_REJECT_ICMPX_UNREACH: case NFT_REJECT_TCP_RST: - base = ctx->pctx.protocol[PROTO_BASE_LL_HDR].desc; + base = pctx->protocol[PROTO_BASE_LL_HDR].desc; protocol = proto_find_num(base, desc); switch (protocol) { case __constant_htons(ETH_P_IP): @@ -2508,29 +3876,29 @@ static int stmt_evaluate_reject_bridge_family(struct eval_ctx *ctx, break; default: return stmt_binary_error(ctx, stmt, - &ctx->pctx.protocol[PROTO_BASE_NETWORK_HDR], + &pctx->protocol[PROTO_BASE_NETWORK_HDR], "cannot reject this network family"); } break; case NFT_REJECT_ICMP_UNREACH: - base = ctx->pctx.protocol[PROTO_BASE_LL_HDR].desc; + base = pctx->protocol[PROTO_BASE_LL_HDR].desc; protocol = proto_find_num(base, desc); switch (protocol) { case __constant_htons(ETH_P_IP): if (NFPROTO_IPV4 == stmt->reject.family) break; return stmt_binary_error(ctx, stmt->reject.expr, - &ctx->pctx.protocol[PROTO_BASE_NETWORK_HDR], + &pctx->protocol[PROTO_BASE_NETWORK_HDR], "conflicting protocols specified: ip vs ip6"); case __constant_htons(ETH_P_IPV6): if (NFPROTO_IPV6 == stmt->reject.family) break; return stmt_binary_error(ctx, stmt->reject.expr, - &ctx->pctx.protocol[PROTO_BASE_NETWORK_HDR], + &pctx->protocol[PROTO_BASE_NETWORK_HDR], "conflicting protocols specified: ip vs ip6"); default: return stmt_binary_error(ctx, stmt, - &ctx->pctx.protocol[PROTO_BASE_NETWORK_HDR], + &pctx->protocol[PROTO_BASE_NETWORK_HDR], "cannot reject this network family"); } break; @@ -2539,57 +3907,70 @@ static int stmt_evaluate_reject_bridge_family(struct eval_ctx *ctx, return 0; } -static int stmt_evaluate_reject_bridge(struct eval_ctx *ctx, struct stmt *stmt, - struct expr *expr) +static int stmt_evaluate_reject_bridge(struct eval_ctx *ctx, struct stmt *stmt) { + struct proto_ctx *pctx = eval_proto_ctx(ctx); const struct proto_desc *desc; - desc = ctx->pctx.protocol[PROTO_BASE_LL_HDR].desc; - if (desc != &proto_eth) - return stmt_binary_error(ctx, - &ctx->pctx.protocol[PROTO_BASE_LL_HDR], - stmt, "unsupported link layer protocol"); + desc = pctx->protocol[PROTO_BASE_LL_HDR].desc; + if (desc != &proto_eth && desc != &proto_vlan && desc != &proto_netdev) + return __stmt_binary_error(ctx, &stmt->location, NULL, + "cannot reject from this link layer protocol"); - desc = ctx->pctx.protocol[PROTO_BASE_NETWORK_HDR].desc; + desc = pctx->protocol[PROTO_BASE_NETWORK_HDR].desc; if (desc != NULL && stmt_evaluate_reject_bridge_family(ctx, stmt, desc) < 0) return -1; if (stmt->reject.type == NFT_REJECT_ICMPX_UNREACH) return 0; - if (stmt_reject_gen_dependency(ctx, stmt, expr) < 0) + if (stmt_reject_gen_dependency(ctx, stmt) < 0) return -1; return 0; } -static int stmt_evaluate_reject_family(struct eval_ctx *ctx, struct stmt *stmt, - struct expr *expr) +static int stmt_reject_error(struct eval_ctx *ctx, + const struct stmt *stmt, + const char *msg) +{ + struct expr *e = stmt->reject.expr; + + if (e) + return stmt_binary_error(ctx, e, stmt, "%s", msg); + + return stmt_error(ctx, stmt, "%s", msg); +} + +static int stmt_evaluate_reject_family(struct eval_ctx *ctx, struct stmt *stmt) { - switch (ctx->pctx.family) { + struct proto_ctx *pctx = eval_proto_ctx(ctx); + + switch (pctx->family) { case NFPROTO_ARP: return stmt_error(ctx, stmt, "cannot use reject with arp"); case NFPROTO_IPV4: case NFPROTO_IPV6: switch (stmt->reject.type) { case NFT_REJECT_TCP_RST: - if (stmt_reject_gen_dependency(ctx, stmt, expr) < 0) + if (stmt_reject_gen_dependency(ctx, stmt) < 0) return -1; break; case NFT_REJECT_ICMPX_UNREACH: - return stmt_binary_error(ctx, stmt->reject.expr, stmt, + return stmt_reject_error(ctx, stmt, "abstracted ICMP unreachable not supported"); case NFT_REJECT_ICMP_UNREACH: - if (stmt->reject.family == ctx->pctx.family) + if (stmt->reject.family == pctx->family) break; - return stmt_binary_error(ctx, stmt->reject.expr, stmt, + return stmt_reject_error(ctx, stmt, "conflicting protocols specified: ip vs ip6"); } break; case NFPROTO_BRIDGE: - if (stmt_evaluate_reject_bridge(ctx, stmt, expr) < 0) + case NFPROTO_NETDEV: + if (stmt_evaluate_reject_bridge(ctx, stmt) < 0) return -1; break; case NFPROTO_INET: - if (stmt_evaluate_reject_inet(ctx, stmt, expr) < 0) + if (stmt_evaluate_reject_inet(ctx, stmt) < 0) return -1; break; } @@ -2601,49 +3982,53 @@ static int stmt_evaluate_reject_family(struct eval_ctx *ctx, struct stmt *stmt, static int stmt_evaluate_reject_default(struct eval_ctx *ctx, struct stmt *stmt) { - int protocol; + struct proto_ctx *pctx = eval_proto_ctx(ctx); const struct proto_desc *desc, *base; + int protocol; - switch (ctx->pctx.family) { + switch (pctx->family) { case NFPROTO_IPV4: case NFPROTO_IPV6: stmt->reject.type = NFT_REJECT_ICMP_UNREACH; - stmt->reject.family = ctx->pctx.family; - if (ctx->pctx.family == NFPROTO_IPV4) + stmt->reject.family = pctx->family; + if (pctx->family == NFPROTO_IPV4) stmt->reject.icmp_code = ICMP_PORT_UNREACH; else stmt->reject.icmp_code = ICMP6_DST_UNREACH_NOPORT; break; case NFPROTO_INET: - desc = ctx->pctx.protocol[PROTO_BASE_NETWORK_HDR].desc; + desc = pctx->protocol[PROTO_BASE_NETWORK_HDR].desc; if (desc == NULL) { stmt->reject.type = NFT_REJECT_ICMPX_UNREACH; stmt->reject.icmp_code = NFT_REJECT_ICMPX_PORT_UNREACH; break; } stmt->reject.type = NFT_REJECT_ICMP_UNREACH; - base = ctx->pctx.protocol[PROTO_BASE_LL_HDR].desc; + base = pctx->protocol[PROTO_BASE_LL_HDR].desc; protocol = proto_find_num(base, desc); switch (protocol) { case NFPROTO_IPV4: + case __constant_htons(ETH_P_IP): stmt->reject.family = NFPROTO_IPV4; stmt->reject.icmp_code = ICMP_PORT_UNREACH; break; case NFPROTO_IPV6: + case __constant_htons(ETH_P_IPV6): stmt->reject.family = NFPROTO_IPV6; stmt->reject.icmp_code = ICMP6_DST_UNREACH_NOPORT; break; } break; case NFPROTO_BRIDGE: - desc = ctx->pctx.protocol[PROTO_BASE_NETWORK_HDR].desc; + case NFPROTO_NETDEV: + desc = pctx->protocol[PROTO_BASE_NETWORK_HDR].desc; if (desc == NULL) { stmt->reject.type = NFT_REJECT_ICMPX_UNREACH; stmt->reject.icmp_code = NFT_REJECT_ICMPX_PORT_UNREACH; break; } stmt->reject.type = NFT_REJECT_ICMP_UNREACH; - base = ctx->pctx.protocol[PROTO_BASE_LL_HDR].desc; + base = pctx->protocol[PROTO_BASE_LL_HDR].desc; protocol = proto_find_num(base, desc); switch (protocol) { case __constant_htons(ETH_P_IP): @@ -2662,7 +4047,10 @@ static int stmt_evaluate_reject_default(struct eval_ctx *ctx, static int stmt_evaluate_reject_icmp(struct eval_ctx *ctx, struct stmt *stmt) { - struct parse_ctx parse_ctx = { .tbl = &ctx->nft->output.tbl, }; + struct parse_ctx parse_ctx = { + .tbl = &ctx->nft->output.tbl, + .input = &ctx->nft->input, + }; struct error_record *erec; struct expr *code; @@ -2671,15 +4059,24 @@ static int stmt_evaluate_reject_icmp(struct eval_ctx *ctx, struct stmt *stmt) erec_queue(erec, ctx->msgs); return -1; } + + if (mpz_cmp_ui(code->value, UINT8_MAX) > 0) { + expr_free(code); + return expr_error(ctx->msgs, stmt->reject.expr, + "reject code must be integer in range 0-255"); + } + stmt->reject.icmp_code = mpz_get_uint8(code->value); + expr_free(code); + return 0; } static int stmt_evaluate_reset(struct eval_ctx *ctx, struct stmt *stmt) { - int protonum; + struct proto_ctx *pctx = eval_proto_ctx(ctx); const struct proto_desc *desc, *base; - struct proto_ctx *pctx = &ctx->pctx; + int protonum; desc = pctx->protocol[PROTO_BASE_TRANSPORT_HDR].desc; if (desc == NULL) @@ -2696,7 +4093,7 @@ static int stmt_evaluate_reset(struct eval_ctx *ctx, struct stmt *stmt) default: if (stmt->reject.type == NFT_REJECT_TCP_RST) { return stmt_binary_error(ctx, stmt, - &ctx->pctx.protocol[PROTO_BASE_TRANSPORT_HDR], + &pctx->protocol[PROTO_BASE_TRANSPORT_HDR], "you cannot use tcp reset with this protocol"); } break; @@ -2706,8 +4103,6 @@ static int stmt_evaluate_reset(struct eval_ctx *ctx, struct stmt *stmt) static int stmt_evaluate_reject(struct eval_ctx *ctx, struct stmt *stmt) { - struct expr *expr = ctx->cmd->expr; - if (stmt->reject.icmp_code < 0) { if (stmt_evaluate_reject_default(ctx, stmt) < 0) return -1; @@ -2719,27 +4114,29 @@ static int stmt_evaluate_reject(struct eval_ctx *ctx, struct stmt *stmt) return -1; } - return stmt_evaluate_reject_family(ctx, stmt, expr); + return stmt_evaluate_reject_family(ctx, stmt); } static int nat_evaluate_family(struct eval_ctx *ctx, struct stmt *stmt) { + struct proto_ctx *pctx = eval_proto_ctx(ctx); const struct proto_desc *nproto; - switch (ctx->pctx.family) { + switch (pctx->family) { case NFPROTO_IPV4: case NFPROTO_IPV6: if (stmt->nat.family == NFPROTO_UNSPEC) - stmt->nat.family = ctx->pctx.family; + stmt->nat.family = pctx->family; return 0; case NFPROTO_INET: - if (!stmt->nat.addr) + if (!stmt->nat.addr) { + stmt->nat.family = NFPROTO_INET; return 0; - + } if (stmt->nat.family != NFPROTO_UNSPEC) return 0; - nproto = ctx->pctx.protocol[PROTO_BASE_NETWORK_HDR].desc; + nproto = pctx->protocol[PROTO_BASE_NETWORK_HDR].desc; if (nproto == &proto_ip) stmt->nat.family = NFPROTO_IPV4; @@ -2753,81 +4150,160 @@ static int nat_evaluate_family(struct eval_ctx *ctx, struct stmt *stmt) } } +static const struct datatype *get_addr_dtype(uint8_t family) +{ + switch (family) { + case NFPROTO_IPV4: + return &ipaddr_type; + case NFPROTO_IPV6: + return &ip6addr_type; + } + + return &invalid_type; +} + static int evaluate_addr(struct eval_ctx *ctx, struct stmt *stmt, struct expr **expr) { - struct proto_ctx *pctx = &ctx->pctx; + struct proto_ctx *pctx = eval_proto_ctx(ctx); const struct datatype *dtype; - unsigned int len; - if (pctx->family == NFPROTO_IPV4) { - dtype = &ipaddr_type; - len = 4 * BITS_PER_BYTE; - } else { - dtype = &ip6addr_type; - len = 16 * BITS_PER_BYTE; - } + dtype = get_addr_dtype(pctx->family); - return stmt_evaluate_arg(ctx, stmt, dtype, len, BYTEORDER_BIG_ENDIAN, + return stmt_evaluate_arg(ctx, stmt, dtype, dtype->size, + BYTEORDER_BIG_ENDIAN, expr); } +static bool nat_evaluate_addr_has_th_expr(const struct expr *map) +{ + const struct expr *i, *concat; + + if (!map || map->etype != EXPR_MAP) + return false; + + concat = map->map; + if (concat ->etype != EXPR_CONCAT) + return false; + + list_for_each_entry(i, &expr_concat(concat)->expressions, list) { + enum proto_bases base; + + if (i->etype == EXPR_PAYLOAD && + i->payload.base == PROTO_BASE_TRANSPORT_HDR && + i->payload.desc != &proto_th) + return true; + + if ((i->flags & EXPR_F_PROTOCOL) == 0) + continue; + + switch (i->etype) { + case EXPR_META: + base = i->meta.base; + break; + case EXPR_PAYLOAD: + base = i->payload.base; + break; + default: + return false; + } + + if (base == PROTO_BASE_NETWORK_HDR) + return true; + } + + return false; +} + static int nat_evaluate_transport(struct eval_ctx *ctx, struct stmt *stmt, struct expr **expr) { - struct proto_ctx *pctx = &ctx->pctx; + struct proto_ctx *pctx = eval_proto_ctx(ctx); + int err; - if (pctx->protocol[PROTO_BASE_TRANSPORT_HDR].desc == NULL) + err = stmt_evaluate_arg(ctx, stmt, + &inet_service_type, 2 * BITS_PER_BYTE, + BYTEORDER_BIG_ENDIAN, expr); + if (err < 0) + return err; + + if (pctx->protocol[PROTO_BASE_TRANSPORT_HDR].desc == NULL && + !nat_evaluate_addr_has_th_expr(stmt->nat.addr)) return stmt_binary_error(ctx, *expr, stmt, "transport protocol mapping is only " "valid after transport protocol match"); - return stmt_evaluate_arg(ctx, stmt, - &inet_service_type, 2 * BITS_PER_BYTE, - BYTEORDER_BIG_ENDIAN, expr); + return 0; } static int stmt_evaluate_l3proto(struct eval_ctx *ctx, struct stmt *stmt, uint8_t family) { + struct proto_ctx *pctx = eval_proto_ctx(ctx); const struct proto_desc *nproto; - nproto = ctx->pctx.protocol[PROTO_BASE_NETWORK_HDR].desc; + nproto = pctx->protocol[PROTO_BASE_NETWORK_HDR].desc; if ((nproto == &proto_ip && family != NFPROTO_IPV4) || (nproto == &proto_ip6 && family != NFPROTO_IPV6)) return stmt_binary_error(ctx, stmt, - &ctx->pctx.protocol[PROTO_BASE_NETWORK_HDR], - "conflicting protocols specified: %s vs. %s. You must specify ip or ip6 family in tproxy statement", - ctx->pctx.protocol[PROTO_BASE_NETWORK_HDR].desc->name, - family2str(stmt->tproxy.family)); + &pctx->protocol[PROTO_BASE_NETWORK_HDR], + "conflicting protocols specified: %s vs. %s. You must specify ip or ip6 family in %s statement", + pctx->protocol[PROTO_BASE_NETWORK_HDR].desc->name, + family2str(family), + stmt_name(stmt)); return 0; } -static int stmt_evaluate_addr(struct eval_ctx *ctx, struct stmt *stmt, - uint8_t family, - struct expr **addr) +static void expr_family_infer(struct proto_ctx *pctx, const struct expr *expr, + uint8_t *family) { - const struct datatype *dtype; - unsigned int len; - int err; + struct expr *i; - if (ctx->pctx.family == NFPROTO_INET) { - switch (family) { - case NFPROTO_IPV4: - dtype = &ipaddr_type; - len = 4 * BITS_PER_BYTE; + if (expr->etype == EXPR_MAP) { + switch (expr->map->etype) { + case EXPR_CONCAT: + list_for_each_entry(i, &expr_concat(expr->map)->expressions, list) { + if (i->etype == EXPR_PAYLOAD) { + if (i->payload.desc == &proto_ip) + *family = NFPROTO_IPV4; + else if (i->payload.desc == &proto_ip6) + *family = NFPROTO_IPV6; + } + } break; - case NFPROTO_IPV6: - dtype = &ip6addr_type; - len = 16 * BITS_PER_BYTE; + case EXPR_PAYLOAD: + if (expr->map->payload.desc == &proto_ip) + *family = NFPROTO_IPV4; + else if (expr->map->payload.desc == &proto_ip6) + *family = NFPROTO_IPV6; break; default: + break; + } + } +} + +static int stmt_evaluate_addr(struct eval_ctx *ctx, struct stmt *stmt, + uint8_t *family, struct expr **addr) +{ + struct proto_ctx *pctx = eval_proto_ctx(ctx); + const struct datatype *dtype; + int err; + + if (pctx->family == NFPROTO_INET) { + if (*family == NFPROTO_INET || + *family == NFPROTO_UNSPEC) + expr_family_infer(pctx, *addr, family); + + dtype = get_addr_dtype(*family); + if (dtype->size == 0) { return stmt_error(ctx, stmt, - "ip or ip6 must be specified with address for inet tables."); + "specify `%s ip' or '%s ip6' in %s table to disambiguate", + stmt_name(stmt), stmt_name(stmt), family2str(pctx->family)); } - err = stmt_evaluate_arg(ctx, stmt, dtype, len, + err = stmt_evaluate_arg(ctx, stmt, dtype, dtype->size, BYTEORDER_BIG_ENDIAN, addr); } else { err = evaluate_addr(ctx, stmt, addr); @@ -2836,6 +4312,141 @@ static int stmt_evaluate_addr(struct eval_ctx *ctx, struct stmt *stmt, return err; } +static int stmt_evaluate_nat_map(struct eval_ctx *ctx, struct stmt *stmt) +{ + struct proto_ctx *pctx = eval_proto_ctx(ctx); + struct expr *one, *two, *data, *tmp; + const struct datatype *dtype = NULL; + const struct datatype *dtype2; + int addr_type; + int err; + + if (stmt->nat.proto) + return stmt_binary_error(ctx, stmt, stmt->nat.proto, + "nat map and protocol are mutually exclusive"); + + if (stmt->nat.family == NFPROTO_INET) + expr_family_infer(pctx, stmt->nat.addr, &stmt->nat.family); + + switch (stmt->nat.family) { + case NFPROTO_IPV4: + addr_type = TYPE_IPADDR; + break; + case NFPROTO_IPV6: + addr_type = TYPE_IP6ADDR; + break; + default: + return stmt_error(ctx, stmt, + "specify `%s ip' or '%s ip6' in %s table to disambiguate", + stmt_name(stmt), stmt_name(stmt), family2str(pctx->family)); + } + dtype = concat_type_alloc((addr_type << TYPE_BITS) | TYPE_INET_SERVICE); + + expr_set_context(&ctx->ectx, dtype, dtype->size); + if (expr_evaluate(ctx, &stmt->nat.addr)) { + err = -1; + goto out; + } + + if (pctx->protocol[PROTO_BASE_TRANSPORT_HDR].desc == NULL && + !nat_evaluate_addr_has_th_expr(stmt->nat.addr)) { + err = stmt_binary_error(ctx, stmt->nat.addr, stmt, + "transport protocol mapping is only " + "valid after transport protocol match"); + goto out; + } + + if (stmt->nat.addr->etype != EXPR_MAP) { + err = 0; + goto out; + } + + data = stmt->nat.addr->mappings->set->data; + if (data->flags & EXPR_F_INTERVAL) + stmt->nat.type_flags |= STMT_NAT_F_INTERVAL; + + datatype_set(data, dtype); + + if (expr_ops(data)->type != EXPR_CONCAT) { + err = __stmt_evaluate_arg(ctx, stmt, dtype, dtype->size, + BYTEORDER_BIG_ENDIAN, + &stmt->nat.addr); + goto out; + } + + one = list_first_entry(&expr_concat(data)->expressions, struct expr, list); + two = list_entry(one->list.next, struct expr, list); + + if (one == two || !list_is_last(&two->list, &expr_concat(data)->expressions)) { + err = __stmt_evaluate_arg(ctx, stmt, dtype, dtype->size, + BYTEORDER_BIG_ENDIAN, + &stmt->nat.addr); + goto out; + } + + dtype2 = get_addr_dtype(stmt->nat.family); + tmp = one; + err = __stmt_evaluate_arg(ctx, stmt, dtype2, dtype2->size, + BYTEORDER_BIG_ENDIAN, + &tmp); + if (err < 0) + goto out; + if (tmp != one) + BUG("Internal error: Unexpected alteration of l3 expression"); + + tmp = two; + err = nat_evaluate_transport(ctx, stmt, &tmp); + if (err < 0) + goto out; + if (tmp != two) + BUG("Internal error: Unexpected alteration of l4 expression"); + +out: + datatype_free(dtype); + return err; +} + +static bool nat_concat_map(struct eval_ctx *ctx, struct stmt *stmt) +{ + struct expr *i; + + if (stmt->nat.addr->etype != EXPR_MAP) + return false; + + switch (stmt->nat.addr->mappings->etype) { + case EXPR_SET: + list_for_each_entry(i, &expr_set(stmt->nat.addr->mappings)->expressions, list) { + if (i->etype == EXPR_MAPPING && + i->right->etype == EXPR_CONCAT) { + stmt->nat.type_flags |= STMT_NAT_F_CONCAT; + return true; + } + } + break; + case EXPR_SYMBOL: + /* expr_evaluate_map() see EXPR_SET_REF after this evaluation. */ + if (expr_evaluate(ctx, &stmt->nat.addr->mappings)) + return false; + + if (!set_is_datamap(stmt->nat.addr->mappings->set->flags)) { + expr_error(ctx->msgs, stmt->nat.addr->mappings, + "Expression is not a map"); + return false; + } + + if (stmt->nat.addr->mappings->set->data->etype == EXPR_CONCAT || + stmt->nat.addr->mappings->set->data->dtype->subtypes) { + stmt->nat.type_flags |= STMT_NAT_F_CONCAT; + return true; + } + break; + default: + break; + } + + return false; +} + static int stmt_evaluate_nat(struct eval_ctx *ctx, struct stmt *stmt) { int err; @@ -2849,15 +4460,29 @@ static int stmt_evaluate_nat(struct eval_ctx *ctx, struct stmt *stmt) if (err < 0) return err; - err = stmt_evaluate_addr(ctx, stmt, stmt->nat.family, + if (nat_concat_map(ctx, stmt) || + stmt->nat.type_flags & STMT_NAT_F_CONCAT) { + + err = stmt_evaluate_nat_map(ctx, stmt); + if (err < 0) + return err; + + stmt->flags |= STMT_F_TERMINAL; + return 0; + } + + err = stmt_evaluate_addr(ctx, stmt, &stmt->nat.family, &stmt->nat.addr); if (err < 0) return err; } + if (stmt->nat.proto != NULL) { err = nat_evaluate_transport(ctx, stmt, &stmt->nat.proto); if (err < 0) return err; + + stmt->nat.flags |= NF_NAT_RANGE_PROTO_SPECIFIED; } stmt->flags |= STMT_F_TERMINAL; @@ -2866,13 +4491,14 @@ static int stmt_evaluate_nat(struct eval_ctx *ctx, struct stmt *stmt) static int stmt_evaluate_tproxy(struct eval_ctx *ctx, struct stmt *stmt) { + struct proto_ctx *pctx = eval_proto_ctx(ctx); int err; - switch (ctx->pctx.family) { + switch (pctx->family) { case NFPROTO_IPV4: case NFPROTO_IPV6: /* fallthrough */ if (stmt->tproxy.family == NFPROTO_UNSPEC) - stmt->tproxy.family = ctx->pctx.family; + stmt->tproxy.family = pctx->family; break; case NFPROTO_INET: break; @@ -2881,7 +4507,7 @@ static int stmt_evaluate_tproxy(struct eval_ctx *ctx, struct stmt *stmt) "tproxy is only supported for IPv4/IPv6/INET"); } - if (ctx->pctx.protocol[PROTO_BASE_TRANSPORT_HDR].desc == NULL) + if (pctx->protocol[PROTO_BASE_TRANSPORT_HDR].desc == NULL) return stmt_error(ctx, stmt, "Transparent proxy support requires" " transport protocol match"); @@ -2893,22 +4519,22 @@ static int stmt_evaluate_tproxy(struct eval_ctx *ctx, struct stmt *stmt) return err; if (stmt->tproxy.addr != NULL) { - if (stmt->tproxy.addr->etype == EXPR_RANGE) - return stmt_error(ctx, stmt, "Address ranges are not supported for tproxy."); - - err = stmt_evaluate_addr(ctx, stmt, stmt->tproxy.family, + err = stmt_evaluate_addr(ctx, stmt, &stmt->tproxy.family, &stmt->tproxy.addr); - if (err < 0) return err; + + if (stmt->tproxy.addr->etype == EXPR_RANGE) + return stmt_error(ctx, stmt, "Address ranges are not supported for tproxy."); } if (stmt->tproxy.port != NULL) { - if (stmt->tproxy.port->etype == EXPR_RANGE) - return stmt_error(ctx, stmt, "Port ranges are not supported for tproxy."); err = nat_evaluate_transport(ctx, stmt, &stmt->tproxy.port); if (err < 0) return err; + + if (stmt->tproxy.port->etype == EXPR_RANGE) + return stmt_error(ctx, stmt, "Port ranges are not supported for tproxy."); } return 0; @@ -2926,11 +4552,75 @@ static int stmt_evaluate_synproxy(struct eval_ctx *ctx, struct stmt *stmt) return 0; } +static int rule_evaluate(struct eval_ctx *ctx, struct rule *rule, + enum cmd_ops op); + +static int stmt_evaluate_chain(struct eval_ctx *ctx, struct stmt *stmt) +{ + struct chain *chain = stmt->chain.chain; + struct cmd *cmd; + + chain->flags |= CHAIN_F_BINDING; + + if (ctx->table != NULL) { + list_add_tail(&chain->list, &ctx->table->chains); + } else { + struct rule *rule, *next; + struct handle h; + + memset(&h, 0, sizeof(h)); + handle_merge(&h, &chain->handle); + h.family = ctx->rule->handle.family; + free_const(h.table.name); + h.table.name = xstrdup(ctx->rule->handle.table.name); + h.chain.location = stmt->location; + h.chain_id = chain->handle.chain_id; + + cmd = cmd_alloc(CMD_ADD, CMD_OBJ_CHAIN, &h, &stmt->location, + chain); + cmd->location = stmt->location; + list_add_tail(&cmd->list, &ctx->cmd->list); + h.chain_id = chain->handle.chain_id; + + list_for_each_entry_safe(rule, next, &chain->rules, list) { + struct eval_ctx rule_ctx = { + .nft = ctx->nft, + .msgs = ctx->msgs, + .cmd = ctx->cmd, + }; + struct handle h2 = {}; + + handle_merge(&rule->handle, &ctx->rule->handle); + free_const(rule->handle.table.name); + rule->handle.table.name = xstrdup(ctx->rule->handle.table.name); + free_const(rule->handle.chain.name); + rule->handle.chain.name = NULL; + rule->handle.chain_id = chain->handle.chain_id; + if (rule_evaluate(&rule_ctx, rule, CMD_INVALID) < 0) + return -1; + + handle_merge(&h2, &rule->handle); + cmd = cmd_alloc(CMD_ADD, CMD_OBJ_RULE, &h2, + &rule->location, rule); + list_add_tail(&cmd->list, &ctx->cmd->list); + list_del(&rule->list); + } + } + + return 0; +} + +static int stmt_evaluate_optstrip(struct eval_ctx *ctx, struct stmt *stmt) +{ + return expr_evaluate(ctx, &stmt->optstrip.expr); +} + static int stmt_evaluate_dup(struct eval_ctx *ctx, struct stmt *stmt) { + struct proto_ctx *pctx = eval_proto_ctx(ctx); int err; - switch (ctx->pctx.family) { + switch (pctx->family) { case NFPROTO_IPV4: case NFPROTO_IPV6: if (stmt->dup.to == NULL) @@ -2947,6 +4637,9 @@ static int stmt_evaluate_dup(struct eval_ctx *ctx, struct stmt *stmt) &stmt->dup.dev); if (err < 0) return err; + + if (stmt->dup.dev->etype == EXPR_RANGE) + return stmt_error_range(ctx, stmt, stmt->dup.dev); } break; case NFPROTO_NETDEV: @@ -2965,15 +4658,20 @@ static int stmt_evaluate_dup(struct eval_ctx *ctx, struct stmt *stmt) default: return stmt_error(ctx, stmt, "unsupported family"); } + + if (stmt->dup.to->etype == EXPR_RANGE) + return stmt_error_range(ctx, stmt, stmt->dup.to); + return 0; } static int stmt_evaluate_fwd(struct eval_ctx *ctx, struct stmt *stmt) { + struct proto_ctx *pctx = eval_proto_ctx(ctx); const struct datatype *dtype; int err, len; - switch (ctx->pctx.family) { + switch (pctx->family) { case NFPROTO_NETDEV: if (stmt->fwd.dev == NULL) return stmt_error(ctx, stmt, @@ -2985,6 +4683,9 @@ static int stmt_evaluate_fwd(struct eval_ctx *ctx, struct stmt *stmt) if (err < 0) return err; + if (stmt->fwd.dev->etype == EXPR_RANGE) + return stmt_error_range(ctx, stmt, stmt->fwd.dev); + if (stmt->fwd.addr != NULL) { switch (stmt->fwd.family) { case NFPROTO_IPV4: @@ -3003,6 +4704,9 @@ static int stmt_evaluate_fwd(struct eval_ctx *ctx, struct stmt *stmt) &stmt->fwd.addr); if (err < 0) return err; + + if (stmt->fwd.addr->etype == EXPR_RANGE) + return stmt_error_range(ctx, stmt, stmt->fwd.addr); } break; default: @@ -3019,21 +4723,37 @@ static int stmt_evaluate_queue(struct eval_ctx *ctx, struct stmt *stmt) BYTEORDER_HOST_ENDIAN, &stmt->queue.queue) < 0) return -1; - if (!expr_is_constant(stmt->queue.queue)) - return expr_error(ctx->msgs, stmt->queue.queue, - "queue number is not constant"); - if (stmt->queue.queue->etype != EXPR_RANGE && - (stmt->queue.flags & NFT_QUEUE_FLAG_CPU_FANOUT)) + + if ((stmt->queue.flags & NFT_QUEUE_FLAG_CPU_FANOUT) && + stmt->queue.queue->etype != EXPR_RANGE) return expr_error(ctx->msgs, stmt->queue.queue, "fanout requires a range to be " "specified"); + + if (ctx->ectx.maxval > USHRT_MAX) + return expr_error(ctx->msgs, stmt->queue.queue, + "queue expression max value exceeds %u", USHRT_MAX); } stmt->flags |= STMT_F_TERMINAL; return 0; } +static int stmt_evaluate_log_prefix(struct eval_ctx *ctx, struct stmt *stmt) +{ + unsigned int len = strlen(stmt->log.prefix); + + if (len >= NF_LOG_PREFIXLEN) + return stmt_error(ctx, stmt, "log prefix is too long"); + else if (len == 0) + return stmt_error(ctx, stmt, "log prefix must have a minimum length of 1 character"); + + return 0; +} + static int stmt_evaluate_log(struct eval_ctx *ctx, struct stmt *stmt) { + int ret = 0; + if (stmt->log.flags & (STMT_LOG_GROUP | STMT_LOG_SNAPLEN | STMT_LOG_QTHRESHOLD)) { if (stmt->log.flags & STMT_LOG_LEVEL) @@ -3047,11 +4767,18 @@ static int stmt_evaluate_log(struct eval_ctx *ctx, struct stmt *stmt) (stmt->log.flags & ~STMT_LOG_LEVEL || stmt->log.logflags)) return stmt_error(ctx, stmt, "log level audit doesn't support any further options"); - return 0; + + if (stmt->log.prefix) + ret = stmt_evaluate_log_prefix(ctx, stmt); + + return ret; } static int stmt_evaluate_set(struct eval_ctx *ctx, struct stmt *stmt) { + struct set *this_set; + struct stmt *this; + expr_set_context(&ctx->ectx, NULL, 0); if (expr_evaluate(ctx, &stmt->set.set) < 0) return -1; @@ -3059,7 +4786,7 @@ static int stmt_evaluate_set(struct eval_ctx *ctx, struct stmt *stmt) return expr_error(ctx->msgs, stmt->set.set, "Expression does not refer to a set"); - if (stmt_evaluate_arg(ctx, stmt, + if (stmt_evaluate_key(ctx, stmt, stmt->set.set->set->key->dtype, stmt->set.set->set->key->len, stmt->set.set->set->key->byteorder, @@ -3071,19 +4798,27 @@ static int stmt_evaluate_set(struct eval_ctx *ctx, struct stmt *stmt) if (stmt->set.key->comment != NULL) return expr_error(ctx->msgs, stmt->set.key, "Key expression comments are not supported"); - if (stmt->set.stmt) { - if (stmt_evaluate(ctx, stmt->set.stmt) < 0) + list_for_each_entry(this, &stmt->set.stmt_list, list) { + if (stmt_evaluate_stateful(ctx, this, "set") < 0) return -1; - if (!(stmt->set.stmt->flags & STMT_F_STATEFUL)) - return stmt_binary_error(ctx, stmt->set.stmt, stmt, - "meter statement must be stateful"); } + this_set = stmt->set.set->set; + + /* Make sure EVAL flag is set on set definition so that kernel + * picks a set that allows updates from the packet path. + * + * Alternatively we could error out in case 'flags dynamic' was + * not given, but we can repair this here. + */ + this_set->flags |= NFT_SET_EVAL; return 0; } static int stmt_evaluate_map(struct eval_ctx *ctx, struct stmt *stmt) { + struct stmt *this; + expr_set_context(&ctx->ectx, NULL, 0); if (expr_evaluate(ctx, &stmt->map.set) < 0) return -1; @@ -3091,7 +4826,11 @@ static int stmt_evaluate_map(struct eval_ctx *ctx, struct stmt *stmt) return expr_error(ctx->msgs, stmt->map.set, "Expression does not refer to a set"); - if (stmt_evaluate_arg(ctx, stmt, + if (!set_is_map(stmt->map.set->set->flags)) + return expr_error(ctx->msgs, stmt->map.set, + "%s is not a map", stmt->map.set->set->handle.set.name); + + if (stmt_evaluate_key(ctx, stmt, stmt->map.set->set->key->dtype, stmt->map.set->set->key->len, stmt->map.set->set->key->byteorder, @@ -3116,13 +4855,13 @@ static int stmt_evaluate_map(struct eval_ctx *ctx, struct stmt *stmt) if (stmt->map.data->comment != NULL) return expr_error(ctx->msgs, stmt->map.data, "Data expression comments are not supported"); + if (stmt->map.data->timeout > 0) + return expr_error(ctx->msgs, stmt->map.data, + "Data expression timeouts are not supported"); - if (stmt->map.stmt) { - if (stmt_evaluate(ctx, stmt->map.stmt) < 0) + list_for_each_entry(this, &stmt->map.stmt_list, list) { + if (stmt_evaluate_stateful(ctx, this, "map") < 0) return -1; - if (!(stmt->map.stmt->flags & STMT_F_STATEFUL)) - return stmt_binary_error(ctx, stmt->map.stmt, stmt, - "meter statement must be stateful"); } return 0; @@ -3131,6 +4870,7 @@ static int stmt_evaluate_map(struct eval_ctx *ctx, struct stmt *stmt) static int stmt_evaluate_objref_map(struct eval_ctx *ctx, struct stmt *stmt) { struct expr *map = stmt->objref.expr; + uint32_t set_flags = NFT_SET_OBJECT; struct expr *mappings; struct expr *key; @@ -3142,23 +4882,22 @@ static int stmt_evaluate_objref_map(struct eval_ctx *ctx, struct stmt *stmt) "Map expression can not be constant"); mappings = map->mappings; - mappings->set_flags |= NFT_SET_OBJECT; switch (map->mappings->etype) { case EXPR_SET: + set_flags |= expr_set(mappings)->set_flags; + /* fallthrough */ + case EXPR_VARIABLE: key = constant_expr_alloc(&stmt->location, ctx->ectx.dtype, ctx->ectx.byteorder, ctx->ectx.len, NULL); mappings = implicit_set_declaration(ctx, "__objmap%d", - key, mappings); - - mappings->set->data = constant_expr_alloc(&netlink_location, - &string_type, - BYTEORDER_HOST_ENDIAN, - NFT_OBJ_MAXNAMELEN * BITS_PER_BYTE, - NULL); + key, NULL, mappings, + set_flags | NFT_SET_ANONYMOUS); + if (!mappings) + return -1; mappings->set->objtype = stmt->objref.type; map->mappings = mappings; @@ -3166,10 +4905,20 @@ static int stmt_evaluate_objref_map(struct eval_ctx *ctx, struct stmt *stmt) ctx->set = mappings->set; if (expr_evaluate(ctx, &map->mappings->set->init) < 0) return -1; + + if (map->mappings->set->init->etype != EXPR_SET) { + return expr_error(ctx->msgs, map->mappings->set->init, + "Expression is not a map"); + } + + if (set_is_interval(expr_set(map->mappings->set->init)->set_flags) && + !(expr_set(map->mappings->set->init)->set_flags & NFT_SET_CONCAT) && + interval_set_eval(ctx, ctx->set, map->mappings->set->init) < 0) + return -1; + ctx->set = NULL; - map->mappings->set->flags |= - map->mappings->set->init->set_flags; + map_set_concat_info(map); /* fall through */ case EXPR_SYMBOL: if (expr_evaluate(ctx, &map->mappings) < 0) @@ -3182,11 +4931,12 @@ static int stmt_evaluate_objref_map(struct eval_ctx *ctx, struct stmt *stmt) "Expression is not a map with objects"); break; default: - BUG("invalid mapping expression %s\n", - expr_name(map->mappings)); + return expr_binary_error(ctx->msgs, map->mappings, map->map, + "invalid mapping expression %s", + expr_name(map->mappings)); } - if (!datatype_equal(map->map->dtype, map->mappings->set->key->dtype)) + if (!datatype_compatible(map->mappings->set->key->dtype, map->map->dtype)) return expr_binary_error(ctx->msgs, map->mappings, map->map, "datatype mismatch, map expects %s, " "mapping expression has type %s", @@ -3227,16 +4977,19 @@ int stmt_evaluate(struct eval_ctx *ctx, struct stmt *stmt) if (ctx->nft->debug_mask & NFT_DEBUG_EVALUATION) { struct error_record *erec; erec = erec_create(EREC_INFORMATIONAL, &stmt->location, - "Evaluate %s", stmt->ops->name); + "Evaluate %s", stmt_name(stmt)); erec_print(&ctx->nft->output, erec, ctx->nft->debug_mask); stmt_print(stmt, &ctx->nft->output); nft_print(&ctx->nft->output, "\n\n"); erec_destroy(erec); } - switch (stmt->ops->type) { + ctx->stmt_len = 0; + + switch (stmt->type) { case STMT_CONNLIMIT: case STMT_COUNTER: + case STMT_LAST: case STMT_LIMIT: case STMT_QUOTA: case STMT_NOTRACK: @@ -3278,30 +5031,52 @@ int stmt_evaluate(struct eval_ctx *ctx, struct stmt *stmt) return stmt_evaluate_map(ctx, stmt); case STMT_SYNPROXY: return stmt_evaluate_synproxy(ctx, stmt); + case STMT_CHAIN: + return stmt_evaluate_chain(ctx, stmt); + case STMT_OPTSTRIP: + return stmt_evaluate_optstrip(ctx, stmt); default: - BUG("unknown statement type %s\n", stmt->ops->name); + BUG("unknown statement type %d\n", stmt->type); } } -static int setelem_evaluate(struct eval_ctx *ctx, struct expr **expr) +static int setelem_evaluate(struct eval_ctx *ctx, struct cmd *cmd) { struct table *table; struct set *set; - table = table_lookup_global(ctx); + table = table_cache_find(&ctx->nft->cache.table_cache, + ctx->cmd->handle.table.name, + ctx->cmd->handle.family); if (table == NULL) return table_not_found(ctx); - set = set_lookup(table, ctx->cmd->handle.set.name); + set = set_cache_find(table, ctx->cmd->handle.set.name); if (set == NULL) return set_not_found(ctx, &ctx->cmd->handle.set.location, ctx->cmd->handle.set.name); + if (set->key == NULL) + return -1; + + set->existing_set = set; ctx->set = set; expr_set_context(&ctx->ectx, set->key->dtype, set->key->len); - if (expr_evaluate(ctx, expr) < 0) + if (expr_evaluate(ctx, &cmd->expr) < 0) return -1; + + cmd->elem.set = set_get(set); + if (set_is_interval(ctx->set->flags)) { + if (!(set->flags & NFT_SET_CONCAT) && + interval_set_eval(ctx, ctx->set, cmd->expr) < 0) + return -1; + + assert(cmd->expr->etype == EXPR_SET); + expr_set(cmd->expr)->set_flags |= NFT_SET_INTERVAL; + } + ctx->set = NULL; + return 0; } @@ -3319,51 +5094,198 @@ static int set_key_data_error(struct eval_ctx *ctx, const struct set *set, dtype->name, name, hint); } +static int set_expr_evaluate_concat(struct eval_ctx *ctx, struct expr **expr) +{ + unsigned int flags = EXPR_F_CONSTANT | EXPR_F_SINGLETON; + uint32_t ntype = 0, size = 0; + struct expr *i, *next; + + list_for_each_entry_safe(i, next, &expr_concat(*expr)->expressions, list) { + unsigned dsize_bytes; + + if (i->etype == EXPR_CT && + (i->ct.key == NFT_CT_SRC || + i->ct.key == NFT_CT_DST)) + return expr_error(ctx->msgs, i, + "specify either ip or ip6 for address matching"); + + if (i->etype == EXPR_PAYLOAD && + i->dtype->type == TYPE_INTEGER) { + struct datatype *dtype; + + dtype = datatype_clone(i->dtype); + dtype->size = i->len; + dtype->byteorder = i->byteorder; + __datatype_set(i, dtype); + } + + if (i->dtype->size == 0 && i->len == 0) + return expr_binary_error(ctx->msgs, i, *expr, + "can not use variable sized " + "data types (%s) in concat " + "expressions", + i->dtype->name); + + flags &= i->flags; + + ntype = concat_subtype_add(ntype, i->dtype->type); + + dsize_bytes = div_round_up(i->len, BITS_PER_BYTE); + + if (i->dtype->size) + assert(dsize_bytes == div_round_up(i->dtype->size, BITS_PER_BYTE)); + + expr_concat(*expr)->field_len[expr_concat(*expr)->field_count++] = dsize_bytes; + size += netlink_padded_len(i->len); + + if (size > NFT_MAX_EXPR_LEN_BITS) + return expr_error(ctx->msgs, i, "Concatenation of size %u exceeds maximum size of %u", + size, NFT_MAX_EXPR_LEN_BITS); + } + + (*expr)->flags |= flags; + __datatype_set(*expr, concat_type_alloc(ntype)); + (*expr)->len = size; + + expr_set_context(&ctx->ectx, (*expr)->dtype, (*expr)->len); + ctx->ectx.key = *expr; + + return 0; +} + +static int elems_evaluate(struct eval_ctx *ctx, struct set *set) +{ + ctx->set = set; + if (set->init != NULL) { + if (set->key == NULL) + return set_error(ctx, set, "set definition does not specify key"); + + __expr_set_context(&ctx->ectx, set->key->dtype, + set->key->byteorder, set->key->len, 0); + if (expr_evaluate(ctx, &set->init) < 0) { + set->errors = true; + return -1; + } + if (set->init->etype != EXPR_SET) + return expr_error(ctx->msgs, set->init, "Set %s: Unexpected initial type %s, missing { }?", + set->handle.set.name, expr_name(set->init)); + } + + if (set_is_interval(ctx->set->flags) && + !(ctx->set->flags & NFT_SET_CONCAT) && + interval_set_eval(ctx, ctx->set, set->init) < 0) + return -1; + + ctx->set = NULL; + + return 0; +} + +static bool set_type_compatible(const struct set *set, const struct set *existing_set) +{ + if (set_is_datamap(set->flags)) + return set_is_datamap(existing_set->flags); + + if (set_is_objmap(set->flags)) + return set_is_objmap(existing_set->flags); + + assert(!set_is_map(set->flags)); + return !set_is_map(existing_set->flags); +} + static int set_evaluate(struct eval_ctx *ctx, struct set *set) { + struct set *existing_set = NULL; + unsigned int num_stmts = 0; struct table *table; + struct stmt *stmt; const char *type; - table = table_lookup_global(ctx); - if (table == NULL) - return table_not_found(ctx); + type = set_is_map(set->flags) ? "map" : "set"; + + if (set->key == NULL) + return set_error(ctx, set, "%s definition does not specify key", + type); + + if (!set_is_anonymous(set->flags)) { + table = table_cache_find(&ctx->nft->cache.table_cache, + set->handle.table.name, + set->handle.family); + if (table == NULL) + return table_not_found(ctx); + + existing_set = set_cache_find(table, set->handle.set.name); + if (existing_set) { + if (existing_set->flags & NFT_SET_EVAL) { + uint32_t existing_flags = existing_set->flags & ~NFT_SET_EVAL; + uint32_t new_flags = set->flags & ~NFT_SET_EVAL; + + if (existing_flags == new_flags) + set->flags |= NFT_SET_EVAL; + } + } else { + set_cache_add(set_get(set), table); + } + } if (!(set->flags & NFT_SET_INTERVAL) && set->automerge) return set_error(ctx, set, "auto-merge only works with interval sets"); - type = set_is_map(set->flags) ? "map" : "set"; - if (set->key == NULL) return set_error(ctx, set, "%s definition does not specify key", type); if (set->key->len == 0) { if (set->key->etype == EXPR_CONCAT && - expr_evaluate_concat(ctx, &set->key, false) < 0) + set_expr_evaluate_concat(ctx, &set->key) < 0) return -1; if (set->key->len == 0) return set_key_data_error(ctx, set, set->key->dtype, type); } - if (set->flags & NFT_SET_INTERVAL && - set->key->etype == EXPR_CONCAT) - return set_error(ctx, set, "concatenated types not supported in interval sets"); + + if (set->flags & NFT_SET_INTERVAL && set->key->etype == EXPR_CONCAT) { + memcpy(&set->desc.field_len, &expr_concat(set->key)->field_len, + sizeof(set->desc.field_len)); + set->desc.field_count = expr_concat(set->key)->field_count; + set->flags |= NFT_SET_CONCAT; + + if (set->automerge) + set->automerge = false; + } + + if (set_is_anonymous(set->flags) && set->key->etype == EXPR_CONCAT) { + struct expr *i; + + list_for_each_entry(i, &expr_set(set->init)->expressions, list) { + if ((i->etype == EXPR_SET_ELEM && + i->key->etype != EXPR_CONCAT && + i->key->etype != EXPR_SET_ELEM_CATCHALL) || + (i->etype == EXPR_MAPPING && + i->left->etype == EXPR_SET_ELEM && + i->left->key->etype != EXPR_CONCAT && + i->left->key->etype != EXPR_SET_ELEM_CATCHALL)) + return expr_error(ctx->msgs, i, "expression is not a concatenation"); + } + } if (set_is_datamap(set->flags)) { if (set->data == NULL) return set_error(ctx, set, "map definition does not " "specify mapping data type"); + if (set->data->etype == EXPR_CONCAT && + set_expr_evaluate_concat(ctx, &set->data) < 0) + return -1; + + if (set->data->flags & EXPR_F_INTERVAL) + set->data->len *= 2; + if (set->data->len == 0 && set->data->dtype->type != TYPE_VERDICT) return set_key_data_error(ctx, set, set->data->dtype, type); } else if (set_is_objmap(set->flags)) { - if (set->data) { - assert(set->data->etype == EXPR_VALUE); - assert(set->data->dtype == &string_type); - } - assert(set->data == NULL); set->data = constant_expr_alloc(&netlink_location, &string_type, BYTEORDER_HOST_ENDIAN, @@ -3372,20 +5294,52 @@ static int set_evaluate(struct eval_ctx *ctx, struct set *set) } - ctx->set = set; - if (set->init != NULL) { - expr_set_context(&ctx->ectx, set->key->dtype, set->key->len); - if (expr_evaluate(ctx, &set->init) < 0) + /* Default timeout value implies timeout support */ + if (set->timeout) + set->flags |= NFT_SET_TIMEOUT; + + list_for_each_entry(stmt, &set->stmt_list, list) { + if (stmt_evaluate_stateful(ctx, stmt,type) < 0) return -1; + num_stmts++; } - ctx->set = NULL; - if (set_lookup(table, set->handle.set.name) == NULL) - set_add_hash(set_get(set), table); + if (num_stmts > 1) + set->flags |= NFT_SET_EXPR; - /* Default timeout value implies timeout support */ - if (set->timeout) - set->flags |= NFT_SET_TIMEOUT; + if (set_is_anonymous(set->flags)) { + if (set->init->etype == EXPR_SET && + set_is_interval(expr_set(set->init)->set_flags) && + !(expr_set(set->init)->set_flags & NFT_SET_CONCAT) && + interval_set_eval(ctx, set, set->init) < 0) + return -1; + + return 0; + } + + if (existing_set) { + if (set_is_interval(set->flags) && !set_is_interval(existing_set->flags)) + return set_error(ctx, set, + "existing %s lacks interval flag", type); + if (set->data && existing_set->data && + !datatype_equal(existing_set->data->dtype, set->data->dtype)) + return set_error(ctx, set, + "%s already exists with different datatype (%s vs %s)", + type, existing_set->data->dtype->desc, + set->data->dtype->desc); + if (!datatype_equal(existing_set->key->dtype, set->key->dtype)) + return set_error(ctx, set, + "%s already exists with different datatype (%s vs %s)", + type, existing_set->key->dtype->desc, + set->key->dtype->desc); + /* Catch attempt to merge set and map */ + if (!set_type_compatible(set, existing_set)) + return set_error(ctx, set, "Cannot merge %s with incompatible existing %s of same name", + type, + set_is_map(existing_set->flags) ? "map" : "set"); + } + + set->existing_set = existing_set; return 0; } @@ -3400,8 +5354,8 @@ static bool evaluate_priority(struct eval_ctx *ctx, struct prio_spec *prio, int prio_snd; char op; - ctx->ectx.dtype = &priority_type; - ctx->ectx.len = NFT_NAME_MAXLEN * BITS_PER_BYTE; + expr_set_context(&ctx->ectx, &priority_type, NFT_NAME_MAXLEN * BITS_PER_BYTE); + if (expr_evaluate(ctx, &prio->expr) < 0) return false; if (prio->expr->etype != EXPR_VALUE) { @@ -3415,9 +5369,8 @@ static bool evaluate_priority(struct eval_ctx *ctx, struct prio_spec *prio, mpz_export_data(prio_str, prio->expr->value, BYTEORDER_HOST_ENDIAN, NFT_NAME_MAXLEN); loc = prio->expr->location; - expr_free(prio->expr); - if (sscanf(prio_str, "%s %c %d", prio_fst, &op, &prio_snd) < 3) { + if (sscanf(prio_str, "%255s %c %d", prio_fst, &op, &prio_snd) < 3) { priority = std_prio_lookup(prio_str, family, hook); if (priority == NF_IP_PRI_LAST) return false; @@ -3432,6 +5385,7 @@ static bool evaluate_priority(struct eval_ctx *ctx, struct prio_spec *prio, else return false; } + expr_free(prio->expr); prio->expr = constant_expr_alloc(&loc, &integer_type, BYTEORDER_HOST_ENDIAN, sizeof(int) * BITS_PER_BYTE, @@ -3439,27 +5393,149 @@ static bool evaluate_priority(struct eval_ctx *ctx, struct prio_spec *prio, return true; } +static bool evaluate_expr_variable(struct eval_ctx *ctx, struct expr **exprp) +{ + struct expr *expr; + + if (expr_evaluate(ctx, exprp) < 0) + return false; + + expr = *exprp; + if (expr->etype != EXPR_VALUE && + expr->etype != EXPR_SET) { + expr_error(ctx->msgs, expr, "%s is not a valid " + "variable expression", expr_name(expr)); + return false; + } + + return true; +} + +static struct expr *expr_set_to_list(struct eval_ctx *ctx, struct expr *dev_expr) +{ + struct expr *expr, *next, *key; + struct location loc; + LIST_HEAD(tmp); + + list_for_each_entry_safe(expr, next, &expr_set(dev_expr)->expressions, list) { + list_del(&expr->list); + + switch (expr->etype) { + case EXPR_VARIABLE: + expr_set_context(&ctx->ectx, &ifname_type, + IFNAMSIZ * BITS_PER_BYTE); + if (!evaluate_expr_variable(ctx, &expr)) + return false; + + if (expr->etype == EXPR_SET) { + expr = expr_set_to_list(ctx, expr); + list_splice_init(&expr_list(expr)->expressions, &tmp); + expr_free(expr); + continue; + } + break; + case EXPR_SET_ELEM: + key = expr_clone(expr->key); + expr_free(expr); + expr = key; + break; + case EXPR_VALUE: + break; + default: + break; + } + + list_add(&expr->list, &tmp); + } + + loc = dev_expr->location; + expr_free(dev_expr); + dev_expr = compound_expr_alloc(&loc, EXPR_LIST); + list_splice_init(&tmp, &expr_list(dev_expr)->expressions); + + return dev_expr; +} + +static bool evaluate_device_expr(struct eval_ctx *ctx, struct expr **dev_expr) +{ + struct expr *expr, *next; + LIST_HEAD(tmp); + + if ((*dev_expr)->etype == EXPR_VARIABLE) { + expr_set_context(&ctx->ectx, &ifname_type, + IFNAMSIZ * BITS_PER_BYTE); + if (!evaluate_expr_variable(ctx, dev_expr)) + return false; + } + + if ((*dev_expr)->etype == EXPR_SET) + *dev_expr = expr_set_to_list(ctx, *dev_expr); + + assert((*dev_expr)->etype == EXPR_LIST); + + list_for_each_entry_safe(expr, next, &expr_list(*dev_expr)->expressions, list) { + list_del(&expr->list); + + switch (expr->etype) { + case EXPR_VARIABLE: + expr_set_context(&ctx->ectx, &ifname_type, + IFNAMSIZ * BITS_PER_BYTE); + if (!evaluate_expr_variable(ctx, &expr)) + return false; + + if (expr->etype == EXPR_SET) { + expr = expr_set_to_list(ctx, expr); + list_splice_init(&expr_list(expr)->expressions, &tmp); + expr_free(expr); + continue; + } + break; + case EXPR_VALUE: + break; + default: + BUG("invalid expression type %s\n", expr_name(expr)); + break; + } + + list_add(&expr->list, &tmp); + } + list_splice_init(&tmp, &expr_list(*dev_expr)->expressions); + + return true; +} + static uint32_t str2hooknum(uint32_t family, const char *hook); static int flowtable_evaluate(struct eval_ctx *ctx, struct flowtable *ft) { struct table *table; - table = table_lookup_global(ctx); + table = table_cache_find(&ctx->nft->cache.table_cache, + ctx->cmd->handle.table.name, + ctx->cmd->handle.family); if (table == NULL) return table_not_found(ctx); - ft->hooknum = str2hooknum(NFPROTO_NETDEV, ft->hookstr); - if (ft->hooknum == NF_INET_NUMHOOKS) - return chain_error(ctx, ft, "invalid hook %s", ft->hookstr); + if (!ft_cache_find(table, ft->handle.flowtable.name)) { + if (!ft->hook.name && !ft->dev_expr) + return chain_error(ctx, ft, "missing hook and priority in flowtable declaration"); - if (!evaluate_priority(ctx, &ft->priority, NFPROTO_NETDEV, ft->hooknum)) - return __stmt_binary_error(ctx, &ft->priority.loc, NULL, - "invalid priority expression %s.", - expr_name(ft->priority.expr)); + ft_cache_add(flowtable_get(ft), table); + } - if (!ft->dev_expr) - return chain_error(ctx, ft, "Unbound flowtable not allowed (must specify devices)"); + if (ft->hook.name) { + ft->hook.num = str2hooknum(NFPROTO_NETDEV, ft->hook.name); + if (ft->hook.num == NF_INET_NUMHOOKS) + return chain_error(ctx, ft, "invalid hook %s", + ft->hook.name); + if (!evaluate_priority(ctx, &ft->priority, NFPROTO_NETDEV, ft->hook.num)) + return __stmt_binary_error(ctx, &ft->priority.loc, NULL, + "invalid priority expression %s.", + expr_name(ft->priority.expr)); + } + + if (ft->dev_expr && !evaluate_device_expr(ctx, &ft->dev_expr)) + return -1; return 0; } @@ -3488,11 +5564,17 @@ static int rule_cache_update(struct eval_ctx *ctx, enum cmd_ops op) struct table *table; struct chain *chain; - table = table_lookup(&rule->handle, &ctx->nft->cache); + table = table_cache_find(&ctx->nft->cache.table_cache, + rule->handle.table.name, + rule->handle.family); if (!table) return table_not_found(ctx); - chain = chain_lookup(table, &rule->handle); + /* chain is anonymous, adding new rules via index is not supported. */ + if (!rule->handle.chain.name) + return 0; + + chain = chain_cache_find(table, rule->handle.chain.name); if (!chain) return chain_not_found(ctx); @@ -3553,7 +5635,9 @@ static int rule_evaluate(struct eval_ctx *ctx, struct rule *rule, struct stmt *stmt, *tstmt = NULL; struct error_record *erec; - proto_ctx_init(&ctx->pctx, rule->handle.family, ctx->nft->debug_mask); + proto_ctx_init(&ctx->_pctx[0], rule->handle.family, ctx->nft->debug_mask, false); + /* use NFPROTO_BRIDGE to set up proto_eth as base protocol. */ + proto_ctx_init(&ctx->_pctx[1], NFPROTO_BRIDGE, ctx->nft->debug_mask, true); memset(&ctx->ectx, 0, sizeof(ctx->ectx)); ctx->rule = rule; @@ -3568,6 +5652,8 @@ static int rule_evaluate(struct eval_ctx *ctx, struct rule *rule, return -1; if (stmt->flags & STMT_F_TERMINAL) tstmt = stmt; + + ctx->inner_desc = NULL; } erec = rule_postprocess(rule); @@ -3576,7 +5662,7 @@ static int rule_evaluate(struct eval_ctx *ctx, struct rule *rule, return -1; } - if (cache_needs_update(&ctx->nft->cache)) + if (nft_cache_needs_update(&ctx->nft->cache)) return rule_cache_update(ctx, op); return 0; @@ -3588,10 +5674,13 @@ static uint32_t str2hooknum(uint32_t family, const char *hook) return NF_INET_NUMHOOKS; switch (family) { + case NFPROTO_INET: + if (!strcmp(hook, "ingress")) + return NF_INET_INGRESS; + /* fall through */ case NFPROTO_IPV4: case NFPROTO_BRIDGE: case NFPROTO_IPV6: - case NFPROTO_INET: /* These families have overlapping values for each hook */ if (!strcmp(hook, "prerouting")) return NF_INET_PRE_ROUTING; @@ -3615,6 +5704,8 @@ static uint32_t str2hooknum(uint32_t family, const char *hook) case NFPROTO_NETDEV: if (!strcmp(hook, "ingress")) return NF_NETDEV_INGRESS; + else if (!strcmp(hook, "egress")) + return NF_NETDEV_EGRESS; break; default: break; @@ -3623,70 +5714,66 @@ static uint32_t str2hooknum(uint32_t family, const char *hook) return NF_INET_NUMHOOKS; } -static bool evaluate_policy(struct eval_ctx *ctx, struct expr **exprp) -{ - struct expr *expr; - - ctx->ectx.dtype = &policy_type; - ctx->ectx.len = NFT_NAME_MAXLEN * BITS_PER_BYTE; - if (expr_evaluate(ctx, exprp) < 0) - return false; - - expr = *exprp; - if (expr->etype != EXPR_VALUE) { - expr_error(ctx->msgs, expr, "%s is not a valid " - "policy expression", expr_name(expr)); - return false; - } - - return true; -} - static int chain_evaluate(struct eval_ctx *ctx, struct chain *chain) { struct table *table; - struct rule *rule; - table = table_lookup_global(ctx); + table = table_cache_find(&ctx->nft->cache.table_cache, + ctx->cmd->handle.table.name, + ctx->cmd->handle.family); if (table == NULL) return table_not_found(ctx); if (chain == NULL) { - if (chain_lookup(table, &ctx->cmd->handle) == NULL) { - chain = chain_alloc(NULL); + if (!chain_cache_find(table, ctx->cmd->handle.chain.name)) { + chain = chain_alloc(); handle_merge(&chain->handle, &ctx->cmd->handle); - chain_add_hash(chain, table); + chain_cache_add(chain, table); } return 0; - } else { - if (chain_lookup(table, &chain->handle) == NULL) - chain_add_hash(chain_get(chain), table); + } else if (!(chain->flags & CHAIN_F_BINDING)) { + if (!chain_cache_find(table, chain->handle.chain.name)) + chain_cache_add(chain_get(chain), table); } if (chain->flags & CHAIN_F_BASECHAIN) { - chain->hooknum = str2hooknum(chain->handle.family, - chain->hookstr); - if (chain->hooknum == NF_INET_NUMHOOKS) - return chain_error(ctx, chain, "invalid hook %s", - chain->hookstr); + chain->hook.num = str2hooknum(chain->handle.family, + chain->hook.name); + if (chain->hook.num == NF_INET_NUMHOOKS) + return __stmt_binary_error(ctx, &chain->hook.loc, NULL, + "The %s family does not support this hook", + family2str(chain->handle.family)); if (!evaluate_priority(ctx, &chain->priority, - chain->handle.family, chain->hooknum)) + chain->handle.family, chain->hook.num)) return __stmt_binary_error(ctx, &chain->priority.loc, NULL, "invalid priority expression %s in this context.", expr_name(chain->priority.expr)); if (chain->policy) { - if (!evaluate_policy(ctx, &chain->policy)) + expr_set_context(&ctx->ectx, &policy_type, + NFT_NAME_MAXLEN * BITS_PER_BYTE); + if (!evaluate_expr_variable(ctx, &chain->policy)) return chain_error(ctx, chain, "invalid policy expression %s", expr_name(chain->policy)); } } - list_for_each_entry(rule, &chain->rules, list) { - handle_merge(&rule->handle, &chain->handle); - if (rule_evaluate(ctx, rule, CMD_INVALID) < 0) - return -1; + if (chain->dev_expr) { + if (!(chain->flags & CHAIN_F_BASECHAIN)) + chain->flags |= CHAIN_F_BASECHAIN; + + if (chain->handle.family == NFPROTO_NETDEV || + (chain->handle.family == NFPROTO_INET && + chain->hook.num == NF_INET_INGRESS)) { + if (chain->dev_expr && + !evaluate_device_expr(ctx, &chain->dev_expr)) + return -1; + } else if (chain->dev_expr) { + return __stmt_binary_error(ctx, &chain->dev_expr->location, NULL, + "This chain type cannot be bound to device"); + } } + return 0; } @@ -3720,7 +5807,8 @@ static int ct_timeout_evaluate(struct eval_ctx *ctx, struct obj *obj) ct->timeout[ts->timeout_index] = ts->timeout_value; list_del(&ts->head); - xfree(ts); + free_const(ts->timeout_str); + free(ts); } return 0; @@ -3728,6 +5816,17 @@ static int ct_timeout_evaluate(struct eval_ctx *ctx, struct obj *obj) static int obj_evaluate(struct eval_ctx *ctx, struct obj *obj) { + struct table *table; + + table = table_cache_find(&ctx->nft->cache.table_cache, + ctx->cmd->handle.table.name, + ctx->cmd->handle.family); + if (!table) + return table_not_found(ctx); + + if (!obj_cache_find(table, obj->handle.obj.name, obj->type)) + obj_cache_add(obj_get(obj), table); + switch (obj->type) { case NFT_OBJECT_CT_TIMEOUT: return ct_timeout_evaluate(ctx, obj); @@ -3742,60 +5841,31 @@ static int obj_evaluate(struct eval_ctx *ctx, struct obj *obj) static int table_evaluate(struct eval_ctx *ctx, struct table *table) { - struct flowtable *ft; - struct chain *chain; - struct set *set; - struct obj *obj; - - if (table_lookup(&ctx->cmd->handle, &ctx->nft->cache) == NULL) { - if (table == NULL) { + if (!table_cache_find(&ctx->nft->cache.table_cache, + ctx->cmd->handle.table.name, + ctx->cmd->handle.family)) { + if (!table) { table = table_alloc(); handle_merge(&table->handle, &ctx->cmd->handle); - table_add_hash(table, &ctx->nft->cache); + table_cache_add(table, &ctx->nft->cache); } else { - table_add_hash(table_get(table), &ctx->nft->cache); + table_cache_add(table_get(table), &ctx->nft->cache); } } - if (ctx->cmd->table == NULL) - return 0; - - ctx->table = table; - list_for_each_entry(set, &table->sets, list) { - expr_set_context(&ctx->ectx, NULL, 0); - handle_merge(&set->handle, &table->handle); - if (set_evaluate(ctx, set) < 0) - return -1; - } - list_for_each_entry(chain, &table->chains, list) { - handle_merge(&chain->handle, &table->handle); - ctx->cmd->handle.chain.location = chain->location; - if (chain_evaluate(ctx, chain) < 0) - return -1; - } - list_for_each_entry(ft, &table->flowtables, list) { - handle_merge(&ft->handle, &table->handle); - if (flowtable_evaluate(ctx, ft) < 0) - return -1; - } - list_for_each_entry(obj, &table->objs, list) { - handle_merge(&obj->handle, &table->handle); - if (obj_evaluate(ctx, obj) < 0) - return -1; - } - - ctx->table = NULL; return 0; } static int cmd_evaluate_add(struct eval_ctx *ctx, struct cmd *cmd) { switch (cmd->obj) { - case CMD_OBJ_SETELEM: - return setelem_evaluate(ctx, &cmd->expr); + case CMD_OBJ_ELEMENTS: + return setelem_evaluate(ctx, cmd); case CMD_OBJ_SET: handle_merge(&cmd->set->handle, &cmd->handle); return set_evaluate(ctx, cmd->set); + case CMD_OBJ_SETELEMS: + return elems_evaluate(ctx, cmd->set); case CMD_OBJ_RULE: handle_merge(&cmd->rule->handle, &cmd->handle); return rule_evaluate(ctx, cmd->rule, cmd->op); @@ -3814,30 +5884,160 @@ static int cmd_evaluate_add(struct eval_ctx *ctx, struct cmd *cmd) case CMD_OBJ_SECMARK: case CMD_OBJ_CT_EXPECT: case CMD_OBJ_SYNPROXY: + handle_merge(&cmd->object->handle, &cmd->handle); return obj_evaluate(ctx, cmd->object); default: BUG("invalid command object type %u\n", cmd->obj); } } +static void table_del_cache(struct eval_ctx *ctx, struct cmd *cmd) +{ + struct table *table; + + if (!cmd->handle.table.name) + return; + + table = table_cache_find(&ctx->nft->cache.table_cache, + cmd->handle.table.name, + cmd->handle.family); + if (!table) + return; + + table_cache_del(table); + table_free(table); +} + +static void chain_del_cache(struct eval_ctx *ctx, struct cmd *cmd) +{ + struct table *table; + struct chain *chain; + + if (!cmd->handle.chain.name) + return; + + table = table_cache_find(&ctx->nft->cache.table_cache, + cmd->handle.table.name, + cmd->handle.family); + if (!table) + return; + + chain = chain_cache_find(table, cmd->handle.chain.name); + if (!chain) + return; + + chain_cache_del(chain); + chain_free(chain); +} + +static void set_del_cache(struct eval_ctx *ctx, struct cmd *cmd) +{ + struct table *table; + struct set *set; + + if (!cmd->handle.set.name) + return; + + table = table_cache_find(&ctx->nft->cache.table_cache, + cmd->handle.table.name, + cmd->handle.family); + if (!table) + return; + + set = set_cache_find(table, cmd->handle.set.name); + if (!set) + return; + + set_cache_del(set); + set_free(set); +} + +static void ft_del_cache(struct eval_ctx *ctx, struct cmd *cmd) +{ + struct flowtable *ft; + struct table *table; + + if (!cmd->handle.flowtable.name) + return; + + table = table_cache_find(&ctx->nft->cache.table_cache, + cmd->handle.table.name, + cmd->handle.family); + if (!table) + return; + + ft = ft_cache_find(table, cmd->handle.flowtable.name); + if (!ft) + return; + + ft_cache_del(ft); + flowtable_free(ft); +} + +static void obj_del_cache(struct eval_ctx *ctx, struct cmd *cmd, int type) +{ + struct table *table; + struct obj *obj; + + if (!cmd->handle.obj.name) + return; + + table = table_cache_find(&ctx->nft->cache.table_cache, + cmd->handle.table.name, + cmd->handle.family); + if (!table) + return; + + obj = obj_cache_find(table, cmd->handle.obj.name, type); + if (!obj) + return; + + obj_cache_del(obj); + obj_free(obj); +} + static int cmd_evaluate_delete(struct eval_ctx *ctx, struct cmd *cmd) { switch (cmd->obj) { - case CMD_OBJ_SETELEM: - return setelem_evaluate(ctx, &cmd->expr); + case CMD_OBJ_ELEMENTS: + return setelem_evaluate(ctx, cmd); case CMD_OBJ_SET: + set_del_cache(ctx, cmd); + return 0; case CMD_OBJ_RULE: + return 0; case CMD_OBJ_CHAIN: + chain_del_cache(ctx, cmd); + return 0; case CMD_OBJ_TABLE: + table_del_cache(ctx, cmd); + return 0; case CMD_OBJ_FLOWTABLE: + ft_del_cache(ctx, cmd); + return 0; case CMD_OBJ_COUNTER: + obj_del_cache(ctx, cmd, NFT_OBJECT_COUNTER); + return 0; case CMD_OBJ_QUOTA: + obj_del_cache(ctx, cmd, NFT_OBJECT_QUOTA); + return 0; case CMD_OBJ_CT_HELPER: + obj_del_cache(ctx, cmd, NFT_OBJECT_CT_HELPER); + return 0; case CMD_OBJ_CT_TIMEOUT: + obj_del_cache(ctx, cmd, NFT_OBJECT_CT_TIMEOUT); + return 0; case CMD_OBJ_LIMIT: + obj_del_cache(ctx, cmd, NFT_OBJECT_LIMIT); + return 0; case CMD_OBJ_SECMARK: + obj_del_cache(ctx, cmd, NFT_OBJECT_SECMARK); + return 0; case CMD_OBJ_CT_EXPECT: + obj_del_cache(ctx, cmd, NFT_OBJECT_CT_EXPECT); + return 0; case CMD_OBJ_SYNPROXY: + obj_del_cache(ctx, cmd, NFT_OBJECT_SYNPROXY); return 0; default: BUG("invalid command object type %u\n", cmd->obj); @@ -3846,21 +6046,9 @@ static int cmd_evaluate_delete(struct eval_ctx *ctx, struct cmd *cmd) static int cmd_evaluate_get(struct eval_ctx *ctx, struct cmd *cmd) { - struct table *table; - struct set *set; - switch (cmd->obj) { - case CMD_OBJ_SETELEM: - table = table_lookup(&cmd->handle, &ctx->nft->cache); - if (table == NULL) - return table_not_found(ctx); - - set = set_lookup(table, cmd->handle.set.name); - if (set == NULL || set_is_map(set->flags)) - return set_not_found(ctx, &ctx->cmd->handle.set.location, - ctx->cmd->handle.set.name); - - return setelem_evaluate(ctx, &cmd->expr); + case CMD_OBJ_ELEMENTS: + return setelem_evaluate(ctx, cmd); default: BUG("invalid command object type %u\n", cmd->obj); } @@ -3877,7 +6065,7 @@ static int obj_not_found(struct eval_ctx *ctx, const struct location *loc, return cmd_error(ctx, loc, "%s", strerror(ENOENT)); return cmd_error(ctx, loc, - "%s; did you mean obj ‘%s’ in table %s ‘%s’?", + "%s; did you mean obj '%s' in table %s '%s'?", strerror(ENOENT), obj->handle.obj.name, family2str(obj->handle.family), table->handle.table.name); @@ -3891,11 +6079,13 @@ static int cmd_evaluate_list_obj(struct eval_ctx *ctx, const struct cmd *cmd, if (obj_type == NFT_OBJECT_UNSPEC) obj_type = NFT_OBJECT_COUNTER; - table = table_lookup(&cmd->handle, &ctx->nft->cache); + table = table_cache_find(&ctx->nft->cache.table_cache, + cmd->handle.table.name, + cmd->handle.family); if (table == NULL) return table_not_found(ctx); - if (obj_lookup(table, cmd->handle.obj.name, obj_type) == NULL) + if (!obj_cache_find(table, cmd->handle.obj.name, obj_type)) return obj_not_found(ctx, &cmd->handle.obj.location, cmd->handle.obj.name); @@ -3913,69 +6103,54 @@ static int cmd_evaluate_list(struct eval_ctx *ctx, struct cmd *cmd) if (cmd->handle.table.name == NULL) return 0; - table = table_lookup(&cmd->handle, &ctx->nft->cache); - if (table == NULL) + table = table_cache_find(&ctx->nft->cache.table_cache, + cmd->handle.table.name, + cmd->handle.family); + if (!table) return table_not_found(ctx); return 0; case CMD_OBJ_SET: - table = table_lookup(&cmd->handle, &ctx->nft->cache); - if (table == NULL) - return table_not_found(ctx); - - set = set_lookup(table, cmd->handle.set.name); - if (set == NULL) - return set_not_found(ctx, &ctx->cmd->handle.set.location, - ctx->cmd->handle.set.name); - else if (!set_is_literal(set->flags)) - return cmd_error(ctx, &ctx->cmd->handle.set.location, - "%s", strerror(ENOENT)); - - return 0; - case CMD_OBJ_METER: - table = table_lookup(&cmd->handle, &ctx->nft->cache); - if (table == NULL) - return table_not_found(ctx); - - set = set_lookup(table, cmd->handle.set.name); - if (set == NULL) - return set_not_found(ctx, &ctx->cmd->handle.set.location, - ctx->cmd->handle.set.name); - else if (!set_is_meter(set->flags)) - return cmd_error(ctx, &ctx->cmd->handle.set.location, - "%s", strerror(ENOENT)); - - return 0; case CMD_OBJ_MAP: - table = table_lookup(&cmd->handle, &ctx->nft->cache); - if (table == NULL) + case CMD_OBJ_METER: + table = table_cache_find(&ctx->nft->cache.table_cache, + cmd->handle.table.name, + cmd->handle.family); + if (!table) return table_not_found(ctx); - set = set_lookup(table, cmd->handle.set.name); + set = set_cache_find(table, cmd->handle.set.name); if (set == NULL) return set_not_found(ctx, &ctx->cmd->handle.set.location, ctx->cmd->handle.set.name); - else if (!map_is_literal(set->flags)) + if ((cmd->obj == CMD_OBJ_SET && !set_is_literal(set->flags)) || + (cmd->obj == CMD_OBJ_MAP && !map_is_literal(set->flags)) || + (cmd->obj == CMD_OBJ_METER && !set_is_meter_compat(set->flags))) return cmd_error(ctx, &ctx->cmd->handle.set.location, "%s", strerror(ENOENT)); + cmd->set = set_get(set); return 0; case CMD_OBJ_CHAIN: - table = table_lookup(&cmd->handle, &ctx->nft->cache); - if (table == NULL) + table = table_cache_find(&ctx->nft->cache.table_cache, + cmd->handle.table.name, + cmd->handle.family); + if (!table) return table_not_found(ctx); - if (chain_lookup(table, &cmd->handle) == NULL) + if (!chain_cache_find(table, cmd->handle.chain.name)) return chain_not_found(ctx); return 0; case CMD_OBJ_FLOWTABLE: - table = table_lookup(&cmd->handle, &ctx->nft->cache); - if (table == NULL) + table = table_cache_find(&ctx->nft->cache.table_cache, + cmd->handle.table.name, + cmd->handle.family); + if (!table) return table_not_found(ctx); - ft = flowtable_lookup(table, cmd->handle.flowtable.name); - if (ft == NULL) + ft = ft_cache_find(table, cmd->handle.flowtable.name); + if (!ft) return flowtable_not_found(ctx, &ctx->cmd->handle.flowtable.location, ctx->cmd->handle.flowtable.name); @@ -4004,9 +6179,13 @@ static int cmd_evaluate_list(struct eval_ctx *ctx, struct cmd *cmd) case CMD_OBJ_FLOWTABLES: case CMD_OBJ_SECMARKS: case CMD_OBJ_SYNPROXYS: + case CMD_OBJ_CT_TIMEOUTS: + case CMD_OBJ_CT_EXPECTATIONS: if (cmd->handle.table.name == NULL) return 0; - if (table_lookup(&cmd->handle, &ctx->nft->cache) == NULL) + if (!table_cache_find(&ctx->nft->cache.table_cache, + cmd->handle.table.name, + cmd->handle.family)) return table_not_found(ctx); return 0; @@ -4015,6 +6194,16 @@ static int cmd_evaluate_list(struct eval_ctx *ctx, struct cmd *cmd) case CMD_OBJ_METERS: case CMD_OBJ_MAPS: return 0; + case CMD_OBJ_HOOKS: + if (cmd->handle.chain.name) { + int hooknum = str2hooknum(cmd->handle.family, cmd->handle.chain.name); + + if (hooknum == NF_INET_NUMHOOKS) + return chain_not_found(ctx); + + cmd->handle.chain_id = hooknum; + } + return 0; default: BUG("invalid command object type %u\n", cmd->obj); } @@ -4027,19 +6216,39 @@ static int cmd_evaluate_reset(struct eval_ctx *ctx, struct cmd *cmd) case CMD_OBJ_QUOTA: case CMD_OBJ_COUNTERS: case CMD_OBJ_QUOTAS: + case CMD_OBJ_RULES: + case CMD_OBJ_RULE: if (cmd->handle.table.name == NULL) return 0; - if (table_lookup(&cmd->handle, &ctx->nft->cache) == NULL) + if (!table_cache_find(&ctx->nft->cache.table_cache, + cmd->handle.table.name, + cmd->handle.family)) return table_not_found(ctx); return 0; + case CMD_OBJ_ELEMENTS: + return setelem_evaluate(ctx, cmd); + case CMD_OBJ_TABLE: + case CMD_OBJ_CHAIN: + case CMD_OBJ_SET: + case CMD_OBJ_MAP: + return cmd_evaluate_list(ctx, cmd); default: BUG("invalid command object type %u\n", cmd->obj); } } +static void __flush_set_cache(struct set *set) +{ + if (set->init != NULL) { + expr_free(set->init); + set->init = NULL; + } +} + static int cmd_evaluate_flush(struct eval_ctx *ctx, struct cmd *cmd) { + struct cache *table_cache = &ctx->nft->cache.table_cache; struct table *table; struct set *set; @@ -4054,24 +6263,29 @@ static int cmd_evaluate_flush(struct eval_ctx *ctx, struct cmd *cmd) /* Chains don't hold sets */ break; case CMD_OBJ_SET: - table = table_lookup(&cmd->handle, &ctx->nft->cache); - if (table == NULL) + table = table_cache_find(table_cache, cmd->handle.table.name, + cmd->handle.family); + if (!table) return table_not_found(ctx); - set = set_lookup(table, cmd->handle.set.name); + set = set_cache_find(table, cmd->handle.set.name); if (set == NULL) return set_not_found(ctx, &ctx->cmd->handle.set.location, ctx->cmd->handle.set.name); else if (!set_is_literal(set->flags)) return cmd_error(ctx, &ctx->cmd->handle.set.location, "%s", strerror(ENOENT)); + + __flush_set_cache(set); + return 0; case CMD_OBJ_MAP: - table = table_lookup(&cmd->handle, &ctx->nft->cache); - if (table == NULL) + table = table_cache_find(table_cache, cmd->handle.table.name, + cmd->handle.family); + if (!table) return table_not_found(ctx); - set = set_lookup(table, cmd->handle.set.name); + set = set_cache_find(table, cmd->handle.set.name); if (set == NULL) return set_not_found(ctx, &ctx->cmd->handle.set.location, ctx->cmd->handle.set.name); @@ -4079,20 +6293,25 @@ static int cmd_evaluate_flush(struct eval_ctx *ctx, struct cmd *cmd) return cmd_error(ctx, &ctx->cmd->handle.set.location, "%s", strerror(ENOENT)); + __flush_set_cache(set); + return 0; case CMD_OBJ_METER: - table = table_lookup(&cmd->handle, &ctx->nft->cache); - if (table == NULL) + table = table_cache_find(table_cache, cmd->handle.table.name, + cmd->handle.family); + if (!table) return table_not_found(ctx); - set = set_lookup(table, cmd->handle.set.name); + set = set_cache_find(table, cmd->handle.set.name); if (set == NULL) return set_not_found(ctx, &ctx->cmd->handle.set.location, ctx->cmd->handle.set.name); - else if (!set_is_meter(set->flags)) + else if (!set_is_meter_compat(set->flags)) return cmd_error(ctx, &ctx->cmd->handle.set.location, "%s", strerror(ENOENT)); + __flush_set_cache(set); + return 0; default: BUG("invalid command object type %u\n", cmd->obj); @@ -4106,11 +6325,13 @@ static int cmd_evaluate_rename(struct eval_ctx *ctx, struct cmd *cmd) switch (cmd->obj) { case CMD_OBJ_CHAIN: - table = table_lookup(&ctx->cmd->handle, &ctx->nft->cache); - if (table == NULL) + table = table_cache_find(&ctx->nft->cache.table_cache, + cmd->handle.table.name, + cmd->handle.family); + if (!table) return table_not_found(ctx); - if (chain_lookup(table, &ctx->cmd->handle) == NULL) + if (!chain_cache_find(table, ctx->cmd->handle.chain.name)) return chain_not_found(ctx); break; @@ -4248,11 +6469,12 @@ static const char * const cmd_op_name[] = { [CMD_EXPORT] = "export", [CMD_MONITOR] = "monitor", [CMD_DESCRIBE] = "describe", + [CMD_DESTROY] = "destroy", }; static const char *cmd_op_to_name(enum cmd_ops op) { - if (op > CMD_DESCRIBE) + if (op >= array_size(cmd_op_name)) return "unknown"; return cmd_op_name[op]; @@ -4280,6 +6502,7 @@ int cmd_evaluate(struct eval_ctx *ctx, struct cmd *cmd) case CMD_INSERT: return cmd_evaluate_add(ctx, cmd); case CMD_DELETE: + case CMD_DESTROY: return cmd_evaluate_delete(ctx, cmd); case CMD_GET: return cmd_evaluate_get(ctx, cmd); @@ -4299,7 +6522,9 @@ int cmd_evaluate(struct eval_ctx *ctx, struct cmd *cmd) return cmd_evaluate_monitor(ctx, cmd); case CMD_IMPORT: return cmd_evaluate_import(ctx, cmd); - default: - BUG("invalid command operation %u\n", cmd->op); + case CMD_INVALID: + break; }; + + BUG("invalid command operation %u\n", cmd->op); } diff --git a/src/expression.c b/src/expression.c index cb11cda4..8cb63979 100644 --- a/src/expression.c +++ b/src/expression.c @@ -8,16 +8,16 @@ * Development of this code funded by Astaro AG (http://www.astaro.com/) */ +#include <nft.h> + #include <stddef.h> -#include <stdlib.h> #include <stdio.h> -#include <stdint.h> -#include <string.h> #include <limits.h> #include <expression.h> #include <statement.h> #include <datatype.h> +#include <netlink.h> #include <rule.h> #include <gmputil.h> #include <utils.h> @@ -28,6 +28,7 @@ extern const struct expr_ops ct_expr_ops; extern const struct expr_ops fib_expr_ops; extern const struct expr_ops hash_expr_ops; +extern const struct expr_ops inner_expr_ops; extern const struct expr_ops meta_expr_ops; extern const struct expr_ops numgen_expr_ops; extern const struct expr_ops osf_expr_ops; @@ -93,7 +94,7 @@ void expr_free(struct expr *expr) */ if (expr->etype != EXPR_INVALID) expr_destroy(expr); - xfree(expr); + free(expr); } void expr_print(const struct expr *expr, struct output_ctx *octx) @@ -138,6 +139,11 @@ void expr_describe(const struct expr *expr, struct output_ctx *octx) } else { nft_print(octx, "%s expression, datatype %s (%s)", expr_name(expr), dtype->name, dtype->desc); + + if (dtype == &invalid_type) { + nft_print(octx, "\n"); + return; + } } if (dtype->basetype != NULL) { @@ -172,6 +178,8 @@ void expr_describe(const struct expr *expr, struct output_ctx *octx) nft_print(octx, "(in hexadecimal):\n"); symbol_table_print(edtype->sym_tbl, edtype, expr->byteorder, octx); + } else if (edtype->describe) { + edtype->describe(octx); } } @@ -243,6 +251,22 @@ static void verdict_expr_destroy(struct expr *expr) expr_free(expr->chain); } +static int verdict_expr_build_udata(struct nftnl_udata_buf *udbuf, + const struct expr *expr) +{ + return 0; +} + +static struct expr *verdict_expr_parse_udata(const struct nftnl_udata *attr) +{ + struct expr *e; + + e = symbol_expr_alloc(&internal_location, SYMBOL_VALUE, NULL, "verdict"); + e->dtype = &verdict_type; + e->len = NFT_REG_SIZE * BITS_PER_BYTE; + return e; +} + static const struct expr_ops verdict_expr_ops = { .type = EXPR_VERDICT, .name = "verdict", @@ -251,6 +275,8 @@ static const struct expr_ops verdict_expr_ops = { .cmp = verdict_expr_cmp, .clone = verdict_expr_clone, .destroy = verdict_expr_destroy, + .build_udata = verdict_expr_build_udata, + .parse_udata = verdict_expr_parse_udata, }; struct expr *verdict_expr_alloc(const struct location *loc, @@ -269,8 +295,7 @@ struct expr *verdict_expr_alloc(const struct location *loc, static void symbol_expr_print(const struct expr *expr, struct output_ctx *octx) { - nft_print(octx, "%s%s", expr->scope != NULL ? "$" : "", - expr->identifier); + nft_print(octx, "%s", expr->identifier); } static void symbol_expr_clone(struct expr *new, const struct expr *expr) @@ -282,7 +307,7 @@ static void symbol_expr_clone(struct expr *new, const struct expr *expr) static void symbol_expr_destroy(struct expr *expr) { - xfree(expr->identifier); + free_const(expr->identifier); } static const struct expr_ops symbol_expr_ops = { @@ -346,6 +371,84 @@ struct expr *variable_expr_alloc(const struct location *loc, return expr; } +#define NFTNL_UDATA_CONSTANT_TYPE 0 +#define NFTNL_UDATA_CONSTANT_MAX NFTNL_UDATA_CONSTANT_TYPE + +#define CONSTANT_EXPR_NFQUEUE_ID 0 + +static int constant_expr_build_udata(struct nftnl_udata_buf *udbuf, + const struct expr *expr) +{ + uint32_t type; + + if (expr->dtype == &queue_type) + type = CONSTANT_EXPR_NFQUEUE_ID; + else + return -1; + + if (!nftnl_udata_put_u32(udbuf, NFTNL_UDATA_CONSTANT_TYPE, type)) + return -1; + + return 0; +} + +static int constant_parse_udata(const struct nftnl_udata *attr, void *data) +{ + const struct nftnl_udata **ud = data; + uint8_t type = nftnl_udata_type(attr); + uint8_t len = nftnl_udata_len(attr); + uint32_t value; + + switch (type) { + case NFTNL_UDATA_CONSTANT_TYPE: + if (len != sizeof(uint32_t)) + return -1; + + value = nftnl_udata_get_u32(attr); + switch (value) { + case CONSTANT_EXPR_NFQUEUE_ID: + break; + default: + return -1; + } + break; + default: + return 0; + } + + ud[type] = attr; + + return 0; +} + +static struct expr *constant_expr_parse_udata(const struct nftnl_udata *attr) +{ + const struct nftnl_udata *ud[NFTNL_UDATA_CONSTANT_MAX + 1] = {}; + const struct datatype *dtype = NULL; + uint32_t type; + int err; + + err = nftnl_udata_parse(nftnl_udata_get(attr), nftnl_udata_len(attr), + constant_parse_udata, ud); + if (err < 0) + return NULL; + + if (!ud[NFTNL_UDATA_CONSTANT_TYPE]) + return NULL; + + type = nftnl_udata_get_u32(ud[NFTNL_UDATA_CONSTANT_TYPE]); + switch (type) { + case CONSTANT_EXPR_NFQUEUE_ID: + dtype = &queue_type; + break; + default: + break; + } + + return constant_expr_alloc(&internal_location, dtype, BYTEORDER_HOST_ENDIAN, + 16, NULL); +} + static void constant_expr_print(const struct expr *expr, struct output_ctx *octx) { @@ -376,6 +479,8 @@ static const struct expr_ops constant_expr_ops = { .cmp = constant_expr_cmp, .clone = constant_expr_clone, .destroy = constant_expr_destroy, + .build_udata = constant_expr_build_udata, + .parse_udata = constant_expr_parse_udata, }; struct expr *constant_expr_alloc(const struct location *loc, @@ -389,7 +494,7 @@ struct expr *constant_expr_alloc(const struct location *loc, expr->flags = EXPR_F_CONSTANT | EXPR_F_SINGLETON; mpz_init2(expr->value, len); - if (data != NULL) + if (data != NULL && len) mpz_import_data(expr->value, data, byteorder, div_round_up(len, BITS_PER_BYTE)); @@ -437,6 +542,128 @@ struct expr *constant_expr_splice(struct expr *expr, unsigned int len) return slice; } +static void constant_range_expr_print_one(const struct expr *expr, + const mpz_t value, + struct output_ctx *octx) +{ + unsigned int len = div_round_up(expr->len, BITS_PER_BYTE); + unsigned char data[len]; + struct expr *dummy; + + /* create dummy temporary constant expression to print range. */ + mpz_export_data(data, value, expr->byteorder, len); + dummy = constant_expr_alloc(&expr->location, expr->dtype, + expr->byteorder, expr->len, data); + expr_print(dummy, octx); + expr_free(dummy); +} + +static void constant_range_expr_print(const struct expr *expr, + struct output_ctx *octx) +{ + unsigned int flags = octx->flags; + + /* similar to range_expr_print(). */ + octx->flags &= ~(NFT_CTX_OUTPUT_SERVICE | + NFT_CTX_OUTPUT_REVERSEDNS | + NFT_CTX_OUTPUT_GUID); + octx->flags |= NFT_CTX_OUTPUT_NUMERIC_ALL; + + constant_range_expr_print_one(expr, expr->range.low, octx); + nft_print(octx, "-"); + constant_range_expr_print_one(expr, expr->range.high, octx); + + octx->flags = flags; +} + +static bool constant_range_expr_cmp(const struct expr *e1, const struct expr *e2) +{ + return expr_basetype(e1) == expr_basetype(e2) && + !mpz_cmp(e1->range.low, e2->range.low) && + !mpz_cmp(e1->range.high, e2->range.high); +} + +static void constant_range_expr_clone(struct expr *new, const struct expr *expr) +{ + mpz_init_set(new->range.low, expr->range.low); + mpz_init_set(new->range.high, expr->range.high); +} + +static void constant_range_expr_destroy(struct expr *expr) +{ + mpz_clear(expr->range.low); + mpz_clear(expr->range.high); +} + +static const struct expr_ops constant_range_expr_ops = { + .type = EXPR_RANGE_VALUE, + .name = "range_value", + .print = constant_range_expr_print, + .cmp = constant_range_expr_cmp, + .clone = constant_range_expr_clone, + .destroy = constant_range_expr_destroy, +}; + +struct expr *constant_range_expr_alloc(const struct location *loc, + const struct datatype *dtype, + enum byteorder byteorder, + unsigned int len, mpz_t low, mpz_t high) +{ + struct expr *expr; + + expr = expr_alloc(loc, EXPR_RANGE_VALUE, dtype, byteorder, len); + expr->flags = EXPR_F_CONSTANT; + + mpz_init_set(expr->range.low, low); + mpz_init_set(expr->range.high, high); + + return expr; +} + +static void symbol_range_expr_print(const struct expr *expr, struct output_ctx *octx) +{ + nft_print(octx, "%s", expr->identifier_range[0]); + nft_print(octx, "-"); + nft_print(octx, "%s", expr->identifier_range[1]); +} + +static void symbol_range_expr_clone(struct expr *new, const struct expr *expr) +{ + new->symtype = expr->symtype; + new->scope = expr->scope; + new->identifier_range[0] = xstrdup(expr->identifier_range[0]); + new->identifier_range[1] = xstrdup(expr->identifier_range[1]); +} + +static void symbol_range_expr_destroy(struct expr *expr) +{ + free_const(expr->identifier_range[0]); + free_const(expr->identifier_range[1]); +} + +static const struct expr_ops symbol_range_expr_ops = { + .type = EXPR_RANGE_SYMBOL, + .name = "range_symbol", + .print = symbol_range_expr_print, + .clone = symbol_range_expr_clone, + .destroy = symbol_range_expr_destroy, +}; + +struct expr *symbol_range_expr_alloc(const struct location *loc, + enum symbol_types type, const struct scope *scope, + const char *identifier_low, const char *identifier_high) +{ + struct expr *expr; + + expr = expr_alloc(loc, EXPR_RANGE_SYMBOL, &invalid_type, + BYTEORDER_INVALID, 0); + expr->symtype = type; + expr->scope = scope; + expr->identifier_range[0] = xstrdup(identifier_low); + expr->identifier_range[1] = xstrdup(identifier_high); + return expr; +} + /* * Allocate a constant expression with a single bit set at position n. */ @@ -551,6 +778,7 @@ const char *expr_op_symbols[] = { [OP_GT] = ">", [OP_LTE] = "<=", [OP_GTE] = ">=", + [OP_NEG] = "!", }; static void unary_expr_print(const struct expr *expr, struct output_ctx *octx) @@ -699,16 +927,37 @@ struct expr *relational_expr_alloc(const struct location *loc, enum ops op, void relational_expr_pctx_update(struct proto_ctx *ctx, const struct expr *expr) { - const struct expr *left = expr->left; + const struct expr *left = expr->left, *right = expr->right; const struct expr_ops *ops; + const struct expr *i; assert(expr->etype == EXPR_RELATIONAL); assert(expr->op == OP_EQ || expr->op == OP_IMPLICIT); ops = expr_ops(left); if (ops->pctx_update && - (left->flags & EXPR_F_PROTOCOL)) - ops->pctx_update(ctx, expr); + (left->flags & EXPR_F_PROTOCOL)) { + if (expr_is_singleton(right)) + ops->pctx_update(ctx, &expr->location, left, right); + else if (right->etype == EXPR_SET) { + list_for_each_entry(i, &expr_set(right)->expressions, list) { + if (i->etype == EXPR_SET_ELEM && + i->key->etype == EXPR_VALUE) + ops->pctx_update(ctx, &expr->location, left, i->key); + } + } else if (ops == &meta_expr_ops && + right->etype == EXPR_SET_REF) { + const struct expr *key = right->set->key; + struct expr *tmp; + + tmp = constant_expr_alloc(&expr->location, key->dtype, + key->byteorder, key->len, + NULL); + + ops->pctx_update(ctx, &expr->location, left, tmp); + expr_free(tmp); + } + } } static void range_expr_print(const struct expr *expr, struct output_ctx *octx) @@ -773,7 +1022,8 @@ struct expr *compound_expr_alloc(const struct location *loc, struct expr *expr; expr = expr_alloc(loc, etype, &invalid_type, BYTEORDER_INVALID, 0); - init_list_head(&expr->expressions); + /* same layout for EXPR_CONCAT, EXPR_SET and EXPR_LIST. */ + init_list_head(&expr->expr_set.expressions); return expr; } @@ -781,8 +1031,8 @@ static void compound_expr_clone(struct expr *new, const struct expr *expr) { struct expr *i; - init_list_head(&new->expressions); - list_for_each_entry(i, &expr->expressions, list) + init_list_head(&new->expr_set.expressions); + list_for_each_entry(i, &expr->expr_set.expressions, list) compound_expr_add(new, expr_clone(i)); } @@ -790,7 +1040,7 @@ static void compound_expr_destroy(struct expr *expr) { struct expr *i, *next; - list_for_each_entry_safe(i, next, &expr->expressions, list) + list_for_each_entry_safe(i, next, &expr->expr_set.expressions, list) expr_free(i); } @@ -800,7 +1050,7 @@ static void compound_expr_print(const struct expr *expr, const char *delim, const struct expr *i; const char *d = ""; - list_for_each_entry(i, &expr->expressions, list) { + list_for_each_entry(i, &expr->expr_set.expressions, list) { nft_print(octx, "%s", d); expr_print(i, octx); d = delim; @@ -809,13 +1059,13 @@ static void compound_expr_print(const struct expr *expr, const char *delim, void compound_expr_add(struct expr *compound, struct expr *expr) { - list_add_tail(&expr->list, &compound->expressions); - compound->size++; + list_add_tail(&expr->list, &compound->expr_set.expressions); + compound->expr_set.size++; } void compound_expr_remove(struct expr *compound, struct expr *expr) { - compound->size--; + compound->expr_set.size--; list_del(&expr->list); } @@ -829,6 +1079,156 @@ static void concat_expr_print(const struct expr *expr, struct output_ctx *octx) compound_expr_print(expr, " . ", octx); } +#define NFTNL_UDATA_SET_KEY_CONCAT_NEST 0 +#define NFTNL_UDATA_SET_KEY_CONCAT_NEST_MAX NFT_REG32_SIZE + +#define NFTNL_UDATA_SET_KEY_CONCAT_SUB_TYPE 0 +#define NFTNL_UDATA_SET_KEY_CONCAT_SUB_DATA 1 +#define NFTNL_UDATA_SET_KEY_CONCAT_SUB_MAX 2 + +static struct expr *expr_build_udata_recurse(struct expr *e) +{ + switch (e->etype) { + case EXPR_BINOP: + return expr_build_udata_recurse(e->left); + default: + break; + } + + return e; +} + +static int concat_expr_build_udata(struct nftnl_udata_buf *udbuf, + const struct expr *concat_expr) +{ + struct nftnl_udata *nest; + struct expr *expr, *tmp; + unsigned int i = 0; + + list_for_each_entry_safe(expr, tmp, &expr_concat(concat_expr)->expressions, list) { + struct nftnl_udata *nest_expr; + int err; + + expr = expr_build_udata_recurse(expr); + if (!expr_ops(expr)->build_udata || i >= NFT_REG32_SIZE) + return -1; + + nest = nftnl_udata_nest_start(udbuf, NFTNL_UDATA_SET_KEY_CONCAT_NEST + i); + nftnl_udata_put_u32(udbuf, NFTNL_UDATA_SET_KEY_CONCAT_SUB_TYPE, expr->etype); + nest_expr = nftnl_udata_nest_start(udbuf, NFTNL_UDATA_SET_KEY_CONCAT_SUB_DATA); + err = expr_ops(expr)->build_udata(udbuf, expr); + if (err < 0) + return err; + nftnl_udata_nest_end(udbuf, nest_expr); + nftnl_udata_nest_end(udbuf, nest); + i++; + } + + return 0; +} + +static int concat_parse_udata_nest(const struct nftnl_udata *attr, void *data) +{ + const struct nftnl_udata **ud = data; + uint8_t type = nftnl_udata_type(attr); + uint8_t len = nftnl_udata_len(attr); + + if (type >= NFTNL_UDATA_SET_KEY_CONCAT_NEST_MAX) + return -1; + + if (len <= sizeof(uint32_t)) + return -1; + + ud[type] = attr; + return 0; +} + +static int concat_parse_udata_nested(const struct nftnl_udata *attr, void *data) +{ + const struct nftnl_udata **ud = data; + uint8_t type = nftnl_udata_type(attr); + uint8_t len = nftnl_udata_len(attr); + + switch (type) { + case NFTNL_UDATA_SET_KEY_CONCAT_SUB_TYPE: + if (len != sizeof(uint32_t)) + return -1; + break; + case NFTNL_UDATA_SET_KEY_CONCAT_SUB_DATA: + if (len <= sizeof(uint32_t)) + return -1; + break; + default: + return 0; + } + + ud[type] = attr; + return 0; +} + +static struct expr *concat_expr_parse_udata(const struct nftnl_udata *attr) +{ + const struct nftnl_udata *ud[NFTNL_UDATA_SET_KEY_CONCAT_NEST_MAX] = {}; + const struct datatype *dtype; + struct expr *concat_expr; + uint32_t dt = 0, len = 0; + unsigned int i; + int err; + + err = nftnl_udata_parse(nftnl_udata_get(attr), nftnl_udata_len(attr), + concat_parse_udata_nest, ud); + if (err < 0) + return NULL; + + concat_expr = concat_expr_alloc(&internal_location); + if (!concat_expr) + return NULL; + + for (i = 0; i < array_size(ud); i++) { + const struct nftnl_udata *nest_ud[NFTNL_UDATA_SET_KEY_CONCAT_SUB_MAX]; + const struct nftnl_udata *nested, *subdata; + const struct expr_ops *ops; + struct expr *expr; + uint32_t etype; + + if (ud[NFTNL_UDATA_SET_KEY_CONCAT_NEST + i] == NULL) + break; + + nested = ud[NFTNL_UDATA_SET_KEY_CONCAT_NEST + i]; + err = nftnl_udata_parse(nftnl_udata_get(nested), nftnl_udata_len(nested), + concat_parse_udata_nested, nest_ud); + if (err < 0) + goto err_free; + + etype = nftnl_udata_get_u32(nest_ud[NFTNL_UDATA_SET_KEY_CONCAT_SUB_TYPE]); + ops = expr_ops_by_type_u32(etype); + if (!ops || !ops->parse_udata) + goto err_free; + + subdata = nest_ud[NFTNL_UDATA_SET_KEY_CONCAT_SUB_DATA]; + expr = ops->parse_udata(subdata); + if (!expr) + goto err_free; + + dt = concat_subtype_add(dt, expr->dtype->type); + compound_expr_add(concat_expr, expr); + len += netlink_padded_len(expr->len); + } + + dtype = concat_type_alloc(dt); + if (!dtype) + goto err_free; + + __datatype_set(concat_expr, dtype); + concat_expr->len = len; + + return concat_expr; + +err_free: + expr_free(concat_expr); + return NULL; +} + static const struct expr_ops concat_expr_ops = { .type = EXPR_CONCAT, .name = "concat", @@ -836,6 +1236,8 @@ static const struct expr_ops concat_expr_ops = { .json = concat_expr_json, .clone = compound_expr_clone, .destroy = concat_expr_destroy, + .build_udata = concat_expr_build_udata, + .parse_udata = concat_expr_parse_udata, }; struct expr *concat_expr_alloc(const struct location *loc) @@ -862,12 +1264,44 @@ struct expr *list_expr_alloc(const struct location *loc) return compound_expr_alloc(loc, EXPR_LIST); } -static const char *calculate_delim(const struct expr *expr, int *count) +/* list is assumed to have two items at least, otherwise extend this! */ +struct expr *list_expr_to_binop(struct expr *expr) +{ + struct expr *first, *last = NULL, *i; + + assert(!list_empty(&expr_list(expr)->expressions)); + + first = list_first_entry(&expr_list(expr)->expressions, struct expr, list); + i = first; + + list_for_each_entry_continue(i, &expr_list(expr)->expressions, list) { + if (first) { + last = binop_expr_alloc(&expr->location, OP_OR, first, i); + first = NULL; + } else { + last = binop_expr_alloc(&expr->location, OP_OR, i, last); + } + } + /* list with one single item only, this should not happen. */ + assert(!first); + + /* zap list expressions, they have been moved to binop expression. */ + init_list_head(&expr_list(expr)->expressions); + expr_free(expr); + + return last; +} + +static const char *calculate_delim(const struct expr *expr, int *count, + struct output_ctx *octx) { const char *newline = ",\n\t\t\t "; const char *singleline = ", "; - if (set_is_anonymous(expr->set_flags)) + if (octx->force_newline) + return newline; + + if (set_is_anonymous(expr_set(expr)->set_flags)) return singleline; if (!expr->dtype) @@ -915,11 +1349,11 @@ static void set_expr_print(const struct expr *expr, struct output_ctx *octx) nft_print(octx, "{ "); - list_for_each_entry(i, &expr->expressions, list) { + list_for_each_entry(i, &expr_set(expr)->expressions, list) { nft_print(octx, "%s", d); expr_print(i, octx); count++; - d = calculate_delim(expr, &count); + d = calculate_delim(expr, &count, octx); } nft_print(octx, " }"); @@ -931,7 +1365,7 @@ static void set_expr_set_type(const struct expr *expr, { struct expr *i; - list_for_each_entry(i, &expr->expressions, list) + list_for_each_entry(i, &expr_set(expr)->expressions, list) expr_set_type(i, dtype, byteorder); } @@ -952,7 +1386,7 @@ struct expr *set_expr_alloc(const struct location *loc, const struct set *set) if (!set) return set_expr; - set_expr->set_flags = set->flags; + expr_set(set_expr)->set_flags = set->flags; datatype_set(set_expr, set->key->dtype); return set_expr; @@ -1006,14 +1440,40 @@ struct expr *mapping_expr_alloc(const struct location *loc, return expr; } +static bool __set_expr_is_vmap(const struct expr *mappings) +{ + const struct expr *mapping; + + if (list_empty(&expr_set(mappings)->expressions)) + return false; + + mapping = list_first_entry(&expr_set(mappings)->expressions, struct expr, list); + if (mapping->etype == EXPR_MAPPING && + mapping->right->etype == EXPR_VERDICT) + return true; + + return false; +} + +static bool set_expr_is_vmap(const struct expr *expr) +{ + + if (expr->mappings->etype == EXPR_SET) + return __set_expr_is_vmap(expr->mappings); + + return false; +} + static void map_expr_print(const struct expr *expr, struct output_ctx *octx) { expr_print(expr->map, octx); - if (expr->mappings->etype == EXPR_SET_REF && - expr->mappings->set->data->dtype->type == TYPE_VERDICT) + if ((expr->mappings->etype == EXPR_SET_REF && + expr->mappings->set->data->dtype->type == TYPE_VERDICT) || + set_expr_is_vmap(expr)) nft_print(octx, " vmap "); else nft_print(octx, " map "); + expr_print(expr->mappings, octx); } @@ -1069,6 +1529,32 @@ static void set_ref_expr_destroy(struct expr *expr) set_free(expr->set); } +static void set_ref_expr_set_type(const struct expr *expr, + const struct datatype *dtype, + enum byteorder byteorder) +{ + const struct set *s = expr->set; + + /* normal sets already have a precise datatype that is given in + * the set definition via type foo. + * + * Anon sets do not have this, and need to rely on type info + * generated at rule creation time. + * + * For most cases, the type info is correct. + * In some cases however, the kernel only stores TYPE_INTEGER. + * + * This happens with expressions that only use an integer alias + * type, e.g. the mptcpopt_subtype datatype. + * + * In this case nft will print the elements as numerical values + * because the base type lacks the ->sym_tbl information of the + * subtypes. + */ + if (s->init && set_is_anonymous(s->flags)) + expr_set_type(s->init, dtype, byteorder); +} + static const struct expr_ops set_ref_expr_ops = { .type = EXPR_SET_REF, .name = "set reference", @@ -1076,6 +1562,7 @@ static const struct expr_ops set_ref_expr_ops = { .json = set_ref_expr_json, .clone = set_ref_expr_clone, .destroy = set_ref_expr_destroy, + .set_type = set_ref_expr_set_type, }; struct expr *set_ref_expr_alloc(const struct location *loc, struct set *set) @@ -1091,37 +1578,60 @@ struct expr *set_ref_expr_alloc(const struct location *loc, struct set *set) static void set_elem_expr_print(const struct expr *expr, struct output_ctx *octx) { + struct stmt *stmt; + expr_print(expr->key, octx); + list_for_each_entry(stmt, &expr->stmt_list, list) { + nft_print(octx, " "); + stmt_print(stmt, octx); + } if (expr->timeout) { nft_print(octx, " timeout "); - time_print(expr->timeout, octx); + if (expr->timeout == NFT_NEVER_TIMEOUT) + nft_print(octx, "never"); + else + time_print(expr->timeout, octx); } - if (!nft_output_stateless(octx) && expr->expiration) { + if (!nft_output_stateless(octx) && + expr->timeout != NFT_NEVER_TIMEOUT && + expr->expiration) { nft_print(octx, " expires "); time_print(expr->expiration, octx); } - if (expr->stmt) { - nft_print(octx, " "); - stmt_print(expr->stmt, octx); - } if (expr->comment) nft_print(octx, " comment \"%s\"", expr->comment); } static void set_elem_expr_destroy(struct expr *expr) { - xfree(expr->comment); + struct stmt *stmt, *next; + + free_const(expr->comment); expr_free(expr->key); - stmt_free(expr->stmt); + list_for_each_entry_safe(stmt, next, &expr->stmt_list, list) + stmt_free(stmt); } -static void set_elem_expr_clone(struct expr *new, const struct expr *expr) +static void __set_elem_expr_clone(struct expr *new, const struct expr *expr) { - new->key = expr_clone(expr->key); new->expiration = expr->expiration; new->timeout = expr->timeout; if (expr->comment) new->comment = xstrdup(expr->comment); + init_list_head(&new->stmt_list); +} + +static void set_elem_expr_clone(struct expr *new, const struct expr *expr) +{ + new->key = expr_clone(expr->key); + __set_elem_expr_clone(new, expr); +} + +static void set_elem_expr_set_type(const struct expr *expr, + const struct datatype *dtype, + enum byteorder byteorder) +{ + expr_set_type(expr->key, dtype, byteorder); } static const struct expr_ops set_elem_expr_ops = { @@ -1131,6 +1641,7 @@ static const struct expr_ops set_elem_expr_ops = { .print = set_elem_expr_print, .json = set_elem_expr_json, .destroy = set_elem_expr_destroy, + .set_type = set_elem_expr_set_type, }; struct expr *set_elem_expr_alloc(const struct location *loc, struct expr *key) @@ -1140,6 +1651,38 @@ struct expr *set_elem_expr_alloc(const struct location *loc, struct expr *key) expr = expr_alloc(loc, EXPR_SET_ELEM, key->dtype, key->byteorder, key->len); expr->key = key; + init_list_head(&expr->stmt_list); + + return expr; +} + +static void set_elem_catchall_expr_print(const struct expr *expr, + struct output_ctx *octx) +{ + nft_print(octx, "*"); +} + +static void set_elem_catchall_expr_clone(struct expr *new, const struct expr *expr) +{ + __set_elem_expr_clone(new, expr); +} + +static const struct expr_ops set_elem_catchall_expr_ops = { + .type = EXPR_SET_ELEM_CATCHALL, + .name = "catch-all set element", + .print = set_elem_catchall_expr_print, + .json = set_elem_catchall_expr_json, + .clone = set_elem_catchall_expr_clone, +}; + +struct expr *set_elem_catchall_expr_alloc(const struct location *loc) +{ + struct expr *expr; + + expr = expr_alloc(loc, EXPR_SET_ELEM_CATCHALL, &invalid_type, + BYTEORDER_INVALID, 0); + expr->flags = EXPR_F_CONSTANT | EXPR_F_SINGLETON; + return expr; } @@ -1148,6 +1691,8 @@ void range_expr_value_low(mpz_t rop, const struct expr *expr) switch (expr->etype) { case EXPR_VALUE: return mpz_set(rop, expr->value); + case EXPR_RANGE_VALUE: + return mpz_set(rop, expr->range.low); case EXPR_PREFIX: return range_expr_value_low(rop, expr->prefix); case EXPR_RANGE: @@ -1168,8 +1713,11 @@ void range_expr_value_high(mpz_t rop, const struct expr *expr) switch (expr->etype) { case EXPR_VALUE: return mpz_set(rop, expr->value); + case EXPR_RANGE_VALUE: + return mpz_set(rop, expr->range.high); case EXPR_PREFIX: range_expr_value_low(rop, expr->prefix); + assert(expr->len >= expr->prefix_len); mpz_init_bitmask(tmp, expr->len - expr->prefix_len); mpz_add(rop, rop, tmp); mpz_clear(tmp); @@ -1185,12 +1733,10 @@ void range_expr_value_high(mpz_t rop, const struct expr *expr) } } -const struct expr_ops *expr_ops(const struct expr *e) +static const struct expr_ops *__expr_ops_by_type(enum expr_types etype) { - switch (e->etype) { - case EXPR_INVALID: - BUG("Invalid expression ops requested"); - break; + switch (etype) { + case EXPR_INVALID: break; case EXPR_VERDICT: return &verdict_expr_ops; case EXPR_SYMBOL: return &symbol_expr_ops; case EXPR_VARIABLE: return &variable_expr_ops; @@ -1218,28 +1764,30 @@ const struct expr_ops *expr_ops(const struct expr *e) case EXPR_RT: return &rt_expr_ops; case EXPR_FIB: return &fib_expr_ops; case EXPR_XFRM: return &xfrm_expr_ops; + case EXPR_SET_ELEM_CATCHALL: return &set_elem_catchall_expr_ops; + case EXPR_RANGE_VALUE: return &constant_range_expr_ops; + case EXPR_RANGE_SYMBOL: return &symbol_range_expr_ops; + case __EXPR_MAX: break; } - BUG("Unknown expression type %d\n", e->etype); + return NULL; } -const struct expr_ops *expr_ops_by_type(enum expr_types etype) +const struct expr_ops *expr_ops(const struct expr *e) { - switch (etype) { - case EXPR_PAYLOAD: return &payload_expr_ops; - case EXPR_EXTHDR: return &exthdr_expr_ops; - case EXPR_META: return &meta_expr_ops; - case EXPR_SOCKET: return &socket_expr_ops; - case EXPR_OSF: return &osf_expr_ops; - case EXPR_CT: return &ct_expr_ops; - case EXPR_NUMGEN: return &numgen_expr_ops; - case EXPR_HASH: return &hash_expr_ops; - case EXPR_RT: return &rt_expr_ops; - case EXPR_FIB: return &fib_expr_ops; - case EXPR_XFRM: return &xfrm_expr_ops; - default: - break; - } + const struct expr_ops *ops; + + ops = __expr_ops_by_type(e->etype); + if (!ops) + BUG("Unknown expression type %d\n", e->etype); + + return ops; +} + +const struct expr_ops *expr_ops_by_type_u32(uint32_t value) +{ + if (value > EXPR_MAX) + return NULL; - BUG("Unknown expression type %d\n", etype); + return __expr_ops_by_type(value); } diff --git a/src/exthdr.c b/src/exthdr.c index 0b23e0d3..c7d876a4 100644 --- a/src/exthdr.c +++ b/src/exthdr.c @@ -10,11 +10,10 @@ * Development of this code funded by Astaro AG (http://www.astaro.com/) */ +#include <nft.h> + #include <stddef.h> -#include <stdlib.h> #include <stdio.h> -#include <stdint.h> -#include <string.h> #include <netinet/in.h> #include <netinet/ip6.h> @@ -22,6 +21,7 @@ #include <headers.h> #include <expression.h> #include <statement.h> +#include <sctp_chunk.h> static const struct exthdr_desc *exthdr_definitions[PROTO_DESC_MAX + 1] = { [EXTHDR_DESC_HBH] = &exthdr_hbh, @@ -45,6 +45,9 @@ static const struct exthdr_desc *exthdr_find_desc(enum exthdr_desc_id desc_id) static void exthdr_expr_print(const struct expr *expr, struct output_ctx *octx) { + const char *name = expr->exthdr.desc ? + expr->exthdr.desc->name : "unknown-exthdr"; + if (expr->exthdr.op == NFT_EXTHDR_OP_TCPOPT) { /* Offset calculation is a bit hacky at this point. * There might be a tcp option one day with another @@ -52,23 +55,42 @@ static void exthdr_expr_print(const struct expr *expr, struct output_ctx *octx) */ unsigned int offset = expr->exthdr.offset / 64; - nft_print(octx, "tcp option %s", expr->exthdr.desc->name); + if (expr->exthdr.desc == NULL) { + if (expr->exthdr.offset == 0 && + expr->exthdr.flags & NFT_EXTHDR_F_PRESENT) { + nft_print(octx, "tcp option %d", expr->exthdr.raw_type); + return; + } + + nft_print(octx, "tcp option @%u,%u,%u", expr->exthdr.raw_type, + expr->exthdr.offset, expr->len); + return; + } + + nft_print(octx, "tcp option %s", name); if (expr->exthdr.flags & NFT_EXTHDR_F_PRESENT) return; if (offset) nft_print(octx, "%d", offset); nft_print(octx, " %s", expr->exthdr.tmpl->token); } else if (expr->exthdr.op == NFT_EXTHDR_OP_IPV4) { - nft_print(octx, "ip option %s", expr->exthdr.desc->name); + nft_print(octx, "ip option %s", name); + if (expr->exthdr.flags & NFT_EXTHDR_F_PRESENT) + return; + nft_print(octx, " %s", expr->exthdr.tmpl->token); + } else if (expr->exthdr.op == NFT_EXTHDR_OP_SCTP) { + nft_print(octx, "sctp chunk %s", expr->exthdr.desc->name); if (expr->exthdr.flags & NFT_EXTHDR_F_PRESENT) return; nft_print(octx, " %s", expr->exthdr.tmpl->token); + } else if (expr->exthdr.op == NFT_EXTHDR_OP_DCCP) { + nft_print(octx, "dccp option %d", expr->exthdr.raw_type); + return; } else { if (expr->exthdr.flags & NFT_EXTHDR_F_PRESENT) - nft_print(octx, "exthdr %s", expr->exthdr.desc->name); + nft_print(octx, "exthdr %s", name); else { - nft_print(octx, "%s %s", - expr->exthdr.desc ? expr->exthdr.desc->name : "unknown-exthdr", + nft_print(octx, "%s %s", name, expr->exthdr.tmpl->token); } } @@ -79,6 +101,7 @@ static bool exthdr_expr_cmp(const struct expr *e1, const struct expr *e2) return e1->exthdr.desc == e2->exthdr.desc && e1->exthdr.tmpl == e2->exthdr.tmpl && e1->exthdr.op == e2->exthdr.op && + e1->exthdr.raw_type == e2->exthdr.raw_type && e1->exthdr.flags == e2->exthdr.flags; } @@ -89,11 +112,13 @@ static void exthdr_expr_clone(struct expr *new, const struct expr *expr) new->exthdr.offset = expr->exthdr.offset; new->exthdr.op = expr->exthdr.op; new->exthdr.flags = expr->exthdr.flags; + new->exthdr.raw_type = expr->exthdr.raw_type; } #define NFTNL_UDATA_EXTHDR_DESC 0 #define NFTNL_UDATA_EXTHDR_TYPE 1 -#define NFTNL_UDATA_EXTHDR_MAX 2 +#define NFTNL_UDATA_EXTHDR_OP 2 +#define NFTNL_UDATA_EXTHDR_MAX 3 static int exthdr_parse_udata(const struct nftnl_udata *attr, void *data) { @@ -104,6 +129,7 @@ static int exthdr_parse_udata(const struct nftnl_udata *attr, void *data) switch (type) { case NFTNL_UDATA_EXTHDR_DESC: case NFTNL_UDATA_EXTHDR_TYPE: + case NFTNL_UDATA_EXTHDR_OP: if (len != sizeof(uint32_t)) return -1; break; @@ -118,6 +144,7 @@ static int exthdr_parse_udata(const struct nftnl_udata *attr, void *data) static struct expr *exthdr_expr_parse_udata(const struct nftnl_udata *attr) { const struct nftnl_udata *ud[NFTNL_UDATA_EXTHDR_MAX + 1] = {}; + enum nft_exthdr_op op = NFT_EXTHDR_OP_IPV6; const struct exthdr_desc *desc; unsigned int type; uint32_t desc_id; @@ -132,22 +159,39 @@ static struct expr *exthdr_expr_parse_udata(const struct nftnl_udata *attr) !ud[NFTNL_UDATA_EXTHDR_TYPE]) return NULL; - desc_id = nftnl_udata_get_u32(ud[NFTNL_UDATA_EXTHDR_DESC]); - desc = exthdr_find_desc(desc_id); - if (!desc) - return NULL; + if (ud[NFTNL_UDATA_EXTHDR_OP]) + op = nftnl_udata_get_u32(ud[NFTNL_UDATA_EXTHDR_OP]); + desc_id = nftnl_udata_get_u32(ud[NFTNL_UDATA_EXTHDR_DESC]); type = nftnl_udata_get_u32(ud[NFTNL_UDATA_EXTHDR_TYPE]); - return exthdr_expr_alloc(&internal_location, desc, type); + switch (op) { + case NFT_EXTHDR_OP_IPV6: + desc = exthdr_find_desc(desc_id); + + return exthdr_expr_alloc(&internal_location, desc, type); + case NFT_EXTHDR_OP_TCPOPT: + return tcpopt_expr_alloc(&internal_location, + desc_id, type); + case NFT_EXTHDR_OP_IPV4: + return ipopt_expr_alloc(&internal_location, + desc_id, type); + case NFT_EXTHDR_OP_SCTP: + return sctp_chunk_expr_alloc(&internal_location, + desc_id, type); + case NFT_EXTHDR_OP_DCCP: + return dccpopt_expr_alloc(&internal_location, type); + case __NFT_EXTHDR_OP_MAX: + return NULL; + } + + return NULL; } static unsigned int expr_exthdr_type(const struct exthdr_desc *desc, const struct proto_hdr_template *tmpl) { - unsigned int offset = (unsigned int)(tmpl - &desc->templates[0]); - - return offset / sizeof(*tmpl); + return (unsigned int)(tmpl - &desc->templates[0]); } static int exthdr_expr_build_udata(struct nftnl_udata_buf *udbuf, @@ -156,9 +200,23 @@ static int exthdr_expr_build_udata(struct nftnl_udata_buf *udbuf, const struct proto_hdr_template *tmpl = expr->exthdr.tmpl; const struct exthdr_desc *desc = expr->exthdr.desc; unsigned int type = expr_exthdr_type(desc, tmpl); + enum nft_exthdr_op op = expr->exthdr.op; - nftnl_udata_put_u32(udbuf, NFTNL_UDATA_EXTHDR_DESC, desc->id); nftnl_udata_put_u32(udbuf, NFTNL_UDATA_EXTHDR_TYPE, type); + switch (op) { + case NFT_EXTHDR_OP_IPV6: + nftnl_udata_put_u32(udbuf, NFTNL_UDATA_EXTHDR_DESC, desc->id); + break; + case NFT_EXTHDR_OP_TCPOPT: + case NFT_EXTHDR_OP_IPV4: + case NFT_EXTHDR_OP_SCTP: + case NFT_EXTHDR_OP_DCCP: + nftnl_udata_put_u32(udbuf, NFTNL_UDATA_EXTHDR_OP, op); + nftnl_udata_put_u32(udbuf, NFTNL_UDATA_EXTHDR_DESC, expr->exthdr.raw_type); + break; + default: + return -1; + } return 0; } @@ -192,7 +250,9 @@ struct expr *exthdr_expr_alloc(const struct location *loc, expr = expr_alloc(loc, EXPR_EXTHDR, tmpl->dtype, BYTEORDER_BIG_ENDIAN, tmpl->len); expr->exthdr.desc = desc; + expr->exthdr.raw_type = desc ? desc->type : 0; expr->exthdr.tmpl = tmpl; + expr->exthdr.offset = tmpl->offset; return expr; } @@ -209,7 +269,7 @@ static void exthdr_stmt_destroy(struct stmt *stmt) expr_free(stmt->exthdr.val); } -static const struct stmt_ops exthdr_stmt_ops = { +const struct stmt_ops exthdr_stmt_ops = { .type = STMT_EXTHDR, .name = "exthdr", .print = exthdr_stmt_print, @@ -228,7 +288,7 @@ struct stmt *exthdr_stmt_alloc(const struct location *loc, return stmt; } -static const struct exthdr_desc *exthdr_protocols[IPPROTO_MAX] = { +static const struct exthdr_desc *exthdr_protocols[UINT8_MAX + 1] = { [IPPROTO_HOPOPTS] = &exthdr_hbh, [IPPROTO_ROUTING] = &exthdr_rt, [IPPROTO_FRAGMENT] = &exthdr_frag, @@ -269,18 +329,23 @@ void exthdr_init_raw(struct expr *expr, uint8_t type, unsigned int i; assert(expr->etype == EXPR_EXTHDR); + expr->exthdr.raw_type = type; + if (op == NFT_EXTHDR_OP_TCPOPT) return tcpopt_init_raw(expr, type, offset, len, flags); if (op == NFT_EXTHDR_OP_IPV4) return ipopt_init_raw(expr, type, offset, len, flags, true); + if (op == NFT_EXTHDR_OP_SCTP) + return sctp_chunk_init_raw(expr, type, offset, len, flags); + if (op == NFT_EXTHDR_OP_DCCP) + return dccpopt_init_raw(expr, type, offset, len); expr->len = len; expr->exthdr.flags = flags; expr->exthdr.offset = offset; expr->exthdr.desc = NULL; - if (type < array_size(exthdr_protocols)) - expr->exthdr.desc = exthdr_protocols[type]; + expr->exthdr.desc = exthdr_protocols[type]; if (expr->exthdr.desc == NULL) goto out; @@ -322,16 +387,7 @@ static unsigned int mask_length(const struct expr *mask) bool exthdr_find_template(struct expr *expr, const struct expr *mask, unsigned int *shift) { unsigned int off, mask_offset, mask_len; - - if (expr->exthdr.op != NFT_EXTHDR_OP_IPV4 && - expr->exthdr.tmpl != &exthdr_unknown_template) - return false; - - /* In case we are handling tcp options instead of the default ipv6 - * extension headers. - */ - if (expr->exthdr.op == NFT_EXTHDR_OP_TCPOPT) - return tcpopt_find_template(expr, mask, shift); + bool found; mask_offset = mpz_scan1(mask->value, 0); mask_len = mask_length(mask); @@ -340,24 +396,31 @@ bool exthdr_find_template(struct expr *expr, const struct expr *mask, unsigned i off += round_up(mask->len, BITS_PER_BYTE) - mask_len; /* Handle ip options after the offset and mask have been calculated. */ - if (expr->exthdr.op == NFT_EXTHDR_OP_IPV4) { - if (ipopt_find_template(expr, off, mask_len - mask_offset)) { - *shift = mask_offset; - return true; - } else { + switch (expr->exthdr.op) { + case NFT_EXTHDR_OP_IPV4: + found = ipopt_find_template(expr, off, mask_len - mask_offset); + break; + case NFT_EXTHDR_OP_TCPOPT: + found = tcpopt_find_template(expr, off, mask_len - mask_offset); + break; + case NFT_EXTHDR_OP_IPV6: + exthdr_init_raw(expr, expr->exthdr.raw_type, + off, mask_len - mask_offset, expr->exthdr.op, 0); + + /* still failed to find a template... Bug. */ + if (expr->exthdr.tmpl == &exthdr_unknown_template) return false; - } + found = true; + break; + default: + found = false; + break; } - exthdr_init_raw(expr, expr->exthdr.desc->type, - off, mask_len - mask_offset, expr->exthdr.op, 0); + if (found) + *shift = mask_offset; - /* still failed to find a template... Bug. */ - if (expr->exthdr.tmpl == &exthdr_unknown_template) - return false; - - *shift = mask_offset; - return true; + return found; } #define HDR_TEMPLATE(__name, __dtype, __type, __member) \ @@ -387,14 +450,23 @@ const struct exthdr_desc exthdr_hbh = { * Routing header */ +/* similar to uapi/linux/ipv6.h */ +struct ip6_rt2_hdr { + struct ip6_rthdr rt_hdr; + uint32_t reserved; + struct in6_addr addr; +}; + +#define RT2_FIELD(__name, __member, __dtype) \ + HDR_TEMPLATE(__name, __dtype, struct ip6_rt2_hdr, __member) + const struct exthdr_desc exthdr_rt2 = { .name = "rt2", .id = EXTHDR_DESC_RT2, .type = IPPROTO_ROUTING, - .proto_key = 2, .templates = { - [RT2HDR_RESERVED] = {}, - [RT2HDR_ADDR] = {}, + [RT2HDR_RESERVED] = RT2_FIELD("reserved", reserved, &integer_type), + [RT2HDR_ADDR] = RT2_FIELD("addr", addr, &ip6addr_type), }, }; @@ -405,7 +477,6 @@ const struct exthdr_desc exthdr_rt0 = { .name = "rt0", .id = EXTHDR_DESC_RT0, .type = IPPROTO_ROUTING, - .proto_key = 0, .templates = { [RT0HDR_RESERVED] = RT0_FIELD("reserved", ip6r0_reserved, &integer_type), [RT0HDR_ADDR_1] = RT0_FIELD("addr[1]", ip6r0_addr[0], &ip6addr_type), @@ -421,7 +492,6 @@ const struct exthdr_desc exthdr_rt4 = { .name = "srh", .id = EXTHDR_DESC_SRH, .type = IPPROTO_ROUTING, - .proto_key = 4, .templates = { [RT4HDR_LASTENT] = RT4_FIELD("last-entry", ip6r4_last_entry, &integer_type), [RT4HDR_FLAGS] = RT4_FIELD("flags", ip6r4_flags, &integer_type), @@ -440,7 +510,6 @@ const struct exthdr_desc exthdr_rt = { .name = "rt", .id = EXTHDR_DESC_RT, .type = IPPROTO_ROUTING, - .proto_key = -1, #if 0 .protocol_key = RTHDR_TYPE, .protocols = { @@ -4,17 +4,18 @@ * Copyright (c) Red Hat GmbH. Author: Florian Westphal <fw@strlen.de> * * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. + * it under the terms of the GNU General Public License version 2 (or any + * later) as published by the Free Software Foundation. */ +#include <nft.h> + #include <nftables.h> #include <erec.h> #include <expression.h> #include <datatype.h> #include <gmputil.h> #include <utils.h> -#include <string.h> #include <fib.h> #include <linux/rtnetlink.h> @@ -52,8 +53,16 @@ const struct datatype fib_addr_type = { .sym_tbl = &addrtype_tbl, }; -const char *fib_result_str(enum nft_fib_result result) +const char *fib_result_str(const struct expr *expr) { + enum nft_fib_result result = expr->fib.result; + uint32_t flags = expr->fib.flags; + + /* Exception: check if route exists. */ + if (result == NFT_FIB_RESULT_OIF && + flags & NFTA_FIB_F_PRESENT) + return "check"; + if (result <= NFT_FIB_RESULT_MAX) return fib_result[result]; @@ -86,7 +95,7 @@ static void fib_expr_print(const struct expr *expr, struct output_ctx *octx) if (flags) nft_print(octx, "0x%x", flags); - nft_print(octx, " %s", fib_result_str(expr->fib.result)); + nft_print(octx, " %s", fib_result_str(expr)); } static bool fib_expr_cmp(const struct expr *e1, const struct expr *e2) @@ -178,7 +187,7 @@ struct expr *fib_expr_alloc(const struct location *loc, type = &ifindex_type; break; case NFT_FIB_RESULT_OIFNAME: - type = &string_type; + type = &ifname_type; len = IFNAMSIZ * BITS_PER_BYTE; break; case NFT_FIB_RESULT_ADDRTYPE: diff --git a/src/gmputil.c b/src/gmputil.c index b356460f..b4529259 100644 --- a/src/gmputil.c +++ b/src/gmputil.c @@ -8,12 +8,12 @@ * Development of this code funded by Astaro AG (http://www.astaro.com/) */ +#include <nft.h> + #include <stddef.h> -#include <stdlib.h> #include <stdarg.h> #include <stdio.h> #include <unistd.h> -#include <string.h> #include <nftables.h> #include <datatype.h> @@ -184,7 +184,7 @@ int mpz_vfprintf(FILE *fp, const char *f, va_list args) str = mpz_get_str(NULL, base, *value); ok = str && fwrite(str, 1, len, fp) == len; - free(str); + nft_gmp_free(str); if (!ok) return -1; @@ -197,12 +197,21 @@ int mpz_vfprintf(FILE *fp, const char *f, va_list args) } #endif -static void *gmp_xrealloc(void *ptr, size_t old_size, size_t new_size) +void nft_gmp_free(void *ptr) { - return xrealloc(ptr, new_size); -} + void (*free_fcn)(void *, size_t); -void gmp_init(void) -{ - mp_set_memory_functions(xmalloc, gmp_xrealloc, NULL); + /* When we get allocated memory from gmp, it was allocated via the + * allocator() from mp_set_memory_functions(). We should pair the free + * with the corresponding free function, which we get via + * mp_get_memory_functions(). + * + * It's not clear what the correct blk_size is. The default allocator + * function of gmp just wraps free() and ignores the extra argument. + * Assume 0 is fine. + */ + + mp_get_memory_functions(NULL, NULL, &free_fcn); + + (*free_fcn)(ptr, 0); } @@ -4,10 +4,12 @@ * Copyright (c) 2016 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 as - * published by the Free Software Foundation. + * it under the terms of the GNU General Public License version 2 (or any + * later) as published by the Free Software Foundation. */ +#include <nft.h> + #include <nftables.h> #include <expression.h> #include <datatype.h> diff --git a/src/iface.c b/src/iface.c index d0e1834c..a85341a1 100644 --- a/src/iface.c +++ b/src/iface.c @@ -2,15 +2,15 @@ * Copyright (c) 2015 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 as - * published by the Free Software Foundation. + * it under the terms of the GNU General Public License version 2 (or any + * later) as published by the Free Software Foundation. */ +#include <nft.h> + #include <stdio.h> -#include <stdlib.h> #include <net/if.h> #include <time.h> -#include <string.h> #include <errno.h> #include <libmnl/libmnl.h> @@ -59,13 +59,13 @@ static int data_cb(const struct nlmsghdr *nlh, void *data) return MNL_CB_OK; } -void iface_cache_update(void) +static int iface_mnl_talk(struct mnl_socket *nl, uint32_t portid) { char buf[MNL_SOCKET_BUFFER_SIZE]; - struct mnl_socket *nl; struct nlmsghdr *nlh; struct rtgenmsg *rt; - uint32_t seq, portid; + bool eintr = false; + uint32_t seq; int ret; nlh = mnl_nlmsg_put_header(buf); @@ -75,6 +75,38 @@ void iface_cache_update(void) rt = mnl_nlmsg_put_extra_header(nlh, sizeof(struct rtgenmsg)); rt->rtgen_family = AF_PACKET; + if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) + return -1; + + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + while (ret > 0) { + ret = mnl_cb_run(buf, ret, seq, portid, data_cb, NULL); + if (ret == 0) + break; + if (ret < 0) { + if (errno != EINTR) + return ret; + + /* process all pending messages before reporting EINTR */ + eintr = true; + } + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + } + + if (eintr) { + ret = -1; + errno = EINTR; + } + + return ret; +} + +void iface_cache_update(void) +{ + struct mnl_socket *nl; + uint32_t portid; + int ret; + nl = mnl_socket_open(NETLINK_ROUTE); if (nl == NULL) netlink_init_error(); @@ -84,16 +116,10 @@ void iface_cache_update(void) portid = mnl_socket_get_portid(nl); - if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) - netlink_init_error(); + do { + ret = iface_mnl_talk(nl, portid); + } while (ret < 0 && errno == EINTR); - ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); - while (ret > 0) { - ret = mnl_cb_run(buf, ret, seq, portid, data_cb, NULL); - if (ret <= MNL_CB_STOP) - break; - ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); - } if (ret == -1) netlink_init_error(); @@ -145,3 +171,20 @@ char *nft_if_indextoname(unsigned int ifindex, char *name) } return NULL; } + +const struct iface *iface_cache_get_next_entry(const struct iface *prev) +{ + if (!iface_cache_init) + iface_cache_update(); + + if (list_empty(&iface_list)) + return NULL; + + if (!prev) + return list_first_entry(&iface_list, struct iface, list); + + if (list_is_last(&prev->list, &iface_list)) + return NULL; + + return list_next_entry(prev, list); +} diff --git a/src/intervals.c b/src/intervals.c new file mode 100644 index 00000000..8c8ce8c8 --- /dev/null +++ b/src/intervals.c @@ -0,0 +1,840 @@ +/* + * Copyright (c) 2022 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. + */ + +#include <nft.h> + +#include <nftables.h> +#include <expression.h> +#include <intervals.h> +#include <rule.h> + +static void set_to_range(struct expr *init); + +static void setelem_expr_to_range(struct expr *expr) +{ + struct expr *key; + mpz_t rop; + + assert(expr->etype == EXPR_SET_ELEM); + + switch (expr->key->etype) { + case EXPR_SET_ELEM_CATCHALL: + case EXPR_RANGE_VALUE: + break; + case EXPR_RANGE: + key = constant_range_expr_alloc(&expr->location, + expr->key->dtype, + expr->key->byteorder, + expr->key->len, + expr->key->left->value, + expr->key->right->value); + expr_free(expr->key); + expr->key = key; + break; + case EXPR_PREFIX: + if (expr->key->prefix->etype != EXPR_VALUE) + BUG("Prefix for unexpected type %d", expr->key->prefix->etype); + + mpz_init(rop); + mpz_bitmask(rop, expr->key->len - expr->key->prefix_len); + if (expr_basetype(expr)->type == TYPE_STRING) + mpz_switch_byteorder(expr->key->prefix->value, expr->len / BITS_PER_BYTE); + + mpz_ior(rop, rop, expr->key->prefix->value); + key = constant_range_expr_alloc(&expr->location, + expr->key->dtype, + expr->key->byteorder, + expr->key->len, + expr->key->prefix->value, + rop); + mpz_clear(rop); + expr_free(expr->key); + expr->key = key; + break; + case EXPR_VALUE: + if (expr_basetype(expr)->type == TYPE_STRING) + mpz_switch_byteorder(expr->key->value, expr->len / BITS_PER_BYTE); + + key = constant_range_expr_alloc(&expr->location, + expr->key->dtype, + expr->key->byteorder, + expr->key->len, + expr->key->value, + expr->key->value); + expr_free(expr->key); + expr->key = key; + break; + default: + BUG("unhandled key type %s\n", expr_name(expr->key)); + } +} + +struct set_automerge_ctx { + struct set *set; + struct expr *init; + struct expr *purge; + unsigned int debug_mask; +}; + +static void purge_elem(struct set_automerge_ctx *ctx, struct expr *i) +{ + if (ctx->debug_mask & NFT_DEBUG_SEGTREE) { + pr_gmp_debug("remove: [%Zx-%Zx]\n", + i->key->range.low, + i->key->range.high); + } + list_move_tail(&i->list, &expr_set(ctx->purge)->expressions); +} + +static void remove_overlapping_range(struct set_automerge_ctx *ctx, + struct expr *prev, struct expr *i) +{ + if (i->flags & EXPR_F_KERNEL) { + i->location = prev->location; + purge_elem(ctx, i); + return; + } + list_del(&i->list); + expr_free(i); + expr_set(ctx->init)->size--; +} + +struct range { + mpz_t low; + mpz_t high; +}; + +static bool merge_ranges(struct set_automerge_ctx *ctx, + struct expr *prev, struct expr *i, + struct range *prev_range, struct range *range) +{ + if (prev->flags & EXPR_F_KERNEL) { + prev->location = i->location; + purge_elem(ctx, prev); + mpz_set(i->key->range.low, prev->key->range.low); + mpz_set(prev_range->high, range->high); + return true; + } else if (i->flags & EXPR_F_KERNEL) { + i->location = prev->location; + purge_elem(ctx, i); + mpz_set(prev->key->range.high, i->key->range.high); + mpz_set(prev_range->high, range->high); + } else { + mpz_set(prev->key->range.high, i->key->range.high); + mpz_set(prev_range->high, range->high); + list_del(&i->list); + expr_free(i); + expr_set(ctx->init)->size--; + } + return false; +} + +static void set_sort_splice(struct expr *init, struct set *set) +{ + struct set *existing_set = set->existing_set; + + set_to_range(init); + list_expr_sort(&expr_set(init)->expressions); + + if (!existing_set || existing_set->errors) + return; + + if (existing_set->init) { + set_to_range(existing_set->init); + list_splice_sorted(&expr_set(existing_set->init)->expressions, + &expr_set(init)->expressions); + init_list_head(&expr_set(existing_set->init)->expressions); + } else { + existing_set->init = set_expr_alloc(&internal_location, set); + } +} + +static void set_prev_elem(struct expr **prev, struct expr *i, + struct range *prev_range, struct range *range) +{ + *prev = i; + mpz_set(prev_range->low, range->low); + mpz_set(prev_range->high, range->high); +} + +static void setelem_automerge(struct set_automerge_ctx *ctx) +{ + struct expr *i, *next, *prev = NULL; + struct range range, prev_range; + mpz_t rop; + + mpz_init(prev_range.low); + mpz_init(prev_range.high); + mpz_init(range.low); + mpz_init(range.high); + mpz_init(rop); + + list_for_each_entry_safe(i, next, &expr_set(ctx->init)->expressions, list) { + if (i->key->etype == EXPR_SET_ELEM_CATCHALL) + continue; + + range_expr_value_low(range.low, i); + range_expr_value_high(range.high, i); + + if (!prev) { + set_prev_elem(&prev, i, &prev_range, &range); + continue; + } + + if (mpz_cmp(prev_range.low, range.low) <= 0 && + mpz_cmp(prev_range.high, range.high) >= 0) { + remove_overlapping_range(ctx, prev, i); + continue; + } else if (mpz_cmp(range.low, prev_range.high) <= 0) { + if (merge_ranges(ctx, prev, i, &prev_range, &range)) + prev = i; + continue; + } else if (ctx->set->automerge) { + mpz_sub(rop, range.low, prev_range.high); + /* two contiguous ranges */ + if (mpz_cmp_ui(rop, 1) == 0) { + if (merge_ranges(ctx, prev, i, &prev_range, &range)) + prev = i; + continue; + } + } + + set_prev_elem(&prev, i, &prev_range, &range); + } + + mpz_clear(prev_range.low); + mpz_clear(prev_range.high); + mpz_clear(range.low); + mpz_clear(range.high); + mpz_clear(rop); +} + +static struct expr *interval_expr_key(struct expr *i) +{ + struct expr *elem; + + switch (i->etype) { + case EXPR_MAPPING: + elem = i->left; + break; + case EXPR_SET_ELEM: + elem = i; + break; + default: + BUG("unhandled expression type %d\n", i->etype); + return NULL; + } + + return elem; +} + +static void set_to_range(struct expr *init) +{ + struct expr *i, *elem; + + list_for_each_entry(i, &expr_set(init)->expressions, list) { + elem = interval_expr_key(i); + setelem_expr_to_range(elem); + } +} + +int set_automerge(struct list_head *msgs, struct cmd *cmd, struct set *set, + struct expr *init, unsigned int debug_mask) +{ + struct set *existing_set = set->existing_set; + struct set_automerge_ctx ctx = { + .set = set, + .init = init, + .debug_mask = debug_mask, + }; + struct expr *i, *next, *clone; + struct cmd *purge_cmd; + struct handle h = {}; + + if (set->flags & NFT_SET_MAP) { + set_to_range(init); + list_expr_sort(&expr_set(init)->expressions); + return 0; + } + + set_sort_splice(init, set); + + ctx.purge = set_expr_alloc(&internal_location, set); + + setelem_automerge(&ctx); + + list_for_each_entry_safe(i, next, &expr_set(init)->expressions, list) { + if (i->flags & EXPR_F_KERNEL) { + list_move_tail(&i->list, &expr_set(existing_set->init)->expressions); + } else if (existing_set) { + if (debug_mask & NFT_DEBUG_SEGTREE) { + pr_gmp_debug("add: [%Zx-%Zx]\n", + i->key->range.low, i->key->range.high); + } + clone = expr_clone(i); + clone->flags |= EXPR_F_KERNEL; + list_add_tail(&clone->list, &expr_set(existing_set->init)->expressions); + } + } + + if (list_empty(&expr_set(ctx.purge)->expressions)) { + expr_free(ctx.purge); + return 0; + } + + handle_merge(&h, &set->handle); + purge_cmd = cmd_alloc(CMD_DELETE, CMD_OBJ_ELEMENTS, &h, &init->location, ctx.purge); + purge_cmd->elem.set = set_get(set); + list_add_tail(&purge_cmd->list, &cmd->list); + + return 0; +} + +static void remove_elem(struct expr *prev, struct set *set, struct expr *purge) +{ + struct expr *clone; + + if (prev->flags & EXPR_F_KERNEL) { + clone = expr_clone(prev); + list_move_tail(&clone->list, &expr_set(purge)->expressions); + } +} + +static void __adjust_elem_left(struct set *set, struct expr *prev, struct expr *i) +{ + prev->flags &= ~EXPR_F_KERNEL; + mpz_set(prev->key->range.low, i->key->range.high); + mpz_add_ui(prev->key->range.low, prev->key->range.low, 1); + list_move(&prev->list, &expr_set(set->existing_set->init)->expressions); +} + +static void adjust_elem_left(struct set *set, struct expr *prev, struct expr *i, + struct expr *purge) +{ + prev->location = i->location; + remove_elem(prev, set, purge); + __adjust_elem_left(set, prev, i); + + list_del(&i->list); + expr_free(i); +} + +static void __adjust_elem_right(struct set *set, struct expr *prev, struct expr *i) +{ + prev->flags &= ~EXPR_F_KERNEL; + mpz_set(prev->key->range.high, i->key->range.low); + mpz_sub_ui(prev->key->range.high, prev->key->range.high, 1); + list_move(&prev->list, &expr_set(set->existing_set->init)->expressions); +} + +static void adjust_elem_right(struct set *set, struct expr *prev, struct expr *i, + struct expr *purge) +{ + prev->location = i->location; + remove_elem(prev, set, purge); + __adjust_elem_right(set, prev, i); + + list_del(&i->list); + expr_free(i); +} + +static void split_range(struct set *set, struct expr *prev, struct expr *i, + struct expr *purge) +{ + struct expr *clone; + + prev->location = i->location; + + if (prev->flags & EXPR_F_KERNEL) { + clone = expr_clone(prev); + list_move_tail(&clone->list, &expr_set(purge)->expressions); + } + + prev->flags &= ~EXPR_F_KERNEL; + clone = expr_clone(prev); + mpz_set(clone->key->range.low, i->key->range.high); + mpz_add_ui(clone->key->range.low, i->key->range.high, 1); + list_add_tail(&clone->list, &expr_set(set->existing_set->init)->expressions); + + mpz_set(prev->key->range.high, i->key->range.low); + mpz_sub_ui(prev->key->range.high, i->key->range.low, 1); + list_move(&prev->list, &expr_set(set->existing_set->init)->expressions); + + list_del(&i->list); + expr_free(i); +} + +static int setelem_adjust(struct set *set, struct expr *purge, + struct range *prev_range, struct range *range, + struct expr *prev, struct expr *i) +{ + if (mpz_cmp(prev_range->low, range->low) == 0 && + mpz_cmp(prev_range->high, range->high) > 0) { + if (i->flags & EXPR_F_REMOVE) + adjust_elem_left(set, prev, i, purge); + } else if (mpz_cmp(prev_range->low, range->low) < 0 && + mpz_cmp(prev_range->high, range->high) == 0) { + if (i->flags & EXPR_F_REMOVE) + adjust_elem_right(set, prev, i, purge); + } else if (mpz_cmp(prev_range->low, range->low) < 0 && + mpz_cmp(prev_range->high, range->high) > 0) { + if (i->flags & EXPR_F_REMOVE) + split_range(set, prev, i, purge); + } else { + return -1; + } + + return 0; +} + +static int setelem_delete(struct list_head *msgs, struct set *set, + struct expr *purge, struct expr *elems, + unsigned int debug_mask) +{ + struct expr *i, *next, *elem, *prev = NULL; + struct range range, prev_range; + int err = 0; + mpz_t rop; + + mpz_init(prev_range.low); + mpz_init(prev_range.high); + mpz_init(range.low); + mpz_init(range.high); + mpz_init(rop); + + list_for_each_entry_safe(elem, next, &expr_set(elems)->expressions, list) { + i = interval_expr_key(elem); + + if (i->key->etype == EXPR_SET_ELEM_CATCHALL) { + /* Assume max value to simplify handling. */ + mpz_bitmask(range.low, i->len); + mpz_bitmask(range.high, i->len); + } else { + range_expr_value_low(range.low, i); + range_expr_value_high(range.high, i); + } + + if (!prev && elem->flags & EXPR_F_REMOVE) { + expr_error(msgs, i, "element does not exist"); + err = -1; + goto err; + } + + if (!(elem->flags & EXPR_F_REMOVE)) { + prev = elem; + mpz_set(prev_range.low, range.low); + mpz_set(prev_range.high, range.high); + continue; + } + + if (mpz_cmp(prev_range.low, range.low) == 0 && + mpz_cmp(prev_range.high, range.high) == 0) { + if (elem->flags & EXPR_F_REMOVE) { + if (prev->flags & EXPR_F_KERNEL) { + prev->location = elem->location; + list_move_tail(&prev->list, &expr_set(purge)->expressions); + } + + list_del(&elem->list); + expr_free(elem); + } + } else if (set->automerge) { + if (setelem_adjust(set, purge, &prev_range, &range, prev, i) < 0) { + expr_error(msgs, i, "element does not exist"); + err = -1; + goto err; + } + } else if (elem->flags & EXPR_F_REMOVE) { + expr_error(msgs, i, "element does not exist"); + err = -1; + goto err; + } + prev = NULL; + } +err: + mpz_clear(prev_range.low); + mpz_clear(prev_range.high); + mpz_clear(range.low); + mpz_clear(range.high); + mpz_clear(rop); + + return err; +} + +static void automerge_delete(struct list_head *msgs, struct set *set, + struct expr *init, unsigned int debug_mask) +{ + struct set_automerge_ctx ctx = { + .set = set, + .init = init, + .debug_mask = debug_mask, + }; + + ctx.purge = set_expr_alloc(&internal_location, set); + list_expr_sort(&expr_set(init)->expressions); + setelem_automerge(&ctx); + expr_free(ctx.purge); +} + +static int __set_delete(struct list_head *msgs, struct expr *i, struct set *set, + struct expr *init, struct set *existing_set, + unsigned int debug_mask) +{ + i->flags |= EXPR_F_REMOVE; + list_move_tail(&i->list, &expr_set(existing_set->init)->expressions); + list_expr_sort(&expr_set(existing_set->init)->expressions); + + return setelem_delete(msgs, set, init, existing_set->init, debug_mask); +} + +/* detection for unexisting intervals already exists in Linux kernels >= 5.7. */ +int set_delete(struct list_head *msgs, struct cmd *cmd, struct set *set, + struct expr *init, unsigned int debug_mask) +{ + struct set *existing_set = set->existing_set; + struct expr *i, *next, *add, *clone; + struct handle h = {}; + struct cmd *add_cmd; + LIST_HEAD(del_list); + int err; + + set_to_range(init); + if (set->automerge) + automerge_delete(msgs, set, init, debug_mask); + + if (existing_set->init) { + set_to_range(existing_set->init); + } else { + existing_set->init = set_expr_alloc(&internal_location, set); + } + + list_splice_init(&expr_set(init)->expressions, &del_list); + + list_for_each_entry_safe(i, next, &del_list, list) { + err = __set_delete(msgs, i, set, init, existing_set, debug_mask); + if (err < 0) { + list_splice(&del_list, &expr_set(init)->expressions); + return err; + } + } + + add = set_expr_alloc(&internal_location, set); + list_for_each_entry(i, &expr_set(existing_set->init)->expressions, list) { + if (!(i->flags & EXPR_F_KERNEL)) { + clone = expr_clone(i); + list_add_tail(&clone->list, &expr_set(add)->expressions); + i->flags |= EXPR_F_KERNEL; + } + } + + if (debug_mask & NFT_DEBUG_SEGTREE) { + list_for_each_entry(i, &expr_set(init)->expressions, list) + pr_gmp_debug("remove: [%Zx-%Zx]\n", + i->key->range.low, i->key->range.high); + list_for_each_entry(i, &expr_set(add)->expressions, list) + pr_gmp_debug("add: [%Zx-%Zx]\n", + i->key->range.low, i->key->range.high); + list_for_each_entry(i, &expr_set(existing_set->init)->expressions, list) + pr_gmp_debug("existing: [%Zx-%Zx]\n", + i->key->range.low, i->key->range.high); + } + + if (list_empty(&expr_set(add)->expressions)) { + expr_free(add); + return 0; + } + + handle_merge(&h, &cmd->handle); + add_cmd = cmd_alloc(CMD_ADD, CMD_OBJ_ELEMENTS, &h, &cmd->location, add); + add_cmd->elem.set = set_get(set); + list_add(&add_cmd->list, &cmd->list); + + return 0; +} + +static int setelem_overlap(struct list_head *msgs, struct set *set, + struct expr *init) +{ + struct expr *i, *next, *elem, *prev = NULL; + struct range range, prev_range; + int err = 0; + mpz_t rop; + + mpz_init(prev_range.low); + mpz_init(prev_range.high); + mpz_init(range.low); + mpz_init(range.high); + mpz_init(rop); + + list_for_each_entry_safe(elem, next, &expr_set(init)->expressions, list) { + i = interval_expr_key(elem); + + if (i->key->etype == EXPR_SET_ELEM_CATCHALL) + continue; + + range_expr_value_low(range.low, i); + range_expr_value_high(range.high, i); + + if (!prev) { + prev = elem; + mpz_set(prev_range.low, range.low); + mpz_set(prev_range.high, range.high); + continue; + } + + if (mpz_cmp(prev_range.low, range.low) == 0 && + mpz_cmp(prev_range.high, range.high) == 0) + goto next; + + if (mpz_cmp(prev_range.low, range.low) <= 0 && + mpz_cmp(prev_range.high, range.high) >= 0) { + if (prev->flags & EXPR_F_KERNEL) + expr_error(msgs, i, "interval overlaps with an existing one"); + else if (elem->flags & EXPR_F_KERNEL) + expr_error(msgs, prev, "interval overlaps with an existing one"); + else + expr_binary_error(msgs, i, prev, + "conflicting intervals specified"); + err = -1; + goto err_out; + } else if (mpz_cmp(range.low, prev_range.high) <= 0) { + if (prev->flags & EXPR_F_KERNEL) + expr_error(msgs, i, "interval overlaps with an existing one"); + else if (elem->flags & EXPR_F_KERNEL) + expr_error(msgs, prev, "interval overlaps with an existing one"); + else + expr_binary_error(msgs, i, prev, + "conflicting intervals specified"); + err = -1; + goto err_out; + } +next: + prev = elem; + mpz_set(prev_range.low, range.low); + mpz_set(prev_range.high, range.high); + } + +err_out: + mpz_clear(prev_range.low); + mpz_clear(prev_range.high); + mpz_clear(range.low); + mpz_clear(range.high); + mpz_clear(rop); + + return err; +} + +/* overlap detection for intervals already exists in Linux kernels >= 5.7. */ +int set_overlap(struct list_head *msgs, struct set *set, struct expr *init) +{ + struct set *existing_set = set->existing_set; + struct expr *i, *n, *clone; + int err; + + set_sort_splice(init, set); + + err = setelem_overlap(msgs, set, init); + + list_for_each_entry_safe(i, n, &expr_set(init)->expressions, list) { + if (i->flags & EXPR_F_KERNEL) + list_move_tail(&i->list, &expr_set(existing_set->init)->expressions); + else if (existing_set) { + clone = expr_clone(i); + clone->flags |= EXPR_F_KERNEL; + list_add_tail(&clone->list, &expr_set(existing_set->init)->expressions); + } + } + + return err; +} + +static bool segtree_needs_first_segment(const struct set *set, + const struct expr *init, bool add) +{ + if (add && !set->root) { + /* Add the first segment in four situations: + * + * 1) This is an anonymous set. + * 2) This set exists and it is empty. + * 3) New empty set and, separately, new elements are added. + * 4) This set is created with a number of initial elements. + */ + if ((set_is_anonymous(set->flags)) || + (set->init && expr_set(set->init)->size == 0) || + (set->init == NULL && init) || + (set->init == init)) { + return true; + } + } + /* This is an update for a set that already contains elements, so don't + * add the first non-matching elements otherwise we hit EEXIST. + */ + return false; +} + +int set_to_intervals(const struct set *set, struct expr *init, bool add) +{ + struct expr *i, *n, *prev = NULL, *elem, *root, *expr; + LIST_HEAD(intervals); + mpz_t p; + + list_for_each_entry_safe(i, n, &expr_set(init)->expressions, list) { + elem = interval_expr_key(i); + + if (elem->key->etype == EXPR_SET_ELEM_CATCHALL) + continue; + + if (prev) + break; + + if (segtree_needs_first_segment(set, init, add) && + mpz_cmp_ui(elem->key->range.low, 0)) { + mpz_init2(p, set->key->len); + mpz_set_ui(p, 0); + expr = constant_range_expr_alloc(&internal_location, + set->key->dtype, + set->key->byteorder, + set->key->len, p, p); + mpz_clear(p); + + root = set_elem_expr_alloc(&internal_location, expr); + if (i->etype == EXPR_MAPPING) { + root = mapping_expr_alloc(&internal_location, + root, + expr_get(i->right)); + } + root->flags |= EXPR_F_INTERVAL_END; + list_add(&root->list, &intervals); + break; + } + prev = i; + } + + list_splice_init(&intervals, &expr_set(init)->expressions); + + return 0; +} + +/* This only works for the supported stateful statements. */ +static void set_elem_stmt_clone(struct expr *dst, const struct expr *src) +{ + struct stmt *stmt, *nstmt; + + list_for_each_entry(stmt, &src->stmt_list, list) { + nstmt = xzalloc(sizeof(*stmt)); + *nstmt = *stmt; + list_add_tail(&nstmt->list, &dst->stmt_list); + } +} + +static void set_elem_expr_copy(struct expr *dst, const struct expr *src) +{ + if (src->comment) + dst->comment = xstrdup(src->comment); + if (src->timeout) + dst->timeout = src->timeout; + if (src->expiration) + dst->expiration = src->expiration; + + set_elem_stmt_clone(dst, src); +} + +static struct expr *setelem_key(struct expr *expr) +{ + struct expr *key; + + switch (expr->etype) { + case EXPR_MAPPING: + key = expr->left->key; + break; + case EXPR_SET_ELEM: + key = expr->key; + break; + default: + BUG("unhandled expression type %d\n", expr->etype); + return NULL; + } + + return key; +} + +int setelem_to_interval(const struct set *set, struct expr *elem, + struct expr *next_elem, struct list_head *intervals) +{ + struct expr *key, *next_key = NULL, *low, *high; + bool adjacent = false; + + key = setelem_key(elem); + if (key->etype == EXPR_SET_ELEM_CATCHALL) + return 0; + + if (next_elem) { + next_key = setelem_key(next_elem); + if (next_key->etype == EXPR_SET_ELEM_CATCHALL) + next_key = NULL; + } + + if (key->etype != EXPR_RANGE_VALUE) + BUG("key must be RANGE_VALUE, not %s\n", expr_name(key)); + + assert(!next_key || next_key->etype == EXPR_RANGE_VALUE); + + /* skip end element for adjacents intervals in anonymous sets. */ + if (!(elem->flags & EXPR_F_INTERVAL_END) && next_key) { + mpz_t p; + + mpz_init2(p, set->key->len); + mpz_add_ui(p, key->range.high, 1); + + if (!mpz_cmp(p, next_key->range.low)) + adjacent = true; + + mpz_clear(p); + } + + low = constant_expr_alloc(&key->location, set->key->dtype, + set->key->byteorder, set->key->len, NULL); + + mpz_set(low->value, key->range.low); + if (set->key->byteorder == BYTEORDER_HOST_ENDIAN) + mpz_switch_byteorder(low->value, set->key->len / BITS_PER_BYTE); + + low = set_elem_expr_alloc(&key->location, low); + set_elem_expr_copy(low, interval_expr_key(elem)); + + if (elem->etype == EXPR_MAPPING) + low = mapping_expr_alloc(&elem->location, + low, expr_get(elem->right)); + + list_add_tail(&low->list, intervals); + + if (adjacent) + return 0; + else if (!mpz_cmp_ui(key->value, 0) && elem->flags & EXPR_F_INTERVAL_END) { + low->flags |= EXPR_F_INTERVAL_END; + return 0; + } else if (mpz_scan0(key->range.high, 0) == set->key->len) { + low->flags |= EXPR_F_INTERVAL_OPEN; + return 0; + } + + high = constant_expr_alloc(&key->location, set->key->dtype, + set->key->byteorder, set->key->len, + NULL); + mpz_set(high->value, key->range.high); + mpz_add_ui(high->value, high->value, 1); + if (set->key->byteorder == BYTEORDER_HOST_ENDIAN) + mpz_switch_byteorder(high->value, set->key->len / BITS_PER_BYTE); + + high = set_elem_expr_alloc(&key->location, high); + + high->flags |= EXPR_F_INTERVAL_END; + list_add_tail(&high->list, intervals); + + return 0; +} diff --git a/src/ipopt.c b/src/ipopt.c index b3d0279d..c03a8041 100644 --- a/src/ipopt.c +++ b/src/ipopt.c @@ -1,4 +1,5 @@ -#include <stdint.h> +#include <nft.h> + #include <netinet/in.h> #include <netinet/ip.h> @@ -23,7 +24,7 @@ static const struct exthdr_desc ipopt_lsrr = { [IPOPT_FIELD_TYPE] = PHT("type", 0, 8), [IPOPT_FIELD_LENGTH] = PHT("length", 8, 8), [IPOPT_FIELD_PTR] = PHT("ptr", 16, 8), - [IPOPT_FIELD_ADDR_0] = PHT("addr", 24, 32), + [IPOPT_FIELD_ADDR_0] = PROTO_HDR_TEMPLATE("addr", &ipaddr_type, BYTEORDER_BIG_ENDIAN, 24, 32), }, }; @@ -34,7 +35,7 @@ static const struct exthdr_desc ipopt_rr = { [IPOPT_FIELD_TYPE] = PHT("type", 0, 8), [IPOPT_FIELD_LENGTH] = PHT("length", 8, 8), [IPOPT_FIELD_PTR] = PHT("ptr", 16, 8), - [IPOPT_FIELD_ADDR_0] = PHT("addr", 24, 32), + [IPOPT_FIELD_ADDR_0] = PROTO_HDR_TEMPLATE("addr", &ipaddr_type, BYTEORDER_BIG_ENDIAN, 24, 32), }, }; @@ -45,7 +46,7 @@ static const struct exthdr_desc ipopt_ssrr = { [IPOPT_FIELD_TYPE] = PHT("type", 0, 8), [IPOPT_FIELD_LENGTH] = PHT("length", 8, 8), [IPOPT_FIELD_PTR] = PHT("ptr", 16, 8), - [IPOPT_FIELD_ADDR_0] = PHT("addr", 24, 32), + [IPOPT_FIELD_ADDR_0] = PROTO_HDR_TEMPLATE("addr", &ipaddr_type, BYTEORDER_BIG_ENDIAN, 24, 32), }, }; @@ -66,27 +67,8 @@ const struct exthdr_desc *ipopt_protocols[UINT8_MAX] = { [IPOPT_RA] = &ipopt_ra, }; -static unsigned int calc_offset(const struct exthdr_desc *desc, - const struct proto_hdr_template *tmpl, - unsigned int arg) -{ - if (!desc || tmpl == &ipopt_unknown_template) - return 0; - - switch (desc->type) { - case IPOPT_RR: - case IPOPT_LSRR: - case IPOPT_SSRR: - if (tmpl == &desc->templates[IPOPT_FIELD_ADDR_0]) - return (tmpl->offset < 24) ? 0 : arg; - return 0; - default: - return 0; - } -} - struct expr *ipopt_expr_alloc(const struct location *loc, uint8_t type, - uint8_t field, uint8_t ptr) + uint8_t field) { const struct proto_hdr_template *tmpl; const struct exthdr_desc *desc; @@ -97,12 +79,16 @@ struct expr *ipopt_expr_alloc(const struct location *loc, uint8_t type, if (!tmpl) return NULL; + if (!tmpl->len) + return NULL; + expr = expr_alloc(loc, EXPR_EXTHDR, tmpl->dtype, BYTEORDER_BIG_ENDIAN, tmpl->len); expr->exthdr.desc = desc; expr->exthdr.tmpl = tmpl; expr->exthdr.op = NFT_EXTHDR_OP_IPV4; - expr->exthdr.offset = calc_offset(desc, tmpl, ptr); + expr->exthdr.offset = tmpl->offset; + expr->exthdr.raw_type = desc->type; return expr; } @@ -1,11 +1,21 @@ -#define _GNU_SOURCE -#include <string.h> +/* + * Copyright (c) Red Hat GmbH. Author: Phil Sutter <phil@nwl.cc> + * + * 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. + */ + +#include <nft.h> + +#include <stdio.h> #include <expression.h> #include <list.h> #include <netlink.h> #include <rule.h> #include <rt.h> +#include "nftutils.h" #include <netdb.h> #include <netinet/icmp6.h> @@ -23,14 +33,44 @@ #include <jansson.h> #include <syslog.h> -#ifdef DEBUG -#define __json_pack json_pack -#define json_pack(...) ({ \ - json_t *__out = __json_pack(__VA_ARGS__); \ - assert(__out); \ - __out; \ -}) -#endif +static json_t *__nft_json_pack(unsigned int line, const char *fmt, ...) +{ + json_error_t error; + json_t *value; + va_list ap; + + va_start(ap, fmt); + value = json_vpack_ex(&error, 0, fmt, ap); + va_end(ap); + + if (value) + return value; + + fprintf(stderr, "%s:%d: json_pack failure (%s)\n", __FILE__, line, error.text); + return NULL; +} +#define nft_json_pack(...) __nft_json_pack(__LINE__, __VA_ARGS__) + +static int json_array_extend_new(json_t *array, json_t *other_array) +{ + int ret; + + ret = json_array_extend(array, other_array); + json_decref(other_array); + return ret; +} + +static void json_add_array_new(json_t *obj, const char *name, json_t *array) +{ + if (json_array_size(array) > 1) { + json_object_set_new(obj, name, array); + } else { + if (json_array_size(array)) + json_object_set(obj, name, + json_array_get(array, 0)); + json_decref(array); + } +} static json_t *expr_print_json(const struct expr *expr, struct output_ctx *octx) { @@ -42,7 +82,8 @@ static json_t *expr_print_json(const struct expr *expr, struct output_ctx *octx) if (ops->json) return ops->json(expr, octx); - printf("warning: expr ops %s have no json callback\n", expr_name(expr)); + fprintf(stderr, "warning: expr ops %s have no json callback\n", + expr_name(expr)); fp = octx->output_fp; octx->output_fp = fmemopen(buf, 1024, "w"); @@ -52,102 +93,161 @@ static json_t *expr_print_json(const struct expr *expr, struct output_ctx *octx) fclose(octx->output_fp); octx->output_fp = fp; - return json_pack("s", buf); + return nft_json_pack("s", buf); } static json_t *set_dtype_json(const struct expr *key) { char *namedup = xstrdup(key->dtype->name), *tok; json_t *root = NULL; + char *tok_safe; - tok = strtok(namedup, " ."); + tok = strtok_r(namedup, " .", &tok_safe); while (tok) { - json_t *jtok = json_string(xstrdup(tok)); + json_t *jtok = json_string(tok); if (!root) root = jtok; else if (json_is_string(root)) - root = json_pack("[o, o]", root, jtok); + root = nft_json_pack("[o, o]", root, jtok); else json_array_append_new(root, jtok); - tok = strtok(NULL, " ."); + tok = strtok_r(NULL, " .", &tok_safe); } - xfree(namedup); + free(namedup); return root; } -static json_t *set_print_json(struct output_ctx *octx, const struct set *set) +static json_t *set_key_dtype_json(const struct set *set, + struct output_ctx *octx) { + bool use_typeof = set->key_typeof_valid; + + if (!use_typeof) + return set_dtype_json(set->key); + + return nft_json_pack("{s:o}", "typeof", expr_print_json(set->key, octx)); +} + +static json_t *stmt_print_json(const struct stmt *stmt, struct output_ctx *octx) +{ + const struct stmt_ops *ops = stmt_ops(stmt); + char buf[1024]; + FILE *fp; + + if (ops->json) + return ops->json(stmt, octx); + + fprintf(stderr, "warning: stmt ops %s have no json callback\n", + ops->name); + + fp = octx->output_fp; + octx->output_fp = fmemopen(buf, 1024, "w"); + + ops->print(stmt, octx); + + fclose(octx->output_fp); + octx->output_fp = fp; + + return nft_json_pack("s", buf); +} + +static json_t *set_stmt_list_json(const struct list_head *stmt_list, + struct output_ctx *octx) +{ + unsigned int flags = octx->flags; json_t *root, *tmp; - const char *type, *datatype_ext = NULL; + struct stmt *i; + + root = json_array(); + octx->flags |= NFT_CTX_OUTPUT_STATELESS; + + list_for_each_entry(i, stmt_list, list) { + tmp = stmt_print_json(i, octx); + json_array_append_new(root, tmp); + } + octx->flags = flags; + + return root; +} + +static json_t *set_print_json(struct output_ctx *octx, const struct set *set) +{ + json_t *root, *tmp, *datatype_ext = NULL; + const char *type; if (set_is_datamap(set->flags)) { type = "map"; - datatype_ext = set->data->dtype->name; + datatype_ext = set_dtype_json(set->data); } else if (set_is_objmap(set->flags)) { type = "map"; - datatype_ext = obj_type_name(set->objtype); + datatype_ext = json_string(obj_type_name(set->objtype)); } else if (set_is_meter(set->flags)) { type = "meter"; } else { type = "set"; } - root = json_pack("{s:s, s:s, s:s, s:o, s:I}", + root = nft_json_pack("{s:s, s:s, s:s, s:o, s:I}", "family", family2str(set->handle.family), "name", set->handle.set.name, "table", set->handle.table.name, - "type", set_dtype_json(set->key), + "type", set_key_dtype_json(set, octx), "handle", set->handle.handle.id); + + if (set->comment) + json_object_set_new(root, "comment", json_string(set->comment)); if (datatype_ext) - json_object_set_new(root, "map", json_string(datatype_ext)); + json_object_set_new(root, "map", datatype_ext); if (!(set->flags & (NFT_SET_CONSTANT))) { if (set->policy != NFT_SET_POL_PERFORMANCE) { - tmp = json_pack("s", set_policy2str(set->policy)); + tmp = nft_json_pack("s", set_policy2str(set->policy)); json_object_set_new(root, "policy", tmp); } if (set->desc.size) { - tmp = json_pack("i", set->desc.size); + tmp = nft_json_pack("i", set->desc.size); json_object_set_new(root, "size", tmp); } } tmp = json_array(); if (set->flags & NFT_SET_CONSTANT) - json_array_append_new(tmp, json_pack("s", "constant")); + json_array_append_new(tmp, nft_json_pack("s", "constant")); if (set->flags & NFT_SET_INTERVAL) - json_array_append_new(tmp, json_pack("s", "interval")); + json_array_append_new(tmp, nft_json_pack("s", "interval")); if (set->flags & NFT_SET_TIMEOUT) - json_array_append_new(tmp, json_pack("s", "timeout")); - - if (json_array_size(tmp) > 0) { - json_object_set_new(root, "flags", tmp); - } else { - if (json_array_size(tmp)) - json_object_set(root, "flags", json_array_get(tmp, 0)); - json_decref(tmp); - } + json_array_append_new(tmp, nft_json_pack("s", "timeout")); + if (set->flags & NFT_SET_EVAL) + json_array_append_new(tmp, nft_json_pack("s", "dynamic")); + json_add_array_new(root, "flags", tmp); if (set->timeout) { tmp = json_integer(set->timeout / 1000); json_object_set_new(root, "timeout", tmp); } if (set->gc_int) { - tmp = json_pack("i", set->gc_int / 1000); + tmp = nft_json_pack("i", set->gc_int / 1000); json_object_set_new(root, "gc-interval", tmp); } + if (set->automerge) + json_object_set_new(root, "auto-merge", json_true()); - if (set->init && set->init->size > 0) { + if (!nft_output_terse(octx) && set->init && expr_set(set->init)->size > 0) { json_t *array = json_array(); const struct expr *i; - list_for_each_entry(i, &set->init->expressions, list) + list_for_each_entry(i, &expr_set(set->init)->expressions, list) json_array_append_new(array, expr_print_json(i, octx)); json_object_set_new(root, "elem", array); } - return json_pack("{s:o}", type, root); + if (!list_empty(&set->stmt_list)) { + json_object_set_new(root, "stmt", + set_stmt_list_json(&set->stmt_list, octx)); + } + + return nft_json_pack("{s:o}", type, root); } /* XXX: Merge with set_print_json()? */ @@ -156,48 +256,20 @@ static json_t *element_print_json(struct output_ctx *octx, { json_t *root = expr_print_json(set->init, octx); - return json_pack("{s: {s:s, s:s, s:s, s:o}}", "element", + return nft_json_pack("{s: {s:s, s:s, s:s, s:o}}", "element", "family", family2str(set->handle.family), "table", set->handle.table.name, "name", set->handle.set.name, "elem", root); } -static json_t *stmt_print_json(const struct stmt *stmt, struct output_ctx *octx) -{ - char buf[1024]; - FILE *fp; - - /* XXX: Can't be supported at this point: - * xt_stmt_xlate() ignores output_fp. - */ - if (stmt->ops->type == STMT_XT) - return json_pack("{s:n}", "xt"); - - if (stmt->ops->json) - return stmt->ops->json(stmt, octx); - - printf("warning: stmt ops %s have no json callback\n", - stmt->ops->name); - - fp = octx->output_fp; - octx->output_fp = fmemopen(buf, 1024, "w"); - - stmt->ops->print(stmt, octx); - - fclose(octx->output_fp); - octx->output_fp = fp; - - return json_pack("s", buf); -} - static json_t *rule_print_json(struct output_ctx *octx, const struct rule *rule) { const struct stmt *stmt; json_t *root, *tmp; - root = json_pack("{s:s, s:s, s:s, s:I}", + root = nft_json_pack("{s:s, s:s, s:s, s:I}", "family", family2str(rule->handle.family), "table", rule->handle.table.name, "chain", rule->handle.chain.name, @@ -217,56 +289,66 @@ static json_t *rule_print_json(struct output_ctx *octx, json_decref(tmp); } - return json_pack("{s:o}", "rule", root); + return nft_json_pack("{s:o}", "rule", root); } static json_t *chain_print_json(const struct chain *chain) { - int priority, policy, n = 0; - struct expr *dev, *expr; - json_t *root, *tmp; + json_t *root, *tmp, *devs = NULL; + int priority, policy, i; - root = json_pack("{s:s, s:s, s:s, s:I}", + root = nft_json_pack("{s:s, s:s, s:s, s:I}", "family", family2str(chain->handle.family), "table", chain->handle.table.name, "name", chain->handle.chain.name, "handle", chain->handle.handle.id); + if (chain->comment) + json_object_set_new(root, "comment", json_string(chain->comment)); + if (chain->flags & CHAIN_F_BASECHAIN) { mpz_export_data(&priority, chain->priority.expr->value, BYTEORDER_HOST_ENDIAN, sizeof(int)); - mpz_export_data(&policy, chain->policy->value, - BYTEORDER_HOST_ENDIAN, sizeof(int)); - tmp = json_pack("{s:s, s:s, s:i, s:s}", - "type", chain->type, + + if (chain->policy) { + mpz_export_data(&policy, chain->policy->value, + BYTEORDER_HOST_ENDIAN, sizeof(int)); + } else { + policy = NF_ACCEPT; + } + + tmp = nft_json_pack("{s:s, s:s, s:i, s:s}", + "type", chain->type.str, "hook", hooknum2str(chain->handle.family, - chain->hooknum), + chain->hook.num), "prio", priority, "policy", chain_policy2str(policy)); - if (chain->dev_expr) { - list_for_each_entry(expr, &chain->dev_expr->expressions, list) { - dev = expr; - n++; - } - } - if (n == 1) { - json_object_set_new(tmp, "dev", - json_string(dev->identifier)); + for (i = 0; i < chain->dev_array_len; i++) { + const char *dev = chain->dev_array[i]; + if (!devs) + devs = json_string(dev); + else if (json_is_string(devs)) + devs = nft_json_pack("[o, s]", devs, dev); + else + json_array_append_new(devs, json_string(dev)); } + if (devs) + json_object_set_new(root, "dev", devs); + json_object_update(root, tmp); json_decref(tmp); } - return json_pack("{s:o}", "chain", root); + return nft_json_pack("{s:o}", "chain", root); } static json_t *proto_name_json(uint8_t proto) { - const struct protoent *p = getprotobynumber(proto); + char name[NFT_PROTONAME_MAXSIZE]; - if (p) - return json_string(p->p_name); + if (nft_getprotobynumber(proto, name, sizeof(name))) + return json_string(name); return json_integer(proto); } @@ -294,22 +376,28 @@ static json_t *obj_print_json(const struct obj *obj) json_t *root, *tmp, *flags; uint64_t rate, burst; - root = json_pack("{s:s, s:s, s:s, s:I}", + root = nft_json_pack("{s:s, s:s, s:s, s:I}", "family", family2str(obj->handle.family), "name", obj->handle.obj.name, "table", obj->handle.table.name, "handle", obj->handle.handle.id); + if (obj->comment) { + tmp = nft_json_pack("{s:s}", "comment", obj->comment); + json_object_update(root, tmp); + json_decref(tmp); + } + switch (obj->type) { case NFT_OBJECT_COUNTER: - tmp = json_pack("{s:I, s:I}", + tmp = nft_json_pack("{s:I, s:I}", "packets", obj->counter.packets, "bytes", obj->counter.bytes); json_object_update(root, tmp); json_decref(tmp); break; case NFT_OBJECT_QUOTA: - tmp = json_pack("{s:I, s:I, s:b}", + tmp = nft_json_pack("{s:I, s:I, s:b}", "bytes", obj->quota.bytes, "used", obj->quota.used, "inv", obj->quota.flags & NFT_QUOTA_F_INV); @@ -317,13 +405,13 @@ static json_t *obj_print_json(const struct obj *obj) json_decref(tmp); break; case NFT_OBJECT_SECMARK: - tmp = json_pack("{s:s}", + tmp = nft_json_pack("{s:s}", "context", obj->secmark.ctx); json_object_update(root, tmp); json_decref(tmp); break; case NFT_OBJECT_CT_HELPER: - tmp = json_pack("{s:s, s:o, s:s}", + tmp = nft_json_pack("{s:s, s:o, s:s}", "type", obj->ct_helper.name, "protocol", proto_name_json(obj->ct_helper.l4proto), "l3proto", family2str(obj->ct_helper.l3proto)); @@ -333,7 +421,7 @@ static json_t *obj_print_json(const struct obj *obj) case NFT_OBJECT_CT_TIMEOUT: tmp = timeout_policy_json(obj->ct_timeout.l4proto, obj->ct_timeout.timeout); - tmp = json_pack("{s:o, s:s, s:o}", + tmp = nft_json_pack("{s:o, s:s, s:o}", "protocol", proto_name_json(obj->ct_timeout.l4proto), "l3proto", family2str(obj->ct_timeout.l3proto), @@ -342,7 +430,7 @@ static json_t *obj_print_json(const struct obj *obj) json_decref(tmp); break; case NFT_OBJECT_CT_EXPECT: - tmp = json_pack("{s:o, s:I, s:I, s:I, s:s}", + tmp = nft_json_pack("{s:o, s:I, s:I, s:I, s:s}", "protocol", proto_name_json(obj->ct_expect.l4proto), "dport", obj->ct_expect.dport, @@ -361,7 +449,7 @@ static json_t *obj_print_json(const struct obj *obj) burst_unit = get_rate(obj->limit.burst, &burst); } - tmp = json_pack("{s:I, s:s}", + tmp = nft_json_pack("{s:I, s:s}", "rate", rate, "per", get_unit(obj->limit.unit)); @@ -381,56 +469,62 @@ static json_t *obj_print_json(const struct obj *obj) json_decref(tmp); break; case NFT_OBJECT_SYNPROXY: + tmp = nft_json_pack("{s:i, s:i}", + "mss", obj->synproxy.mss, + "wscale", obj->synproxy.wscale); + flags = json_array(); - tmp = json_pack("{s:i, s:i}", - "mss", obj->synproxy.mss, - "wscale", obj->synproxy.wscale); if (obj->synproxy.flags & NF_SYNPROXY_OPT_TIMESTAMP) json_array_append_new(flags, json_string("timestamp")); if (obj->synproxy.flags & NF_SYNPROXY_OPT_SACK_PERM) json_array_append_new(flags, json_string("sack-perm")); - - if (json_array_size(flags) > 0) - json_object_set_new(tmp, "flags", flags); - else - json_decref(flags); + json_add_array_new(tmp, "flags", flags); json_object_update(root, tmp); json_decref(tmp); break; } - return json_pack("{s:o}", type, root); + return nft_json_pack("{s:o}", type, root); } static json_t *flowtable_print_json(const struct flowtable *ftable) { json_t *root, *devs = NULL; - int i, priority; + int i, priority = 0; - mpz_export_data(&priority, ftable->priority.expr->value, - BYTEORDER_HOST_ENDIAN, sizeof(int)); - root = json_pack("{s:s, s:s, s:s, s:I, s:s, s:i}", + root = nft_json_pack("{s:s, s:s, s:s, s:I}", "family", family2str(ftable->handle.family), "name", ftable->handle.flowtable.name, "table", ftable->handle.table.name, - "handle", ftable->handle.handle.id, - "hook", hooknum2str(NFPROTO_NETDEV, ftable->hooknum), - "prio", priority); + "handle", ftable->handle.handle.id); + + if (ftable->priority.expr) { + json_t *tmp; + + mpz_export_data(&priority, ftable->priority.expr->value, + BYTEORDER_HOST_ENDIAN, sizeof(int)); + + tmp = nft_json_pack("{s:s, s:i}", + "hook", hooknum2str(NFPROTO_NETDEV, + ftable->hook.num), + "prio", priority); + json_object_update_new(root, tmp); + } for (i = 0; i < ftable->dev_array_len; i++) { const char *dev = ftable->dev_array[i]; if (!devs) devs = json_string(dev); else if (json_is_string(devs)) - devs = json_pack("[o, s]", devs, dev); + devs = nft_json_pack("[o, s]", devs, dev); else json_array_append_new(devs, json_string(dev)); } if (devs) json_object_set_new(root, "dev", devs); - return json_pack("{s:o}", "flowtable", root); + return nft_json_pack("{s:o}", "flowtable", root); } static json_t *table_flags_json(const struct table *table) @@ -441,51 +535,56 @@ static json_t *table_flags_json(const struct table *table) while (flags) { if (flags & 0x1) { - tmp = json_string(table_flags_name[i]); + tmp = json_string(table_flag_name(i)); json_array_append_new(root, tmp); } flags >>= 1; i++; } - switch (json_array_size(root)) { - case 0: - json_decref(root); - return NULL; - case 1: - json_unpack(root, "[o]", &tmp); - json_decref(root); - root = tmp; - break; - } return root; } static json_t *table_print_json(const struct table *table) { - json_t *root, *tmp; + json_t *root; - root = json_pack("{s:s, s:s, s:I}", + root = nft_json_pack("{s:s, s:s, s:I}", "family", family2str(table->handle.family), "name", table->handle.table.name, "handle", table->handle.handle.id); + json_add_array_new(root, "flags", table_flags_json(table)); - tmp = table_flags_json(table); - if (tmp) - json_object_set_new(root, "flags", tmp); + if (table->comment) + json_object_set_new(root, "comment", json_string(table->comment)); + + return nft_json_pack("{s:o}", "table", root); +} - return json_pack("{s:o}", "table", root); +static json_t * +__binop_expr_json(int op, const struct expr *expr, struct output_ctx *octx) +{ + json_t *a = json_array(); + + if (expr->etype == EXPR_BINOP && expr->op == op) { + json_array_extend_new(a, + __binop_expr_json(op, expr->left, octx)); + json_array_extend_new(a, + __binop_expr_json(op, expr->right, octx)); + } else { + json_array_append_new(a, expr_print_json(expr, octx)); + } + return a; } json_t *binop_expr_json(const struct expr *expr, struct output_ctx *octx) { - return json_pack("{s:[o, o]}", expr_op_symbols[expr->op], - expr_print_json(expr->left, octx), - expr_print_json(expr->right, octx)); + return nft_json_pack("{s:o}", expr_op_symbols[expr->op], + __binop_expr_json(expr->op, expr, octx)); } json_t *relational_expr_json(const struct expr *expr, struct output_ctx *octx) { - return json_pack("{s:{s:s, s:o, s:o}}", "match", + return nft_json_pack("{s:{s:s, s:o, s:o}}", "match", "op", expr_op_symbols[expr->op] ? : "in", "left", expr_print_json(expr->left, octx), "right", expr_print_json(expr->right, octx)); @@ -498,7 +597,7 @@ json_t *range_expr_json(const struct expr *expr, struct output_ctx *octx) octx->flags &= ~NFT_CTX_OUTPUT_SERVICE; octx->flags |= NFT_CTX_OUTPUT_NUMERIC_PROTO; - root = json_pack("{s:[o, o]}", "range", + root = nft_json_pack("{s:[o, o]}", "range", expr_print_json(expr->left, octx), expr_print_json(expr->right, octx)); octx->flags = flags; @@ -508,7 +607,7 @@ json_t *range_expr_json(const struct expr *expr, struct output_ctx *octx) json_t *meta_expr_json(const struct expr *expr, struct output_ctx *octx) { - return json_pack("{s:{s:s}}", "meta", + return nft_json_pack("{s:{s:s}}", "meta", "key", meta_templates[expr->meta.key].token); } @@ -516,17 +615,25 @@ json_t *payload_expr_json(const struct expr *expr, struct output_ctx *octx) { json_t *root; - if (payload_is_known(expr)) - root = json_pack("{s:s, s:s}", - "protocol", expr->payload.desc->name, - "field", expr->payload.tmpl->token); - else - root = json_pack("{s:s, s:i, s:i}", + if (payload_is_known(expr)) { + if (expr->payload.inner_desc) { + root = nft_json_pack("{s:s, s:s, s:s}", + "tunnel", expr->payload.inner_desc->name, + "protocol", expr->payload.desc->name, + "field", expr->payload.tmpl->token); + } else { + root = nft_json_pack("{s:s, s:s}", + "protocol", expr->payload.desc->name, + "field", expr->payload.tmpl->token); + } + } else { + root = nft_json_pack("{s:s, s:i, s:i}", "base", proto_base_tokens[expr->payload.base], "offset", expr->payload.offset, "len", expr->len); + } - return json_pack("{s:o}", "payload", root); + return nft_json_pack("{s:o}", "payload", root); } json_t *ct_expr_json(const struct expr *expr, struct output_ctx *octx) @@ -535,7 +642,7 @@ json_t *ct_expr_json(const struct expr *expr, struct output_ctx *octx) enum nft_ct_keys key = expr->ct.key; json_t *root; - root = json_pack("{s:s}", "key", ct_templates[key].token); + root = nft_json_pack("{s:s}", "key", ct_templates[key].token); if (expr->ct.direction < 0) goto out; @@ -543,7 +650,7 @@ json_t *ct_expr_json(const struct expr *expr, struct output_ctx *octx) if (dirstr) json_object_set_new(root, "dir", json_string(dirstr)); out: - return json_pack("{s:o}", "ct", root); + return nft_json_pack("{s:o}", "ct", root); } json_t *concat_expr_json(const struct expr *expr, struct output_ctx *octx) @@ -551,10 +658,10 @@ json_t *concat_expr_json(const struct expr *expr, struct output_ctx *octx) json_t *array = json_array(); const struct expr *i; - list_for_each_entry(i, &expr->expressions, list) + list_for_each_entry(i, &expr_concat(expr)->expressions, list) json_array_append_new(array, expr_print_json(i, octx)); - return json_pack("{s:o}", "concat", array); + return nft_json_pack("{s:o}", "concat", array); } json_t *set_expr_json(const struct expr *expr, struct output_ctx *octx) @@ -562,10 +669,10 @@ json_t *set_expr_json(const struct expr *expr, struct output_ctx *octx) json_t *array = json_array(); const struct expr *i; - list_for_each_entry(i, &expr->expressions, list) + list_for_each_entry(i, &expr_set(expr)->expressions, list) json_array_append_new(array, expr_print_json(i, octx)); - return json_pack("{s:o}", "set", array); + return nft_json_pack("{s:o}", "set", array); } json_t *set_ref_expr_json(const struct expr *expr, struct output_ctx *octx) @@ -573,21 +680,23 @@ json_t *set_ref_expr_json(const struct expr *expr, struct output_ctx *octx) if (set_is_anonymous(expr->set->flags)) { return expr_print_json(expr->set->init, octx); } else { - return json_pack("s+", "@", expr->set->handle.set.name); + return nft_json_pack("s+", "@", expr->set->handle.set.name); } } json_t *set_elem_expr_json(const struct expr *expr, struct output_ctx *octx) { json_t *root = expr_print_json(expr->key, octx); + struct stmt *stmt; json_t *tmp; if (!root) return NULL; /* these element attributes require formal set elem syntax */ - if (expr->timeout || expr->expiration || expr->comment) { - root = json_pack("{s:o}", "val", root); + if (expr->timeout || expr->expiration || expr->comment || + !list_empty(&expr->stmt_list)) { + root = nft_json_pack("{s:o}", "val", root); if (expr->timeout) { tmp = json_integer(expr->timeout / 1000); @@ -601,7 +710,15 @@ json_t *set_elem_expr_json(const struct expr *expr, struct output_ctx *octx) tmp = json_string(expr->comment); json_object_set_new(root, "comment", tmp); } - return json_pack("{s:o}", "elem", root); + list_for_each_entry(stmt, &expr->stmt_list, list) { + tmp = stmt_print_json(stmt, octx); + /* XXX: detect and complain about clashes? */ + json_object_update_missing(root, tmp); + json_decref(tmp); + /* TODO: only one statement per element. */ + break; + } + return nft_json_pack("{s:o}", "elem", root); } return root; @@ -611,7 +728,7 @@ json_t *prefix_expr_json(const struct expr *expr, struct output_ctx *octx) { json_t *root = expr_print_json(expr->prefix, octx); - return json_pack("{s:{s:o, s:i}}", "prefix", + return nft_json_pack("{s:{s:o, s:i}}", "prefix", "addr", root, "len", expr->prefix_len); } @@ -621,10 +738,10 @@ json_t *list_expr_json(const struct expr *expr, struct output_ctx *octx) json_t *array = json_array(); const struct expr *i; - list_for_each_entry(i, &expr->expressions, list) + list_for_each_entry(i, &expr_list(expr)->expressions, list) json_array_append_new(array, expr_print_json(i, octx)); - //return json_pack("{s:s, s:o}", "type", "list", "val", array); + //return nft_json_pack("{s:s, s:o}", "type", "list", "val", array); return array; } @@ -635,7 +752,7 @@ json_t *unary_expr_json(const struct expr *expr, struct output_ctx *octx) json_t *mapping_expr_json(const struct expr *expr, struct output_ctx *octx) { - return json_pack("[o, o]", + return nft_json_pack("[o, o]", expr_print_json(expr->left, octx), expr_print_json(expr->right, octx)); } @@ -648,7 +765,7 @@ json_t *map_expr_json(const struct expr *expr, struct output_ctx *octx) expr->mappings->set->data->dtype->type == TYPE_VERDICT) type = "vmap"; - return json_pack("{s:{s:o, s:o}}", type, + return nft_json_pack("{s:{s:o, s:o}}", type, "key", expr_print_json(expr->map, octx), "data", expr_print_json(expr->mappings, octx)); } @@ -656,47 +773,51 @@ json_t *map_expr_json(const struct expr *expr, struct output_ctx *octx) json_t *exthdr_expr_json(const struct expr *expr, struct output_ctx *octx) { const char *desc = expr->exthdr.desc ? - expr->exthdr.desc->name : - "unknown-exthdr"; + expr->exthdr.desc->name : NULL; const char *field = expr->exthdr.tmpl->token; json_t *root; bool is_exists = expr->exthdr.flags & NFT_EXTHDR_F_PRESENT; if (expr->exthdr.op == NFT_EXTHDR_OP_TCPOPT) { + static const char *offstrs[] = { "", "1", "2", "3" }; unsigned int offset = expr->exthdr.offset / 64; + const char *offstr = ""; - if (offset) { - const char *offstrs[] = { "0", "1", "2", "3" }; - const char *offstr = ""; - + if (desc) { if (offset < 4) offstr = offstrs[offset]; - root = json_pack("{s:s+}", "name", desc, offstr); + root = nft_json_pack("{s:s+}", "name", desc, offstr); + + if (!is_exists) + json_object_set_new(root, "field", json_string(field)); } else { - root = json_pack("{s:s}", "name", desc); + root = nft_json_pack("{s:i, s:i, s:i}", + "base", expr->exthdr.raw_type, + "offset", expr->exthdr.offset, + "len", expr->len); } - if (!is_exists) - json_object_set_new(root, "field", json_string(field)); - - return json_pack("{s:o}", "tcp option", root); + return nft_json_pack("{s:o}", "tcp option", root); } - if (expr->exthdr.op == NFT_EXTHDR_OP_IPV4) { - root = json_pack("{s:s}", "name", desc); - - if (!is_exists) - json_object_set_new(root, "field", json_string(field)); - return json_pack("{s:o}", "ip option", root); + if (expr->exthdr.op == NFT_EXTHDR_OP_DCCP) { + root = nft_json_pack("{s:i}", "type", expr->exthdr.raw_type); + return nft_json_pack("{s:o}", "dccp option", root); } - root = json_pack("{s:s}", - "name", desc); + root = nft_json_pack("{s:s}", "name", desc); if (!is_exists) json_object_set_new(root, "field", json_string(field)); - return json_pack("{s:o}", "exthdr", root); + switch (expr->exthdr.op) { + case NFT_EXTHDR_OP_IPV4: + return nft_json_pack("{s:o}", "ip option", root); + case NFT_EXTHDR_OP_SCTP: + return nft_json_pack("{s:o}", "sctp chunk", root); + default: + return nft_json_pack("{s:o}", "exthdr", root); + } } json_t *verdict_expr_json(const struct expr *expr, struct output_ctx *octx) @@ -732,15 +853,15 @@ json_t *verdict_expr_json(const struct expr *expr, struct output_ctx *octx) return NULL; } if (chain) - return json_pack("{s:{s:o}}", name, "target", chain); + return nft_json_pack("{s:{s:o}}", name, "target", chain); else - return json_pack("{s:n}", name); + return nft_json_pack("{s:n}", name); } json_t *rt_expr_json(const struct expr *expr, struct output_ctx *octx) { const char *key = rt_templates[expr->rt.key].token; - json_t *root = json_pack("{s:s}", "key", key); + json_t *root = nft_json_pack("{s:s}", "key", key); const char *family = NULL; switch (expr->rt.key) { @@ -757,7 +878,7 @@ json_t *rt_expr_json(const struct expr *expr, struct output_ctx *octx) if (family) json_object_set_new(root, "family", json_string(family)); - return json_pack("{s:o}", "rt", root); + return nft_json_pack("{s:o}", "rt", root); } json_t *numgen_expr_json(const struct expr *expr, struct output_ctx *octx) @@ -776,7 +897,7 @@ json_t *numgen_expr_json(const struct expr *expr, struct output_ctx *octx) break; } - return json_pack("{s:{s:s, s:i, s:i}}", "numgen", + return nft_json_pack("{s:{s:s, s:i, s:i}}", "numgen", "mode", mode, "mod", expr->numgen.mod, "offset", expr->numgen.offset); @@ -798,7 +919,7 @@ json_t *hash_expr_json(const struct expr *expr, struct output_ctx *octx) break; } - root = json_pack("{s:i}", "mod", expr->hash.mod); + root = nft_json_pack("{s:i}", "mod", expr->hash.mod); if (expr->hash.seed_set) json_object_set_new(root, "seed", json_integer(expr->hash.seed)); @@ -808,7 +929,7 @@ json_t *hash_expr_json(const struct expr *expr, struct output_ctx *octx) if (jexpr) json_object_set_new(root, "expr", jexpr); - return json_pack("{s:o}", type, root); + return nft_json_pack("{s:o}", type, root); } json_t *fib_expr_json(const struct expr *expr, struct output_ctx *octx) @@ -817,7 +938,7 @@ json_t *fib_expr_json(const struct expr *expr, struct output_ctx *octx) unsigned int flags = expr->fib.flags & ~NFTA_FIB_F_PRESENT; json_t *root; - root = json_pack("{s:s}", "result", fib_result_str(expr->fib.result)); + root = nft_json_pack("{s:s}", "result", fib_result_str(expr)); if (flags) { json_t *tmp = json_array(); @@ -831,9 +952,10 @@ json_t *fib_expr_json(const struct expr *expr, struct output_ctx *octx) } if (flags) json_array_append_new(tmp, json_integer(flags)); - json_object_set_new(root, "flags", tmp); + + json_add_array_new(root, "flags", tmp); } - return json_pack("{s:o}", "fib", root); + return nft_json_pack("{s:o}", "fib", root); } static json_t *symbolic_constant_json(const struct symbol_table *tbl, @@ -862,6 +984,11 @@ static json_t *symbolic_constant_json(const struct symbol_table *tbl, return json_string(s->identifier); } +json_t *set_elem_catchall_expr_json(const struct expr *expr, struct output_ctx *octx) +{ + return json_string("*"); +} + static json_t *datatype_json(const struct expr *expr, struct output_ctx *octx) { const struct datatype *dtype = expr->dtype; @@ -901,7 +1028,7 @@ json_t *constant_expr_json(const struct expr *expr, struct output_ctx *octx) json_t *socket_expr_json(const struct expr *expr, struct output_ctx *octx) { - return json_pack("{s:{s:s}}", "socket", "key", + return nft_json_pack("{s:{s:s}}", "socket", "key", socket_templates[expr->socket.key].token); } @@ -910,9 +1037,9 @@ json_t *osf_expr_json(const struct expr *expr, struct output_ctx *octx) json_t *root; if (expr->osf.flags & NFT_OSF_F_VERSION) - root = json_pack("{s:s}", "key", "version"); + root = nft_json_pack("{s:s}", "key", "version"); else - root = json_pack("{s:s}", "key", "name"); + root = nft_json_pack("{s:s}", "key", "name"); switch (expr->osf.ttl) { case 1: @@ -923,7 +1050,7 @@ json_t *osf_expr_json(const struct expr *expr, struct output_ctx *octx) break; } - return json_pack("{s:o}", "osf", root); + return nft_json_pack("{s:o}", "osf", root); } json_t *xfrm_expr_json(const struct expr *expr, struct output_ctx *octx) @@ -960,7 +1087,7 @@ json_t *xfrm_expr_json(const struct expr *expr, struct output_ctx *octx) break; } - root = json_pack("{s:s}", "key", name); + root = nft_json_pack("{s:s}", "key", name); if (family) json_object_set_new(root, "family", json_string(family)); @@ -968,7 +1095,7 @@ json_t *xfrm_expr_json(const struct expr *expr, struct output_ctx *octx) json_object_set_new(root, "dir", json_string(dirstr)); json_object_set_new(root, "spnum", json_integer(expr->xfrm.spnum)); - return json_pack("{s:o}", "ipsec", root); + return nft_json_pack("{s:o}", "ipsec", root); } json_t *integer_type_json(const struct expr *expr, struct output_ctx *octx) @@ -1009,35 +1136,25 @@ json_t *boolean_type_json(const struct expr *expr, struct output_ctx *octx) json_t *inet_protocol_type_json(const struct expr *expr, struct output_ctx *octx) { - struct protoent *p; - if (!nft_output_numeric_proto(octx)) { - p = getprotobynumber(mpz_get_uint8(expr->value)); - if (p != NULL) - return json_string(p->p_name); + char name[NFT_PROTONAME_MAXSIZE]; + + if (nft_getprotobynumber(mpz_get_uint8(expr->value), name, sizeof(name))) + return json_string(name); } return integer_type_json(expr, octx); } json_t *inet_service_type_json(const struct expr *expr, struct output_ctx *octx) { - struct sockaddr_in sin = { - .sin_family = AF_INET, - .sin_port = mpz_get_be16(expr->value), - }; - char buf[NI_MAXSERV]; + uint16_t port = mpz_get_be16(expr->value); + char name[NFT_SERVNAME_MAXSIZE]; if (!nft_output_service(octx) || - getnameinfo((struct sockaddr *)&sin, sizeof(sin), - NULL, 0, buf, sizeof(buf), 0)) - return json_integer(ntohs(sin.sin_port)); + !nft_getservbyport(port, NULL, name, sizeof(name))) + return json_integer(ntohs(port)); - if (htons(atoi(buf)) == sin.sin_port || - getnameinfo((struct sockaddr *)&sin, sizeof(sin), - NULL, 0, buf, sizeof(buf), NI_DGRAM)) - return json_integer(ntohs(sin.sin_port)); - - return json_string(buf); + return json_string(name); } json_t *mark_type_json(const struct expr *expr, struct output_ctx *octx) @@ -1098,16 +1215,23 @@ json_t *expr_stmt_json(const struct stmt *stmt, struct output_ctx *octx) return expr_print_json(stmt->expr, octx); } +json_t *flow_offload_stmt_json(const struct stmt *stmt, struct output_ctx *octx) +{ + return nft_json_pack("{s:{s:s, s:s+}}", "flow", + "op", "add", "flowtable", + "@", stmt->flow.table_name); +} + json_t *payload_stmt_json(const struct stmt *stmt, struct output_ctx *octx) { - return json_pack("{s: {s:o, s:o}}", "mangle", + return nft_json_pack("{s: {s:o, s:o}}", "mangle", "key", expr_print_json(stmt->payload.expr, octx), "value", expr_print_json(stmt->payload.val, octx)); } json_t *exthdr_stmt_json(const struct stmt *stmt, struct output_ctx *octx) { - return json_pack("{s: {s:o, s:o}}", "mangle", + return nft_json_pack("{s: {s:o, s:o}}", "mangle", "key", expr_print_json(stmt->exthdr.expr, octx), "value", expr_print_json(stmt->exthdr.val, octx)); } @@ -1119,7 +1243,7 @@ json_t *quota_stmt_json(const struct stmt *stmt, struct output_ctx *octx) json_t *root; data_unit = get_rate(stmt->quota.bytes, &bytes); - root = json_pack("{s:I, s:s}", + root = nft_json_pack("{s:I, s:s}", "val", bytes, "val_unit", data_unit); @@ -1131,7 +1255,7 @@ json_t *quota_stmt_json(const struct stmt *stmt, struct output_ctx *octx) json_object_set_new(root, "used_unit", json_string(data_unit)); } - return json_pack("{s:o}", "quota", root); + return nft_json_pack("{s:o}", "quota", root); } json_t *ct_stmt_json(const struct stmt *stmt, struct output_ctx *octx) @@ -1144,7 +1268,7 @@ json_t *ct_stmt_json(const struct stmt *stmt, struct output_ctx *octx) }, }; - return json_pack("{s:{s:o, s:o}}", "mangle", + return nft_json_pack("{s:{s:o, s:o}}", "mangle", "key", ct_expr_json(&expr, octx), "value", expr_print_json(stmt->ct.expr, octx)); } @@ -1162,28 +1286,26 @@ json_t *limit_stmt_json(const struct stmt *stmt, struct output_ctx *octx) burst_unit = get_rate(stmt->limit.burst, &burst); } - root = json_pack("{s:I, s:s}", + root = nft_json_pack("{s:I, s:I, s:s}", "rate", rate, + "burst", burst, "per", get_unit(stmt->limit.unit)); if (inv) json_object_set_new(root, "inv", json_boolean(inv)); if (rate_unit) json_object_set_new(root, "rate_unit", json_string(rate_unit)); - if (burst && burst != 5) { - json_object_set_new(root, "burst", json_integer(burst)); - if (burst_unit) - json_object_set_new(root, "burst_unit", - json_string(burst_unit)); - } + if (burst_unit) + json_object_set_new(root, "burst_unit", + json_string(burst_unit)); - return json_pack("{s:o}", "limit", root); + return nft_json_pack("{s:o}", "limit", root); } json_t *fwd_stmt_json(const struct stmt *stmt, struct output_ctx *octx) { json_t *root, *tmp; - root = json_pack("{s:o}", "dev", expr_print_json(stmt->fwd.dev, octx)); + root = nft_json_pack("{s:o}", "dev", expr_print_json(stmt->fwd.dev, octx)); if (stmt->fwd.addr) { tmp = json_string(family2str(stmt->fwd.family)); @@ -1193,12 +1315,12 @@ json_t *fwd_stmt_json(const struct stmt *stmt, struct output_ctx *octx) json_object_set_new(root, "addr", tmp); } - return json_pack("{s:o}", "fwd", root); + return nft_json_pack("{s:o}", "fwd", root); } json_t *notrack_stmt_json(const struct stmt *stmt, struct output_ctx *octx) { - return json_pack("{s:n}", "notrack"); + return nft_json_pack("{s:n}", "notrack"); } json_t *dup_stmt_json(const struct stmt *stmt, struct output_ctx *octx) @@ -1206,27 +1328,27 @@ json_t *dup_stmt_json(const struct stmt *stmt, struct output_ctx *octx) json_t *root; if (stmt->dup.to) { - root = json_pack("{s:o}", "addr", expr_print_json(stmt->dup.to, octx)); + root = nft_json_pack("{s:o}", "addr", expr_print_json(stmt->dup.to, octx)); if (stmt->dup.dev) json_object_set_new(root, "dev", expr_print_json(stmt->dup.dev, octx)); } else { root = json_null(); } - return json_pack("{s:o}", "dup", root); + return nft_json_pack("{s:o}", "dup", root); } json_t *meta_stmt_json(const struct stmt *stmt, struct output_ctx *octx) { json_t *root; - root = json_pack("{s:{s:s}}", "meta", + root = nft_json_pack("{s:{s:s}}", "meta", "key", meta_templates[stmt->meta.key].token); - root = json_pack("{s:o, s:o}", + root = nft_json_pack("{s:o, s:o}", "key", root, "value", expr_print_json(stmt->meta.expr, octx)); - return json_pack("{s:o}", "mangle", root); + return nft_json_pack("{s:o}", "mangle", root); } json_t *log_stmt_json(const struct stmt *stmt, struct output_ctx *octx) @@ -1234,8 +1356,8 @@ json_t *log_stmt_json(const struct stmt *stmt, struct output_ctx *octx) json_t *root = json_object(), *flags; if (stmt->log.flags & STMT_LOG_PREFIX) - json_object_set_new(root, "prefix", - json_string(stmt->log.prefix)); + json_object_set_new(root, "prefix", json_string(stmt->log.prefix)); + if (stmt->log.flags & STMT_LOG_GROUP) json_object_set_new(root, "group", json_integer(stmt->log.group)); @@ -1268,24 +1390,17 @@ json_t *log_stmt_json(const struct stmt *stmt, struct output_ctx *octx) if (stmt->log.logflags & NF_LOG_MACDECODE) json_array_append_new(flags, json_string("ether")); } - if (json_array_size(flags) > 1) { - json_object_set_new(root, "flags", flags); - } else { - if (json_array_size(flags)) - json_object_set(root, "flags", - json_array_get(flags, 0)); - json_decref(flags); - } + json_add_array_new(root, "flags", flags); if (!json_object_size(root)) { json_decref(root); root = json_null(); } - return json_pack("{s:o}", "log", root); + return nft_json_pack("{s:o}", "log", root); } -static json_t *nat_flags_json(int flags) +static json_t *nat_flags_json(uint32_t flags) { json_t *array = json_array(); @@ -1295,6 +1410,18 @@ static json_t *nat_flags_json(int flags) json_array_append_new(array, json_string("fully-random")); if (flags & NF_NAT_RANGE_PERSISTENT) json_array_append_new(array, json_string("persistent")); + if (flags & NF_NAT_RANGE_NETMAP) + json_array_append_new(array, json_string("netmap")); + return array; +} + +static json_t *nat_type_flags_json(uint32_t type_flags) +{ + json_t *array = json_array(); + + if (type_flags & STMT_NAT_F_PREFIX) + json_array_append_new(array, json_string("prefix")); + return array; } @@ -1319,13 +1446,12 @@ json_t *nat_stmt_json(const struct stmt *stmt, struct output_ctx *octx) json_object_set_new(root, "port", expr_print_json(stmt->nat.proto, octx)); - if (json_array_size(array) > 1) { - json_object_set_new(root, "flags", array); - } else { - if (json_array_size(array)) - json_object_set(root, "flags", - json_array_get(array, 0)); - json_decref(array); + json_add_array_new(root, "flags", array); + + if (stmt->nat.type_flags) { + array = nat_type_flags_json(stmt->nat.type_flags); + + json_add_array_new(root, "type_flags", array); } if (!json_object_size(root)) { @@ -1333,7 +1459,7 @@ json_t *nat_stmt_json(const struct stmt *stmt, struct output_ctx *octx) root = json_null(); } - return json_pack("{s:o}", nat_etype2str(stmt->nat.type), root); + return nft_json_pack("{s:o}", nat_etype2str(stmt->nat.type), root); } json_t *reject_stmt_json(const struct stmt *stmt, struct output_ctx *octx) @@ -1346,24 +1472,16 @@ json_t *reject_stmt_json(const struct stmt *stmt, struct output_ctx *octx) type = "tcp reset"; break; case NFT_REJECT_ICMPX_UNREACH: - if (stmt->reject.icmp_code == NFT_REJECT_ICMPX_PORT_UNREACH) - break; type = "icmpx"; jexpr = expr_print_json(stmt->reject.expr, octx); break; case NFT_REJECT_ICMP_UNREACH: switch (stmt->reject.family) { case NFPROTO_IPV4: - if (!stmt->reject.verbose_print && - stmt->reject.icmp_code == ICMP_PORT_UNREACH) - break; type = "icmp"; jexpr = expr_print_json(stmt->reject.expr, octx); break; case NFPROTO_IPV6: - if (!stmt->reject.verbose_print && - stmt->reject.icmp_code == ICMP6_DST_UNREACH_NOPORT) - break; type = "icmpv6"; jexpr = expr_print_json(stmt->reject.expr, octx); break; @@ -1371,7 +1489,7 @@ json_t *reject_stmt_json(const struct stmt *stmt, struct output_ctx *octx) } if (!type && !jexpr) - return json_pack("{s:n}", "reject"); + return nft_json_pack("{s:n}", "reject"); root = json_object(); if (type) @@ -1379,25 +1497,62 @@ json_t *reject_stmt_json(const struct stmt *stmt, struct output_ctx *octx) if (jexpr) json_object_set_new(root, "expr", jexpr); - return json_pack("{s:o}", "reject", root); + return nft_json_pack("{s:o}", "reject", root); } json_t *counter_stmt_json(const struct stmt *stmt, struct output_ctx *octx) { if (nft_output_stateless(octx)) - return json_pack("{s:n}", "counter"); + return nft_json_pack("{s:n}", "counter"); - return json_pack("{s:{s:I, s:I}}", "counter", + return nft_json_pack("{s:{s:I, s:I}}", "counter", "packets", stmt->counter.packets, "bytes", stmt->counter.bytes); } +json_t *last_stmt_json(const struct stmt *stmt, struct output_ctx *octx) +{ + if (nft_output_stateless(octx) || stmt->last.set == 0) + return nft_json_pack("{s:n}", "last"); + + return nft_json_pack("{s:{s:I}}", "last", "used", stmt->last.used); +} + json_t *set_stmt_json(const struct stmt *stmt, struct output_ctx *octx) { - return json_pack("{s:{s:s, s:o, s:s+}}", "set", + json_t *root; + + root = nft_json_pack("{s:s, s:o, s:s+}", "op", set_stmt_op_names[stmt->set.op], "elem", expr_print_json(stmt->set.key, octx), "set", "@", stmt->set.set->set->handle.set.name); + + if (!list_empty(&stmt->set.stmt_list)) { + json_object_set_new(root, "stmt", + set_stmt_list_json(&stmt->set.stmt_list, + octx)); + } + + return nft_json_pack("{s:o}", "set", root); +} + +json_t *map_stmt_json(const struct stmt *stmt, struct output_ctx *octx) +{ + json_t *root; + + root = nft_json_pack("{s:s, s:o, s:o, s:s+}", + "op", set_stmt_op_names[stmt->map.op], + "elem", expr_print_json(stmt->map.key, octx), + "data", expr_print_json(stmt->map.data, octx), + "map", "@", stmt->map.set->set->handle.set.name); + + if (!list_empty(&stmt->map.stmt_list)) { + json_object_set_new(root, "stmt", + set_stmt_list_json(&stmt->map.stmt_list, + octx)); + } + + return nft_json_pack("{s:o}", "map", root); } json_t *objref_stmt_json(const struct stmt *stmt, struct output_ctx *octx) @@ -1409,7 +1564,7 @@ json_t *objref_stmt_json(const struct stmt *stmt, struct output_ctx *octx) else name = objref_type_name(stmt->objref.type); - return json_pack("{s:o}", name, expr_print_json(stmt->objref.expr, octx)); + return nft_json_pack("{s:o}", name, expr_print_json(stmt->objref.expr, octx)); } json_t *meter_stmt_json(const struct stmt *stmt, struct output_ctx *octx) @@ -1421,7 +1576,7 @@ json_t *meter_stmt_json(const struct stmt *stmt, struct output_ctx *octx) tmp = stmt_print_json(stmt->meter.stmt, octx); octx->flags = flags; - root = json_pack("{s:o, s:o, s:i}", + root = nft_json_pack("{s:o, s:o, s:i}", "key", expr_print_json(stmt->meter.key, octx), "stmt", tmp, "size", stmt->meter.size); @@ -1430,7 +1585,7 @@ json_t *meter_stmt_json(const struct stmt *stmt, struct output_ctx *octx) json_object_set_new(root, "name", tmp); } - return json_pack("{s:o}", "meter", root); + return nft_json_pack("{s:o}", "meter", root); } json_t *queue_stmt_json(const struct stmt *stmt, struct output_ctx *octx) @@ -1448,21 +1603,14 @@ json_t *queue_stmt_json(const struct stmt *stmt, struct output_ctx *octx) json_array_append_new(flags, json_string("bypass")); if (stmt->queue.flags & NFT_QUEUE_FLAG_CPU_FANOUT) json_array_append_new(flags, json_string("fanout")); - if (json_array_size(flags) > 1) { - json_object_set_new(root, "flags", flags); - } else { - if (json_array_size(flags)) - json_object_set(root, "flags", - json_array_get(flags, 0)); - json_decref(flags); - } + json_add_array_new(root, "flags", flags); if (!json_object_size(root)) { json_decref(root); root = json_null(); } - return json_pack("{s:o}", "queue", root); + return nft_json_pack("{s:o}", "queue", root); } json_t *verdict_stmt_json(const struct stmt *stmt, struct output_ctx *octx) @@ -1472,12 +1620,12 @@ json_t *verdict_stmt_json(const struct stmt *stmt, struct output_ctx *octx) json_t *connlimit_stmt_json(const struct stmt *stmt, struct output_ctx *octx) { - json_t *root = json_pack("{s:i}", "val", stmt->connlimit.count); + json_t *root = nft_json_pack("{s:i}", "val", stmt->connlimit.count); if (stmt->connlimit.flags & NFT_CONNLIMIT_F_INV) json_object_set_new(root, "inv", json_true()); - return json_pack("{s:o}", "ct count", root); + return nft_json_pack("{s:o}", "ct count", root); } json_t *tproxy_stmt_json(const struct stmt *stmt, struct output_ctx *octx) @@ -1500,7 +1648,7 @@ json_t *tproxy_stmt_json(const struct stmt *stmt, struct output_ctx *octx) json_object_set_new(root, "port", tmp); } - return json_pack("{s:o}", "tproxy", root); + return nft_json_pack("{s:o}", "tproxy", root); } json_t *synproxy_stmt_json(const struct stmt *stmt, struct output_ctx *octx) @@ -1518,23 +1666,39 @@ json_t *synproxy_stmt_json(const struct stmt *stmt, struct output_ctx *octx) if (stmt->synproxy.flags & NF_SYNPROXY_OPT_SACK_PERM) json_array_append_new(flags, json_string("sack-perm")); - if (json_array_size(flags) > 0) - json_object_set_new(root, "flags", flags); - else - json_decref(flags); + json_add_array_new(root, "flags", flags); if (!json_object_size(root)) { json_decref(root); root = json_null(); } - return json_pack("{s:o}", "synproxy", root); + return nft_json_pack("{s:o}", "synproxy", root); +} + +json_t *optstrip_stmt_json(const struct stmt *stmt, struct output_ctx *octx) +{ + return nft_json_pack("{s:o}", "reset", + expr_print_json(stmt->optstrip.expr, octx)); +} + +json_t *xt_stmt_json(const struct stmt *stmt, struct output_ctx *octx) +{ + static const char *xt_typename[NFT_XT_MAX] = { + [NFT_XT_MATCH] = "match", + [NFT_XT_TARGET] = "target", + [NFT_XT_WATCHER] = "watcher", + }; + + return nft_json_pack("{s:{s:s, s:s}}", "xt", + "type", xt_typename[stmt->xt.type], + "name", stmt->xt.name); } static json_t *table_print_json_full(struct netlink_ctx *ctx, struct table *table) { - json_t *root = json_array(), *tmp; + json_t *root = json_array(), *rules = json_array(), *tmp; struct flowtable *flowtable; struct chain *chain; struct rule *rule; @@ -1544,30 +1708,34 @@ static json_t *table_print_json_full(struct netlink_ctx *ctx, tmp = table_print_json(table); json_array_append_new(root, tmp); - list_for_each_entry(obj, &table->objs, list) { + /* both maps and rules may refer to chains, list them first */ + list_for_each_entry(chain, &table->chain_cache.list, cache.list) { + tmp = chain_print_json(chain); + json_array_append_new(root, tmp); + } + list_for_each_entry(obj, &table->obj_cache.list, cache.list) { tmp = obj_print_json(obj); json_array_append_new(root, tmp); } - list_for_each_entry(set, &table->sets, list) { + list_for_each_entry(set, &table->set_cache.list, cache.list) { if (set_is_anonymous(set->flags)) continue; tmp = set_print_json(&ctx->nft->output, set); json_array_append_new(root, tmp); } - list_for_each_entry(flowtable, &table->flowtables, list) { + list_for_each_entry(flowtable, &table->ft_cache.list, cache.list) { tmp = flowtable_print_json(flowtable); json_array_append_new(root, tmp); } - list_for_each_entry(chain, &table->chains, list) { - tmp = chain_print_json(chain); - json_array_append_new(root, tmp); - + list_for_each_entry(chain, &table->chain_cache.list, cache.list) { list_for_each_entry(rule, &chain->rules, list) { tmp = rule_print_json(&ctx->nft->output, rule); - json_array_append_new(root, tmp); + json_array_append_new(rules, tmp); } } + json_array_extend_new(root, rules); + return root; } @@ -1577,12 +1745,12 @@ static json_t *do_list_ruleset_json(struct netlink_ctx *ctx, struct cmd *cmd) json_t *root = json_array(); struct table *table; - list_for_each_entry(table, &ctx->nft->cache.list, list) { + list_for_each_entry(table, &ctx->nft->cache.table_cache.list, cache.list) { if (family != NFPROTO_UNSPEC && table->handle.family != family) continue; - json_array_extend(root, table_print_json_full(ctx, table)); + json_array_extend_new(root, table_print_json_full(ctx, table)); } return root; @@ -1594,7 +1762,7 @@ static json_t *do_list_tables_json(struct netlink_ctx *ctx, struct cmd *cmd) json_t *root = json_array(); struct table *table; - list_for_each_entry(table, &ctx->nft->cache.list, list) { + list_for_each_entry(table, &ctx->nft->cache.table_cache.list, cache.list) { if (family != NFPROTO_UNSPEC && table->handle.family != family) continue; @@ -1618,7 +1786,7 @@ static json_t *do_list_chain_json(struct netlink_ctx *ctx, struct chain *chain; struct rule *rule; - list_for_each_entry(chain, &table->chains, list) { + list_for_each_entry(chain, &table->chain_cache.list, cache.list) { if (chain->handle.family != cmd->handle.family || strcmp(cmd->handle.chain.name, chain->handle.chain.name)) continue; @@ -1641,12 +1809,12 @@ static json_t *do_list_chains_json(struct netlink_ctx *ctx, struct cmd *cmd) struct table *table; struct chain *chain; - list_for_each_entry(table, &ctx->nft->cache.list, list) { + list_for_each_entry(table, &ctx->nft->cache.table_cache.list, cache.list) { if (cmd->handle.family != NFPROTO_UNSPEC && cmd->handle.family != table->handle.family) continue; - list_for_each_entry(chain, &table->chains, list) { + list_for_each_entry(chain, &table->chain_cache.list, cache.list) { json_t *tmp = chain_print_json(chain); json_array_append_new(root, tmp); @@ -1659,12 +1827,15 @@ static json_t *do_list_chains_json(struct netlink_ctx *ctx, struct cmd *cmd) static json_t *do_list_set_json(struct netlink_ctx *ctx, struct cmd *cmd, struct table *table) { - struct set *set = set_lookup(table, cmd->handle.set.name); + struct set *set = cmd->set; - if (set == NULL) - return json_null(); + if (!set) { + set = set_cache_find(table, cmd->handle.set.name); + if (set == NULL) + return json_null(); + } - return json_pack("[o]", set_print_json(&ctx->nft->output, set)); + return nft_json_pack("[o]", set_print_json(&ctx->nft->output, set)); } static json_t *do_list_sets_json(struct netlink_ctx *ctx, struct cmd *cmd) @@ -1674,12 +1845,12 @@ static json_t *do_list_sets_json(struct netlink_ctx *ctx, struct cmd *cmd) struct table *table; struct set *set; - list_for_each_entry(table, &ctx->nft->cache.list, list) { + list_for_each_entry(table, &ctx->nft->cache.table_cache.list, cache.list) { if (cmd->handle.family != NFPROTO_UNSPEC && cmd->handle.family != table->handle.family) continue; - list_for_each_entry(set, &table->sets, list) { + list_for_each_entry(set, &table->set_cache.list, cache.list) { if (cmd->obj == CMD_OBJ_SETS && !set_is_literal(set->flags)) continue; @@ -1703,7 +1874,7 @@ static json_t *do_list_obj_json(struct netlink_ctx *ctx, struct table *table; struct obj *obj; - list_for_each_entry(table, &ctx->nft->cache.list, list) { + list_for_each_entry(table, &ctx->nft->cache.table_cache.list, cache.list) { if (cmd->handle.family != NFPROTO_UNSPEC && cmd->handle.family != table->handle.family) continue; @@ -1712,7 +1883,7 @@ static json_t *do_list_obj_json(struct netlink_ctx *ctx, strcmp(cmd->handle.table.name, table->handle.table.name)) continue; - list_for_each_entry(obj, &table->objs, list) { + list_for_each_entry(obj, &table->obj_cache.list, cache.list) { if (obj->type != type || (cmd->handle.obj.name && strcmp(cmd->handle.obj.name, obj->handle.obj.name))) @@ -1731,8 +1902,8 @@ static json_t *do_list_flowtable_json(struct netlink_ctx *ctx, json_t *root = json_array(); struct flowtable *ft; - ft = flowtable_lookup(table, cmd->handle.flowtable.name); - if (ft == NULL) + ft = ft_cache_find(table, cmd->handle.flowtable.name); + if (!ft) return json_null(); json_array_append_new(root, flowtable_print_json(ft)); @@ -1746,12 +1917,12 @@ static json_t *do_list_flowtables_json(struct netlink_ctx *ctx, struct cmd *cmd) struct flowtable *flowtable; struct table *table; - list_for_each_entry(table, &ctx->nft->cache.list, list) { + list_for_each_entry(table, &ctx->nft->cache.table_cache.list, cache.list) { if (cmd->handle.family != NFPROTO_UNSPEC && cmd->handle.family != table->handle.family) continue; - list_for_each_entry(flowtable, &table->flowtables, list) { + list_for_each_entry(flowtable, &table->ft_cache.list, cache.list) { tmp = flowtable_print_json(flowtable); json_array_append_new(root, tmp); } @@ -1762,7 +1933,7 @@ static json_t *do_list_flowtables_json(struct netlink_ctx *ctx, struct cmd *cmd) static json_t *generate_json_metainfo(void) { - return json_pack("{s: {s:s, s:s, s:i}}", "metainfo", + return nft_json_pack("{s: {s:s, s:s, s:i}}", "metainfo", "version", PACKAGE_VERSION, "release_name", RELEASE_NAME, "json_schema_version", JSON_SCHEMA_VERSION); @@ -1771,10 +1942,17 @@ static json_t *generate_json_metainfo(void) int do_command_list_json(struct netlink_ctx *ctx, struct cmd *cmd) { struct table *table = NULL; - json_t *root; + json_t *root = NULL; - if (cmd->handle.table.name) - table = table_lookup(&cmd->handle, &ctx->nft->cache); + if (cmd->handle.table.name) { + table = table_cache_find(&ctx->nft->cache.table_cache, + cmd->handle.table.name, + cmd->handle.family); + if (!table) { + errno = ENOENT; + return -1; + } + } switch (cmd->obj) { case CMD_OBJ_TABLE: @@ -1796,6 +1974,7 @@ int do_command_list_json(struct netlink_ctx *ctx, struct cmd *cmd) case CMD_OBJ_SET: root = do_list_set_json(ctx, cmd, table); break; + case CMD_OBJ_RULES: case CMD_OBJ_RULESET: root = do_list_ruleset_json(ctx, cmd); break; @@ -1823,6 +2002,13 @@ int do_command_list_json(struct netlink_ctx *ctx, struct cmd *cmd) case CMD_OBJ_CT_HELPERS: root = do_list_obj_json(ctx, cmd, NFT_OBJECT_CT_HELPER); break; + case CMD_OBJ_CT_TIMEOUT: + case CMD_OBJ_CT_TIMEOUTS: + root = do_list_obj_json(ctx, cmd, NFT_OBJECT_CT_TIMEOUT); + case CMD_OBJ_CT_EXPECT: + case CMD_OBJ_CT_EXPECTATIONS: + root = do_list_obj_json(ctx, cmd, NFT_OBJECT_CT_EXPECT); + break; case CMD_OBJ_LIMIT: case CMD_OBJ_LIMITS: root = do_list_obj_json(ctx, cmd, NFT_OBJECT_LIMIT); @@ -1831,14 +2017,29 @@ int do_command_list_json(struct netlink_ctx *ctx, struct cmd *cmd) case CMD_OBJ_SECMARKS: root = do_list_obj_json(ctx, cmd, NFT_OBJECT_SECMARK); break; + case CMD_OBJ_SYNPROXY: + case CMD_OBJ_SYNPROXYS: + root = do_list_obj_json(ctx, cmd, NFT_OBJECT_SYNPROXY); + break; case CMD_OBJ_FLOWTABLE: root = do_list_flowtable_json(ctx, cmd, table); break; case CMD_OBJ_FLOWTABLES: root = do_list_flowtables_json(ctx, cmd); break; - default: + case CMD_OBJ_HOOKS: + return 0; + case CMD_OBJ_MONITOR: + case CMD_OBJ_MARKUP: + case CMD_OBJ_SETELEMS: + case CMD_OBJ_RULE: + case CMD_OBJ_EXPR: + case CMD_OBJ_ELEMENTS: + errno = EOPNOTSUPP; + return -1; + case CMD_OBJ_INVALID: BUG("invalid command object type %u\n", cmd->obj); + break; } if (!json_is_array(root)) { @@ -1850,7 +2051,7 @@ int do_command_list_json(struct netlink_ctx *ctx, struct cmd *cmd) json_array_insert_new(root, 0, generate_json_metainfo()); - root = json_pack("{s:o}", "nftables", root); + root = nft_json_pack("{s:o}", "nftables", root); json_dumpf(root, ctx->nft->output.output_fp, 0); json_decref(root); fprintf(ctx->nft->output.output_fp, "\n"); @@ -1861,9 +2062,15 @@ int do_command_list_json(struct netlink_ctx *ctx, struct cmd *cmd) static void monitor_print_json(struct netlink_mon_handler *monh, const char *cmd, json_t *obj) { - obj = json_pack("{s:o}", cmd, obj); - json_dumpf(obj, monh->ctx->nft->output.output_fp, 0); - json_decref(obj); + struct nft_ctx *nft = monh->ctx->nft; + + obj = nft_json_pack("{s:o}", cmd, obj); + if (nft_output_echo(&nft->output) && !nft->json_root) { + json_array_append_new(nft->json_echo, obj); + } else { + json_dumpf(obj, nft->output.output_fp, 0); + json_decref(obj); + } } void monitor_print_table_json(struct netlink_mon_handler *monh, @@ -1900,6 +2107,12 @@ void monitor_print_obj_json(struct netlink_mon_handler *monh, monitor_print_json(monh, cmd, obj_print_json(o)); } +void monitor_print_flowtable_json(struct netlink_mon_handler *monh, + const char *cmd, struct flowtable *ft) +{ + monitor_print_json(monh, cmd, flowtable_print_json(ft)); +} + void monitor_print_rule_json(struct netlink_mon_handler *monh, const char *cmd, struct rule *r) { @@ -1907,3 +2120,10 @@ void monitor_print_rule_json(struct netlink_mon_handler *monh, monitor_print_json(monh, cmd, rule_print_json(octx, r)); } + +void json_alloc_echo(struct nft_ctx *nft) +{ + nft->json_echo = json_array(); + if (!nft->json_echo) + memory_allocation_error(); +} diff --git a/src/libnftables.c b/src/libnftables.c index cd2fcf2f..c8293f77 100644 --- a/src/libnftables.c +++ b/src/libnftables.c @@ -2,26 +2,27 @@ * Copyright (c) 2017 Eric Leblond <eric@regit.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 as - * published by the Free Software Foundation. - * + * it under the terms of the GNU General Public License version 2 (or any + * later) as published by the Free Software Foundation. */ + +#include <nft.h> + #include <nftables/libnftables.h> #include <erec.h> #include <mnl.h> #include <parser.h> #include <utils.h> #include <iface.h> - +#include <cmd.h> #include <errno.h> -#include <stdlib.h> -#include <string.h> +#include <sys/stat.h> +#include <libgen.h> static int nft_netlink(struct nft_ctx *nft, - struct list_head *cmds, struct list_head *msgs, - struct mnl_socket *nf_sock) + struct list_head *cmds, struct list_head *msgs) { - uint32_t batch_seqnum, seqnum = 0, num_cmds = 0; + uint32_t batch_seqnum, seqnum = 0, last_seqnum = UINT32_MAX, num_cmds = 0; struct netlink_ctx ctx = { .nft = nft, .msgs = msgs, @@ -36,9 +37,9 @@ static int nft_netlink(struct nft_ctx *nft, if (list_empty(cmds)) goto out; - batch_seqnum = mnl_batch_begin(ctx.batch, mnl_seqnum_alloc(&seqnum)); + batch_seqnum = mnl_batch_begin(ctx.batch, mnl_seqnum_inc(&seqnum)); list_for_each_entry(cmd, cmds, list) { - ctx.seqnum = cmd->seqnum = mnl_seqnum_alloc(&seqnum); + ctx.seqnum = cmd->seqnum_from = mnl_seqnum_inc(&seqnum); ret = do_command(&ctx, cmd); if (ret < 0) { netlink_io_error(&ctx, &cmd->location, @@ -46,16 +47,25 @@ static int nft_netlink(struct nft_ctx *nft, strerror(errno)); goto out; } + seqnum = cmd->seqnum_to = ctx.seqnum; + mnl_seqnum_inc(&seqnum); num_cmds++; } if (!nft->check) - mnl_batch_end(ctx.batch, mnl_seqnum_alloc(&seqnum)); + mnl_batch_end(ctx.batch, mnl_seqnum_inc(&seqnum)); if (!mnl_batch_ready(ctx.batch)) goto out; ret = mnl_batch_talk(&ctx, &err_list, num_cmds); if (ret < 0) { + if (ctx.maybe_emsgsize && errno == EMSGSIZE) { + netlink_io_error(&ctx, NULL, + "Could not process rule: %s\n" + "Please, rise /proc/sys/net/core/wmem_max on the host namespace. Hint: %d bytes", + strerror(errno), round_pow_2(ctx.maybe_emsgsize)); + goto out; + } netlink_io_error(&ctx, NULL, "Could not process rule: %s", strerror(errno)); goto out; @@ -65,20 +75,38 @@ static int nft_netlink(struct nft_ctx *nft, ret = -1; list_for_each_entry_safe(err, tmp, &err_list, head) { - list_for_each_entry(cmd, cmds, list) { - if (err->seqnum == cmd->seqnum || + /* cmd seqnums are monotonic: only reset the starting position + * if the error seqnum is lower than the previous one. + */ + if (err->seqnum < last_seqnum) + cmd = list_first_entry(cmds, struct cmd, list); + + list_for_each_entry_from(cmd, cmds, list) { + last_seqnum = cmd->seqnum_to; + if ((err->seqnum >= cmd->seqnum_from && + err->seqnum <= cmd->seqnum_to) || err->seqnum == batch_seqnum) { - netlink_io_error(&ctx, &cmd->location, - "Could not process rule: %s", - strerror(err->err)); + nft_cmd_error(&ctx, cmd, err); errno = err->err; - if (err->seqnum == cmd->seqnum) { + if (err->seqnum >= cmd->seqnum_from || + err->seqnum <= cmd->seqnum_to) { mnl_err_list_free(err); break; } } } + + if (&cmd->list == cmds) { + /* not found, rewind */ + last_seqnum = UINT32_MAX; + } } + /* nfnetlink uses the first netlink message header in the batch whose + * sequence number is zero to report for EOPNOTSUPP and EPERM errors in + * some scenarios. Now it is safe to release pending errors here. + */ + list_for_each_entry_safe(err, tmp, &err_list, head) + mnl_err_list_free(err); out: mnl_batch_reset(ctx.batch); return ret; @@ -94,21 +122,69 @@ static void nft_init(struct nft_ctx *ctx) static void nft_exit(struct nft_ctx *ctx) { + cache_free(&ctx->cache.table_cache); ct_label_table_exit(ctx); realm_table_rt_exit(ctx); devgroup_table_exit(ctx); mark_table_exit(ctx); } -EXPORT_SYMBOL(nft_ctx_add_include_path); -int nft_ctx_add_include_path(struct nft_ctx *ctx, const char *path) +EXPORT_SYMBOL(nft_ctx_add_var); +int nft_ctx_add_var(struct nft_ctx *ctx, const char *var) +{ + char *separator = strchr(var, '='); + int pcount = ctx->num_vars; + struct nft_vars *tmp; + const char *value; + + if (!separator) + return -1; + + tmp = xrealloc(ctx->vars, (pcount + 1) * sizeof(struct nft_vars)); + + *separator = '\0'; + value = separator + 1; + + ctx->vars = tmp; + ctx->vars[pcount].key = xstrdup(var); + ctx->vars[pcount].value = xstrdup(value); + ctx->num_vars++; + + return 0; +} + +EXPORT_SYMBOL(nft_ctx_clear_vars); +void nft_ctx_clear_vars(struct nft_ctx *ctx) +{ + unsigned int i; + + for (i = 0; i < ctx->num_vars; i++) { + free_const(ctx->vars[i].key); + free_const(ctx->vars[i].value); + } + ctx->num_vars = 0; + free(ctx->vars); + ctx->vars = NULL; +} + +static bool nft_ctx_find_include_path(struct nft_ctx *ctx, const char *path) +{ + unsigned int i; + + for (i = 0; i < ctx->num_include_paths; i++) { + if (!strcmp(ctx->include_paths[i], path)) + return true; + } + + return false; +} + +static int __nft_ctx_add_include_path(struct nft_ctx *ctx, const char *path) { char **tmp; int pcount = ctx->num_include_paths; - tmp = realloc(ctx->include_paths, (pcount + 1) * sizeof(char *)); - if (!tmp) - return -1; + tmp = xrealloc(ctx->include_paths, (pcount + 1) * sizeof(char *)); ctx->include_paths = tmp; @@ -119,49 +195,52 @@ int nft_ctx_add_include_path(struct nft_ctx *ctx, const char *path) return 0; } +EXPORT_SYMBOL(nft_ctx_add_include_path); +int nft_ctx_add_include_path(struct nft_ctx *ctx, const char *path) +{ + char canonical_path[PATH_MAX]; + + if (!realpath(path, canonical_path)) + return -1; + + if (nft_ctx_find_include_path(ctx, canonical_path)) + return 0; + + return __nft_ctx_add_include_path(ctx, canonical_path); +} + EXPORT_SYMBOL(nft_ctx_clear_include_paths); void nft_ctx_clear_include_paths(struct nft_ctx *ctx) { while (ctx->num_include_paths) - xfree(ctx->include_paths[--ctx->num_include_paths]); + free(ctx->include_paths[--ctx->num_include_paths]); - xfree(ctx->include_paths); + free(ctx->include_paths); ctx->include_paths = NULL; } -static void nft_ctx_netlink_init(struct nft_ctx *ctx) -{ - ctx->nf_sock = nft_mnl_socket_open(); -} - EXPORT_SYMBOL(nft_ctx_new); struct nft_ctx *nft_ctx_new(uint32_t flags) { - static bool init_once; struct nft_ctx *ctx; - if (!init_once) { - init_once = true; - gmp_init(); #ifdef HAVE_LIBXTABLES - xt_init(); + xt_init(); #endif - } ctx = xzalloc(sizeof(struct nft_ctx)); nft_init(ctx); ctx->state = xzalloc(sizeof(struct parser_state)); - nft_ctx_add_include_path(ctx, DEFAULT_INCLUDE_PATH); ctx->parser_max_errors = 10; - init_list_head(&ctx->cache.list); + cache_init(&ctx->cache.table_cache); ctx->top_scope = scope_alloc(); ctx->flags = flags; ctx->output.output_fp = stdout; ctx->output.error_fp = stderr; + init_list_head(&ctx->vars_ctx.indesc_list); - if (flags == NFT_CTX_DEFAULT) - nft_ctx_netlink_init(ctx); + ctx->nf_sock = nft_mnl_socket_open(); return ctx; } @@ -285,18 +364,18 @@ const char *nft_ctx_get_error_buffer(struct nft_ctx *ctx) EXPORT_SYMBOL(nft_ctx_free); void nft_ctx_free(struct nft_ctx *ctx) { - if (ctx->nf_sock) - mnl_socket_close(ctx->nf_sock); + mnl_socket_close(ctx->nf_sock); exit_cookie(&ctx->output.output_cookie); exit_cookie(&ctx->output.error_cookie); iface_cache_release(); - cache_release(&ctx->cache); + nft_cache_release(&ctx->cache); + nft_ctx_clear_vars(ctx); nft_ctx_clear_include_paths(ctx); scope_free(ctx->top_scope); - xfree(ctx->state); + free(ctx->state); nft_exit(ctx); - xfree(ctx); + free(ctx); } EXPORT_SYMBOL(nft_ctx_set_output); @@ -337,6 +416,34 @@ void nft_ctx_set_dry_run(struct nft_ctx *ctx, bool dry) ctx->check = dry; } +EXPORT_SYMBOL(nft_ctx_get_optimize); +uint32_t nft_ctx_get_optimize(struct nft_ctx *ctx) +{ + return ctx->optimize_flags; +} + +EXPORT_SYMBOL(nft_ctx_set_optimize); +void nft_ctx_set_optimize(struct nft_ctx *ctx, uint32_t flags) +{ + ctx->optimize_flags = flags; +} + +EXPORT_SYMBOL(nft_ctx_input_get_flags); +unsigned int nft_ctx_input_get_flags(struct nft_ctx *ctx) +{ + return ctx->input.flags; +} + +EXPORT_SYMBOL(nft_ctx_input_set_flags); +unsigned int nft_ctx_input_set_flags(struct nft_ctx *ctx, unsigned int flags) +{ + unsigned int old_flags; + + old_flags = ctx->input.flags; + ctx->input.flags = flags; + return old_flags; +} + EXPORT_SYMBOL(nft_ctx_output_get_flags); unsigned int nft_ctx_output_get_flags(struct nft_ctx *ctx) { @@ -366,13 +473,14 @@ static const struct input_descriptor indesc_cmdline = { }; static int nft_parse_bison_buffer(struct nft_ctx *nft, const char *buf, - struct list_head *msgs, struct list_head *cmds) + struct list_head *msgs, struct list_head *cmds, + const struct input_descriptor *indesc) { int ret; parser_init(nft, nft->state, msgs, cmds, nft->top_scope); nft->scanner = scanner_init(nft->state); - scanner_push_buffer(nft->scanner, &indesc_cmdline, buf); + scanner_push_buffer(nft->scanner, indesc, buf); ret = nft_parse(nft, nft->scanner, nft->state); if (ret != 0 || nft->state->nerrs > 0) @@ -381,11 +489,42 @@ static int nft_parse_bison_buffer(struct nft_ctx *nft, const char *buf, return 0; } +static char *stdin_to_buffer(void) +{ + unsigned int bufsiz = 16384, consumed = 0; + int numbytes; + char *buf; + + buf = xmalloc(bufsiz); + + numbytes = read(STDIN_FILENO, buf, bufsiz); + while (numbytes > 0) { + consumed += numbytes; + if (consumed == bufsiz) { + bufsiz *= 2; + buf = xrealloc(buf, bufsiz); + } + numbytes = read(STDIN_FILENO, buf + consumed, bufsiz - consumed); + } + buf[consumed] = '\0'; + + return buf; +} + +static const struct input_descriptor indesc_stdin = { + .type = INDESC_STDIN, + .name = "/dev/stdin", +}; + static int nft_parse_bison_filename(struct nft_ctx *nft, const char *filename, struct list_head *msgs, struct list_head *cmds) { int ret; + if (nft->stdin_buf) + return nft_parse_bison_buffer(nft, nft->stdin_buf, msgs, cmds, + &indesc_stdin); + parser_init(nft, nft->state, msgs, cmds, nft->top_scope); nft->scanner = scanner_init(nft->state); if (scanner_read_file(nft, filename, &internal_location) < 0) @@ -401,29 +540,47 @@ static int nft_parse_bison_filename(struct nft_ctx *nft, const char *filename, static int nft_evaluate(struct nft_ctx *nft, struct list_head *msgs, struct list_head *cmds) { + struct nft_cache_filter *filter; + struct cmd *cmd, *next; unsigned int flags; - struct cmd *cmd; + int err = 0; - flags = cache_evaluate(nft, cmds); - if (cache_update(nft, flags, msgs) < 0) + filter = nft_cache_filter_init(); + if (nft_cache_evaluate(nft, cmds, msgs, filter, &flags) < 0) { + nft_cache_filter_fini(filter); return -1; + } + if (nft_cache_update(nft, flags, msgs, filter) < 0) { + nft_cache_filter_fini(filter); + return -1; + } + + nft_cache_filter_fini(filter); list_for_each_entry(cmd, cmds, list) { + if (cmd->op != CMD_ADD && + cmd->op != CMD_CREATE) + continue; + + nft_cmd_expand(cmd); + } + + list_for_each_entry_safe(cmd, next, cmds, list) { struct eval_ctx ectx = { .nft = nft, .msgs = msgs, }; + if (cmd_evaluate(&ectx, cmd) < 0 && - ++nft->state->nerrs == nft->parser_max_errors) - return -1; + ++nft->state->nerrs == nft->parser_max_errors) { + err = -1; + break; + } } - if (nft->state->nerrs) + if (err < 0 || nft->state->nerrs) return -1; - list_for_each_entry(cmd, cmds, list) - nft_cmd_expand(cmd); - return 0; } @@ -439,23 +596,29 @@ int nft_run_cmd_from_buffer(struct nft_ctx *nft, const char *buf) nlbuf = xzalloc(strlen(buf) + 2); sprintf(nlbuf, "%s\n", buf); - if (nft_output_json(&nft->output)) + if (nft_output_json(&nft->output) || nft_input_json(&nft->input)) rc = nft_parse_json_buffer(nft, nlbuf, &msgs, &cmds); if (rc == -EINVAL) - rc = nft_parse_bison_buffer(nft, nlbuf, &msgs, &cmds); + rc = nft_parse_bison_buffer(nft, nlbuf, &msgs, &cmds, + &indesc_cmdline); parser_rc = rc; rc = nft_evaluate(nft, &msgs, &cmds); - if (rc < 0) + if (rc < 0) { + if (errno == EPERM) { + fprintf(stderr, "%s (you must be root)\n", + strerror(errno)); + } goto err; + } if (parser_rc) { rc = parser_rc; goto err; } - if (nft_netlink(nft, &cmds, &msgs, nft->nf_sock) != 0) + if (nft_netlink(nft, &cmds, &msgs) != 0) rc = -1; err: erec_print_list(&nft->output, &msgs, nft->debug_mask); @@ -474,30 +637,110 @@ err: nft_output_json(&nft->output) && nft_output_echo(&nft->output)) json_print_echo(nft); - if (rc) - cache_release(&nft->cache); + + if (rc || nft->check) + nft_cache_release(&nft->cache); + return rc; } -EXPORT_SYMBOL(nft_run_cmd_from_filename); -int nft_run_cmd_from_filename(struct nft_ctx *nft, const char *filename) +static int load_cmdline_vars(struct nft_ctx *ctx, struct list_head *msgs) { + unsigned int bufsize, ret, i, offset = 0; + LIST_HEAD(cmds); + char *buf; + int rc; + + if (ctx->num_vars == 0) + return 0; + + bufsize = 1024; + buf = xzalloc(bufsize + 1); + for (i = 0; i < ctx->num_vars; i++) { +retry: + ret = snprintf(buf + offset, bufsize - offset, + "define %s=%s; ", + ctx->vars[i].key, ctx->vars[i].value); + if (ret >= bufsize - offset) { + bufsize *= 2; + buf = xrealloc(buf, bufsize + 1); + goto retry; + } + offset += ret; + } + snprintf(buf + offset, bufsize - offset, "\n"); + + rc = nft_parse_bison_buffer(ctx, buf, msgs, &cmds, &indesc_cmdline); + + assert(list_empty(&cmds)); + /* Stash the buffer that contains the variable definitions and zap the + * list of input descriptors before releasing the scanner state, + * otherwise error reporting path walks over released objects. + */ + ctx->vars_ctx.buf = buf; + list_splice_init(&ctx->state->indesc_list, &ctx->vars_ctx.indesc_list); + scanner_destroy(ctx); + ctx->scanner = NULL; + + return rc; +} + +/* need to use stat() to, fopen() will block for named fifos and + * libjansson makes no checks before or after open either. + * /dev/stdin is *never* used, read() from STDIN_FILENO is used instead. + */ +static struct error_record *filename_is_useable(struct nft_ctx *nft, const char *name) +{ + unsigned int type; + struct stat sb; + int err; + + if (!strcmp(name, "/dev/stdin")) + return NULL; + + err = stat(name, &sb); + if (err) + return error(&internal_location, "Could not open file \"%s\": %s\n", + name, strerror(errno)); + + type = sb.st_mode & S_IFMT; + + if (type == S_IFREG || type == S_IFIFO) + return NULL; + + return error(&internal_location, "Not a regular file: \"%s\"\n", name); +} + +static int __nft_run_cmd_from_filename(struct nft_ctx *nft, const char *filename) +{ + struct error_record *erec; struct cmd *cmd, *next; int rc, parser_rc; LIST_HEAD(msgs); LIST_HEAD(cmds); - if (!strcmp(filename, "-")) - filename = "/dev/stdin"; + erec = filename_is_useable(nft, filename); + if (erec) { + erec_print(&nft->output, erec, nft->debug_mask); + erec_destroy(erec); + return -1; + } + + rc = load_cmdline_vars(nft, &msgs); + if (rc < 0) + goto err; rc = -EINVAL; - if (nft_output_json(&nft->output)) + if (nft_output_json(&nft->output) || nft_input_json(&nft->input)) rc = nft_parse_json_filename(nft, filename, &msgs, &cmds); if (rc == -EINVAL) rc = nft_parse_bison_filename(nft, filename, &msgs, &cmds); parser_rc = rc; + if (nft->optimize_flags) + nft_optimize(nft, &cmds); + rc = nft_evaluate(nft, &msgs, &cmds); if (rc < 0) goto err; @@ -507,7 +750,7 @@ int nft_run_cmd_from_filename(struct nft_ctx *nft, const char *filename) goto err; } - if (nft_netlink(nft, &cmds, &msgs, nft->nf_sock) != 0) + if (nft_netlink(nft, &cmds, &msgs) != 0) rc = -1; err: erec_print_list(&nft->output, &msgs, nft->debug_mask); @@ -520,12 +763,89 @@ err: scanner_destroy(nft); nft->scanner = NULL; } + if (!list_empty(&nft->vars_ctx.indesc_list)) { + struct input_descriptor *indesc, *next; + + list_for_each_entry_safe(indesc, next, &nft->vars_ctx.indesc_list, list) { + if (indesc->name) + free_const(indesc->name); + + free(indesc); + } + } + free_const(nft->vars_ctx.buf); if (!rc && nft_output_json(&nft->output) && nft_output_echo(&nft->output)) json_print_echo(nft); - if (rc) - cache_release(&nft->cache); + + if (rc || nft->check) + nft_cache_release(&nft->cache); + + scope_release(nft->state->scopes[0]); + return rc; } + +static int nft_run_optimized_file(struct nft_ctx *nft, const char *filename) +{ + uint32_t optimize_flags; + bool check; + int ret; + + check = nft->check; + nft->check = true; + optimize_flags = nft->optimize_flags; + nft->optimize_flags = 0; + + /* First check the original ruleset loads fine as is. */ + ret = __nft_run_cmd_from_filename(nft, filename); + if (ret < 0) + return ret; + + nft->check = check; + nft->optimize_flags = optimize_flags; + + return __nft_run_cmd_from_filename(nft, filename); +} + +static int nft_ctx_add_basedir_include_path(struct nft_ctx *nft, + const char *filename) +{ + char *basedir = xstrdup(filename); + int ret; + + ret = nft_ctx_add_include_path(nft, dirname(basedir)); + + free(basedir); + + return ret; +} + +EXPORT_SYMBOL(nft_run_cmd_from_filename); +int nft_run_cmd_from_filename(struct nft_ctx *nft, const char *filename) +{ + int ret; + + if (!strcmp(filename, "-")) + filename = "/dev/stdin"; + + if (!strcmp(filename, "/dev/stdin")) + nft->stdin_buf = stdin_to_buffer(); + + if (!nft->stdin_buf && + nft_ctx_add_basedir_include_path(nft, filename) < 0) + return -1; + + if (nft->optimize_flags) { + ret = nft_run_optimized_file(nft, filename); + free_const(nft->stdin_buf); + return ret; + } + + ret = __nft_run_cmd_from_filename(nft, filename); + free_const(nft->stdin_buf); + + return ret; +} diff --git a/src/libnftables.map b/src/libnftables.map index 955af380..9369f44f 100644 --- a/src/libnftables.map +++ b/src/libnftables.map @@ -1,7 +1,7 @@ LIBNFTABLES_1 { global: nft_ctx_add_include_path; - nft_ctx_clear_include_pat; + nft_ctx_clear_include_paths; nft_ctx_new; nft_ctx_buffer_output; nft_ctx_unbuffer_output; @@ -23,3 +23,18 @@ global: local: *; }; + +LIBNFTABLES_2 { + nft_ctx_add_var; + nft_ctx_clear_vars; +} LIBNFTABLES_1; + +LIBNFTABLES_3 { + nft_ctx_set_optimize; + nft_ctx_get_optimize; +} LIBNFTABLES_2; + +LIBNFTABLES_4 { + nft_ctx_input_get_flags; + nft_ctx_input_set_flags; +} LIBNFTABLES_3; @@ -8,12 +8,12 @@ * Development of this code funded by Astaro AG (http://www.astaro.com/) */ -#include <stdlib.h> +#include <nft.h> + #include <stddef.h> #include <unistd.h> #include <stdio.h> #include <errno.h> -#include <string.h> #include <getopt.h> #include <fcntl.h> #include <sys/types.h> @@ -24,11 +24,48 @@ static struct nft_ctx *nft; +enum opt_indices { + /* General options */ + IDX_HELP, + IDX_VERSION, + IDX_VERSION_LONG, + /* Ruleset input handling */ + IDX_FILE, +#define IDX_RULESET_INPUT_START IDX_FILE + IDX_DEFINE, + IDX_INTERACTIVE, + IDX_INCLUDEPATH, + IDX_CHECK, + IDX_OPTIMIZE, +#define IDX_RULESET_INPUT_END IDX_OPTIMIZE + /* Ruleset list formatting */ + IDX_HANDLE, +#define IDX_RULESET_LIST_START IDX_HANDLE + IDX_STATELESS, + IDX_TERSE, + IDX_SERVICE, + IDX_REVERSEDNS, + IDX_GUID, + IDX_NUMERIC, + IDX_NUMERIC_PRIO, + IDX_NUMERIC_PROTO, + IDX_NUMERIC_TIME, +#define IDX_RULESET_LIST_END IDX_NUMERIC_TIME + /* Command output formatting */ + IDX_ECHO, +#define IDX_CMD_OUTPUT_START IDX_ECHO + IDX_JSON, + IDX_DEBUG, +#define IDX_CMD_OUTPUT_END IDX_DEBUG +}; + enum opt_vals { OPT_HELP = 'h', OPT_VERSION = 'v', + OPT_VERSION_LONG = 'V', OPT_CHECK = 'c', OPT_FILE = 'f', + OPT_DEFINE = 'D', OPT_INTERACTIVE = 'i', OPT_INCLUDEPATH = 'I', OPT_JSON = 'j', @@ -44,124 +81,196 @@ enum opt_vals { OPT_NUMERIC_PROTO = 'p', OPT_NUMERIC_TIME = 'T', OPT_TERSE = 't', + OPT_OPTIMIZE = 'o', OPT_INVALID = '?', }; -#define OPTSTRING "+hvd:cf:iI:jvnsNaeSupypTt" -static const struct option options[] = { - { - .name = "help", - .val = OPT_HELP, - }, - { - .name = "version", - .val = OPT_VERSION, - }, - { - .name = "check", - .val = OPT_CHECK, - }, - { - .name = "file", - .val = OPT_FILE, - .has_arg = 1, - }, - { - .name = "interactive", - .val = OPT_INTERACTIVE, - }, - { - .name = "numeric", - .val = OPT_NUMERIC, - }, - { - .name = "stateless", - .val = OPT_STATELESS, - }, - { - .name = "reversedns", - .val = OPT_IP2NAME, - }, - { - .name = "service", - .val = OPT_SERVICE, - }, - { - .name = "includepath", - .val = OPT_INCLUDEPATH, - .has_arg = 1, - }, - { - .name = "debug", - .val = OPT_DEBUG, - .has_arg = 1, - }, - { - .name = "handle", - .val = OPT_HANDLE_OUTPUT, - }, - { - .name = "echo", - .val = OPT_ECHO, - }, - { - .name = "json", - .val = OPT_JSON, - }, - { - .name = "guid", - .val = OPT_GUID, - }, - { - .name = "numeric-priority", - .val = OPT_NUMERIC_PRIO, - }, - { - .name = "numeric-protocol", - .val = OPT_NUMERIC_PROTO, - }, - { - .name = "numeric-time", - .val = OPT_NUMERIC_TIME, - }, - { - .name = "terse", - .val = OPT_TERSE, - }, - { - .name = NULL - } +struct nft_opt { + const char *name; + enum opt_vals val; + const char *arg; + const char *help; }; +#define NFT_OPT(n, v, a, h) \ + (struct nft_opt) { .name = n, .val = v, .arg = a, .help = h } + +static const struct nft_opt nft_options[] = { + [IDX_HELP] = NFT_OPT("help", OPT_HELP, NULL, + "Show this help"), + [IDX_VERSION] = NFT_OPT("version", OPT_VERSION, NULL, + "Show version information"), + [IDX_VERSION_LONG] = NFT_OPT(NULL, OPT_VERSION_LONG, NULL, + "Show extended version information"), + [IDX_FILE] = NFT_OPT("file", OPT_FILE, "<filename>", + "Read input from <filename>"), + [IDX_DEFINE] = NFT_OPT("define", OPT_DEFINE, "<name=value>", + "Define variable, e.g. --define foo=1.2.3.4"), + [IDX_INTERACTIVE] = NFT_OPT("interactive", OPT_INTERACTIVE, NULL, + "Read input from interactive CLI"), + [IDX_INCLUDEPATH] = NFT_OPT("includepath", OPT_INCLUDEPATH, "<directory>", + "Add <directory> to the paths searched for include files. Default is: " DEFAULT_INCLUDE_PATH), + [IDX_CHECK] = NFT_OPT("check", OPT_CHECK, NULL, + "Check commands validity without actually applying the changes."), + [IDX_HANDLE] = NFT_OPT("handle", OPT_HANDLE_OUTPUT, NULL, + "Output rule handle."), + [IDX_STATELESS] = NFT_OPT("stateless", OPT_STATELESS, NULL, + "Omit stateful information of ruleset."), + [IDX_TERSE] = NFT_OPT("terse", OPT_TERSE, NULL, + "Omit contents of sets."), + [IDX_SERVICE] = NFT_OPT("service", OPT_SERVICE, NULL, + "Translate ports to service names as described in /etc/services."), + [IDX_REVERSEDNS] = NFT_OPT("reversedns", OPT_IP2NAME, NULL, + "Translate IP addresses to names."), + [IDX_GUID] = NFT_OPT("guid", OPT_GUID, NULL, + "Print UID/GID as defined in /etc/passwd and /etc/group."), + [IDX_NUMERIC] = NFT_OPT("numeric", OPT_NUMERIC, NULL, + "Print fully numerical output."), + [IDX_NUMERIC_PRIO] = NFT_OPT("numeric-priority", OPT_NUMERIC_PRIO, NULL, + "Print chain priority numerically."), + [IDX_NUMERIC_PROTO] = NFT_OPT("numeric-protocol", OPT_NUMERIC_PROTO, NULL, + "Print layer 4 protocols numerically."), + [IDX_NUMERIC_TIME] = NFT_OPT("numeric-time", OPT_NUMERIC_TIME, NULL, + "Print time values numerically."), + [IDX_ECHO] = NFT_OPT("echo", OPT_ECHO, NULL, + "Echo what has been added, inserted or replaced."), + [IDX_JSON] = NFT_OPT("json", OPT_JSON, NULL, + "Format output in JSON"), + [IDX_DEBUG] = NFT_OPT("debug", OPT_DEBUG, "<level [,level...]>", + "Specify debugging level (scanner, parser, eval, netlink, mnl, proto-ctx, segtree, all)"), + [IDX_OPTIMIZE] = NFT_OPT("optimize", OPT_OPTIMIZE, NULL, + "Optimize ruleset"), +}; + +#define NR_NFT_OPTIONS (sizeof(nft_options) / sizeof(nft_options[0])) + +static const char *get_optstring(void) +{ + static char optstring[2 * NR_NFT_OPTIONS + 2]; + + if (!optstring[0]) { + size_t i, j; + + optstring[0] = '+'; + for (i = 0, j = 1; i < NR_NFT_OPTIONS && j < sizeof(optstring); i++) + j += snprintf(optstring + j, sizeof(optstring) - j, "%c%s", + nft_options[i].val, + nft_options[i].arg ? ":" : ""); + + assert(j < sizeof(optstring)); + } + return optstring; +} + +static const struct option *get_options(void) +{ + static struct option options[NR_NFT_OPTIONS + 1]; + + if (!options[0].name) { + size_t i, j; + + for (i = 0, j = 0; i < NR_NFT_OPTIONS; ++i) { + if (nft_options[i].name) { + options[j].name = nft_options[i].name; + options[j].val = nft_options[i].val; + options[j].has_arg = nft_options[i].arg != NULL; + j++; + } + } + } + return options; +} + +static void print_option(const struct nft_opt *opt) +{ + char optbuf[35] = ""; + int i; + + i = snprintf(optbuf, sizeof(optbuf), " -%c", opt->val); + if (opt->name) + i += snprintf(optbuf + i, sizeof(optbuf) - i, ", --%s", + opt->name); + if (opt->arg) + i += snprintf(optbuf + i, sizeof(optbuf) - i, " %s", opt->arg); + + printf("%-34s%s\n", optbuf, opt->help); +} + static void show_help(const char *name) { - printf( -"Usage: %s [ options ] [ cmds... ]\n" -"\n" -"Options:\n" -" -h, --help Show this help\n" -" -v, --version Show version information\n" -"\n" -" -c, --check Check commands validity without actually applying the changes.\n" -" -f, --file <filename> Read input from <filename>\n" -" -i, --interactive Read input from interactive CLI\n" -"\n" -" -j, --json Format output in JSON\n" -" -n, --numeric Print fully numerical output.\n" -" -s, --stateless Omit stateful information of ruleset.\n" -" -t, --terse Omit contents of sets.\n" -" -u, --guid Print UID/GID as defined in /etc/passwd and /etc/group.\n" -" -N Translate IP addresses to names.\n" -" -S, --service Translate ports to service names as described in /etc/services.\n" -" -p, --numeric-protocol Print layer 4 protocols numerically.\n" -" -y, --numeric-priority Print chain priority numerically.\n" -" -T, --numeric-time Print time values numerically.\n" -" -a, --handle Output rule handle.\n" -" -e, --echo Echo what has been added, inserted or replaced.\n" -" -I, --includepath <directory> Add <directory> to the paths searched for include files. Default is: %s\n" -" --debug <level [,level...]> Specify debugging level (scanner, parser, eval, netlink, mnl, proto-ctx, segtree, all)\n" -"\n", - name, DEFAULT_INCLUDE_PATH); + int i; + + printf("Usage: %s [ options ] [ cmds... ]\n" + "\n" + "Options (general):\n", name); + + print_option(&nft_options[IDX_HELP]); + print_option(&nft_options[IDX_VERSION]); + print_option(&nft_options[IDX_VERSION_LONG]); + + printf("\n" + "Options (ruleset input handling):" + "\n"); + + for (i = IDX_RULESET_INPUT_START; i <= IDX_RULESET_INPUT_END; i++) + print_option(&nft_options[i]); + + printf("\n" + "Options (ruleset list formatting):" + "\n"); + + for (i = IDX_RULESET_LIST_START; i <= IDX_RULESET_LIST_END; i++) + print_option(&nft_options[i]); + + printf("\n" + "Options (command output formatting):" + "\n"); + + for (i = IDX_CMD_OUTPUT_START; i <= IDX_CMD_OUTPUT_END; i++) + print_option(&nft_options[i]); + + fputs("\n", stdout); +} + +static void show_version(void) +{ + const char *cli, *minigmp, *json, *xt; + +#if defined(HAVE_LIBREADLINE) + cli = "readline"; +#elif defined(HAVE_LIBEDIT) + cli = "editline"; +#elif defined(HAVE_LIBLINENOISE) + cli = "linenoise"; +#else + cli = "no"; +#endif + +#if defined(HAVE_MINIGMP) + minigmp = "yes"; +#else + minigmp = "no"; +#endif + +#if defined(HAVE_JSON) + json = "yes"; +#else + json = "no"; +#endif + +#if defined(HAVE_XTABLES) + xt = "yes"; +#else + xt = "no"; +#endif + + printf("%s v%s (%s)\n" + " cli: %s\n" + " json: %s\n" + " minigmp: %s\n" + " libxtables: %s\n", + PACKAGE_NAME, PACKAGE_VERSION, RELEASE_NAME, + cli, json, minigmp, xt); } static const struct { @@ -216,6 +325,7 @@ static bool nft_options_check(int argc, char * const argv[]) { bool skip = false, nonoption = false; int pos = 0, i; + size_t j; for (i = 1; i < argc; i++) { pos += strlen(argv[i - 1]) + 1; @@ -224,21 +334,22 @@ static bool nft_options_check(int argc, char * const argv[]) } else if (skip) { skip = false; continue; - } else if (argv[i][0] == '-') { - if (nonoption) { - nft_options_error(argc, argv, pos); - return false; - } else if (argv[i][1] == 'd' || - argv[i][1] == 'I' || - argv[i][1] == 'f' || - !strcmp(argv[i], "--debug") || - !strcmp(argv[i], "--includepath") || - !strcmp(argv[i], "--file")) { - skip = true; - continue; - } } else if (argv[i][0] != '-') { nonoption = true; + continue; + } + if (nonoption) { + nft_options_error(argc, argv, pos); + return false; + } + for (j = 0; j < NR_NFT_OPTIONS; j++) { + if (nft_options[j].arg && + (argv[i][1] == (char)nft_options[j].val || + (argv[i][1] == '-' && + !strcmp(argv[i] + 2, nft_options[j].name)))) { + skip = true; + break; + } } } @@ -247,12 +358,18 @@ static bool nft_options_check(int argc, char * const argv[]) int main(int argc, char * const *argv) { - char *buf = NULL, *filename = NULL; + const struct option *options = get_options(); + bool interactive = false, define = false; + const char *optstring = get_optstring(); unsigned int output_flags = 0; - bool interactive = false; + int i, val, rc = EXIT_SUCCESS; unsigned int debug_mask; + char *filename = NULL; unsigned int len; - int i, val, rc; + + /* nftables cannot be used with setuid in a safe way. */ + if (getuid() != geteuid()) + _exit(111); if (!nft_options_check(argc, argv)) exit(EXIT_FAILURE); @@ -260,33 +377,54 @@ int main(int argc, char * const *argv) nft = nft_ctx_new(NFT_CTX_DEFAULT); while (1) { - val = getopt_long(argc, argv, OPTSTRING, options, NULL); + val = getopt_long(argc, argv, optstring, options, NULL); if (val == -1) break; switch (val) { case OPT_HELP: show_help(argv[0]); - exit(EXIT_SUCCESS); + goto out; case OPT_VERSION: printf("%s v%s (%s)\n", PACKAGE_NAME, PACKAGE_VERSION, RELEASE_NAME); - exit(EXIT_SUCCESS); + goto out; + case OPT_VERSION_LONG: + show_version(); + goto out; + case OPT_DEFINE: + if (nft_ctx_add_var(nft, optarg)) { + fprintf(stderr, + "Error: Failed to define variable '%s'\n", + optarg); + goto out_fail; + } + define = true; + break; case OPT_CHECK: nft_ctx_set_dry_run(nft, true); break; case OPT_FILE: + if (interactive) { + fprintf(stderr, + "Error: -i/--interactive and -f/--file options cannot be combined\n"); + goto out_fail; + } filename = optarg; break; case OPT_INTERACTIVE: + if (filename) { + fprintf(stderr, + "Error: -i/--interactive and -f/--file options cannot be combined\n"); + goto out_fail; + } interactive = true; break; case OPT_INCLUDEPATH: if (nft_ctx_add_include_path(nft, optarg)) { fprintf(stderr, - "Failed to add include path '%s'\n", + "Warning: Cannot include path '%s'\n", optarg); - exit(EXIT_FAILURE); } break; case OPT_NUMERIC: @@ -319,9 +457,9 @@ int main(int argc, char * const *argv) } if (i == array_size(debug_param)) { - fprintf(stderr, "invalid debug parameter `%s'\n", + fprintf(stderr, "Error: invalid debug parameter `%s'\n", optarg); - exit(EXIT_FAILURE); + goto out_fail; } if (end == NULL) @@ -340,8 +478,8 @@ int main(int argc, char * const *argv) #ifdef HAVE_LIBJANSSON output_flags |= NFT_CTX_OUTPUT_JSON; #else - fprintf(stderr, "JSON support not compiled-in\n"); - exit(EXIT_FAILURE); + fprintf(stderr, "Error: JSON support not compiled-in\n"); + goto out_fail; #endif break; case OPT_GUID: @@ -359,14 +497,24 @@ int main(int argc, char * const *argv) case OPT_TERSE: output_flags |= NFT_CTX_OUTPUT_TERSE; break; + case OPT_OPTIMIZE: + nft_ctx_set_optimize(nft, 0x1); + break; case OPT_INVALID: - exit(EXIT_FAILURE); + goto out_fail; } } + if (!filename && define) { + fprintf(stderr, "Error: -D/--define can only be used with -f/--filename\n"); + goto out_fail; + } + nft_ctx_output_set_flags(nft, output_flags); if (optind != argc) { + char *buf; + for (len = 0, i = optind; i < argc; i++) len += strlen(argv[i]) + strlen(" "); @@ -374,7 +522,7 @@ int main(int argc, char * const *argv) if (buf == NULL) { fprintf(stderr, "%s:%u: Memory allocation failure\n", __FILE__, __LINE__); - exit(EXIT_FAILURE); + goto out_fail; } for (i = optind; i < argc; i++) { strcat(buf, argv[i]); @@ -382,22 +530,24 @@ int main(int argc, char * const *argv) strcat(buf, " "); } rc = !!nft_run_cmd_from_buffer(nft, buf); + free(buf); } else if (filename != NULL) { rc = !!nft_run_cmd_from_filename(nft, filename); } else if (interactive) { if (cli_init(nft) < 0) { fprintf(stderr, "%s: interactive CLI not supported in this build\n", argv[0]); - exit(EXIT_FAILURE); + goto out_fail; } - return EXIT_SUCCESS; } else { fprintf(stderr, "%s: no command specified\n", argv[0]); - exit(EXIT_FAILURE); + goto out_fail; } - free(buf); +out: nft_ctx_free(nft); - return rc; +out_fail: + nft_ctx_free(nft); + return EXIT_FAILURE; } diff --git a/src/mergesort.c b/src/mergesort.c index 649b7806..bd1c2187 100644 --- a/src/mergesort.c +++ b/src/mergesort.c @@ -2,53 +2,77 @@ * Copyright (c) 2017 Elise Lennion <elise.lennion@gmail.com> * * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. + * it under the terms of the GNU General Public License version 2 (or any + * later) as published by the Free Software Foundation. */ -#include <stdint.h> +#include <nft.h> + #include <expression.h> #include <gmputil.h> #include <list.h> -static int expr_msort_cmp(const struct expr *e1, const struct expr *e2); - -static int concat_expr_msort_cmp(const struct expr *e1, const struct expr *e2) +static void concat_expr_msort_value(const struct expr *expr, mpz_t value) { - struct list_head *l = (&e2->expressions)->next; - const struct expr *i1, *i2; - int ret; - - list_for_each_entry(i1, &e1->expressions, list) { - i2 = list_entry(l, typeof(struct expr), list); - - ret = expr_msort_cmp(i1, i2); - if (ret) - return ret; - - l = l->next; + unsigned int len = 0, ilen; + const struct expr *i; + char data[512]; + + list_for_each_entry(i, &expr_concat(expr)->expressions, list) { + ilen = div_round_up(i->len, BITS_PER_BYTE); + mpz_export_data(data + len, i->value, i->byteorder, ilen); + len += ilen; } - return false; + mpz_import_data(value, data, BYTEORDER_HOST_ENDIAN, len); } -static int expr_msort_cmp(const struct expr *e1, const struct expr *e2) +static mpz_srcptr expr_msort_value(const struct expr *expr, mpz_t value) { - switch (e1->etype) { + switch (expr->etype) { case EXPR_SET_ELEM: - return expr_msort_cmp(e1->key, e2->key); + return expr_msort_value(expr->key, value); + case EXPR_BINOP: + case EXPR_MAPPING: + case EXPR_RANGE: + return expr_msort_value(expr->left, value); case EXPR_VALUE: - return mpz_cmp(e1->value, e2->value); + return expr->value; + case EXPR_RANGE_VALUE: + return expr->range.low; case EXPR_CONCAT: - return concat_expr_msort_cmp(e1, e2); - case EXPR_MAPPING: - return expr_msort_cmp(e1->left, e2->left); + concat_expr_msort_value(expr, value); + break; + case EXPR_SET_ELEM_CATCHALL: + /* max value to ensure listing shows it in the last position */ + mpz_bitmask(value, expr->len); + break; default: - BUG("Unknown expression %s\n", expr_name(e1)); + BUG("Unknown expression %s\n", expr_name(expr)); } + return value; +} + +static int expr_msort_cmp(const struct expr *e1, const struct expr *e2) +{ + mpz_srcptr value1; + mpz_srcptr value2; + mpz_t value1_tmp; + mpz_t value2_tmp; + int ret; + + mpz_init(value1_tmp); + mpz_init(value2_tmp); + value1 = expr_msort_value(e1, value1_tmp); + value2 = expr_msort_value(e2, value2_tmp); + ret = mpz_cmp(value1, value2); + mpz_clear(value1_tmp); + mpz_clear(value2_tmp); + + return ret; } -static void list_splice_sorted(struct list_head *list, struct list_head *head) +void list_splice_sorted(struct list_head *list, struct list_head *head) { struct list_head *h = head->next; struct list_head *l = list->next; @@ -56,7 +80,7 @@ static void list_splice_sorted(struct list_head *list, struct list_head *head) while (l != list) { if (h == head || expr_msort_cmp(list_entry(l, typeof(struct expr), list), - list_entry(h, typeof(struct expr), list)) < 0) { + list_entry(h, typeof(struct expr), list)) <= 0) { l = l->next; list_add_tail(l->prev, h); continue; @@ -10,13 +10,12 @@ * Development of this code funded by Astaro AG (http://www.astaro.com/) */ +#include <nft.h> + #include <errno.h> #include <limits.h> #include <stddef.h> -#include <stdlib.h> #include <stdio.h> -#include <stdint.h> -#include <string.h> #include <net/if.h> #include <net/if_arp.h> #include <pwd.h> @@ -25,6 +24,7 @@ #include <linux/netfilter.h> #include <linux/pkt_sched.h> #include <linux/if_packet.h> +#include <time.h> #include <nftables.h> #include <expression.h> @@ -37,10 +37,6 @@ #include <iface.h> #include <json.h> -#define _XOPEN_SOURCE -#define __USE_XOPEN -#include <time.h> - static void tchandle_type_print(const struct expr *expr, struct output_ctx *octx) { @@ -66,50 +62,39 @@ static struct error_record *tchandle_type_parse(struct parse_ctx *ctx, struct expr **res) { uint32_t handle; - char *str = NULL; if (strcmp(sym->identifier, "root") == 0) handle = TC_H_ROOT; else if (strcmp(sym->identifier, "none") == 0) handle = TC_H_UNSPEC; else if (strchr(sym->identifier, ':')) { - uint16_t tmp; - char *colon; - - str = xstrdup(sym->identifier); - - colon = strchr(str, ':'); - if (!colon) - goto err; - - *colon = '\0'; + char *colon, *end; + uint32_t tmp; errno = 0; - tmp = strtoull(str, NULL, 16); - if (errno != 0) + tmp = strtoul(sym->identifier, &colon, 16); + if (errno != 0 || sym->identifier == colon) goto err; - handle = (tmp << 16); - if (str[strlen(str) - 1] == ':') - goto out; + if (*colon != ':') + goto err; + handle = tmp << 16; errno = 0; - tmp = strtoull(colon + 1, NULL, 16); - if (errno != 0) + tmp = strtoul(colon + 1, &end, 16); + if (errno != 0 || *end) goto err; handle |= tmp; } else { handle = strtoull(sym->identifier, NULL, 0); } -out: - xfree(str); + *res = constant_expr_alloc(&sym->location, sym->dtype, BYTEORDER_HOST_ENDIAN, sizeof(handle) * BITS_PER_BYTE, &handle); return NULL; err: - xfree(str); return error(&sym->location, "Could not parse %s", sym->dtype->desc); } @@ -220,18 +205,20 @@ static struct error_record *uid_type_parse(struct parse_ctx *ctx, struct expr **res) { struct passwd *pw; - uint64_t uid; + uid_t uid; char *endptr = NULL; pw = getpwnam(sym->identifier); if (pw != NULL) uid = pw->pw_uid; else { - uid = strtoull(sym->identifier, &endptr, 10); - if (uid > UINT32_MAX) + uint64_t _uid = strtoull(sym->identifier, &endptr, 10); + + if (_uid > UINT32_MAX) return error(&sym->location, "Value too large"); else if (*endptr) return error(&sym->location, "User does not exist"); + uid = _uid; } *res = constant_expr_alloc(&sym->location, sym->dtype, @@ -274,18 +261,20 @@ static struct error_record *gid_type_parse(struct parse_ctx *ctx, struct expr **res) { struct group *gr; - uint64_t gid; + gid_t gid; char *endptr = NULL; gr = getgrnam(sym->identifier); if (gr != NULL) gid = gr->gr_gid; else { - gid = strtoull(sym->identifier, &endptr, 0); - if (gid > UINT32_MAX) + uint64_t _gid = strtoull(sym->identifier, &endptr, 0); + + if (_gid > UINT32_MAX) return error(&sym->location, "Value too large"); else if (*endptr) return error(&sym->location, "Group does not exist"); + gid = _gid; } *res = constant_expr_alloc(&sym->location, sym->dtype, @@ -336,7 +325,7 @@ const struct datatype pkttype_type = { void devgroup_table_init(struct nft_ctx *ctx) { - ctx->output.tbl.devgroup = rt_symbol_table_init("/etc/iproute2/group"); + ctx->output.tbl.devgroup = rt_symbol_table_init("group"); } void devgroup_table_exit(struct nft_ctx *ctx) @@ -357,17 +346,23 @@ static struct error_record *devgroup_type_parse(struct parse_ctx *ctx, return symbolic_constant_parse(ctx, sym, ctx->tbl->devgroup, res); } +static void devgroup_type_describe(struct output_ctx *octx) +{ + rt_symbol_table_describe(octx, "group", + octx->tbl.devgroup, &devgroup_type); +} + const struct datatype devgroup_type = { .type = TYPE_DEVGROUP, .name = "devgroup", .desc = "devgroup name", + .describe = devgroup_type_describe, .byteorder = BYTEORDER_HOST_ENDIAN, .size = 4 * BITS_PER_BYTE, .basetype = &integer_type, .print = devgroup_type_print, .json = devgroup_type_json, .parse = devgroup_type_parse, - .flags = DTYPE_F_PREFIX, }; const struct datatype ifname_type = { @@ -381,51 +376,43 @@ const struct datatype ifname_type = { static void date_type_print(const struct expr *expr, struct output_ctx *octx) { - uint64_t tstamp = mpz_get_uint64(expr->value); - struct tm *tm, *cur_tm; + uint64_t tstamp64 = mpz_get_uint64(expr->value); char timestr[21]; + time_t tstamp; + struct tm tm; /* Convert from nanoseconds to seconds */ - tstamp /= 1000000000L; - - if (!nft_output_seconds(octx)) { - /* Obtain current tm, to add tm_gmtoff to the timestamp */ - cur_tm = localtime((time_t *) &tstamp); - - if (cur_tm) - tstamp += cur_tm->tm_gmtoff; + tstamp64 /= 1000000000L; - if ((tm = gmtime((time_t *) &tstamp)) != NULL && - strftime(timestr, sizeof(timestr) - 1, "%F %T", tm)) - nft_print(octx, "\"%s\"", timestr); - else - nft_print(octx, "Error converting timestamp to printed time"); - - return; - } + /* Obtain current tm, to add tm_gmtoff to the timestamp */ + tstamp = tstamp64; + if (localtime_r(&tstamp, &tm)) + tstamp64 += tm.tm_gmtoff; - /* - * Do our own printing. The default print function will print in - * nanoseconds, which is ugly. - */ - nft_print(octx, "%lu", tstamp); + tstamp = tstamp64; + if (gmtime_r(&tstamp, &tm) && + strftime(timestr, sizeof(timestr) - 1, "%Y-%m-%d %T", &tm)) + nft_print(octx, "\"%s\"", timestr); + else + nft_print(octx, "Error converting timestamp to printed time"); } -static time_t parse_iso_date(const char *sym) +static bool parse_iso_date(uint64_t *tstamp, const char *sym) { - struct tm tm, *cur_tm; + struct tm cur_tm; + struct tm tm; time_t ts; memset(&tm, 0, sizeof(struct tm)); - if (strptime(sym, "%F %T", &tm)) + if (strptime(sym, "%Y-%m-%d %T", &tm)) goto success; - if (strptime(sym, "%F %R", &tm)) + if (strptime(sym, "%Y-%m-%d %R", &tm)) goto success; - if (strptime(sym, "%F", &tm)) + if (strptime(sym, "%Y-%m-%d", &tm)) goto success; - return -1; + return false; success: /* @@ -435,14 +422,17 @@ success: */ ts = timegm(&tm); - /* Obtain current tm as well (at the specified time), so that we can substract tm_gmtoff */ - cur_tm = localtime(&ts); + if (ts == (time_t) -1) + return false; - if (ts == (time_t) -1 || cur_tm == NULL) - return ts; + /* Obtain current tm as well (at the specified time), so that we can substract tm_gmtoff */ + if (!localtime_r(&ts, &cur_tm)) + return false; /* Substract tm_gmtoff to get the current time */ - return ts - cur_tm->tm_gmtoff; + *tstamp = ts - cur_tm.tm_gmtoff; + + return true; } static struct error_record *date_type_parse(struct parse_ctx *ctx, @@ -450,9 +440,9 @@ static struct error_record *date_type_parse(struct parse_ctx *ctx, struct expr **res) { const char *endptr = sym->identifier; - time_t tstamp; + uint64_t tstamp; - if ((tstamp = parse_iso_date(sym->identifier)) != -1) + if (parse_iso_date(&tstamp, sym->identifier)) goto success; tstamp = strtoul(sym->identifier, (char **) &endptr, 10); @@ -495,21 +485,21 @@ static void day_type_print(const struct expr *expr, struct output_ctx *octx) static void hour_type_print(const struct expr *expr, struct output_ctx *octx) { uint32_t seconds = mpz_get_uint32(expr->value), minutes, hours; - struct tm *cur_tm; + struct tm cur_tm; time_t ts; - if (nft_output_seconds(octx)) { - expr_basetype(expr)->print(expr, octx); - return; - } - /* Obtain current tm, so that we can add tm_gmtoff */ ts = time(NULL); - cur_tm = localtime(&ts); + if (ts != ((time_t) -1) && localtime_r(&ts, &cur_tm)) { + int32_t adj = seconds + cur_tm.tm_gmtoff; - if (cur_tm) - seconds = (seconds + cur_tm->tm_gmtoff) % SECONDS_PER_DAY; + if (adj < 0) + adj += SECONDS_PER_DAY; + else if (adj >= SECONDS_PER_DAY) + adj -= SECONDS_PER_DAY; + seconds = adj; + } minutes = seconds / 60; seconds %= 60; hours = minutes / 60; @@ -526,9 +516,12 @@ static struct error_record *hour_type_parse(struct parse_ctx *ctx, struct expr **res) { struct error_record *er; - struct tm tm, *cur_tm; - uint64_t result = 0; + struct tm cur_tm_data; + struct tm *cur_tm; + uint32_t result; + uint64_t tmp; char *endptr; + struct tm tm; time_t ts; memset(&tm, 0, sizeof(struct tm)); @@ -542,7 +535,10 @@ static struct error_record *hour_type_parse(struct parse_ctx *ctx, /* Obtain current tm, so that we can substract tm_gmtoff */ ts = time(NULL); - cur_tm = localtime(&ts); + if (ts != ((time_t) -1) && localtime_r(&ts, &cur_tm_data)) + cur_tm = &cur_tm_data; + else + cur_tm = NULL; endptr = strptime(sym->identifier, "%T", &tm); if (endptr && *endptr == '\0') @@ -555,8 +551,8 @@ static struct error_record *hour_type_parse(struct parse_ctx *ctx, if (endptr && *endptr) return error(&sym->location, "Can't parse trailing input: \"%s\"\n", endptr); - if ((er = time_parse(&sym->location, sym->identifier, &result)) == NULL) { - result /= 1000; + if ((er = time_parse(&sym->location, sym->identifier, &tmp)) == NULL) { + result = tmp / 1000; goto convert; } @@ -610,7 +606,7 @@ const struct datatype hour_type = { .name = "hour", .desc = "Hour of day of packet reception", .byteorder = BYTEORDER_HOST_ENDIAN, - .size = sizeof(uint64_t) * BITS_PER_BYTE, + .size = sizeof(uint32_t) * BITS_PER_BYTE, .basetype = &integer_type, .print = hour_type_print, .parse = hour_type_parse, @@ -706,6 +702,8 @@ const struct meta_template meta_templates[] = { [NFT_META_SDIFNAME] = META_TEMPLATE("sdifname", &ifname_type, IFNAMSIZ * BITS_PER_BYTE, BYTEORDER_HOST_ENDIAN), + [NFT_META_BRI_BROUTE] = META_TEMPLATE("broute", &integer_type, + 1 , BYTEORDER_HOST_ENDIAN), }; static bool meta_key_is_unqualified(enum nft_meta_keys key) @@ -725,12 +723,16 @@ static bool meta_key_is_unqualified(enum nft_meta_keys key) static void meta_expr_print(const struct expr *expr, struct output_ctx *octx) { - if (meta_key_is_unqualified(expr->meta.key)) - nft_print(octx, "%s", - meta_templates[expr->meta.key].token); + const char *token = "unknown"; + uint32_t key = expr->meta.key; + + if (key < array_size(meta_templates)) + token = meta_templates[key].token; + + if (meta_key_is_unqualified(key)) + nft_print(octx, "%s", token); else - nft_print(octx, "meta %s", - meta_templates[expr->meta.key].token); + nft_print(octx, "meta %s", token); } static bool meta_expr_cmp(const struct expr *e1, const struct expr *e2) @@ -742,6 +744,7 @@ static void meta_expr_clone(struct expr *new, const struct expr *expr) { new->meta.key = expr->meta.key; new->meta.base = expr->meta.base; + new->meta.inner_desc = expr->meta.inner_desc; } /** @@ -753,10 +756,11 @@ static void meta_expr_clone(struct expr *new, const struct expr *expr) * Update LL protocol context based on IIFTYPE meta match in non-LL hooks. */ static void meta_expr_pctx_update(struct proto_ctx *ctx, - const struct expr *expr) + const struct location *loc, + const struct expr *left, + const struct expr *right) { const struct hook_proto_desc *h = &hook_proto_desc[ctx->family]; - const struct expr *left = expr->left, *right = expr->right; const struct proto_desc *desc; uint8_t protonum; @@ -771,10 +775,15 @@ static void meta_expr_pctx_update(struct proto_ctx *ctx, if (desc == NULL) desc = &proto_unknown; - proto_ctx_update(ctx, PROTO_BASE_LL_HDR, &expr->location, desc); + proto_ctx_update(ctx, PROTO_BASE_LL_HDR, loc, desc); break; case NFT_META_NFPROTO: protonum = mpz_get_uint8(right->value); + if (protonum == NFPROTO_IPV4 && h->desc == &proto_ip) + break; + else if (protonum == NFPROTO_IPV6 && h->desc == &proto_ip6) + break; + desc = proto_find_upper(h->desc, protonum); if (desc == NULL) { desc = &proto_unknown; @@ -784,7 +793,7 @@ static void meta_expr_pctx_update(struct proto_ctx *ctx, desc = h->desc; } - proto_ctx_update(ctx, PROTO_BASE_NETWORK_HDR, &expr->location, desc); + proto_ctx_update(ctx, PROTO_BASE_NETWORK_HDR, loc, desc); break; case NFT_META_L4PROTO: desc = proto_find_upper(&proto_inet_service, @@ -792,7 +801,7 @@ static void meta_expr_pctx_update(struct proto_ctx *ctx, if (desc == NULL) desc = &proto_unknown; - proto_ctx_update(ctx, PROTO_BASE_TRANSPORT_HDR, &expr->location, desc); + proto_ctx_update(ctx, PROTO_BASE_TRANSPORT_HDR, loc, desc); break; case NFT_META_PROTOCOL: if (h->base != PROTO_BASE_LL_HDR) @@ -806,7 +815,7 @@ static void meta_expr_pctx_update(struct proto_ctx *ctx, if (desc == NULL) desc = &proto_unknown; - proto_ctx_update(ctx, PROTO_BASE_NETWORK_HDR, &expr->location, desc); + proto_ctx_update(ctx, PROTO_BASE_NETWORK_HDR, loc, desc); break; default: break; @@ -814,13 +823,19 @@ static void meta_expr_pctx_update(struct proto_ctx *ctx, } #define NFTNL_UDATA_META_KEY 0 -#define NFTNL_UDATA_META_MAX 1 +#define NFTNL_UDATA_META_INNER_DESC 1 +#define NFTNL_UDATA_META_MAX 2 static int meta_expr_build_udata(struct nftnl_udata_buf *udbuf, const struct expr *expr) { nftnl_udata_put_u32(udbuf, NFTNL_UDATA_META_KEY, expr->meta.key); + if (expr->meta.inner_desc) { + nftnl_udata_put_u32(udbuf, NFTNL_UDATA_META_INNER_DESC, + expr->meta.inner_desc->id); + } + return 0; } @@ -832,6 +847,7 @@ static int meta_parse_udata(const struct nftnl_udata *attr, void *data) switch (type) { case NFTNL_UDATA_META_KEY: + case NFTNL_UDATA_META_INNER_DESC: if (len != sizeof(uint32_t)) return -1; break; @@ -846,6 +862,8 @@ static int meta_parse_udata(const struct nftnl_udata *attr, void *data) static struct expr *meta_expr_parse_udata(const struct nftnl_udata *attr) { const struct nftnl_udata *ud[NFTNL_UDATA_META_MAX + 1] = {}; + const struct proto_desc *desc; + struct expr *expr; uint32_t key; int err; @@ -859,7 +877,14 @@ static struct expr *meta_expr_parse_udata(const struct nftnl_udata *attr) key = nftnl_udata_get_u32(ud[NFTNL_UDATA_META_KEY]); - return meta_expr_alloc(&internal_location, key); + expr = meta_expr_alloc(&internal_location, key); + + if (ud[NFTNL_UDATA_META_INNER_DESC]) { + desc = find_proto_desc(ud[NFTNL_UDATA_META_INNER_DESC]); + expr->meta.inner_desc = desc; + } + + return expr; } const struct expr_ops meta_expr_ops = { @@ -908,12 +933,16 @@ struct expr *meta_expr_alloc(const struct location *loc, enum nft_meta_keys key) static void meta_stmt_print(const struct stmt *stmt, struct output_ctx *octx) { + const char *token = "unknown"; + uint32_t key = stmt->meta.key; + + if (key < array_size(meta_templates)) + token = meta_templates[key].token; + if (meta_key_is_unqualified(stmt->meta.key)) - nft_print(octx, "%s set ", - meta_templates[stmt->meta.key].token); + nft_print(octx, "%s set ", token); else - nft_print(octx, "meta %s set ", - meta_templates[stmt->meta.key].token); + nft_print(octx, "meta %s set ", token); expr_print(stmt->meta.expr, octx); } @@ -923,7 +952,7 @@ static void meta_stmt_destroy(struct stmt *stmt) expr_free(stmt->meta.expr); } -static const struct stmt_ops meta_stmt_ops = { +const struct stmt_ops meta_stmt_ops = { .type = STMT_META, .name = "meta", .print = meta_stmt_print, @@ -938,8 +967,11 @@ struct stmt *meta_stmt_alloc(const struct location *loc, enum nft_meta_keys key, stmt = stmt_alloc(loc, &meta_stmt_ops); stmt->meta.key = key; - stmt->meta.tmpl = &meta_templates[key]; stmt->meta.expr = expr; + + if (key < array_size(meta_templates)) + stmt->meta.tmpl = &meta_templates[key]; + return stmt; } @@ -967,11 +999,11 @@ struct error_record *meta_key_parse(const struct location *loc, const char *str, unsigned int *value) { - int ret, len, offset = 0; const char *sep = ""; + size_t offset = 0; unsigned int i; char buf[1024]; - size_t size; + size_t len; for (i = 0; i < array_size(meta_templates); i++) { if (!meta_templates[i].token || strcmp(meta_templates[i].token, str)) @@ -994,9 +1026,10 @@ struct error_record *meta_key_parse(const struct location *loc, } len = (int)sizeof(buf); - size = sizeof(buf); for (i = 0; i < array_size(meta_templates); i++) { + int ret; + if (!meta_templates[i].token) continue; @@ -1004,8 +1037,8 @@ struct error_record *meta_key_parse(const struct location *loc, sep = ", "; ret = snprintf(buf+offset, len, "%s%s", sep, meta_templates[i].token); - SNPRINTF_BUFFER_SIZE(ret, size, len, offset); - assert(offset < (int)sizeof(buf)); + SNPRINTF_BUFFER_SIZE(ret, &len, &offset); + assert(len > 0); } return error(loc, "syntax error, unexpected %s, known keys are %s", str, buf); diff --git a/src/mini-gmp.c b/src/mini-gmp.c index 04bed3f5..186dc3a4 100644 --- a/src/mini-gmp.c +++ b/src/mini-gmp.c @@ -41,12 +41,12 @@ see https://www.gnu.org/licenses/. */ mpn/generic/sbpi1_div_qr.c, mpn/generic/sub_n.c, mpn/generic/submul_1.c. */ +#include <nft.h> + #include <assert.h> #include <ctype.h> #include <limits.h> #include <stdio.h> -#include <stdlib.h> -#include <string.h> #include "mini-gmp.h" diff --git a/src/misspell.c b/src/misspell.c index 6536d755..f5354fa8 100644 --- a/src/misspell.c +++ b/src/misspell.c @@ -1,5 +1,13 @@ -#include <stdlib.h> -#include <string.h> +/* + * Copyright (c) 2018 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. + */ + +#include <nft.h> + #include <limits.h> #include <utils.h> #include <misspell.h> @@ -64,7 +72,7 @@ static unsigned int string_distance(const char *a, const char *b) ret = DISTANCE(len_a, len_b); - xfree(distance); + free(distance); return ret; } @@ -2,12 +2,15 @@ * Copyright (c) 2013-2017 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 as - * published by the Free Software Foundation. + * it under the terms of the GNU General Public License version 2 (or any + * later) as published by the Free Software Foundation. * * Development of this code funded by Astaro AG (http://www.astaro.com/) */ +#include <nft.h> +#include <iface.h> + #include <libmnl/libmnl.h> #include <libnftnl/common.h> #include <libnftnl/ruleset.h> @@ -22,21 +25,39 @@ #include <libnftnl/udata.h> #include <linux/netfilter/nfnetlink.h> +#include <linux/netfilter/nfnetlink_hook.h> #include <linux/netfilter/nf_tables.h> #include <mnl.h> -#include <string.h> +#include <cmd.h> +#include <intervals.h> +#include <net/if.h> #include <sys/socket.h> #include <arpa/inet.h> #include <fcntl.h> #include <errno.h> -#include <stdlib.h> #include <utils.h> #include <nftables.h> +#include <linux/netfilter.h> +#include <linux/netfilter_arp.h> + +struct basehook { + struct list_head list; + const char *module_name; + const char *hookfn; + const char *table; + const char *chain; + const char *devname; + int family; + int chain_family; + uint32_t num; + int prio; +}; struct mnl_socket *nft_mnl_socket_open(void) { struct mnl_socket *nf_sock; + int one = 1; nf_sock = mnl_socket_open(NETLINK_NETFILTER); if (!nf_sock) @@ -45,17 +66,12 @@ struct mnl_socket *nft_mnl_socket_open(void) if (fcntl(mnl_socket_get_fd(nf_sock), F_SETFL, O_NONBLOCK)) netlink_init_error(); - return nf_sock; -} - -struct mnl_socket *nft_mnl_socket_reopen(struct mnl_socket *nf_sock) -{ - mnl_socket_close(nf_sock); + mnl_socket_setsockopt(nf_sock, NETLINK_EXT_ACK, &one, sizeof(one)); - return nft_mnl_socket_open(); + return nf_sock; } -uint32_t mnl_seqnum_alloc(unsigned int *seqnum) +uint32_t mnl_seqnum_inc(unsigned int *seqnum) { return (*seqnum)++; } @@ -73,20 +89,31 @@ nft_mnl_recv(struct netlink_ctx *ctx, uint32_t portid, int (*cb)(const struct nlmsghdr *nlh, void *data), void *cb_data) { char buf[NFT_NLMSG_MAXSIZE]; + bool eintr = false; int ret; ret = mnl_socket_recvfrom(ctx->nft->nf_sock, buf, sizeof(buf)); while (ret > 0) { ret = mnl_cb_run(buf, ret, ctx->seqnum, portid, cb, cb_data); - if (ret <= 0) - goto out; + if (ret == 0) + break; + if (ret < 0) { + if (errno == EAGAIN) { + ret = 0; + break; + } + if (errno != EINTR) + break; + /* process all pending messages before reporting EINTR */ + eintr = true; + } ret = mnl_socket_recvfrom(ctx->nft->nf_sock, buf, sizeof(buf)); } -out: - if (ret < 0 && errno == EAGAIN) - return 0; - + if (eintr) { + ret = -1; + errno = EINTR; + } return ret; } @@ -152,11 +179,11 @@ static int check_genid(const struct nlmsghdr *nlh) * Batching */ -/* selected batch page is 256 Kbytes long to load ruleset of - * half a million rules without hitting -EMSGSIZE due to large - * iovec. +/* Selected batch page is 2 Mbytes long to support loading a ruleset of 3.5M + * rules matching on source and destination address as well as input and output + * interfaces. This is what legacy iptables supports. */ -#define BATCH_PAGE_SIZE getpagesize() * 32 +#define BATCH_PAGE_SIZE 2 * 1024 * 1024 struct nftnl_batch *mnl_batch_init(void) { @@ -204,11 +231,13 @@ void mnl_batch_reset(struct nftnl_batch *batch) } static void mnl_err_list_node_add(struct list_head *err_list, int error, - int seqnum) + int seqnum, uint32_t offset, + const char *errmsg) { struct mnl_err *err = xmalloc(sizeof(struct mnl_err)); err->seqnum = seqnum; + err->offset = offset; err->err = error; list_add_tail(&err->head, err_list); } @@ -216,12 +245,13 @@ static void mnl_err_list_node_add(struct list_head *err_list, int error, void mnl_err_list_free(struct mnl_err *err) { list_del(&err->head); - xfree(err); + free(err); } -static void mnl_set_sndbuffer(const struct mnl_socket *nl, - struct nftnl_batch *batch) +static void mnl_set_sndbuffer(struct netlink_ctx *ctx) { + struct mnl_socket *nl = ctx->nft->nf_sock; + struct nftnl_batch *batch = ctx->batch; socklen_t len = sizeof(int); int sndnlbuffsiz = 0; int newbuffsiz; @@ -234,9 +264,15 @@ static void mnl_set_sndbuffer(const struct mnl_socket *nl, return; /* 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. */ if (setsockopt(mnl_socket_get_fd(nl), SOL_SOCKET, SO_SNDBUFFORCE, - &newbuffsiz, sizeof(socklen_t)) < 0) - return; + &newbuffsiz, sizeof(socklen_t)) < 0) { + if (errno == EPERM) + ctx->maybe_emsgsize = newbuffsiz; + } } static unsigned int nlsndbufsiz; @@ -267,24 +303,16 @@ static int mnl_set_rcvbuffer(const struct mnl_socket *nl, socklen_t bufsiz) return ret; } -static size_t mnl_nft_batch_to_msg(struct netlink_ctx *ctx, struct msghdr *msg, - const struct sockaddr_nl *snl, - struct iovec *iov, unsigned int iov_len) +static void mnl_nft_batch_to_msg(struct netlink_ctx *ctx, struct msghdr *msg, + const struct sockaddr_nl *snl, + struct iovec *iov, unsigned int iov_len) { - unsigned int i; - size_t len = 0; - msg->msg_name = (struct sockaddr_nl *)snl; msg->msg_namelen = sizeof(*snl); msg->msg_iov = iov; msg->msg_iovlen = iov_len; nftnl_batch_iovec(ctx->batch, iov, iov_len); - - for (i = 0; i < iov_len; i++) - len += msg->msg_iov[i].iov_len; - - return len; } static ssize_t mnl_nft_socket_sendmsg(struct netlink_ctx *ctx, @@ -305,7 +333,65 @@ static ssize_t mnl_nft_socket_sendmsg(struct netlink_ctx *ctx, return sendmsg(mnl_socket_get_fd(ctx->nft->nf_sock), msg, 0); } -#define NFT_MNL_ECHO_RCVBUFF_DEFAULT (MNL_SOCKET_BUFFER_SIZE * 1024) +static int err_attr_cb(const struct nlattr *attr, void *data) +{ + const struct nlattr **tb = data; + uint16_t type; + + if (mnl_attr_type_valid(attr, NLMSGERR_ATTR_MAX) < 0) + return MNL_CB_ERROR; + + type = mnl_attr_get_type(attr); + switch (type) { + case NLMSGERR_ATTR_OFFS: + if (mnl_attr_validate(attr, MNL_TYPE_U32) < 0) + return MNL_CB_ERROR; + break; + } + + tb[type] = attr; + return MNL_CB_OK; +} + +static int mnl_batch_extack_cb(const struct nlmsghdr *nlh, void *data) +{ + struct netlink_cb_data *cb_data = data; + struct nlattr *tb[NLMSGERR_ATTR_MAX + 1] = {}; + const struct nlmsgerr *err = mnl_nlmsg_get_payload(nlh); + unsigned int hlen = sizeof(*err); + const char *msg = NULL; + uint32_t off = 0; + 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 == 0) + return MNL_CB_STOP; + + if (!(nlh->nlmsg_flags & NLM_F_CAPPED)) + hlen += mnl_nlmsg_get_payload_len(&err->msg); + + if (mnl_attr_parse(nlh, hlen, err_attr_cb, tb) != MNL_CB_OK) + return MNL_CB_ERROR; + + if (tb[NLMSGERR_ATTR_OFFS]) + off = mnl_attr_get_u32(tb[NLMSGERR_ATTR_OFFS]); + + mnl_err_list_node_add(cb_data->err_list, errval, + nlh->nlmsg_seq, off, msg); + return MNL_CB_ERROR; +} + +#define NFT_MNL_ECHO_RCVBUFF_DEFAULT (MNL_SOCKET_BUFFER_SIZE * 1024U) +#define NFT_MNL_ACK_MAXSIZE ((sizeof(struct nlmsghdr) + \ + sizeof(struct nfgenmsg) + (1 << 16)) + \ + MNL_SOCKET_BUFFER_SIZE) int mnl_batch_talk(struct netlink_ctx *ctx, struct list_head *err_list, uint32_t num_cmds) @@ -313,7 +399,7 @@ int mnl_batch_talk(struct netlink_ctx *ctx, struct list_head *err_list, struct mnl_socket *nl = ctx->nft->nf_sock; int ret, fd = mnl_socket_get_fd(nl), portid = mnl_socket_get_portid(nl); uint32_t iov_len = nftnl_batch_iovec_len(ctx->batch); - char rcv_buf[MNL_SOCKET_BUFFER_SIZE]; + char rcv_buf[NFT_MNL_ACK_MAXSIZE]; const struct sockaddr_nl snl = { .nl_family = AF_NETLINK }; @@ -324,19 +410,23 @@ int mnl_batch_talk(struct netlink_ctx *ctx, struct list_head *err_list, struct iovec iov[iov_len]; struct msghdr msg = {}; unsigned int rcvbufsiz; - size_t batch_size; fd_set readfds; + static mnl_cb_t cb_ctl_array[NLMSG_MIN_TYPE] = { + [NLMSG_ERROR] = mnl_batch_extack_cb, + }; + struct netlink_cb_data cb_data = { + .err_list = err_list, + .nl_ctx = ctx, + }; - mnl_set_sndbuffer(ctx->nft->nf_sock, ctx->batch); + mnl_set_sndbuffer(ctx); - batch_size = mnl_nft_batch_to_msg(ctx, &msg, &snl, iov, iov_len); + mnl_nft_batch_to_msg(ctx, &msg, &snl, iov, iov_len); + rcvbufsiz = num_cmds * 1024; if (nft_output_echo(&ctx->nft->output)) { - rcvbufsiz = num_cmds * 1024; if (rcvbufsiz < NFT_MNL_ECHO_RCVBUFF_DEFAULT) rcvbufsiz = NFT_MNL_ECHO_RCVBUFF_DEFAULT; - } else { - rcvbufsiz = num_cmds * div_round_up(batch_size, num_cmds) * 4; } mnl_set_rcvbuffer(ctx->nft->nf_sock, rcvbufsiz); @@ -361,32 +451,69 @@ int mnl_batch_talk(struct netlink_ctx *ctx, struct list_head *err_list, if (ret == -1) return -1; - ret = mnl_cb_run(rcv_buf, ret, 0, portid, &netlink_echo_callback, ctx); /* Continue on error, make sure we get all acknowledgments */ - if (ret == -1) { - struct nlmsghdr *nlh = (struct nlmsghdr *)rcv_buf; - - mnl_err_list_node_add(err_list, errno, nlh->nlmsg_seq); - } + ret = mnl_cb_run2(rcv_buf, ret, 0, portid, + netlink_echo_callback, &cb_data, + cb_ctl_array, MNL_ARRAY_SIZE(cb_ctl_array)); } return 0; } -int mnl_nft_rule_add(struct netlink_ctx *ctx, const struct cmd *cmd, +struct mnl_nft_rule_build_ctx { + struct netlink_linearize_ctx *lctx; + struct nlmsghdr *nlh; + struct cmd *cmd; +}; + +static int mnl_nft_expr_build_cb(struct nftnl_expr *nle, void *data) +{ + struct mnl_nft_rule_build_ctx *ctx = data; + struct nlmsghdr *nlh = ctx->nlh; + struct cmd *cmd = ctx->cmd; + struct nft_expr_loc *eloc; + struct nlattr *nest; + + eloc = nft_expr_loc_find(nle, ctx->lctx); + if (eloc) + cmd_add_loc(cmd, nlh, eloc->loc); + + nest = mnl_attr_nest_start(nlh, NFTA_LIST_ELEM); + nftnl_expr_build_payload(nlh, nle); + mnl_attr_nest_end(nlh, nest); + + nftnl_rule_del_expr(nle); + nftnl_expr_free(nle); + + return 0; +} + +static void mnl_nft_rule_build_ctx_init(struct mnl_nft_rule_build_ctx *rule_ctx, + struct nlmsghdr *nlh, + struct cmd *cmd, + struct netlink_linearize_ctx *lctx) +{ + memset(rule_ctx, 0, sizeof(*rule_ctx)); + rule_ctx->nlh = nlh; + rule_ctx->cmd = cmd; + rule_ctx->lctx = lctx; +} + +int mnl_nft_rule_add(struct netlink_ctx *ctx, struct cmd *cmd, unsigned int flags) { + struct mnl_nft_rule_build_ctx rule_ctx; + struct netlink_linearize_ctx lctx; struct rule *rule = cmd->rule; struct handle *h = &rule->handle; struct nftnl_rule *nlr; struct nlmsghdr *nlh; + struct nlattr *nest; nlr = nftnl_rule_alloc(); if (!nlr) memory_allocation_error(); nftnl_rule_set_u32(nlr, NFTNL_RULE_FAMILY, h->family); - nftnl_rule_set_str(nlr, NFTNL_RULE_TABLE, h->table.name); - nftnl_rule_set_str(nlr, NFTNL_RULE_CHAIN, h->chain.name); if (h->position.id) nftnl_rule_set_u64(nlr, NFTNL_RULE_POSITION, h->position.id); if (h->rule_id) @@ -394,26 +521,47 @@ int mnl_nft_rule_add(struct netlink_ctx *ctx, const struct cmd *cmd, if (h->position_id) nftnl_rule_set_u32(nlr, NFTNL_RULE_POSITION_ID, h->position_id); - netlink_linearize_rule(ctx, nlr, rule); + netlink_linearize_init(&lctx, nlr); + netlink_linearize_rule(ctx, rule, &lctx); nlh = nftnl_nlmsg_build_hdr(nftnl_batch_buffer(ctx->batch), NFT_MSG_NEWRULE, cmd->handle.family, NLM_F_CREATE | flags, ctx->seqnum); + + cmd_add_loc(cmd, nlh, &h->table.location); + mnl_attr_put_strz(nlh, NFTA_RULE_TABLE, h->table.name); + cmd_add_loc(cmd, nlh, &h->chain.location); + + if (h->chain_id) + mnl_attr_put_u32(nlh, NFTA_RULE_CHAIN_ID, htonl(h->chain_id)); + else + mnl_attr_put_strz(nlh, NFTA_RULE_CHAIN, h->chain.name); + + mnl_nft_rule_build_ctx_init(&rule_ctx, nlh, cmd, &lctx); + + nest = mnl_attr_nest_start(nlh, NFTA_RULE_EXPRESSIONS); + nftnl_expr_foreach(nlr, mnl_nft_expr_build_cb, &rule_ctx); + mnl_attr_nest_end(nlh, nest); + nftnl_rule_nlmsg_build_payload(nlh, nlr); nftnl_rule_free(nlr); + netlink_linearize_fini(&lctx); mnl_nft_batch_continue(ctx->batch); return 0; } -int mnl_nft_rule_replace(struct netlink_ctx *ctx, const struct cmd *cmd) +int mnl_nft_rule_replace(struct netlink_ctx *ctx, struct cmd *cmd) { + struct mnl_nft_rule_build_ctx rule_ctx; + struct netlink_linearize_ctx lctx; struct rule *rule = cmd->rule; struct handle *h = &rule->handle; unsigned int flags = 0; struct nftnl_rule *nlr; struct nlmsghdr *nlh; + struct nlattr *nest; if (nft_output_echo(&ctx->nft->output)) flags |= NLM_F_ECHO; @@ -423,26 +571,40 @@ int mnl_nft_rule_replace(struct netlink_ctx *ctx, const struct cmd *cmd) memory_allocation_error(); nftnl_rule_set_u32(nlr, NFTNL_RULE_FAMILY, h->family); - nftnl_rule_set_str(nlr, NFTNL_RULE_TABLE, h->table.name); - nftnl_rule_set_str(nlr, NFTNL_RULE_CHAIN, h->chain.name); - nftnl_rule_set_u64(nlr, NFTNL_RULE_HANDLE, h->handle.id); - netlink_linearize_rule(ctx, nlr, rule); + netlink_linearize_init(&lctx, nlr); + netlink_linearize_rule(ctx, rule, &lctx); nlh = nftnl_nlmsg_build_hdr(nftnl_batch_buffer(ctx->batch), NFT_MSG_NEWRULE, cmd->handle.family, NLM_F_REPLACE | flags, ctx->seqnum); + + cmd_add_loc(cmd, nlh, &h->table.location); + mnl_attr_put_strz(nlh, NFTA_RULE_TABLE, h->table.name); + cmd_add_loc(cmd, nlh, &h->chain.location); + mnl_attr_put_strz(nlh, NFTA_RULE_CHAIN, h->chain.name); + cmd_add_loc(cmd, nlh, &h->handle.location); + mnl_attr_put_u64(nlh, NFTA_RULE_HANDLE, htobe64(h->handle.id)); + + mnl_nft_rule_build_ctx_init(&rule_ctx, nlh, cmd, &lctx); + + nest = mnl_attr_nest_start(nlh, NFTA_RULE_EXPRESSIONS); + nftnl_expr_foreach(nlr, mnl_nft_expr_build_cb, &rule_ctx); + mnl_attr_nest_end(nlh, nest); + nftnl_rule_nlmsg_build_payload(nlh, nlr); nftnl_rule_free(nlr); + netlink_linearize_fini(&lctx); mnl_nft_batch_continue(ctx->batch); return 0; } -int mnl_nft_rule_del(struct netlink_ctx *ctx, const struct cmd *cmd) +int mnl_nft_rule_del(struct netlink_ctx *ctx, struct cmd *cmd) { - const struct handle *h = &cmd->handle; + enum nf_tables_msg_types msg_type = NFT_MSG_DELRULE; + struct handle *h = &cmd->handle; struct nftnl_rule *nlr; struct nlmsghdr *nlh; @@ -451,16 +613,26 @@ int mnl_nft_rule_del(struct netlink_ctx *ctx, const struct cmd *cmd) memory_allocation_error(); nftnl_rule_set_u32(nlr, NFTNL_RULE_FAMILY, h->family); - nftnl_rule_set_str(nlr, NFTNL_RULE_TABLE, h->table.name); - if (h->chain.name) - nftnl_rule_set_str(nlr, NFTNL_RULE_CHAIN, h->chain.name); - if (h->handle.id) - nftnl_rule_set_u64(nlr, NFTNL_RULE_HANDLE, h->handle.id); + + if (cmd->op == CMD_DESTROY) + msg_type = NFT_MSG_DESTROYRULE; nlh = nftnl_nlmsg_build_hdr(nftnl_batch_buffer(ctx->batch), - NFT_MSG_DELRULE, + msg_type, nftnl_rule_get_u32(nlr, NFTNL_RULE_FAMILY), 0, ctx->seqnum); + + cmd_add_loc(cmd, nlh, &h->table.location); + mnl_attr_put_strz(nlh, NFTA_RULE_TABLE, h->table.name); + if (h->chain.name) { + cmd_add_loc(cmd, nlh, &h->chain.location); + mnl_attr_put_strz(nlh, NFTA_RULE_CHAIN, h->chain.name); + } + if (h->handle.id) { + cmd_add_loc(cmd, nlh, &h->handle.location); + mnl_attr_put_u64(nlh, NFTA_RULE_HANDLE, htobe64(h->handle.id)); + } + nftnl_rule_nlmsg_build_payload(nlh, nlr); nftnl_rule_free(nlr); @@ -496,20 +668,45 @@ err_free: return MNL_CB_OK; } -struct nftnl_rule_list *mnl_nft_rule_dump(struct netlink_ctx *ctx, - int family) +struct nftnl_rule_list *mnl_nft_rule_dump(struct netlink_ctx *ctx, int family, + const char *table, const char *chain, + uint64_t rule_handle, + bool dump, bool reset) { + uint16_t nl_flags = dump ? NLM_F_DUMP : NLM_F_ACK; char buf[MNL_SOCKET_BUFFER_SIZE]; struct nftnl_rule_list *nlr_list; + struct nftnl_rule *nlr = NULL; struct nlmsghdr *nlh; - int ret; + int msg_type, ret; + + if (reset) + msg_type = NFT_MSG_GETRULE_RESET; + else + msg_type = NFT_MSG_GETRULE; + + if (table) { + nlr = nftnl_rule_alloc(); + if (!nlr) + memory_allocation_error(); + + nftnl_rule_set_str(nlr, NFTNL_RULE_TABLE, table); + if (chain) + nftnl_rule_set_str(nlr, NFTNL_RULE_CHAIN, chain); + if (rule_handle) + nftnl_rule_set_u64(nlr, NFTNL_RULE_HANDLE, rule_handle); + } nlr_list = nftnl_rule_list_alloc(); if (nlr_list == NULL) memory_allocation_error(); - nlh = nftnl_nlmsg_build_hdr(buf, NFT_MSG_GETRULE, family, - NLM_F_DUMP, ctx->seqnum); + nlh = nftnl_nlmsg_build_hdr(buf, msg_type, family, + nl_flags, ctx->seqnum); + if (nlr) { + nftnl_rule_nlmsg_build_payload(nlh, nlr); + nftnl_rule_free(nlr); + } ret = nft_mnl_talk(ctx, nlh, nlh->nlmsg_len, rule_cb, nlr_list); if (ret < 0) @@ -524,69 +721,196 @@ err: /* * Chain */ -int mnl_nft_chain_add(struct netlink_ctx *ctx, const struct cmd *cmd, + +struct nft_dev { + const char *ifname; + const struct location *location; +}; + +static void nft_dev_add(struct nft_dev *dev_array, const struct expr *expr, int i) +{ + unsigned int ifname_len; + char ifname[IFNAMSIZ]; + + if (expr->etype != EXPR_VALUE) + BUG("Must be a value, not %s\n", expr_name(expr)); + + ifname_len = div_round_up(expr->len, BITS_PER_BYTE); + memset(ifname, 0, sizeof(ifname)); + + if (ifname_len > sizeof(ifname)) + BUG("Interface length %u exceeds limit\n", ifname_len); + + mpz_export_data(ifname, expr->value, BYTEORDER_HOST_ENDIAN, ifname_len); + + if (strnlen(ifname, IFNAMSIZ) >= IFNAMSIZ) + BUG("Interface length %zu exceeds limit, no NUL byte\n", strnlen(ifname, IFNAMSIZ)); + + dev_array[i].ifname = xstrdup(ifname); + dev_array[i].location = &expr->location; +} + +static struct nft_dev *nft_dev_array(const struct expr *dev_expr, int *num_devs) +{ + struct nft_dev *dev_array; + int i = 0, len = 1; + struct expr *expr; + + switch (dev_expr->etype) { + case EXPR_LIST: + list_for_each_entry(expr, &expr_list(dev_expr)->expressions, list) + len++; + + dev_array = xmalloc(sizeof(struct nft_dev) * len); + + list_for_each_entry(expr, &expr_list(dev_expr)->expressions, list) { + nft_dev_add(dev_array, expr, i); + i++; + } + break; + case EXPR_VALUE: + len++; + dev_array = xmalloc(sizeof(struct nft_dev) * len); + nft_dev_add(dev_array, dev_expr, i); + i++; + break; + default: + assert(0); + } + + dev_array[i].ifname = NULL; + *num_devs = i; + + return dev_array; +} + +static void nft_dev_array_free(const struct nft_dev *dev_array) +{ + int i = 0; + + while (dev_array[i].ifname != NULL) + free_const(dev_array[i++].ifname); + + free_const(dev_array); +} + +static void mnl_nft_chain_devs_build(struct nlmsghdr *nlh, struct cmd *cmd) +{ + const struct expr *dev_expr = cmd->chain->dev_expr; + const struct nft_dev *dev_array; + struct nlattr *nest_dev; + int i, num_devs = 0; + + dev_array = nft_dev_array(dev_expr, &num_devs); + if (num_devs == 1) { + cmd_add_loc(cmd, nlh, dev_array[0].location); + mnl_attr_put_strz(nlh, NFTA_HOOK_DEV, dev_array[0].ifname); + } else { + nest_dev = mnl_attr_nest_start(nlh, NFTA_HOOK_DEVS); + for (i = 0; i < num_devs; i++) { + cmd_add_loc(cmd, nlh, dev_array[i].location); + mnl_attr_put_strz(nlh, NFTA_DEVICE_NAME, dev_array[i].ifname); + mnl_attr_nest_end(nlh, nest_dev); + } + } + nft_dev_array_free(dev_array); +} + +int mnl_nft_chain_add(struct netlink_ctx *ctx, struct cmd *cmd, unsigned int flags) { - int priority, policy, i = 0; + struct nftnl_udata_buf *udbuf; struct nftnl_chain *nlc; - const char **dev_array; struct nlmsghdr *nlh; - struct expr *expr; - int dev_array_len; + int priority, policy; nlc = nftnl_chain_alloc(); if (nlc == NULL) memory_allocation_error(); nftnl_chain_set_u32(nlc, NFTNL_CHAIN_FAMILY, cmd->handle.family); - nftnl_chain_set_str(nlc, NFTNL_CHAIN_TABLE, cmd->handle.table.name); - nftnl_chain_set_str(nlc, NFTNL_CHAIN_NAME, cmd->handle.chain.name); if (cmd->chain) { - if (cmd->chain->flags & CHAIN_F_BASECHAIN) { - nftnl_chain_set_u32(nlc, NFTNL_CHAIN_HOOKNUM, - cmd->chain->hooknum); - mpz_export_data(&priority, - cmd->chain->priority.expr->value, - BYTEORDER_HOST_ENDIAN, sizeof(int)); - nftnl_chain_set_s32(nlc, NFTNL_CHAIN_PRIO, priority); - nftnl_chain_set_str(nlc, NFTNL_CHAIN_TYPE, - cmd->chain->type); + if (cmd->chain->flags & CHAIN_F_HW_OFFLOAD) { + nftnl_chain_set_u32(nlc, NFTNL_CHAIN_FLAGS, + CHAIN_F_HW_OFFLOAD); } - if (cmd->chain->policy) { - mpz_export_data(&policy, cmd->chain->policy->value, - BYTEORDER_HOST_ENDIAN, sizeof(int)); - nftnl_chain_set_u32(nlc, NFTNL_CHAIN_POLICY, policy); + if (cmd->chain->comment) { + udbuf = nftnl_udata_buf_alloc(NFT_USERDATA_MAXLEN); + if (!udbuf) + memory_allocation_error(); + if (!nftnl_udata_put_strz(udbuf, NFTNL_UDATA_CHAIN_COMMENT, cmd->chain->comment)) + memory_allocation_error(); + nftnl_chain_set_data(nlc, NFTNL_CHAIN_USERDATA, nftnl_udata_buf_data(udbuf), + nftnl_udata_buf_len(udbuf)); + nftnl_udata_buf_free(udbuf); } - if (cmd->chain->dev_expr) { - dev_array = xmalloc(sizeof(char *) * 8); - dev_array_len = 8; - list_for_each_entry(expr, &cmd->chain->dev_expr->expressions, list) { - dev_array[i++] = expr->identifier; - if (i == dev_array_len) { - dev_array_len *= 2; - dev_array = xrealloc(dev_array, - dev_array_len * sizeof(char *)); - } - } + } - dev_array[i] = NULL; - if (i == 1) - nftnl_chain_set_str(nlc, NFTNL_CHAIN_DEV, dev_array[0]); - else if (i > 1) - nftnl_chain_set_data(nlc, NFTNL_CHAIN_DEVICES, dev_array, - sizeof(char *) * dev_array_len); + nftnl_chain_set_str(nlc, NFTNL_CHAIN_TABLE, cmd->handle.table.name); + if (cmd->handle.chain.name) + nftnl_chain_set_str(nlc, NFTNL_CHAIN_NAME, cmd->handle.chain.name); - xfree(dev_array); - } - } netlink_dump_chain(nlc, ctx); + nftnl_chain_unset(nlc, NFTNL_CHAIN_TABLE); + nftnl_chain_unset(nlc, NFTNL_CHAIN_NAME); + nlh = nftnl_nlmsg_build_hdr(nftnl_batch_buffer(ctx->batch), NFT_MSG_NEWCHAIN, cmd->handle.family, NLM_F_CREATE | flags, ctx->seqnum); + + cmd_add_loc(cmd, nlh, &cmd->handle.table.location); + mnl_attr_put_strz(nlh, NFTA_CHAIN_TABLE, cmd->handle.table.name); + cmd_add_loc(cmd, nlh, &cmd->handle.chain.location); + + if (!cmd->chain || !(cmd->chain->flags & CHAIN_F_BINDING)) { + mnl_attr_put_strz(nlh, NFTA_CHAIN_NAME, cmd->handle.chain.name); + } else { + if (cmd->handle.chain.name) + mnl_attr_put_strz(nlh, NFTA_CHAIN_NAME, + cmd->handle.chain.name); + + mnl_attr_put_u32(nlh, NFTA_CHAIN_ID, htonl(cmd->handle.chain_id)); + if (cmd->chain->flags) + nftnl_chain_set_u32(nlc, NFTNL_CHAIN_FLAGS, cmd->chain->flags); + } + + if (cmd->chain && cmd->chain->policy) { + mpz_export_data(&policy, cmd->chain->policy->value, + BYTEORDER_HOST_ENDIAN, sizeof(int)); + cmd_add_loc(cmd, nlh, &cmd->chain->policy->location); + mnl_attr_put_u32(nlh, NFTA_CHAIN_POLICY, htonl(policy)); + } + + nftnl_chain_unset(nlc, NFTNL_CHAIN_TYPE); + nftnl_chain_nlmsg_build_payload(nlh, nlc); + + if (cmd->chain && cmd->chain->flags & CHAIN_F_BASECHAIN) { + struct nlattr *nest; + + if (cmd->chain->type.str) { + cmd_add_loc(cmd, nlh, &cmd->chain->type.loc); + mnl_attr_put_strz(nlh, NFTA_CHAIN_TYPE, cmd->chain->type.str); + } + + nest = mnl_attr_nest_start(nlh, NFTA_CHAIN_HOOK); + + if (cmd->chain->type.str) { + mnl_attr_put_u32(nlh, NFTA_HOOK_HOOKNUM, htonl(cmd->chain->hook.num)); + mpz_export_data(&priority, cmd->chain->priority.expr->value, + BYTEORDER_HOST_ENDIAN, sizeof(int)); + mnl_attr_put_u32(nlh, NFTA_HOOK_PRIORITY, htonl(priority)); + } + + if (cmd->chain && cmd->chain->dev_expr) + mnl_nft_chain_devs_build(nlh, cmd); + + mnl_attr_nest_end(nlh, nest); + } + nftnl_chain_free(nlc); mnl_nft_batch_continue(ctx->batch); @@ -624,8 +948,9 @@ int mnl_nft_chain_rename(struct netlink_ctx *ctx, const struct cmd *cmd, return 0; } -int mnl_nft_chain_del(struct netlink_ctx *ctx, const struct cmd *cmd) +int mnl_nft_chain_del(struct netlink_ctx *ctx, struct cmd *cmd) { + enum nf_tables_msg_types msg_type = NFT_MSG_DELCHAIN; struct nftnl_chain *nlc; struct nlmsghdr *nlh; @@ -634,18 +959,35 @@ int mnl_nft_chain_del(struct netlink_ctx *ctx, const struct cmd *cmd) memory_allocation_error(); nftnl_chain_set_u32(nlc, NFTNL_CHAIN_FAMILY, cmd->handle.family); - nftnl_chain_set_str(nlc, NFTNL_CHAIN_TABLE, cmd->handle.table.name); - if (cmd->handle.chain.name) - nftnl_chain_set_str(nlc, NFTNL_CHAIN_NAME, - cmd->handle.chain.name); - else if (cmd->handle.handle.id) - nftnl_chain_set_u64(nlc, NFTNL_CHAIN_HANDLE, - cmd->handle.handle.id); + + if (cmd->op == CMD_DESTROY) + msg_type = NFT_MSG_DESTROYCHAIN; nlh = nftnl_nlmsg_build_hdr(nftnl_batch_buffer(ctx->batch), - NFT_MSG_DELCHAIN, + msg_type, cmd->handle.family, 0, ctx->seqnum); + + cmd_add_loc(cmd, nlh, &cmd->handle.table.location); + mnl_attr_put_strz(nlh, NFTA_CHAIN_TABLE, cmd->handle.table.name); + if (cmd->handle.chain.name) { + cmd_add_loc(cmd, nlh, &cmd->handle.chain.location); + mnl_attr_put_strz(nlh, NFTA_CHAIN_NAME, cmd->handle.chain.name); + } else if (cmd->handle.handle.id) { + cmd_add_loc(cmd, nlh, &cmd->handle.handle.location); + mnl_attr_put_u64(nlh, NFTA_CHAIN_HANDLE, + htobe64(cmd->handle.handle.id)); + } + + if (cmd->op == CMD_DELETE && + cmd->chain && cmd->chain->dev_expr) { + struct nlattr *nest; + + nest = mnl_attr_nest_start(nlh, NFTA_CHAIN_HOOK); + mnl_nft_chain_devs_build(nlh, cmd); + mnl_attr_nest_end(nlh, nest); + } + nftnl_chain_nlmsg_build_payload(nlh, nlc); nftnl_chain_free(nlc); @@ -678,10 +1020,12 @@ err_free: } struct nftnl_chain_list *mnl_nft_chain_dump(struct netlink_ctx *ctx, - int family) + int family, const char *table, + const char *chain) { char buf[MNL_SOCKET_BUFFER_SIZE]; struct nftnl_chain_list *nlc_list; + struct nftnl_chain *nlc = NULL; struct nlmsghdr *nlh; int ret; @@ -689,11 +1033,24 @@ struct nftnl_chain_list *mnl_nft_chain_dump(struct netlink_ctx *ctx, if (nlc_list == NULL) memory_allocation_error(); + if (table && chain) { + nlc = nftnl_chain_alloc(); + if (!nlc) + memory_allocation_error(); + + nftnl_chain_set_str(nlc, NFTNL_CHAIN_TABLE, table); + nftnl_chain_set_str(nlc, NFTNL_CHAIN_NAME, chain); + } + nlh = nftnl_nlmsg_build_hdr(buf, NFT_MSG_GETCHAIN, family, - NLM_F_DUMP, ctx->seqnum); + nlc ? NLM_F_ACK : NLM_F_DUMP, ctx->seqnum); + if (nlc) { + nftnl_chain_nlmsg_build_payload(nlh, nlc); + nftnl_chain_free(nlc); + } ret = nft_mnl_talk(ctx, nlh, nlh->nlmsg_len, chain_cb, nlc_list); - if (ret < 0) + if (ret < 0 && errno != ENOENT) goto err; return nlc_list; @@ -705,9 +1062,10 @@ err: /* * Table */ -int mnl_nft_table_add(struct netlink_ctx *ctx, const struct cmd *cmd, +int mnl_nft_table_add(struct netlink_ctx *ctx, struct cmd *cmd, unsigned int flags) { + struct nftnl_udata_buf *udbuf; struct nftnl_table *nlt; struct nlmsghdr *nlh; @@ -716,16 +1074,30 @@ int mnl_nft_table_add(struct netlink_ctx *ctx, const struct cmd *cmd, memory_allocation_error(); nftnl_table_set_u32(nlt, NFTNL_TABLE_FAMILY, cmd->handle.family); - nftnl_table_set_str(nlt, NFTNL_TABLE_NAME, cmd->handle.table.name); - if (cmd->table) + if (cmd->table) { nftnl_table_set_u32(nlt, NFTNL_TABLE_FLAGS, cmd->table->flags); - else + + if (cmd->table->comment) { + udbuf = nftnl_udata_buf_alloc(NFT_USERDATA_MAXLEN); + if (!udbuf) + memory_allocation_error(); + if (!nftnl_udata_put_strz(udbuf, NFTNL_UDATA_TABLE_COMMENT, cmd->table->comment)) + memory_allocation_error(); + nftnl_table_set_data(nlt, NFTNL_TABLE_USERDATA, nftnl_udata_buf_data(udbuf), + nftnl_udata_buf_len(udbuf)); + nftnl_udata_buf_free(udbuf); + } + } else { nftnl_table_set_u32(nlt, NFTNL_TABLE_FLAGS, 0); + } nlh = nftnl_nlmsg_build_hdr(nftnl_batch_buffer(ctx->batch), NFT_MSG_NEWTABLE, cmd->handle.family, flags, ctx->seqnum); + + cmd_add_loc(cmd, nlh, &cmd->handle.table.location); + mnl_attr_put_strz(nlh, NFTA_TABLE_NAME, cmd->handle.table.name); nftnl_table_nlmsg_build_payload(nlh, nlt); nftnl_table_free(nlt); @@ -734,8 +1106,9 @@ int mnl_nft_table_add(struct netlink_ctx *ctx, const struct cmd *cmd, return 0; } -int mnl_nft_table_del(struct netlink_ctx *ctx, const struct cmd *cmd) +int mnl_nft_table_del(struct netlink_ctx *ctx, struct cmd *cmd) { + enum nf_tables_msg_types msg_type = NFT_MSG_DELTABLE; struct nftnl_table *nlt; struct nlmsghdr *nlh; @@ -744,17 +1117,21 @@ int mnl_nft_table_del(struct netlink_ctx *ctx, const struct cmd *cmd) memory_allocation_error(); nftnl_table_set_u32(nlt, NFTNL_TABLE_FAMILY, cmd->handle.family); - if (cmd->handle.table.name) - nftnl_table_set_str(nlt, NFTNL_TABLE_NAME, - cmd->handle.table.name); - else if (cmd->handle.handle.id) - nftnl_table_set_u64(nlt, NFTNL_TABLE_HANDLE, - cmd->handle.handle.id); - nlh = nftnl_nlmsg_build_hdr(nftnl_batch_buffer(ctx->batch), - NFT_MSG_DELTABLE, - cmd->handle.family, - 0, ctx->seqnum); + if (cmd->op == CMD_DESTROY) + msg_type = NFT_MSG_DESTROYTABLE; + + nlh = nftnl_nlmsg_build_hdr(nftnl_batch_buffer(ctx->batch), msg_type, + cmd->handle.family, 0, ctx->seqnum); + + if (cmd->handle.table.name) { + cmd_add_loc(cmd, nlh, &cmd->handle.table.location); + mnl_attr_put_strz(nlh, NFTA_TABLE_NAME, cmd->handle.table.name); + } else if (cmd->handle.handle.id) { + cmd_add_loc(cmd, nlh, &cmd->handle.handle.location); + mnl_attr_put_u64(nlh, NFTA_TABLE_HANDLE, + htobe64(cmd->handle.handle.id)); + } nftnl_table_nlmsg_build_payload(nlh, nlt); nftnl_table_free(nlt); @@ -787,10 +1164,12 @@ err_free: } struct nftnl_table_list *mnl_nft_table_dump(struct netlink_ctx *ctx, - int family) + int family, const char *table) { char buf[MNL_SOCKET_BUFFER_SIZE]; struct nftnl_table_list *nlt_list; + struct nftnl_table *nlt = NULL; + int flags = NLM_F_DUMP; struct nlmsghdr *nlh; int ret; @@ -798,11 +1177,28 @@ struct nftnl_table_list *mnl_nft_table_dump(struct netlink_ctx *ctx, if (nlt_list == NULL) return NULL; + if (table) { + nlt = nftnl_table_alloc(); + if (!nlt) + memory_allocation_error(); + + if (family != NFPROTO_UNSPEC) + nftnl_table_set_u32(nlt, NFTNL_TABLE_FAMILY, family); + if (table) + nftnl_table_set_str(nlt, NFTNL_TABLE_NAME, table); + + flags = NLM_F_ACK; + } + nlh = nftnl_nlmsg_build_hdr(buf, NFT_MSG_GETTABLE, family, - NLM_F_DUMP, ctx->seqnum); + flags, ctx->seqnum); + if (nlt) { + nftnl_table_nlmsg_build_payload(nlh, nlt); + nftnl_table_free(nlt); + } ret = nft_mnl_talk(ctx, nlh, nlh->nlmsg_len, table_cb, nlt_list); - if (ret < 0) + if (ret < 0 && errno != ENOENT) goto err; return nlt_list; @@ -818,9 +1214,7 @@ static void set_key_expression(struct netlink_ctx *ctx, { struct nftnl_udata *nest1, *nest2; - if (expr->flags & EXPR_F_CONSTANT || - set_flags & NFT_SET_ANONYMOUS || - !expr_ops(expr)->build_udata) + if (!expr_ops(expr)->build_udata) return; nest1 = nftnl_udata_nest_start(udbuf, type); @@ -834,14 +1228,16 @@ static void set_key_expression(struct netlink_ctx *ctx, /* * Set */ -int mnl_nft_set_add(struct netlink_ctx *ctx, const struct cmd *cmd, +int mnl_nft_set_add(struct netlink_ctx *ctx, struct cmd *cmd, unsigned int flags) { - const struct handle *h = &cmd->handle; + struct handle *h = &cmd->handle; struct nftnl_udata_buf *udbuf; struct set *set = cmd->set; struct nftnl_set *nls; struct nlmsghdr *nlh; + struct stmt *stmt; + int num_stmts = 0; nls = nftnl_set_alloc(); if (!nls) @@ -880,8 +1276,6 @@ int mnl_nft_set_add(struct netlink_ctx *ctx, const struct cmd *cmd, if (set->desc.size != 0) nftnl_set_set_u32(nls, NFTNL_SET_DESC_SIZE, set->desc.size); - } else if (set->init) { - nftnl_set_set_u32(nls, NFTNL_SET_DESC_SIZE, set->init->size); } udbuf = nftnl_udata_buf_alloc(NFT_USERDATA_MAXLEN); @@ -902,19 +1296,57 @@ int mnl_nft_set_add(struct netlink_ctx *ctx, const struct cmd *cmd, memory_allocation_error(); set_key_expression(ctx, set->key, set->flags, udbuf, NFTNL_UDATA_SET_KEY_TYPEOF); - if (set->data) + if (set->data) { set_key_expression(ctx, set->data, set->flags, udbuf, NFTNL_UDATA_SET_DATA_TYPEOF); + nftnl_udata_put_u32(udbuf, NFTNL_UDATA_SET_DATA_INTERVAL, + !!(set->data->flags & EXPR_F_INTERVAL)); + } + + if (set->desc.field_len[0]) { + nftnl_set_set_data(nls, NFTNL_SET_DESC_CONCAT, + set->desc.field_len, + set->desc.field_count * + sizeof(set->desc.field_len[0])); + } + + if (set->comment) { + if (!nftnl_udata_put_strz(udbuf, NFTNL_UDATA_SET_COMMENT, set->comment)) + memory_allocation_error(); + } nftnl_set_set_data(nls, NFTNL_SET_USERDATA, nftnl_udata_buf_data(udbuf), nftnl_udata_buf_len(udbuf)); nftnl_udata_buf_free(udbuf); + list_for_each_entry(stmt, &set->stmt_list, list) + num_stmts++; + + if (num_stmts == 1) { + list_for_each_entry(stmt, &set->stmt_list, list) { + nftnl_set_set_data(nls, NFTNL_SET_EXPR, + netlink_gen_stmt_stateful(stmt), 0); + break; + } + } else if (num_stmts > 1) { + list_for_each_entry(stmt, &set->stmt_list, list) + nftnl_set_add_expr(nls, netlink_gen_stmt_stateful(stmt)); + } + netlink_dump_set(nls, ctx); + nftnl_set_unset(nls, NFTNL_SET_TABLE); + nftnl_set_unset(nls, NFTNL_SET_NAME); + nlh = nftnl_nlmsg_build_hdr(nftnl_batch_buffer(ctx->batch), NFT_MSG_NEWSET, h->family, NLM_F_CREATE | flags, ctx->seqnum); + + cmd_add_loc(cmd, nlh, &h->table.location); + mnl_attr_put_strz(nlh, NFTA_SET_TABLE, h->table.name); + cmd_add_loc(cmd, nlh, &h->set.location); + mnl_attr_put_strz(nlh, NFTA_SET_NAME, h->set.name); + nftnl_set_nlmsg_build_payload(nlh, nls); nftnl_set_free(nls); @@ -923,8 +1355,9 @@ int mnl_nft_set_add(struct netlink_ctx *ctx, const struct cmd *cmd, return 0; } -int mnl_nft_set_del(struct netlink_ctx *ctx, const struct cmd *cmd) +int mnl_nft_set_del(struct netlink_ctx *ctx, struct cmd *cmd) { + enum nf_tables_msg_types msg_type = NFT_MSG_DELSET; const struct handle *h = &cmd->handle; struct nftnl_set *nls; struct nlmsghdr *nlh; @@ -934,16 +1367,26 @@ int mnl_nft_set_del(struct netlink_ctx *ctx, const struct cmd *cmd) memory_allocation_error(); nftnl_set_set_u32(nls, NFTNL_SET_FAMILY, h->family); - nftnl_set_set_str(nls, NFTNL_SET_TABLE, h->table.name); - if (h->set.name) - nftnl_set_set_str(nls, NFTNL_SET_NAME, h->set.name); - else if (h->handle.id) - nftnl_set_set_u64(nls, NFTNL_SET_HANDLE, h->handle.id); + + if (cmd->op == CMD_DESTROY) + msg_type = NFT_MSG_DESTROYSET; nlh = nftnl_nlmsg_build_hdr(nftnl_batch_buffer(ctx->batch), - NFT_MSG_DELSET, + msg_type, h->family, 0, ctx->seqnum); + + cmd_add_loc(cmd, nlh, &cmd->handle.table.location); + mnl_attr_put_strz(nlh, NFTA_SET_TABLE, cmd->handle.table.name); + if (h->set.name) { + cmd_add_loc(cmd, nlh, &cmd->handle.set.location); + mnl_attr_put_strz(nlh, NFTA_SET_NAME, cmd->handle.set.name); + } else if (h->handle.id) { + cmd_add_loc(cmd, nlh, &cmd->handle.handle.location); + mnl_attr_put_u64(nlh, NFTA_SET_HANDLE, + htobe64(cmd->handle.handle.id)); + } + nftnl_set_nlmsg_build_payload(nlh, nls); nftnl_set_free(nls); @@ -952,9 +1395,15 @@ int mnl_nft_set_del(struct netlink_ctx *ctx, const struct cmd *cmd) return 0; } +struct set_cb_args { + struct netlink_ctx *ctx; + struct nftnl_set_list *list; +}; + static int set_cb(const struct nlmsghdr *nlh, void *data) { - struct nftnl_set_list *nls_list = data; + struct set_cb_args *args = data; + struct nftnl_set_list *nls_list = args->list; struct nftnl_set *s; if (check_genid(nlh) < 0) @@ -967,6 +1416,8 @@ static int set_cb(const struct nlmsghdr *nlh, void *data) if (nftnl_set_nlmsg_parse(nlh, s) < 0) goto err_free; + netlink_dump_set(s, args->ctx); + nftnl_set_list_add_tail(s, nls_list); return MNL_CB_OK; @@ -976,22 +1427,30 @@ err_free: } struct nftnl_set_list * -mnl_nft_set_dump(struct netlink_ctx *ctx, int family, const char *table) +mnl_nft_set_dump(struct netlink_ctx *ctx, int family, + const char *table, const char *set) { char buf[MNL_SOCKET_BUFFER_SIZE]; struct nftnl_set_list *nls_list; + int flags = NLM_F_DUMP; struct nlmsghdr *nlh; struct nftnl_set *s; int ret; + struct set_cb_args args; s = nftnl_set_alloc(); if (s == NULL) memory_allocation_error(); - nlh = nftnl_nlmsg_build_hdr(buf, NFT_MSG_GETSET, family, - NLM_F_DUMP, ctx->seqnum); if (table != NULL) nftnl_set_set_str(s, NFTNL_SET_TABLE, table); + if (set) { + nftnl_set_set_str(s, NFTNL_SET_NAME, set); + flags = NLM_F_ACK; + } + + nlh = nftnl_nlmsg_build_hdr(buf, NFT_MSG_GETSET, family, + flags, ctx->seqnum); nftnl_set_nlmsg_build_payload(nlh, s); nftnl_set_free(s); @@ -999,8 +1458,10 @@ mnl_nft_set_dump(struct netlink_ctx *ctx, int family, const char *table) if (nls_list == NULL) memory_allocation_error(); - ret = nft_mnl_talk(ctx, nlh, nlh->nlmsg_len, set_cb, nls_list); - if (ret < 0) + args.list = nls_list; + args.ctx = ctx; + ret = nft_mnl_talk(ctx, nlh, nlh->nlmsg_len, set_cb, &args); + if (ret < 0 && errno != ENOENT) goto err; return nls_list; @@ -1009,10 +1470,11 @@ err: return NULL; } -int mnl_nft_obj_add(struct netlink_ctx *ctx, const struct cmd *cmd, +int mnl_nft_obj_add(struct netlink_ctx *ctx, struct cmd *cmd, unsigned int flags) { struct obj *obj = cmd->object; + struct nftnl_udata_buf *udbuf; struct nftnl_obj *nlo; struct nlmsghdr *nlh; @@ -1021,10 +1483,19 @@ int mnl_nft_obj_add(struct netlink_ctx *ctx, const struct cmd *cmd, memory_allocation_error(); nftnl_obj_set_u32(nlo, NFTNL_OBJ_FAMILY, cmd->handle.family); - nftnl_obj_set_str(nlo, NFTNL_OBJ_TABLE, cmd->handle.table.name); - nftnl_obj_set_str(nlo, NFTNL_OBJ_NAME, cmd->handle.obj.name); nftnl_obj_set_u32(nlo, NFTNL_OBJ_TYPE, obj->type); + if (obj->comment) { + udbuf = nftnl_udata_buf_alloc(NFT_USERDATA_MAXLEN); + if (!udbuf) + memory_allocation_error(); + if (!nftnl_udata_put_strz(udbuf, NFTNL_UDATA_OBJ_COMMENT, obj->comment)) + memory_allocation_error(); + nftnl_obj_set_data(nlo, NFTNL_OBJ_USERDATA, nftnl_udata_buf_data(udbuf), + nftnl_udata_buf_len(udbuf)); + nftnl_udata_buf_free(udbuf); + } + switch (obj->type) { case NFT_OBJECT_COUNTER: nftnl_obj_set_u64(nlo, NFTNL_OBJ_CTR_PKTS, @@ -1100,6 +1571,12 @@ int mnl_nft_obj_add(struct netlink_ctx *ctx, const struct cmd *cmd, nlh = nftnl_nlmsg_build_hdr(nftnl_batch_buffer(ctx->batch), NFT_MSG_NEWOBJ, cmd->handle.family, NLM_F_CREATE | flags, ctx->seqnum); + + cmd_add_loc(cmd, nlh, &cmd->handle.table.location); + mnl_attr_put_strz(nlh, NFTA_OBJ_TABLE, cmd->handle.table.name); + cmd_add_loc(cmd, nlh, &cmd->handle.obj.location); + mnl_attr_put_strz(nlh, NFTA_OBJ_NAME, cmd->handle.obj.name); + nftnl_obj_nlmsg_build_payload(nlh, nlo); nftnl_obj_free(nlo); @@ -1108,8 +1585,9 @@ int mnl_nft_obj_add(struct netlink_ctx *ctx, const struct cmd *cmd, return 0; } -int mnl_nft_obj_del(struct netlink_ctx *ctx, const struct cmd *cmd, int type) +int mnl_nft_obj_del(struct netlink_ctx *ctx, struct cmd *cmd, int type) { + enum nf_tables_msg_types msg_type = NFT_MSG_DELOBJ; struct nftnl_obj *nlo; struct nlmsghdr *nlh; @@ -1118,16 +1596,27 @@ int mnl_nft_obj_del(struct netlink_ctx *ctx, const struct cmd *cmd, int type) memory_allocation_error(); nftnl_obj_set_u32(nlo, NFTNL_OBJ_FAMILY, cmd->handle.family); - nftnl_obj_set_str(nlo, NFTNL_OBJ_TABLE, cmd->handle.table.name); nftnl_obj_set_u32(nlo, NFTNL_OBJ_TYPE, type); - if (cmd->handle.obj.name) - nftnl_obj_set_str(nlo, NFTNL_OBJ_NAME, cmd->handle.obj.name); - else if (cmd->handle.handle.id) - nftnl_obj_set_u64(nlo, NFTNL_OBJ_HANDLE, cmd->handle.handle.id); + + if (cmd->op == CMD_DESTROY) + msg_type = NFT_MSG_DESTROYOBJ; nlh = nftnl_nlmsg_build_hdr(nftnl_batch_buffer(ctx->batch), - NFT_MSG_DELOBJ, cmd->handle.family, + msg_type, cmd->handle.family, 0, ctx->seqnum); + + cmd_add_loc(cmd, nlh, &cmd->handle.table.location); + mnl_attr_put_strz(nlh, NFTA_OBJ_TABLE, cmd->handle.table.name); + + if (cmd->handle.obj.name) { + cmd_add_loc(cmd, nlh, &cmd->handle.obj.location); + mnl_attr_put_strz(nlh, NFTA_OBJ_NAME, cmd->handle.obj.name); + } else if (cmd->handle.handle.id) { + cmd_add_loc(cmd, nlh, &cmd->handle.handle.location); + mnl_attr_put_u64(nlh, NFTA_OBJ_HANDLE, + htobe64(cmd->handle.handle.id)); + } + nftnl_obj_nlmsg_build_payload(nlh, nlo); nftnl_obj_free(nlo); @@ -1218,39 +1707,169 @@ static int set_elem_cb(const struct nlmsghdr *nlh, void *data) return MNL_CB_OK; } -static int mnl_nft_setelem_batch(struct nftnl_set *nls, - struct nftnl_batch *batch, - enum nf_tables_msg_types cmd, - unsigned int flags, uint32_t seqnum) +static bool mnl_nft_attr_nest_overflow(struct nlmsghdr *nlh, + const struct nlattr *from, + const struct nlattr *to) { - struct nlmsghdr *nlh; - struct nftnl_set_elems_iter *iter; - int ret; + int len = (void *)to + to->nla_len - (void *)from; + + /* The attribute length field is 16 bits long, thus the maximum payload + * that an attribute can convey is UINT16_MAX. In case of overflow, + * discard the last attribute that did not fit into the nest. + */ + if (len > UINT16_MAX) { + nlh->nlmsg_len -= to->nla_len; + return true; + } + return false; +} + +static void netlink_dump_setelem(const struct nftnl_set_elem *nlse, + struct netlink_ctx *ctx) +{ + FILE *fp = ctx->nft->output.output_fp; + char buf[4096]; + + if (!(ctx->nft->debug_mask & NFT_DEBUG_NETLINK) || !fp) + return; + + nftnl_set_elem_snprintf(buf, sizeof(buf), nlse, NFTNL_OUTPUT_DEFAULT, 0); + fprintf(fp, "\t%s", buf); +} + +static void netlink_dump_setelem_done(struct netlink_ctx *ctx) +{ + FILE *fp = ctx->nft->output.output_fp; + + if (!(ctx->nft->debug_mask & NFT_DEBUG_NETLINK) || !fp) + return; + + fprintf(fp, "\n"); +} + +static struct nftnl_set_elem * +alloc_nftnl_setelem_interval(const struct set *set, const struct expr *init, + struct expr *elem, struct expr *next_elem, + struct nftnl_set_elem **nlse_high) +{ + struct nftnl_set_elem *nlse[2] = {}; + LIST_HEAD(interval_list); + struct expr *expr, *next; + int i = 0; - iter = nftnl_set_elems_iter_create(nls); - if (iter == NULL) + if (setelem_to_interval(set, elem, next_elem, &interval_list) < 0) memory_allocation_error(); - if (cmd == NFT_MSG_NEWSETELEM) + if (list_empty(&interval_list)) { + *nlse_high = NULL; + nlse[i++] = alloc_nftnl_setelem(init, elem); + return nlse[0]; + } + + list_for_each_entry_safe(expr, next, &interval_list, list) { + nlse[i++] = alloc_nftnl_setelem(init, expr); + list_del(&expr->list); + expr_free(expr); + } + *nlse_high = nlse[1]; + + return nlse[0]; +} + +static int mnl_nft_setelem_batch(const struct nftnl_set *nls, struct cmd *cmd, + struct nftnl_batch *batch, + enum nf_tables_msg_types msg_type, + unsigned int flags, uint32_t *seqnum, + const struct set *set, const struct expr *init, + struct netlink_ctx *ctx) +{ + struct nftnl_set_elem *nlse, *nlse_high = NULL; + struct expr *expr = NULL, *next; + struct nlattr *nest1, *nest2; + struct nlmsghdr *nlh; + int i = 0; + + if (msg_type == NFT_MSG_NEWSETELEM) flags |= NLM_F_CREATE; - while (nftnl_set_elems_iter_cur(iter)) { - nlh = nftnl_nlmsg_build_hdr(nftnl_batch_buffer(batch), cmd, - nftnl_set_get_u32(nls, NFTNL_SET_FAMILY), - flags, seqnum); - ret = nftnl_set_elems_nlmsg_build_payload_iter(nlh, iter); - mnl_nft_batch_continue(batch); - if (ret <= 0) - break; + if (init) + expr = list_first_entry(&expr_set(init)->expressions, struct expr, list); + +next: + nlh = nftnl_nlmsg_build_hdr(nftnl_batch_buffer(batch), msg_type, + nftnl_set_get_u32(nls, NFTNL_SET_FAMILY), + flags, *seqnum); + + if (nftnl_set_is_set(nls, NFTNL_SET_TABLE)) { + mnl_attr_put_strz(nlh, NFTA_SET_ELEM_LIST_TABLE, + nftnl_set_get_str(nls, NFTNL_SET_TABLE)); } + if (nftnl_set_is_set(nls, NFTNL_SET_NAME)) { + mnl_attr_put_strz(nlh, NFTA_SET_ELEM_LIST_SET, + nftnl_set_get_str(nls, NFTNL_SET_NAME)); + } + if (nftnl_set_is_set(nls, NFTNL_SET_ID)) { + mnl_attr_put_u32(nlh, NFTA_SET_ELEM_LIST_SET_ID, + htonl(nftnl_set_get_u32(nls, NFTNL_SET_ID))); + } + + if (!init || list_empty(&expr_set(init)->expressions)) + return 0; + + assert(expr); + nest1 = mnl_attr_nest_start(nlh, NFTA_SET_ELEM_LIST_ELEMENTS); + list_for_each_entry_from(expr, &expr_set(init)->expressions, list) { + + if (set_is_non_concat_range(set)) { + if (set_is_anonymous(set->flags) && + !list_is_last(&expr->list, &expr_set(init)->expressions)) + next = list_next_entry(expr, list); + else + next = NULL; + + if (!nlse_high) { + nlse = alloc_nftnl_setelem_interval(set, init, expr, next, &nlse_high); + } else { + nlse = nlse_high; + nlse_high = NULL; + } + } else { + nlse = alloc_nftnl_setelem(init, expr); + } + + cmd_add_loc(cmd, nlh, &expr->location); - nftnl_set_elems_iter_destroy(iter); + /* remain with this element, range high still needs to be added. */ + if (nlse_high) + expr = list_prev_entry(expr, list); + + nest2 = mnl_attr_nest_start(nlh, ++i); + nftnl_set_elem_nlmsg_build_payload(nlh, nlse); + mnl_attr_nest_end(nlh, nest2); + + netlink_dump_setelem(nlse, ctx); + nftnl_set_elem_free(nlse); + if (mnl_nft_attr_nest_overflow(nlh, nest1, nest2)) { + if (nlse_high) { + nftnl_set_elem_free(nlse_high); + nlse_high = NULL; + } + mnl_attr_nest_end(nlh, nest1); + mnl_nft_batch_continue(batch); + mnl_seqnum_inc(seqnum); + goto next; + } + } + mnl_attr_nest_end(nlh, nest1); + mnl_nft_batch_continue(batch); + netlink_dump_setelem_done(ctx); return 0; } -int mnl_nft_setelem_add(struct netlink_ctx *ctx, const struct set *set, - const struct expr *expr, unsigned int flags) +int mnl_nft_setelem_add(struct netlink_ctx *ctx, struct cmd *cmd, + const struct set *set, const struct expr *expr, + unsigned int flags) { const struct handle *h = &set->handle; struct nftnl_set *nls; @@ -1265,12 +1884,14 @@ int mnl_nft_setelem_add(struct netlink_ctx *ctx, const struct set *set, nftnl_set_set_str(nls, NFTNL_SET_NAME, h->set.name); if (h->set_id) nftnl_set_set_u32(nls, NFTNL_SET_ID, h->set_id); + if (set_is_datamap(set->flags)) + nftnl_set_set_u32(nls, NFTNL_SET_DATA_TYPE, + dtype_map_to_kernel(set->data->dtype)); - alloc_setelem_cache(expr, nls); netlink_dump_set(nls, ctx); - err = mnl_nft_setelem_batch(nls, ctx->batch, NFT_MSG_NEWSETELEM, flags, - ctx->seqnum); + err = mnl_nft_setelem_batch(nls, cmd, ctx->batch, NFT_MSG_NEWSETELEM, + flags, &ctx->seqnum, set, expr, ctx); nftnl_set_free(nls); return err; @@ -1306,9 +1927,11 @@ int mnl_nft_setelem_flush(struct netlink_ctx *ctx, const struct cmd *cmd) return 0; } -int mnl_nft_setelem_del(struct netlink_ctx *ctx, const struct cmd *cmd) +int mnl_nft_setelem_del(struct netlink_ctx *ctx, struct cmd *cmd, + const struct handle *h, const struct set *set, + const struct expr *init) { - const struct handle *h = &cmd->handle; + enum nf_tables_msg_types msg_type = NFT_MSG_DELSETELEM; struct nftnl_set *nls; int err; @@ -1323,26 +1946,34 @@ int mnl_nft_setelem_del(struct netlink_ctx *ctx, const struct cmd *cmd) else if (h->handle.id) nftnl_set_set_u64(nls, NFTNL_SET_HANDLE, h->handle.id); - if (cmd->expr) - alloc_setelem_cache(cmd->expr, nls); netlink_dump_set(nls, ctx); - err = mnl_nft_setelem_batch(nls, ctx->batch, NFT_MSG_DELSETELEM, 0, - ctx->seqnum); + if (cmd->op == CMD_DESTROY) + msg_type = NFT_MSG_DESTROYSETELEM; + + err = mnl_nft_setelem_batch(nls, cmd, ctx->batch, msg_type, 0, + &ctx->seqnum, set, init, ctx); nftnl_set_free(nls); return err; } struct nftnl_set *mnl_nft_setelem_get_one(struct netlink_ctx *ctx, - struct nftnl_set *nls_in) + struct nftnl_set *nls_in, + bool reset) { char buf[MNL_SOCKET_BUFFER_SIZE]; struct nftnl_set *nls_out; struct nlmsghdr *nlh; + int msg_type; int err; - nlh = nftnl_nlmsg_build_hdr(buf, NFT_MSG_GETSETELEM, + if (reset) + msg_type = NFT_MSG_GETSETELEM_RESET; + else + msg_type = NFT_MSG_GETSETELEM; + + nlh = nftnl_nlmsg_build_hdr(buf, msg_type, nftnl_set_get_u32(nls_in, NFTNL_SET_FAMILY), NLM_F_ACK, ctx->seqnum); nftnl_set_elems_nlmsg_build_payload(nlh, nls_in); @@ -1365,12 +1996,19 @@ struct nftnl_set *mnl_nft_setelem_get_one(struct netlink_ctx *ctx, return nls_out; } -int mnl_nft_setelem_get(struct netlink_ctx *ctx, struct nftnl_set *nls) +int mnl_nft_setelem_get(struct netlink_ctx *ctx, struct nftnl_set *nls, + bool reset) { char buf[MNL_SOCKET_BUFFER_SIZE]; struct nlmsghdr *nlh; + int msg_type; + + if (reset) + msg_type = NFT_MSG_GETSETELEM_RESET; + else + msg_type = NFT_MSG_GETSETELEM; - nlh = nftnl_nlmsg_build_hdr(buf, NFT_MSG_GETSETELEM, + nlh = nftnl_nlmsg_build_hdr(buf, msg_type, nftnl_set_get_u32(nls, NFTNL_SET_FAMILY), NLM_F_DUMP, ctx->seqnum); nftnl_set_elems_nlmsg_build_payload(nlh, nls); @@ -1402,11 +2040,13 @@ err_free: } struct nftnl_flowtable_list * -mnl_nft_flowtable_dump(struct netlink_ctx *ctx, int family, const char *table) +mnl_nft_flowtable_dump(struct netlink_ctx *ctx, int family, + const char *table, const char *ft) { struct nftnl_flowtable_list *nln_list; char buf[MNL_SOCKET_BUFFER_SIZE]; struct nftnl_flowtable *n; + int flags = NLM_F_DUMP; struct nlmsghdr *nlh; int ret; @@ -1414,10 +2054,14 @@ mnl_nft_flowtable_dump(struct netlink_ctx *ctx, int family, const char *table) if (n == NULL) memory_allocation_error(); - nlh = nftnl_nlmsg_build_hdr(buf, NFT_MSG_GETFLOWTABLE, family, - NLM_F_DUMP, ctx->seqnum); if (table != NULL) nftnl_flowtable_set_str(n, NFTNL_FLOWTABLE_TABLE, table); + if (ft) { + nftnl_flowtable_set_str(n, NFTNL_FLOWTABLE_NAME, ft); + flags = NLM_F_ACK; + } + nlh = nftnl_nlmsg_build_hdr(buf, NFT_MSG_GETFLOWTABLE, family, + flags, ctx->seqnum); nftnl_flowtable_nlmsg_build_payload(nlh, n); nftnl_flowtable_free(n); @@ -1426,7 +2070,7 @@ mnl_nft_flowtable_dump(struct netlink_ctx *ctx, int family, const char *table) memory_allocation_error(); ret = nft_mnl_talk(ctx, nlh, nlh->nlmsg_len, flowtable_cb, nln_list); - if (ret < 0) + if (ret < 0 && errno != ENOENT) goto err; return nln_list; @@ -1435,14 +2079,30 @@ err: return NULL; } -int mnl_nft_flowtable_add(struct netlink_ctx *ctx, const struct cmd *cmd, +static void mnl_nft_ft_devs_build(struct nlmsghdr *nlh, struct cmd *cmd) +{ + const struct expr *dev_expr = cmd->flowtable->dev_expr; + const struct nft_dev *dev_array; + struct nlattr *nest_dev; + int i, num_devs= 0; + + dev_array = nft_dev_array(dev_expr, &num_devs); + nest_dev = mnl_attr_nest_start(nlh, NFTA_FLOWTABLE_HOOK_DEVS); + for (i = 0; i < num_devs; i++) { + cmd_add_loc(cmd, nlh, dev_array[i].location); + mnl_attr_put_strz(nlh, NFTA_DEVICE_NAME, dev_array[i].ifname); + } + + mnl_attr_nest_end(nlh, nest_dev); + nft_dev_array_free(dev_array); +} + +int mnl_nft_flowtable_add(struct netlink_ctx *ctx, struct cmd *cmd, unsigned int flags) { struct nftnl_flowtable *flo; - const char **dev_array; struct nlmsghdr *nlh; - int i = 0, len = 1; - struct expr *expr; + struct nlattr *nest; int priority; flo = nftnl_flowtable_alloc(); @@ -1451,34 +2111,37 @@ int mnl_nft_flowtable_add(struct netlink_ctx *ctx, const struct cmd *cmd, nftnl_flowtable_set_u32(flo, NFTNL_FLOWTABLE_FAMILY, cmd->handle.family); - nftnl_flowtable_set_str(flo, NFTNL_FLOWTABLE_TABLE, - cmd->handle.table.name); - nftnl_flowtable_set_str(flo, NFTNL_FLOWTABLE_NAME, - cmd->handle.flowtable.name); - nftnl_flowtable_set_u32(flo, NFTNL_FLOWTABLE_HOOKNUM, - cmd->flowtable->hooknum); - mpz_export_data(&priority, cmd->flowtable->priority.expr->value, - BYTEORDER_HOST_ENDIAN, sizeof(int)); - nftnl_flowtable_set_u32(flo, NFTNL_FLOWTABLE_PRIO, priority); - - list_for_each_entry(expr, &cmd->flowtable->dev_expr->expressions, list) - len++; - - dev_array = calloc(len, sizeof(char *)); - list_for_each_entry(expr, &cmd->flowtable->dev_expr->expressions, list) - dev_array[i++] = expr->identifier; - dev_array[i] = NULL; - nftnl_flowtable_set_data(flo, NFTNL_FLOWTABLE_DEVICES, - dev_array, sizeof(char *) * len); - free(dev_array); + nftnl_flowtable_set_u32(flo, NFTNL_FLOWTABLE_FLAGS, + cmd->flowtable->flags); netlink_dump_flowtable(flo, ctx); nlh = nftnl_nlmsg_build_hdr(nftnl_batch_buffer(ctx->batch), NFT_MSG_NEWFLOWTABLE, cmd->handle.family, NLM_F_CREATE | flags, ctx->seqnum); + + cmd_add_loc(cmd, nlh, &cmd->handle.table.location); + mnl_attr_put_strz(nlh, NFTA_FLOWTABLE_TABLE, cmd->handle.table.name); + cmd_add_loc(cmd, nlh, &cmd->handle.flowtable.location); + mnl_attr_put_strz(nlh, NFTA_FLOWTABLE_NAME, cmd->handle.flowtable.name); + nftnl_flowtable_nlmsg_build_payload(nlh, flo); + + nest = mnl_attr_nest_start(nlh, NFTA_FLOWTABLE_HOOK); + + if (cmd->flowtable && cmd->flowtable->priority.expr) { + mnl_attr_put_u32(nlh, NFTA_FLOWTABLE_HOOK_NUM, htonl(cmd->flowtable->hook.num)); + mpz_export_data(&priority, cmd->flowtable->priority.expr->value, + BYTEORDER_HOST_ENDIAN, sizeof(int)); + mnl_attr_put_u32(nlh, NFTA_FLOWTABLE_HOOK_PRIORITY, htonl(priority)); + } + + if (cmd->flowtable->dev_expr) + mnl_nft_ft_devs_build(nlh, cmd); + + mnl_attr_nest_end(nlh, nest); + nftnl_flowtable_free(flo); mnl_nft_batch_continue(ctx->batch); @@ -1486,10 +2149,12 @@ int mnl_nft_flowtable_add(struct netlink_ctx *ctx, const struct cmd *cmd, return 0; } -int mnl_nft_flowtable_del(struct netlink_ctx *ctx, const struct cmd *cmd) +int mnl_nft_flowtable_del(struct netlink_ctx *ctx, struct cmd *cmd) { + enum nf_tables_msg_types msg_type = NFT_MSG_DELFLOWTABLE; struct nftnl_flowtable *flo; struct nlmsghdr *nlh; + struct nlattr *nest; flo = nftnl_flowtable_alloc(); if (!flo) @@ -1497,19 +2162,36 @@ int mnl_nft_flowtable_del(struct netlink_ctx *ctx, const struct cmd *cmd) nftnl_flowtable_set_u32(flo, NFTNL_FLOWTABLE_FAMILY, cmd->handle.family); - nftnl_flowtable_set_str(flo, NFTNL_FLOWTABLE_TABLE, - cmd->handle.table.name); - if (cmd->handle.flowtable.name) - nftnl_flowtable_set_str(flo, NFTNL_FLOWTABLE_NAME, - cmd->handle.flowtable.name); - else if (cmd->handle.handle.id) - nftnl_flowtable_set_u64(flo, NFTNL_FLOWTABLE_HANDLE, - cmd->handle.handle.id); + + if (cmd->op == CMD_DESTROY) + msg_type = NFT_MSG_DESTROYFLOWTABLE; nlh = nftnl_nlmsg_build_hdr(nftnl_batch_buffer(ctx->batch), - NFT_MSG_DELFLOWTABLE, cmd->handle.family, + msg_type, cmd->handle.family, 0, ctx->seqnum); + + cmd_add_loc(cmd, nlh, &cmd->handle.table.location); + mnl_attr_put_strz(nlh, NFTA_FLOWTABLE_TABLE, cmd->handle.table.name); + + if (cmd->handle.flowtable.name) { + cmd_add_loc(cmd, nlh, &cmd->handle.flowtable.location); + mnl_attr_put_strz(nlh, NFTA_FLOWTABLE_NAME, + cmd->handle.flowtable.name); + } else if (cmd->handle.handle.id) { + cmd_add_loc(cmd, nlh, &cmd->handle.handle.location); + mnl_attr_put_u64(nlh, NFTA_FLOWTABLE_HANDLE, + htobe64(cmd->handle.handle.id)); + } + nftnl_flowtable_nlmsg_build_payload(nlh, flo); + + if (cmd->op == CMD_DELETE && + cmd->flowtable && cmd->flowtable->dev_expr) { + nest = mnl_attr_nest_start(nlh, NFTA_FLOWTABLE_HOOK); + mnl_nft_ft_devs_build(nlh, cmd); + mnl_attr_nest_end(nlh, nest); + } + nftnl_flowtable_free(flo); mnl_nft_batch_continue(ctx->batch); @@ -1528,7 +2210,7 @@ int mnl_nft_event_listener(struct mnl_socket *nf_sock, unsigned int debug_mask, void *cb_data) { /* Set netlink socket buffer size to 16 Mbytes to reduce chances of - * message loss due to ENOBUFS. + * message loss due to ENOBUFS. */ unsigned int bufsiz = NFTABLES_NLEVENT_BUFSIZ; int fd = mnl_socket_get_fd(nf_sock); @@ -1572,3 +2254,481 @@ int mnl_nft_event_listener(struct mnl_socket *nf_sock, unsigned int debug_mask, } return ret; } + +static struct basehook *basehook_alloc(void) +{ + return xzalloc(sizeof(struct basehook)); +} + +static void basehook_free(struct basehook *b) +{ + list_del(&b->list); + free_const(b->module_name); + free_const(b->hookfn); + free_const(b->chain); + free_const(b->table); + free_const(b->devname); + free(b); +} + +static bool basehook_eq(const struct basehook *prev, const struct basehook *hook) +{ + if (prev->num != hook->num) + return false; + + if (prev->devname != NULL && hook->devname != NULL) + return strcmp(prev->devname, hook->devname) == 0; + + if (prev->devname == NULL && hook->devname == NULL) + return true; + + return false; +} + +static void basehook_list_add_tail(struct basehook *b, struct list_head *head) +{ + struct basehook *hook; + + list_for_each_entry(hook, head, list) { + if (hook->family != b->family) + continue; + if (!basehook_eq(hook, b)) + continue; + if (hook->prio < b->prio) + continue; + + list_add(&b->list, &hook->list); + return; + } + + list_add_tail(&b->list, head); +} + +static int dump_nf_attr_cb(const struct nlattr *attr, void *data) +{ + int type = mnl_attr_get_type(attr); + const struct nlattr **tb = data; + + if (mnl_attr_type_valid(attr, NFNLA_HOOK_MAX) < 0) + return MNL_CB_OK; + + switch(type) { + case NFNLA_HOOK_HOOKNUM: + case NFNLA_HOOK_PRIORITY: + if (mnl_attr_validate(attr, MNL_TYPE_U32) < 0) + return MNL_CB_ERROR; + break; + case NFNLA_HOOK_DEV: + if (mnl_attr_validate(attr, MNL_TYPE_STRING) < 0) + return MNL_CB_ERROR; + break; + case NFNLA_HOOK_MODULE_NAME: + case NFNLA_HOOK_FUNCTION_NAME: + if (mnl_attr_validate(attr, MNL_TYPE_NUL_STRING) < 0) + return MNL_CB_ERROR; + break; + case NFNLA_HOOK_CHAIN_INFO: + if (mnl_attr_validate(attr, MNL_TYPE_NESTED) < 0) + return MNL_CB_ERROR; + break; + default: + return MNL_CB_OK; + } + + tb[type] = attr; + return MNL_CB_OK; +} + +static int dump_nf_chain_info_cb(const struct nlattr *attr, void *data) +{ + int type = mnl_attr_get_type(attr); + const struct nlattr **tb = data; + + if (mnl_attr_type_valid(attr, NFNLA_HOOK_INFO_MAX) < 0) + return MNL_CB_OK; + + switch(type) { + case NFNLA_HOOK_INFO_DESC: + if (mnl_attr_validate(attr, MNL_TYPE_NESTED) < 0) + return MNL_CB_ERROR; + break; + case NFNLA_HOOK_INFO_TYPE: + if (mnl_attr_validate(attr, MNL_TYPE_U32) < 0) + return MNL_CB_ERROR; + break; + default: + return MNL_CB_OK; + } + + tb[type] = attr; + return MNL_CB_OK; +} + +static int dump_nf_attr_chain_cb(const struct nlattr *attr, void *data) +{ + int type = mnl_attr_get_type(attr); + const struct nlattr **tb = data; + + if (mnl_attr_type_valid(attr, NFNLA_CHAIN_MAX) < 0) + return MNL_CB_OK; + + switch(type) { + case NFNLA_CHAIN_TABLE: + case NFNLA_CHAIN_NAME: + if (mnl_attr_validate(attr, MNL_TYPE_NUL_STRING) < 0) + return MNL_CB_ERROR; + break; + case NFNLA_CHAIN_FAMILY: + if (mnl_attr_validate(attr, MNL_TYPE_U8) < 0) + return MNL_CB_ERROR; + break; + default: + return MNL_CB_OK; + } + + tb[type] = attr; + return MNL_CB_OK; +} + +static int dump_nf_attr_bpf_cb(const struct nlattr *attr, void *data) +{ + int type = mnl_attr_get_type(attr); + const struct nlattr **tb = data; + + if (mnl_attr_type_valid(attr, NFNLA_HOOK_BPF_MAX) < 0) + return MNL_CB_OK; + + switch(type) { + case NFNLA_HOOK_BPF_ID: + if (mnl_attr_validate(attr, MNL_TYPE_U32) < 0) + return MNL_CB_ERROR; + break; + default: + return MNL_CB_OK; + } + + tb[type] = attr; + return MNL_CB_OK; +} + +struct dump_nf_hook_data { + struct list_head *hook_list; + const char *devname; + int family; +}; + +static int dump_nf_hooks(const struct nlmsghdr *nlh, void *_data) +{ + const struct nfgenmsg *nfg = mnl_nlmsg_get_payload(nlh); + struct nlattr *tb[NFNLA_HOOK_MAX + 1] = {}; + struct dump_nf_hook_data *data = _data; + struct basehook *hook; + + /* NB: Don't check the nft generation ID, this is not + * an nftables subsystem. + */ + if (mnl_attr_parse(nlh, sizeof(*nfg), dump_nf_attr_cb, tb) < 0) + return -1; + + if (!tb[NFNLA_HOOK_PRIORITY]) + netlink_abi_error(); + + hook = basehook_alloc(); + hook->prio = ntohl(mnl_attr_get_u32(tb[NFNLA_HOOK_PRIORITY])); + hook->devname = data->devname ? xstrdup(data->devname) : NULL; + + if (tb[NFNLA_HOOK_FUNCTION_NAME]) + hook->hookfn = xstrdup(mnl_attr_get_str(tb[NFNLA_HOOK_FUNCTION_NAME])); + + if (tb[NFNLA_HOOK_MODULE_NAME]) + hook->module_name = xstrdup(mnl_attr_get_str(tb[NFNLA_HOOK_MODULE_NAME])); + + if (tb[NFNLA_HOOK_CHAIN_INFO]) { + struct nlattr *nested[NFNLA_HOOK_INFO_MAX + 1] = {}; + uint32_t type; + + if (mnl_attr_parse_nested(tb[NFNLA_HOOK_CHAIN_INFO], + dump_nf_chain_info_cb, nested) < 0) { + basehook_free(hook); + return -1; + } + + type = ntohl(mnl_attr_get_u32(nested[NFNLA_HOOK_INFO_TYPE])); + if (type == NFNL_HOOK_TYPE_NFTABLES) { + struct nlattr *info[NFNLA_CHAIN_MAX + 1] = {}; + const char *tablename, *chainname; + + if (mnl_attr_parse_nested(nested[NFNLA_HOOK_INFO_DESC], + dump_nf_attr_chain_cb, + info) < 0) { + basehook_free(hook); + return -1; + } + + tablename = mnl_attr_get_str(info[NFNLA_CHAIN_TABLE]); + chainname = mnl_attr_get_str(info[NFNLA_CHAIN_NAME]); + if (tablename && chainname) { + hook->table = xstrdup(tablename); + hook->chain = xstrdup(chainname); + } + hook->chain_family = mnl_attr_get_u8(info[NFNLA_CHAIN_FAMILY]); + } else if (type == NFNL_HOOK_TYPE_BPF) { + struct nlattr *info[NFNLA_HOOK_BPF_MAX + 1] = {}; + + if (mnl_attr_parse_nested(nested[NFNLA_HOOK_INFO_DESC], + dump_nf_attr_bpf_cb, info) < 0) { + basehook_free(hook); + return -1; + } + + if (info[NFNLA_HOOK_BPF_ID]) { + char tmpbuf[16]; + + snprintf(tmpbuf, sizeof(tmpbuf), "id %u", + ntohl(mnl_attr_get_u32(info[NFNLA_HOOK_BPF_ID]))); + + hook->chain = xstrdup(tmpbuf); + } + } + } + if (tb[NFNLA_HOOK_HOOKNUM]) + hook->num = ntohl(mnl_attr_get_u32(tb[NFNLA_HOOK_HOOKNUM])); + + hook->family = nfg->nfgen_family; + + basehook_list_add_tail(hook, data->hook_list); + + return MNL_CB_OK; +} + +static struct nlmsghdr *nf_hook_dump_request(char *buf, uint8_t family, uint32_t seq) +{ + 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 = seq; + + nfg = mnl_nlmsg_put_extra_header(nlh, sizeof(*nfg)); + nfg->nfgen_family = family; + nfg->version = NFNETLINK_V0; + + return nlh; +} + +static int __mnl_nft_dump_nf_hooks(struct netlink_ctx *ctx, uint8_t query_family, + uint8_t family, uint8_t hooknum, + const char *devname, + struct list_head *hook_list) +{ + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct dump_nf_hook_data data = { + .hook_list = hook_list, + .devname = devname, + .family = query_family, + }; + struct nlmsghdr *nlh; + + nlh = nf_hook_dump_request(buf, family, ctx->seqnum); + 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(ctx, nlh, nlh->nlmsg_len, dump_nf_hooks, &data); +} + +static void print_hooks(struct netlink_ctx *ctx, int family, struct list_head *hook_list) +{ + struct basehook *hook, *tmp, *prev = NULL; + bool same, family_in_use = false; + int prio; + FILE *fp; + + fp = ctx->nft->output.output_fp; + + list_for_each_entry_safe(hook, tmp, hook_list, list) { + if (hook->family == family) { + family_in_use = true; + break; + } + } + + if (!family_in_use) + return; + + fprintf(fp, "family %s {\n", family2str(family)); + + list_for_each_entry_safe(hook, tmp, hook_list, list) { + if (hook->family != family) + continue; + + if (prev) { + if (basehook_eq(prev, hook)) { + fprintf(fp, "\n"); + same = true; + } else { + same = false; + fprintf(fp, "\n\t}\n"); + } + } else { + same = false; + } + prev = hook; + + if (!same) { + if (hook->devname) + fprintf(fp, "\thook %s device %s {\n", + hooknum2str(family, hook->num), hook->devname); + else + fprintf(fp, "\thook %s {\n", + hooknum2str(family, hook->num)); + } + + prio = hook->prio; + if (prio < 0) + fprintf(fp, "\t\t%011d", prio); /* outputs a '-' sign */ + else if (prio == 0) + fprintf(fp, "\t\t %010u", prio); + else + fprintf(fp, "\t\t+%010u", prio); + + if (hook->table && hook->chain) + fprintf(fp, " chain %s %s %s", family2str(hook->chain_family), hook->table, hook->chain); + else if (hook->hookfn && hook->chain) + fprintf(fp, " %s %s", hook->hookfn, hook->chain); + else if (hook->hookfn) { + fprintf(fp, " %s", hook->hookfn); + } + if (hook->module_name) + fprintf(fp, " [%s]", hook->module_name); + } + + fprintf(fp, "\n\t}\n"); + fprintf(fp, "}\n"); +} + +static int mnl_nft_dump_nf(struct netlink_ctx *ctx, int family, + const char *devname, struct list_head *hook_list) +{ + int i, err; + + for (i = 0; i <= NF_INET_POST_ROUTING; i++) { + int tmp; + + tmp = __mnl_nft_dump_nf_hooks(ctx, family, family, i, devname, hook_list); + if (tmp == 0) + err = 0; + } + + return err; +} + +static int mnl_nft_dump_nf_arp(struct netlink_ctx *ctx, int family, + const char *devname, struct list_head *hook_list) +{ + int err1, err2; + + err1 = __mnl_nft_dump_nf_hooks(ctx, family, family, NF_ARP_IN, devname, hook_list); + err2 = __mnl_nft_dump_nf_hooks(ctx, family, family, NF_ARP_OUT, devname, hook_list); + + return err1 ? err2 : err1; +} + +static int mnl_nft_dump_nf_netdev(struct netlink_ctx *ctx, int family, + const char *devname, struct list_head *hook_list) +{ + int err1, err2; + + err1 = __mnl_nft_dump_nf_hooks(ctx, family, NFPROTO_NETDEV, NF_NETDEV_INGRESS, devname, hook_list); + err2 = __mnl_nft_dump_nf_hooks(ctx, family, NFPROTO_NETDEV, NF_NETDEV_EGRESS, devname, hook_list); + + return err1 ? err2 : err1; +} + +static void release_hook_list(struct list_head *hook_list) +{ + struct basehook *hook, *next; + + list_for_each_entry_safe(hook, next, hook_list, list) + basehook_free(hook); +} + +static void warn_if_device(struct nft_ctx *nft, const char *devname) +{ + if (devname) + nft_print(&nft->output, "# device keyword (%s) unexpected for this family\n", devname); +} + +int mnl_nft_dump_nf_hooks(struct netlink_ctx *ctx, int family, const char *devname) +{ + LIST_HEAD(hook_list); + int ret = -1, tmp; + + errno = 0; + + switch (family) { + case NFPROTO_UNSPEC: + ret = mnl_nft_dump_nf_hooks(ctx, NFPROTO_ARP, NULL); + tmp = mnl_nft_dump_nf_hooks(ctx, NFPROTO_INET, NULL); + if (tmp == 0) + ret = 0; + tmp = mnl_nft_dump_nf_hooks(ctx, NFPROTO_BRIDGE, NULL); + if (tmp == 0) + ret = 0; + + tmp = mnl_nft_dump_nf_hooks(ctx, NFPROTO_NETDEV, devname); + if (tmp == 0) + ret = 0; + + return ret; + case NFPROTO_INET: + ret = 0; + if (devname) + ret = __mnl_nft_dump_nf_hooks(ctx, family, NFPROTO_NETDEV, + NF_NETDEV_INGRESS, devname, &hook_list); + tmp = mnl_nft_dump_nf_hooks(ctx, NFPROTO_IPV4, NULL); + if (tmp == 0) + ret = 0; + tmp = mnl_nft_dump_nf_hooks(ctx, NFPROTO_IPV6, NULL); + if (tmp == 0) + ret = 0; + + break; + case NFPROTO_IPV4: + case NFPROTO_IPV6: + case NFPROTO_BRIDGE: + warn_if_device(ctx->nft, devname); + ret = mnl_nft_dump_nf(ctx, family, devname, &hook_list); + break; + case NFPROTO_ARP: + warn_if_device(ctx->nft, devname); + ret = mnl_nft_dump_nf_arp(ctx, family, devname, &hook_list); + break; + case NFPROTO_NETDEV: + if (devname) { + ret = mnl_nft_dump_nf_netdev(ctx, family, devname, &hook_list); + } else { + const struct iface *iface; + + iface = iface_cache_get_next_entry(NULL); + ret = 0; + + while (iface) { + tmp = mnl_nft_dump_nf_netdev(ctx, family, iface->name, &hook_list); + if (tmp == 0) + ret = 0; + + iface = iface_cache_get_next_entry(iface); + } + } + + break; + } + + print_hooks(ctx, family, &hook_list); + release_hook_list(&hook_list); + + return ret; +} diff --git a/src/monitor.c b/src/monitor.c index 142cc929..e0f97b4a 100644 --- a/src/monitor.c +++ b/src/monitor.c @@ -2,21 +2,20 @@ * Copyright (c) 2015 Arturo Borrero Gonzalez <arturo@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 as - * published by the Free Software Foundation. + * it under the terms of the GNU General Public License version 2 (or any + * later) as published by the Free Software Foundation. */ -#include <string.h> +#include <nft.h> + #include <fcntl.h> #include <errno.h> #include <libmnl/libmnl.h> #include <netinet/in.h> #include <arpa/inet.h> -#include <stdlib.h> #include <inttypes.h> #include <libnftnl/table.h> -#include <libnftnl/trace.h> #include <libnftnl/chain.h> #include <libnftnl/expr.h> #include <libnftnl/object.h> @@ -32,6 +31,7 @@ #include <nftables.h> #include <netlink.h> #include <mnl.h> +#include <trace.h> #include <expression.h> #include <statement.h> #include <gmputil.h> @@ -40,6 +40,13 @@ #include <iface.h> #include <json.h> +enum { + NFT_OF_EVENT_ADD, + NFT_OF_EVENT_INSERT, + NFT_OF_EVENT_DEL, + NFT_OF_EVENT_CREATE, +}; + #define nft_mon_print(monh, ...) nft_print(&monh->ctx->nft->output, __VA_ARGS__) struct nftnl_table *netlink_table_alloc(const struct nlmsghdr *nlh) @@ -120,17 +127,37 @@ struct nftnl_obj *netlink_obj_alloc(const struct nlmsghdr *nlh) return nlo; } -static uint32_t netlink_msg2nftnl_of(uint32_t msg) +struct nftnl_flowtable *netlink_flowtable_alloc(const struct nlmsghdr *nlh) { - switch (msg) { + struct nftnl_flowtable *nlf; + + nlf = nftnl_flowtable_alloc(); + if (nlf == NULL) + memory_allocation_error(); + if (nftnl_flowtable_nlmsg_parse(nlh, nlf) < 0) + netlink_abi_error(); + + return nlf; +} + +static uint32_t netlink_msg2nftnl_of(uint32_t type, uint16_t flags) +{ + switch (type) { + case NFT_MSG_NEWRULE: + if (flags & NLM_F_APPEND) + return NFT_OF_EVENT_ADD; + else + return NFT_OF_EVENT_INSERT; case NFT_MSG_NEWTABLE: case NFT_MSG_NEWCHAIN: case NFT_MSG_NEWSET: case NFT_MSG_NEWSETELEM: - case NFT_MSG_NEWRULE: case NFT_MSG_NEWOBJ: case NFT_MSG_NEWFLOWTABLE: - return NFTNL_OF_EVENT_NEW; + if (flags & NLM_F_EXCL) + return NFT_OF_EVENT_CREATE; + else + return NFT_OF_EVENT_ADD; case NFT_MSG_DELTABLE: case NFT_MSG_DELCHAIN: case NFT_MSG_DELSET: @@ -147,18 +174,22 @@ static uint32_t netlink_msg2nftnl_of(uint32_t msg) static const char *nftnl_of2cmd(uint32_t of) { switch (of) { - case NFTNL_OF_EVENT_NEW: + case NFT_OF_EVENT_ADD: return "add"; - case NFTNL_OF_EVENT_DEL: + case NFT_OF_EVENT_CREATE: + return "create"; + case NFT_OF_EVENT_INSERT: + return "insert"; + case NFT_OF_EVENT_DEL: return "delete"; default: return "???"; } } -static const char *netlink_msg2cmd(uint32_t msg) +static const char *netlink_msg2cmd(uint32_t type, uint16_t flags) { - return nftnl_of2cmd(netlink_msg2nftnl_of(msg)); + return nftnl_of2cmd(netlink_msg2nftnl_of(type, flags)); } static void nlr_for_each_set(struct nftnl_rule *nlr, @@ -206,7 +237,7 @@ static int netlink_events_table_cb(const struct nlmsghdr *nlh, int type, nlt = netlink_table_alloc(nlh); t = netlink_delinearize_table(monh->ctx, nlt); - cmd = netlink_msg2cmd(type); + cmd = netlink_msg2cmd(type, nlh->nlmsg_flags); switch (monh->format) { case NFTNL_OUTPUT_DEFAULT: @@ -214,15 +245,21 @@ static int netlink_events_table_cb(const struct nlmsghdr *nlh, int type, nft_mon_print(monh, "%s %s", family2str(t->handle.family), t->handle.table.name); + + if (t->flags & TABLE_F_DORMANT) + nft_mon_print(monh, " { flags dormant; }"); + if (nft_output_handle(&monh->ctx->nft->output)) nft_mon_print(monh, " # handle %" PRIu64 "", t->handle.handle.id); + nft_mon_print(monh, "\n"); break; case NFTNL_OUTPUT_JSON: monitor_print_table_json(monh, cmd, t); + if (!nft_output_echo(&monh->ctx->nft->output)) + nft_mon_print(monh, "\n"); break; } - nft_mon_print(monh, "\n"); table_free(t); nftnl_table_free(nlt); return MNL_CB_OK; @@ -237,7 +274,7 @@ static int netlink_events_chain_cb(const struct nlmsghdr *nlh, int type, nlc = netlink_chain_alloc(nlh); c = netlink_delinearize_chain(monh->ctx, nlc); - cmd = netlink_msg2cmd(type); + cmd = netlink_msg2cmd(type, nlh->nlmsg_flags); switch (monh->format) { case NFTNL_OUTPUT_DEFAULT: @@ -248,18 +285,23 @@ static int netlink_events_chain_cb(const struct nlmsghdr *nlh, int type, chain_print_plain(c, &monh->ctx->nft->output); break; case NFT_MSG_DELCHAIN: - nft_mon_print(monh, "chain %s %s %s", - family2str(c->handle.family), - c->handle.table.name, - c->handle.chain.name); + if (c->dev_array_len > 0) + chain_print_plain(c, &monh->ctx->nft->output); + else + nft_mon_print(monh, "chain %s %s %s", + family2str(c->handle.family), + c->handle.table.name, + c->handle.chain.name); break; } + nft_mon_print(monh, "\n"); break; case NFTNL_OUTPUT_JSON: monitor_print_chain_json(monh, cmd, c); + if (!nft_output_echo(&monh->ctx->nft->output)) + nft_mon_print(monh, "\n"); break; } - nft_mon_print(monh, "\n"); chain_free(c); nftnl_chain_free(nlc); return MNL_CB_OK; @@ -284,7 +326,7 @@ static int netlink_events_set_cb(const struct nlmsghdr *nlh, int type, return MNL_CB_ERROR; } family = family2str(set->handle.family); - cmd = netlink_msg2cmd(type); + cmd = netlink_msg2cmd(type, nlh->nlmsg_flags); switch (monh->format) { case NFTNL_OUTPUT_DEFAULT: @@ -300,12 +342,14 @@ static int netlink_events_set_cb(const struct nlmsghdr *nlh, int type, set->handle.set.name); break; } + nft_mon_print(monh, "\n"); break; case NFTNL_OUTPUT_JSON: monitor_print_set_json(monh, cmd, set); + if (!nft_output_echo(&monh->ctx->nft->output)) + nft_mon_print(monh, "\n"); break; } - nft_mon_print(monh, "\n"); set_free(set); out: nftnl_set_free(nls); @@ -332,7 +376,7 @@ static bool set_elem_is_open_interval(struct expr *elem) { switch (elem->etype) { case EXPR_SET_ELEM: - return elem->elem_flags & NFTNL_SET_ELEM_F_INTERVAL_OPEN; + return elem->flags & EXPR_F_INTERVAL_OPEN; case EXPR_MAPPING: return set_elem_is_open_interval(elem->left); default: @@ -358,14 +402,20 @@ static bool netlink_event_range_cache(struct set *cached_set, } /* don't cache half-open range elements */ - elem = list_entry(dummyset->init->expressions.prev, struct expr, list); - if (!set_elem_is_open_interval(elem)) { + elem = list_entry(expr_set(dummyset->init)->expressions.prev, struct expr, list); + if (!set_elem_is_open_interval(elem) && + dummyset->desc.field_count <= 1) { cached_set->rg_cache = expr_clone(elem); return true; } out_decompose: - interval_map_decompose(dummyset->init); + if (dummyset->flags & NFT_SET_INTERVAL && + dummyset->desc.field_count > 1) + concat_range_aggregate(dummyset->init); + else + interval_map_decompose(dummyset->init); + return false; } @@ -384,7 +434,7 @@ static int netlink_events_setelem_cb(const struct nlmsghdr *nlh, int type, table = nftnl_set_get_str(nls, NFTNL_SET_TABLE); setname = nftnl_set_get_str(nls, NFTNL_SET_NAME); family = nftnl_set_get_u32(nls, NFTNL_SET_FAMILY); - cmd = netlink_msg2cmd(type); + cmd = netlink_msg2cmd(type, nlh->nlmsg_flags); set = set_lookup_global(family, table, setname, &monh->ctx->nft->cache); if (set == NULL) { @@ -400,11 +450,13 @@ static int netlink_events_setelem_cb(const struct nlmsghdr *nlh, int type, * used by named sets, so use a dummy set. */ dummyset = set_alloc(monh->loc); + handle_merge(&dummyset->handle, &set->handle); dummyset->key = expr_clone(set->key); if (set->data) dummyset->data = expr_clone(set->data); dummyset->flags = set->flags; dummyset->init = set_expr_alloc(monh->loc, set); + dummyset->desc.field_count = set->desc.field_count; nlsei = nftnl_set_elems_iter_create(nls); if (nlsei == NULL) @@ -417,8 +469,8 @@ static int netlink_events_setelem_cb(const struct nlmsghdr *nlh, int type, nftnl_set_elems_iter_destroy(nlsei); goto out; } - if (netlink_delinearize_setelem(nlse, dummyset, - &monh->ctx->nft->cache) < 0) { + if (netlink_delinearize_setelem(monh->ctx, + nlse, dummyset) < 0) { set_free(dummyset); nftnl_set_elems_iter_destroy(nlsei); goto out; @@ -437,6 +489,7 @@ static int netlink_events_setelem_cb(const struct nlmsghdr *nlh, int type, nft_mon_print(monh, "%s element %s %s %s ", cmd, family2str(family), table, setname); expr_print(dummyset->init, &monh->ctx->nft->output); + nft_mon_print(monh, "\n"); break; case NFTNL_OUTPUT_JSON: dummyset->handle.family = family; @@ -446,9 +499,10 @@ static int netlink_events_setelem_cb(const struct nlmsghdr *nlh, int type, /* prevent set_free() from trying to free those */ dummyset->handle.set.name = NULL; dummyset->handle.table.name = NULL; + if (!nft_output_echo(&monh->ctx->nft->output)) + nft_mon_print(monh, "\n"); break; } - nft_mon_print(monh, "\n"); set_free(dummyset); out: nftnl_set_free(nls); @@ -465,12 +519,12 @@ static int netlink_events_obj_cb(const struct nlmsghdr *nlh, int type, nlo = netlink_obj_alloc(nlh); obj = netlink_delinearize_obj(monh->ctx, nlo); - if (obj == NULL) { + if (!obj) { nftnl_obj_free(nlo); return MNL_CB_ERROR; } family = family2str(obj->handle.family); - cmd = netlink_msg2cmd(type); + cmd = netlink_msg2cmd(type, nlh->nlmsg_flags); switch (monh->format) { case NFTNL_OUTPUT_DEFAULT: @@ -488,21 +542,76 @@ static int netlink_events_obj_cb(const struct nlmsghdr *nlh, int type, obj->handle.obj.name); break; } + nft_mon_print(monh, "\n"); break; case NFTNL_OUTPUT_JSON: monitor_print_obj_json(monh, cmd, obj); + if (!nft_output_echo(&monh->ctx->nft->output)) + nft_mon_print(monh, "\n"); break; } - nft_mon_print(monh, "\n"); obj_free(obj); nftnl_obj_free(nlo); return MNL_CB_OK; } +static int netlink_events_flowtable_cb(const struct nlmsghdr *nlh, int type, + struct netlink_mon_handler *monh) +{ + const char *family, *cmd; + struct nftnl_flowtable *nlf; + struct flowtable *ft; + + nlf = netlink_flowtable_alloc(nlh); + + ft = netlink_delinearize_flowtable(monh->ctx, nlf); + if (!ft) { + nftnl_flowtable_free(nlf); + return MNL_CB_ERROR; + } + family = family2str(ft->handle.family); + cmd = netlink_msg2cmd(type, nlh->nlmsg_flags); + + switch (monh->format) { + case NFTNL_OUTPUT_DEFAULT: + nft_mon_print(monh, "%s ", cmd); + + switch (type) { + case NFT_MSG_DELFLOWTABLE: + if (!ft->dev_array_len) { + nft_mon_print(monh, "flowtable %s %s %s", + family, + ft->handle.table.name, + ft->handle.flowtable.name); + break; + } + /* fall through */ + case NFT_MSG_NEWFLOWTABLE: + flowtable_print_plain(ft, &monh->ctx->nft->output); + break; + } + nft_mon_print(monh, "\n"); + break; + case NFTNL_OUTPUT_JSON: + monitor_print_flowtable_json(monh, cmd, ft); + if (!nft_output_echo(&monh->ctx->nft->output)) + nft_mon_print(monh, "\n"); + break; + } + flowtable_free(ft); + nftnl_flowtable_free(nlf); + return MNL_CB_OK; +} + static void rule_map_decompose_cb(struct set *s, void *data) { - if (set_is_interval(s->flags) && set_is_anonymous(s->flags)) + if (!set_is_anonymous(s->flags)) + return; + + if (set_is_non_concat_range(s)) interval_map_decompose(s->init); + else if (set_is_interval(s->flags)) + concat_range_aggregate(s->init); } static int netlink_events_rule_cb(const struct nlmsghdr *nlh, int type, @@ -514,9 +623,13 @@ static int netlink_events_rule_cb(const struct nlmsghdr *nlh, int type, nlr = netlink_rule_alloc(nlh); r = netlink_delinearize_rule(monh->ctx, nlr); + if (!r) { + fprintf(stderr, "W: Received event for an unknown table.\n"); + goto out_free_nlr; + } nlr_for_each_set(nlr, rule_map_decompose_cb, NULL, &monh->ctx->nft->cache); - cmd = netlink_msg2cmd(type); + cmd = netlink_msg2cmd(type, nlh->nlmsg_flags); switch (monh->format) { case NFTNL_OUTPUT_DEFAULT: @@ -527,7 +640,10 @@ static int netlink_events_rule_cb(const struct nlmsghdr *nlh, int type, family, r->handle.table.name, r->handle.chain.name); - + if (r->handle.position.id) { + nft_mon_print(monh, "handle %" PRIu64" ", + r->handle.position.id); + } switch (type) { case NFT_MSG_NEWRULE: rule_print(r, &monh->ctx->nft->output); @@ -538,13 +654,16 @@ static int netlink_events_rule_cb(const struct nlmsghdr *nlh, int type, r->handle.handle.id); break; } + nft_mon_print(monh, "\n"); break; case NFTNL_OUTPUT_JSON: monitor_print_rule_json(monh, cmd, r); + if (!nft_output_echo(&monh->ctx->nft->output)) + nft_mon_print(monh, "\n"); break; } - nft_mon_print(monh, "\n"); rule_free(r); +out_free_nlr: nftnl_rule_free(nlr); return MNL_CB_OK; } @@ -559,7 +678,7 @@ static void netlink_events_cache_addtable(struct netlink_mon_handler *monh, t = netlink_delinearize_table(monh->ctx, nlt); nftnl_table_free(nlt); - table_add_hash(t, &monh->ctx->nft->cache); + table_cache_add(t, &monh->ctx->nft->cache); } static void netlink_events_cache_deltable(struct netlink_mon_handler *monh, @@ -573,11 +692,12 @@ static void netlink_events_cache_deltable(struct netlink_mon_handler *monh, h.family = nftnl_table_get_u32(nlt, NFTNL_TABLE_FAMILY); h.table.name = nftnl_table_get_str(nlt, NFTNL_TABLE_NAME); - t = table_lookup(&h, &monh->ctx->nft->cache); + t = table_cache_find(&monh->ctx->nft->cache.table_cache, + h.table.name, h.family); if (t == NULL) goto out; - list_del(&t->list); + table_cache_del(t); table_free(t); out: nftnl_table_free(nlt); @@ -595,6 +715,7 @@ static void netlink_events_cache_addset(struct netlink_mon_handler *monh, memset(&set_tmpctx, 0, sizeof(set_tmpctx)); init_list_head(&set_tmpctx.list); init_list_head(&msgs); + set_tmpctx.nft = monh->ctx->nft; set_tmpctx.msgs = &msgs; nls = netlink_set_alloc(nlh); @@ -603,7 +724,8 @@ static void netlink_events_cache_addset(struct netlink_mon_handler *monh, goto out; s->init = set_expr_alloc(monh->loc, s); - t = table_lookup(&s->handle, &monh->ctx->nft->cache); + t = table_cache_find(&monh->ctx->nft->cache.table_cache, + s->handle.table.name, s->handle.family); if (t == NULL) { fprintf(stderr, "W: Unable to cache set: table not found.\n"); set_free(s); @@ -616,7 +738,7 @@ static void netlink_events_cache_addset(struct netlink_mon_handler *monh, goto out; } - set_add_hash(s, t); + set_cache_add(s, t); out: nftnl_set_free(nls); } @@ -653,8 +775,7 @@ static void netlink_events_cache_addsetelem(struct netlink_mon_handler *monh, nlse = nftnl_set_elems_iter_next(nlsei); while (nlse != NULL) { - if (netlink_delinearize_setelem(nlse, set, - &monh->ctx->nft->cache) < 0) { + if (netlink_delinearize_setelem(monh->ctx, nlse, set) < 0) { fprintf(stderr, "W: Unable to cache set_elem. " "Delinearize failed.\n"); @@ -671,7 +792,7 @@ out: static void netlink_events_cache_delset_cb(struct set *s, void *data) { - list_del(&s->list); + set_cache_del(s); set_free(s); } @@ -704,14 +825,15 @@ static void netlink_events_cache_addobj(struct netlink_mon_handler *monh, if (obj == NULL) goto out; - t = table_lookup(&obj->handle, &monh->ctx->nft->cache); + t = table_cache_find(&monh->ctx->nft->cache.table_cache, + obj->handle.table.name, obj->handle.family); if (t == NULL) { fprintf(stderr, "W: Unable to cache object: table not found.\n"); obj_free(obj); goto out; } - obj_add_hash(obj, t); + obj_cache_add(obj, t); out: nftnl_obj_free(nlo); } @@ -734,19 +856,20 @@ static void netlink_events_cache_delobj(struct netlink_mon_handler *monh, type = nftnl_obj_get_u32(nlo, NFTNL_OBJ_TYPE); h.handle.id = nftnl_obj_get_u64(nlo, NFTNL_OBJ_HANDLE); - t = table_lookup(&h, &monh->ctx->nft->cache); + t = table_cache_find(&monh->ctx->nft->cache.table_cache, + h.table.name, h.family); if (t == NULL) { fprintf(stderr, "W: Unable to cache object: table not found.\n"); goto out; } - obj = obj_lookup(t, name, type); + obj = obj_cache_find(t, name, type); if (obj == NULL) { fprintf(stderr, "W: Unable to find object in cache\n"); goto out; } - list_del(&obj->list); + obj_cache_del(obj); obj_free(obj); out: nftnl_obj_free(nlo); @@ -826,6 +949,9 @@ static int netlink_events_newgen_cb(const struct nlmsghdr *nlh, int type, char name[256] = ""; int genid = -1, pid = -1; + if (monh->format != NFTNL_OUTPUT_DEFAULT) + return MNL_CB_OK; + mnl_attr_for_each(attr, nlh, sizeof(struct nfgenmsg)) { switch (mnl_attr_get_type(attr)) { case NFTA_GEN_ID: @@ -896,6 +1022,10 @@ static int netlink_events_cb(const struct nlmsghdr *nlh, void *data) case NFT_MSG_DELOBJ: ret = netlink_events_obj_cb(nlh, type, monh); break; + case NFT_MSG_NEWFLOWTABLE: + case NFT_MSG_DELFLOWTABLE: + ret = netlink_events_flowtable_cb(nlh, type, monh); + break; case NFT_MSG_NEWGEN: ret = netlink_events_newgen_cb(nlh, type, monh); break; @@ -906,7 +1036,10 @@ static int netlink_events_cb(const struct nlmsghdr *nlh, void *data) int netlink_echo_callback(const struct nlmsghdr *nlh, void *data) { - struct netlink_ctx *ctx = data; + struct netlink_cb_data *nl_cb_data = data; + struct netlink_ctx *ctx = nl_cb_data->nl_ctx; + struct nft_ctx *nft = ctx->nft; + struct netlink_mon_handler echo_monh = { .format = NFTNL_OUTPUT_DEFAULT, .ctx = ctx, @@ -917,8 +1050,13 @@ int netlink_echo_callback(const struct nlmsghdr *nlh, void *data) if (!nft_output_echo(&echo_monh.ctx->nft->output)) return MNL_CB_OK; - if (nft_output_json(&ctx->nft->output)) - return json_events_cb(nlh, &echo_monh); + if (nft_output_json(&nft->output)) { + if (nft->json_root) + return json_events_cb(nlh, &echo_monh); + if (!nft->json_echo) + json_alloc_echo(nft); + echo_monh.format = NFTNL_OUTPUT_JSON; + } return netlink_events_cb(nlh, &echo_monh); } diff --git a/src/netlink.c b/src/netlink.c index a9ccebaf..f2f4c5ea 100644 --- a/src/netlink.c +++ b/src/netlink.c @@ -9,16 +9,15 @@ * Development of this code funded by Astaro AG (http://www.astaro.com/) */ -#include <string.h> +#include <nft.h> + #include <errno.h> #include <libmnl/libmnl.h> #include <netinet/in.h> #include <arpa/inet.h> -#include <stdlib.h> #include <inttypes.h> #include <libnftnl/table.h> -#include <libnftnl/trace.h> #include <libnftnl/chain.h> #include <libnftnl/expr.h> #include <libnftnl/object.h> @@ -41,7 +40,6 @@ #include <gmputil.h> #include <utils.h> #include <erec.h> -#include <iface.h> #define nft_mon_print(monh, ...) nft_print(&monh->ctx->nft->output, __VA_ARGS__) @@ -59,7 +57,7 @@ void __noreturn __netlink_abi_error(const char *file, int line, { fprintf(stderr, "E: Contact urgently your Linux kernel vendor. " "Netlink ABI is broken: %s:%d %s\n", file, line, reason); - exit(NFT_EXIT_FAILURE); + abort(); } int netlink_io_error(struct netlink_ctx *ctx, const struct location *loc, @@ -96,14 +94,22 @@ struct nftnl_expr *alloc_nft_expr(const char *name) return nle; } +static void netlink_gen_key(const struct expr *expr, + struct nft_data_linearize *data); +static void __netlink_gen_data(const struct expr *expr, + struct nft_data_linearize *data, bool expand); -static struct nftnl_set_elem *alloc_nftnl_setelem(const struct expr *set, - const struct expr *expr) +struct nftnl_set_elem *alloc_nftnl_setelem(const struct expr *set, + const struct expr *expr) { - const struct expr *elem, *key, *data; + const struct expr *elem, *data; struct nftnl_set_elem *nlse; struct nft_data_linearize nld; struct nftnl_udata_buf *udbuf = NULL; + uint32_t flags = 0; + int num_exprs = 0; + struct stmt *stmt; + struct expr *key; nlse = nftnl_set_elem_alloc(); if (nlse == NULL) @@ -117,17 +123,62 @@ static struct nftnl_set_elem *alloc_nftnl_setelem(const struct expr *set, } else { elem = expr; } + if (elem->etype != EXPR_SET_ELEM) + BUG("Unexpected expression type: got %d\n", elem->etype); + key = elem->key; - netlink_gen_data(key, &nld); - nftnl_set_elem_set(nlse, NFTNL_SET_ELEM_KEY, &nld.value, nld.len); - if (elem->timeout) - nftnl_set_elem_set_u64(nlse, NFTNL_SET_ELEM_TIMEOUT, - elem->timeout); + switch (key->etype) { + case EXPR_SET_ELEM_CATCHALL: + break; + default: + if (expr_set(set)->set_flags & NFT_SET_INTERVAL && + key->etype == EXPR_CONCAT && expr_concat(key)->field_count > 1) { + key->flags |= EXPR_F_INTERVAL; + netlink_gen_key(key, &nld); + key->flags &= ~EXPR_F_INTERVAL; + + nftnl_set_elem_set(nlse, NFTNL_SET_ELEM_KEY, &nld.value, nld.len); + + key->flags |= EXPR_F_INTERVAL_END; + netlink_gen_key(key, &nld); + key->flags &= ~EXPR_F_INTERVAL_END; + + nftnl_set_elem_set(nlse, NFTNL_SET_ELEM_KEY_END, + &nld.value, nld.len); + } else { + netlink_gen_key(key, &nld); + nftnl_set_elem_set(nlse, NFTNL_SET_ELEM_KEY, &nld.value, nld.len); + } + break; + } + + if (elem->timeout) { + uint64_t timeout = elem->timeout; + + if (elem->timeout == NFT_NEVER_TIMEOUT) + timeout = 0; + + nftnl_set_elem_set_u64(nlse, NFTNL_SET_ELEM_TIMEOUT, timeout); + } if (elem->expiration) nftnl_set_elem_set_u64(nlse, NFTNL_SET_ELEM_EXPIRATION, elem->expiration); - if (elem->comment || expr->elem_flags) { + list_for_each_entry(stmt, &elem->stmt_list, list) + num_exprs++; + + if (num_exprs == 1) { + list_for_each_entry(stmt, &elem->stmt_list, list) { + nftnl_set_elem_set(nlse, NFTNL_SET_ELEM_EXPR, + netlink_gen_stmt_stateful(stmt), 0); + } + } else if (num_exprs > 1) { + list_for_each_entry(stmt, &elem->stmt_list, list) { + nftnl_set_elem_add_expr(nlse, + netlink_gen_stmt_stateful(stmt)); + } + } + if (elem->comment || expr->flags & EXPR_F_INTERVAL_OPEN) { udbuf = nftnl_udata_buf_alloc(NFT_USERDATA_MAXLEN); if (!udbuf) memory_allocation_error(); @@ -137,9 +188,9 @@ static struct nftnl_set_elem *alloc_nftnl_setelem(const struct expr *set, elem->comment)) memory_allocation_error(); } - if (expr->elem_flags) { + if (expr->flags & EXPR_F_INTERVAL_OPEN) { if (!nftnl_udata_put_u32(udbuf, NFTNL_UDATA_SET_ELEM_FLAGS, - expr->elem_flags)) + NFTNL_SET_ELEM_F_INTERVAL_OPEN)) memory_allocation_error(); } if (udbuf) { @@ -148,8 +199,8 @@ static struct nftnl_set_elem *alloc_nftnl_setelem(const struct expr *set, nftnl_udata_buf_len(udbuf)); nftnl_udata_buf_free(udbuf); } - if (set_is_datamap(set->set_flags) && data != NULL) { - netlink_gen_data(data, &nld); + if (set_is_datamap(expr_set(set)->set_flags) && data != NULL) { + __netlink_gen_data(data, &nld, !(data->flags & EXPR_F_SINGLETON)); switch (data->etype) { case EXPR_VERDICT: nftnl_set_elem_set_u32(nlse, NFTNL_SET_ELEM_VERDICT, @@ -158,7 +209,13 @@ static struct nftnl_set_elem *alloc_nftnl_setelem(const struct expr *set, nftnl_set_elem_set(nlse, NFTNL_SET_ELEM_CHAIN, nld.chain, strlen(nld.chain)); break; + case EXPR_CONCAT: + assert(nld.len > 0); + /* fallthrough */ case EXPR_VALUE: + case EXPR_RANGE: + case EXPR_RANGE_VALUE: + case EXPR_PREFIX: nftnl_set_elem_set(nlse, NFTNL_SET_ELEM_DATA, nld.value, nld.len); break; @@ -167,15 +224,19 @@ static struct nftnl_set_elem *alloc_nftnl_setelem(const struct expr *set, break; } } - if (set_is_objmap(set->set_flags) && data != NULL) { + if (set_is_objmap(expr_set(set)->set_flags) && data != NULL) { netlink_gen_data(data, &nld); nftnl_set_elem_set(nlse, NFTNL_SET_ELEM_OBJREF, nld.value, nld.len); } if (expr->flags & EXPR_F_INTERVAL_END) - nftnl_set_elem_set_u32(nlse, NFTNL_SET_ELEM_FLAGS, - NFT_SET_ELEM_INTERVAL_END); + flags |= NFT_SET_ELEM_INTERVAL_END; + if (key->etype == EXPR_SET_ELEM_CATCHALL) + flags |= NFT_SET_ELEM_CATCHALL; + + if (flags) + nftnl_set_elem_set_u32(nlse, NFTNL_SET_ELEM_FLAGS, flags); return nlse; } @@ -188,28 +249,215 @@ void netlink_gen_raw_data(const mpz_t value, enum byteorder byteorder, data->len = len; } -static void netlink_gen_concat_data(const struct expr *expr, +static int netlink_export_pad(unsigned char *data, const mpz_t v, + const struct expr *i) +{ + mpz_export_data(data, v, i->byteorder, + div_round_up(i->len, BITS_PER_BYTE)); + + return netlink_padded_len(i->len) / BITS_PER_BYTE; +} + +static void byteorder_switch_expr_value(mpz_t v, const struct expr *e) +{ + mpz_switch_byteorder(v, div_round_up(e->len, BITS_PER_BYTE)); +} + +static int __netlink_gen_concat_key(uint32_t flags, const struct expr *i, + unsigned char *data) +{ + struct expr *expr; + mpz_t value; + int ret; + + switch (i->etype) { + case EXPR_RANGE: + if (flags & EXPR_F_INTERVAL_END) + expr = i->right; + else + expr = i->left; + + mpz_init_set(value, expr->value); + + if (expr_basetype(expr)->type == TYPE_INTEGER && + expr->byteorder == BYTEORDER_HOST_ENDIAN) + byteorder_switch_expr_value(value, expr); + + i = expr; + break; + case EXPR_RANGE_VALUE: + if (flags & EXPR_F_INTERVAL_END) + mpz_init_set(value, i->range.high); + else + mpz_init_set(value, i->range.low); + + if (expr_basetype(i)->type == TYPE_INTEGER && + i->byteorder == BYTEORDER_HOST_ENDIAN) + byteorder_switch_expr_value(value, i); + + break; + case EXPR_PREFIX: + if (flags & EXPR_F_INTERVAL_END) { + int count; + mpz_t v; + + mpz_init_bitmask(v, i->len - i->prefix_len); + + if (i->byteorder == BYTEORDER_HOST_ENDIAN) + byteorder_switch_expr_value(v, i); + + mpz_add(v, i->prefix->value, v); + count = netlink_export_pad(data, v, i); + mpz_clear(v); + return count; + } + return netlink_export_pad(data, i->prefix->value, i); + case EXPR_VALUE: + mpz_init_set(value, i->value); + + /* Switch byteorder to big endian representation when the set + * contains concatenation of intervals. + */ + if (!(flags & (EXPR_F_INTERVAL| EXPR_F_INTERVAL_END))) + break; + + expr = (struct expr *)i; + if (expr_basetype(expr)->type == TYPE_INTEGER && + expr->byteorder == BYTEORDER_HOST_ENDIAN) + byteorder_switch_expr_value(value, expr); + break; + default: + BUG("invalid expression type '%s' in set", expr_ops(i)->name); + } + + ret = netlink_export_pad(data, value, i); + mpz_clear(value); + + return ret; +} + +static void nft_data_memcpy(struct nft_data_linearize *nld, + const void *src, unsigned int len) +{ + if (len > sizeof(nld->value)) + BUG("nld buffer overflow: want to copy %u, max %u\n", len, (unsigned int)sizeof(nld->value)); + + memcpy(nld->value, src, len); + nld->len = len; +} + +static void netlink_gen_concat_key(const struct expr *expr, struct nft_data_linearize *nld) { + unsigned int len = netlink_padded_len(expr->len) / BITS_PER_BYTE; + unsigned char data[NFT_MAX_EXPR_LEN_BYTES]; + unsigned int offset = 0; const struct expr *i; - unsigned int len, offset; - - len = expr->len / BITS_PER_BYTE; - if (1) { - unsigned char data[len]; - - memset(data, 0, sizeof(data)); - offset = 0; - list_for_each_entry(i, &expr->expressions, list) { - assert(i->etype == EXPR_VALUE); - mpz_export_data(data + offset, i->value, i->byteorder, - div_round_up(i->len, BITS_PER_BYTE)); - offset += netlink_padded_len(i->len) / BITS_PER_BYTE; - } - memcpy(nld->value, data, len); - nld->len = len; + if (len > sizeof(data)) + BUG("Value export of %u bytes would overflow", len); + + memset(data, 0, sizeof(data)); + + list_for_each_entry(i, &expr_concat(expr)->expressions, list) + offset += __netlink_gen_concat_key(expr->flags, i, data + offset); + + nft_data_memcpy(nld, data, len); +} + +static int __netlink_gen_concat_data(int end, const struct expr *i, + unsigned char *data) +{ + mpz_t value; + int ret; + + switch (i->etype) { + case EXPR_RANGE: + if (end) + i = i->right; + else + i = i->left; + + mpz_init_set(value, i->value); + break; + case EXPR_RANGE_VALUE: + if (end) + mpz_init_set(value, i->range.high); + else + mpz_init_set(value, i->range.low); + break; + case EXPR_PREFIX: + if (end) { + int count; + + mpz_init_bitmask(value, i->len - i->prefix_len); + mpz_add(value, i->prefix->value, value); + count = netlink_export_pad(data, value, i); + mpz_clear(value); + return count; + } + return netlink_export_pad(data, i->prefix->value, i); + case EXPR_VALUE: + mpz_init_set(value, i->value); + break; + default: + BUG("invalid expression type '%s' in set", expr_ops(i)->name); } + + ret = netlink_export_pad(data, value, i); + mpz_clear(value); + + return ret; +} + +static void __netlink_gen_concat_expand(const struct expr *expr, + struct nft_data_linearize *nld) +{ + unsigned int len = (netlink_padded_len(expr->len) / BITS_PER_BYTE) * 2; + unsigned char data[NFT_MAX_EXPR_LEN_BYTES]; + unsigned int offset = 0; + const struct expr *i; + + if (len > sizeof(data)) + BUG("Value export of %u bytes would overflow", len); + + memset(data, 0, sizeof(data)); + + list_for_each_entry(i, &expr_concat(expr)->expressions, list) + offset += __netlink_gen_concat_data(false, i, data + offset); + + list_for_each_entry(i, &expr_concat(expr)->expressions, list) + offset += __netlink_gen_concat_data(true, i, data + offset); + + nft_data_memcpy(nld, data, len); +} + +static void __netlink_gen_concat(const struct expr *expr, + struct nft_data_linearize *nld) +{ + unsigned int len = netlink_padded_len(expr->len) / BITS_PER_BYTE; + unsigned char data[NFT_MAX_EXPR_LEN_BYTES]; + unsigned int offset = 0; + const struct expr *i; + + if (len > sizeof(data)) + BUG("Value export of %u bytes would overflow", len); + + memset(data, 0, sizeof(data)); + + list_for_each_entry(i, &expr_concat(expr)->expressions, list) + offset += __netlink_gen_concat_data(expr->flags, i, data + offset); + + nft_data_memcpy(nld, data, len); +} + +static void netlink_gen_concat_data(const struct expr *expr, + struct nft_data_linearize *nld, bool expand) +{ + if (expand) + __netlink_gen_concat_expand(expr, nld); + else + __netlink_gen_concat(expr, nld); } static void netlink_gen_constant_data(const struct expr *expr, @@ -220,49 +468,140 @@ static void netlink_gen_constant_data(const struct expr *expr, div_round_up(expr->len, BITS_PER_BYTE), data); } -static void netlink_gen_verdict(const struct expr *expr, - struct nft_data_linearize *data) +static void netlink_gen_chain(const struct expr *expr, + struct nft_data_linearize *data) { char chain[NFT_CHAIN_MAXNAMELEN]; unsigned int len; + len = expr->chain->len / BITS_PER_BYTE; + + if (!len) + BUG("chain length is 0"); + + if (len > sizeof(chain)) + BUG("chain is too large (%u, %u max)", + len, (unsigned int)sizeof(chain)); + + memset(chain, 0, sizeof(chain)); + + mpz_export_data(chain, expr->chain->value, + BYTEORDER_HOST_ENDIAN, len); + snprintf(data->chain, NFT_CHAIN_MAXNAMELEN, "%s", chain); +} + +static void netlink_gen_verdict(const struct expr *expr, + struct nft_data_linearize *data) +{ + data->verdict = expr->verdict; switch (expr->verdict) { case NFT_JUMP: case NFT_GOTO: - len = expr->chain->len / BITS_PER_BYTE; + if (expr->chain) + netlink_gen_chain(expr, data); + else + data->chain_id = expr->chain_id; + break; + } +} + +static void netlink_gen_range(const struct expr *expr, + struct nft_data_linearize *nld) +{ + unsigned int len = (netlink_padded_len(expr->left->len) / BITS_PER_BYTE) * 2; + unsigned char data[NFT_MAX_EXPR_LEN_BYTES]; + unsigned int offset; + + if (len > sizeof(data)) + BUG("Value export of %u bytes would overflow", len); + + memset(data, 0, sizeof(data)); + offset = netlink_export_pad(data, expr->left->value, expr->left); + netlink_export_pad(data + offset, expr->right->value, expr->right); + nft_data_memcpy(nld, data, len); +} - if (!len) - BUG("chain length is 0"); +static void netlink_gen_range_value(const struct expr *expr, + struct nft_data_linearize *nld) +{ + unsigned int len = (netlink_padded_len(expr->len) / BITS_PER_BYTE) * 2; + unsigned char data[NFT_MAX_EXPR_LEN_BYTES]; + unsigned int offset; - if (len > sizeof(chain)) - BUG("chain is too large (%u, %u max)", - len, (unsigned int)sizeof(chain)); + if (len > sizeof(data)) + BUG("Value export of %u bytes would overflow", len); - memset(chain, 0, sizeof(chain)); + memset(data, 0, sizeof(data)); + offset = netlink_export_pad(data, expr->range.low, expr); + netlink_export_pad(data + offset, expr->range.high, expr); + nft_data_memcpy(nld, data, len); +} - mpz_export_data(chain, expr->chain->value, - BYTEORDER_HOST_ENDIAN, len); - snprintf(data->chain, NFT_CHAIN_MAXNAMELEN, "%s", chain); - break; +static void netlink_gen_prefix(const struct expr *expr, + struct nft_data_linearize *nld) +{ + unsigned int len = (netlink_padded_len(expr->len) / BITS_PER_BYTE) * 2; + unsigned char data[NFT_MAX_EXPR_LEN_BYTES]; + int offset; + mpz_t v; + + if (len > sizeof(data)) + BUG("Value export of %u bytes would overflow", len); + + offset = netlink_export_pad(data, expr->prefix->value, expr); + mpz_init_bitmask(v, expr->len - expr->prefix_len); + mpz_add(v, expr->prefix->value, v); + netlink_export_pad(data + offset, v, expr->prefix); + mpz_clear(v); + + nft_data_memcpy(nld, data, len); +} + +static void netlink_gen_key(const struct expr *expr, + struct nft_data_linearize *data) +{ + switch (expr->etype) { + case EXPR_VALUE: + return netlink_gen_constant_data(expr, data); + case EXPR_CONCAT: + return netlink_gen_concat_key(expr, data); + case EXPR_RANGE: + return netlink_gen_range(expr, data); + case EXPR_PREFIX: + return netlink_gen_prefix(expr, data); + default: + BUG("invalid data expression type %s\n", expr_name(expr)); } } -void netlink_gen_data(const struct expr *expr, struct nft_data_linearize *data) +static void __netlink_gen_data(const struct expr *expr, + struct nft_data_linearize *data, bool expand) { switch (expr->etype) { case EXPR_VALUE: return netlink_gen_constant_data(expr, data); case EXPR_CONCAT: - return netlink_gen_concat_data(expr, data); + return netlink_gen_concat_data(expr, data, expand); case EXPR_VERDICT: return netlink_gen_verdict(expr, data); + case EXPR_RANGE: + return netlink_gen_range(expr, data); + case EXPR_RANGE_VALUE: + return netlink_gen_range_value(expr, data); + case EXPR_PREFIX: + return netlink_gen_prefix(expr, data); default: BUG("invalid data expression type %s\n", expr_name(expr)); } } +void netlink_gen_data(const struct expr *expr, struct nft_data_linearize *data) +{ + __netlink_gen_data(expr, data, false); +} + struct expr *netlink_alloc_value(const struct location *loc, const struct nft_data_delinearize *nld) { @@ -324,82 +663,74 @@ void netlink_dump_expr(const struct nftnl_expr *nle, fprintf(fp, "\n"); } -static int list_rule_cb(struct nftnl_rule *nlr, void *arg) +void netlink_dump_chain(const struct nftnl_chain *nlc, struct netlink_ctx *ctx) { - struct netlink_ctx *ctx = arg; - const struct handle *h = ctx->data; - struct rule *rule; - const char *table, *chain; - uint32_t family; - - family = nftnl_rule_get_u32(nlr, NFTNL_RULE_FAMILY); - table = nftnl_rule_get_str(nlr, NFTNL_RULE_TABLE); - chain = nftnl_rule_get_str(nlr, NFTNL_RULE_CHAIN); - - if (h->family != family || - strcmp(table, h->table.name) != 0 || - (h->chain.name && strcmp(chain, h->chain.name) != 0)) - return 0; + FILE *fp = ctx->nft->output.output_fp; - netlink_dump_rule(nlr, ctx); - rule = netlink_delinearize_rule(ctx, nlr); - list_add_tail(&rule->list, &ctx->list); + if (!(ctx->nft->debug_mask & NFT_DEBUG_NETLINK) || !fp) + return; - return 0; + nftnl_chain_fprintf(fp, nlc, 0, 0); + fprintf(fp, "\n"); } -int netlink_list_rules(struct netlink_ctx *ctx, const struct handle *h) +static int chain_parse_udata_cb(const struct nftnl_udata *attr, void *data) { - struct nftnl_rule_list *rule_cache; - - rule_cache = mnl_nft_rule_dump(ctx, h->family); - if (rule_cache == NULL) { - if (errno == EINTR) - return -1; + unsigned char *value = nftnl_udata_get(attr); + uint8_t type = nftnl_udata_type(attr); + const struct nftnl_udata **tb = data; + uint8_t len = nftnl_udata_len(attr); - return 0; + switch (type) { + case NFTNL_UDATA_CHAIN_COMMENT: + if (value[len - 1] != '\0') + return -1; + break; + default: + return 0; } - - ctx->data = h; - nftnl_rule_list_foreach(rule_cache, list_rule_cb, ctx); - nftnl_rule_list_free(rule_cache); + tb[type] = attr; return 0; } -void netlink_dump_chain(const struct nftnl_chain *nlc, struct netlink_ctx *ctx) +static int qsort_device_cmp(const void *a, const void *b) { - FILE *fp = ctx->nft->output.output_fp; - - if (!(ctx->nft->debug_mask & NFT_DEBUG_NETLINK) || !fp) - return; + const char **x = (const char **)a; + const char **y = (const char **)b; - nftnl_chain_fprintf(fp, nlc, 0, 0); - fprintf(fp, "\n"); + return strcmp(*x, *y); } struct chain *netlink_delinearize_chain(struct netlink_ctx *ctx, const struct nftnl_chain *nlc) { + const struct nftnl_udata *ud[NFTNL_UDATA_CHAIN_MAX + 1] = {}; int priority, policy, len = 0, i; const char * const *dev_array; struct chain *chain; + const char *udata; + uint32_t ulen; - chain = chain_alloc(nftnl_chain_get_str(nlc, NFTNL_CHAIN_NAME)); + chain = chain_alloc(); chain->handle.family = nftnl_chain_get_u32(nlc, NFTNL_CHAIN_FAMILY); chain->handle.table.name = xstrdup(nftnl_chain_get_str(nlc, NFTNL_CHAIN_TABLE)); + chain->handle.chain.name = + xstrdup(nftnl_chain_get_str(nlc, NFTNL_CHAIN_NAME)); chain->handle.handle.id = nftnl_chain_get_u64(nlc, NFTNL_CHAIN_HANDLE); + if (nftnl_chain_is_set(nlc, NFTNL_CHAIN_FLAGS)) + chain->flags = nftnl_chain_get_u32(nlc, NFTNL_CHAIN_FLAGS); if (nftnl_chain_is_set(nlc, NFTNL_CHAIN_HOOKNUM) && nftnl_chain_is_set(nlc, NFTNL_CHAIN_PRIO) && nftnl_chain_is_set(nlc, NFTNL_CHAIN_TYPE) && nftnl_chain_is_set(nlc, NFTNL_CHAIN_POLICY)) { - chain->hooknum = + chain->hook.num = nftnl_chain_get_u32(nlc, NFTNL_CHAIN_HOOKNUM); - chain->hookstr = - hooknum2str(chain->handle.family, chain->hooknum); + chain->hook.name = + hooknum2str(chain->handle.family, chain->hook.num); priority = nftnl_chain_get_s32(nlc, NFTNL_CHAIN_PRIO); chain->priority.expr = constant_expr_alloc(&netlink_location, @@ -407,7 +738,7 @@ struct chain *netlink_delinearize_chain(struct netlink_ctx *ctx, BYTEORDER_HOST_ENDIAN, sizeof(int) * BITS_PER_BYTE, &priority); - chain->type = + chain->type.str = xstrdup(nftnl_chain_get_str(nlc, NFTNL_CHAIN_TYPE)); policy = nftnl_chain_get_u32(nlc, NFTNL_CHAIN_POLICY); chain->policy = constant_expr_alloc(&netlink_location, @@ -435,64 +766,71 @@ struct chain *netlink_delinearize_chain(struct netlink_ctx *ctx, chain->dev_array_len = len; } chain->flags |= CHAIN_F_BASECHAIN; - } - - return chain; -} -static int list_chain_cb(struct nftnl_chain *nlc, void *arg) -{ - struct netlink_ctx *ctx = arg; - const struct handle *h = ctx->data; - const char *table; - const char *name; - struct chain *chain; - uint32_t family; - - table = nftnl_chain_get_str(nlc, NFTNL_CHAIN_TABLE); - name = nftnl_chain_get_str(nlc, NFTNL_CHAIN_NAME); - family = nftnl_chain_get_u32(nlc, NFTNL_CHAIN_FAMILY); - - if (h->family != family || strcmp(table, h->table.name) != 0) - return 0; - if (h->chain.name && strcmp(name, h->chain.name) != 0) - return 0; + if (chain->dev_array_len) { + qsort(chain->dev_array, chain->dev_array_len, + sizeof(char *), qsort_device_cmp); + } + } - chain = netlink_delinearize_chain(ctx, nlc); - list_add_tail(&chain->list, &ctx->list); + if (nftnl_chain_is_set(nlc, NFTNL_CHAIN_USERDATA)) { + udata = nftnl_chain_get_data(nlc, NFTNL_CHAIN_USERDATA, &ulen); + if (nftnl_udata_parse(udata, ulen, chain_parse_udata_cb, ud) < 0) { + netlink_io_error(ctx, NULL, "Cannot parse userdata"); + chain_free(chain); + return NULL; + } + if (ud[NFTNL_UDATA_CHAIN_COMMENT]) + chain->comment = xstrdup(nftnl_udata_get(ud[NFTNL_UDATA_CHAIN_COMMENT])); + } - return 0; + return chain; } -int netlink_list_chains(struct netlink_ctx *ctx, const struct handle *h) +static int table_parse_udata_cb(const struct nftnl_udata *attr, void *data) { - struct nftnl_chain_list *chain_cache; - - chain_cache = mnl_nft_chain_dump(ctx, h->family); - if (chain_cache == NULL) { - if (errno == EINTR) - return -1; + unsigned char *value = nftnl_udata_get(attr); + const struct nftnl_udata **tb = data; + uint8_t type = nftnl_udata_type(attr); + uint8_t len = nftnl_udata_len(attr); - return 0; + switch (type) { + case NFTNL_UDATA_TABLE_COMMENT: + if (value[len - 1] != '\0') + return -1; + break; + default: + return 0; } - - ctx->data = h; - nftnl_chain_list_foreach(chain_cache, list_chain_cb, ctx); - nftnl_chain_list_free(chain_cache); - + tb[type] = attr; return 0; } struct table *netlink_delinearize_table(struct netlink_ctx *ctx, const struct nftnl_table *nlt) { + const struct nftnl_udata *ud[NFTNL_UDATA_TABLE_MAX + 1] = {}; struct table *table; + const char *udata; + uint32_t ulen; table = table_alloc(); table->handle.family = nftnl_table_get_u32(nlt, NFTNL_TABLE_FAMILY); table->handle.table.name = xstrdup(nftnl_table_get_str(nlt, NFTNL_TABLE_NAME)); table->flags = nftnl_table_get_u32(nlt, NFTNL_TABLE_FLAGS); table->handle.handle.id = nftnl_table_get_u64(nlt, NFTNL_TABLE_HANDLE); + table->owner = nftnl_table_get_u32(nlt, NFTNL_TABLE_OWNER); + + if (nftnl_table_is_set(nlt, NFTNL_TABLE_USERDATA)) { + udata = nftnl_table_get_data(nlt, NFTNL_TABLE_USERDATA, &ulen); + if (nftnl_udata_parse(udata, ulen, table_parse_udata_cb, ud) < 0) { + netlink_io_error(ctx, NULL, "Cannot parse userdata"); + table_free(table); + return NULL; + } + if (ud[NFTNL_UDATA_TABLE_COMMENT]) + table->comment = xstrdup(nftnl_udata_get(ud[NFTNL_UDATA_TABLE_COMMENT])); + } return table; } @@ -508,16 +846,24 @@ static int list_table_cb(struct nftnl_table *nlt, void *arg) return 0; } -int netlink_list_tables(struct netlink_ctx *ctx, const struct handle *h) +int netlink_list_tables(struct netlink_ctx *ctx, const struct handle *h, + const struct nft_cache_filter *filter) { struct nftnl_table_list *table_cache; + uint32_t family = h->family; + const char *table = NULL; + + if (filter) { + family = filter->list.family; + table = filter->list.table; + } - table_cache = mnl_nft_table_dump(ctx, h->family); + table_cache = mnl_nft_table_dump(ctx, family, table); if (table_cache == NULL) { if (errno == EINTR) return -1; - return 0; + return -1; } ctx->data = h; @@ -538,29 +884,37 @@ enum nft_data_types dtype_map_to_kernel(const struct datatype *dtype) static const struct datatype *dtype_map_from_kernel(enum nft_data_types type) { + /* The function always returns ownership of a reference. But for + * &verdict_Type and datatype_lookup(), those are static instances, + * we can omit the datatype_get() call. + */ switch (type) { case NFT_DATA_VERDICT: return &verdict_type; default: if (type & ~TYPE_MASK) return concat_type_alloc(type); - return datatype_lookup(type); + return datatype_lookup((enum datatypes) type); } } void netlink_dump_set(const struct nftnl_set *nls, struct netlink_ctx *ctx) { FILE *fp = ctx->nft->output.output_fp; + uint32_t family; if (!(ctx->nft->debug_mask & NFT_DEBUG_NETLINK) || !fp) return; + family = nftnl_set_get_u32(nls, NFTNL_SET_FAMILY); + fprintf(fp, "family %d ", family); nftnl_set_fprintf(fp, nls, 0, 0); fprintf(fp, "\n"); } static int set_parse_udata_cb(const struct nftnl_udata *attr, void *data) { + unsigned char *value = nftnl_udata_get(attr); const struct nftnl_udata **tb = data; uint8_t type = nftnl_udata_type(attr); uint8_t len = nftnl_udata_len(attr); @@ -569,6 +923,7 @@ static int set_parse_udata_cb(const struct nftnl_udata *attr, void *data) case NFTNL_UDATA_SET_KEYBYTEORDER: case NFTNL_UDATA_SET_DATABYTEORDER: case NFTNL_UDATA_SET_MERGE_ELEMENTS: + case NFTNL_UDATA_SET_DATA_INTERVAL: if (len != sizeof(uint32_t)) return -1; break; @@ -577,6 +932,10 @@ static int set_parse_udata_cb(const struct nftnl_udata *attr, void *data) if (len < 3) return -1; break; + case NFTNL_UDATA_SET_COMMENT: + if (value[len - 1] != '\0') + return -1; + break; default: return 0; } @@ -608,8 +967,8 @@ static struct expr *set_make_key(const struct nftnl_udata *attr) { const struct nftnl_udata *ud[NFTNL_UDATA_SET_TYPEOF_MAX + 1] = {}; const struct expr_ops *ops; - enum expr_types etype; struct expr *expr; + uint32_t etype; int err; if (!attr) @@ -625,7 +984,9 @@ static struct expr *set_make_key(const struct nftnl_udata *attr) return NULL; etype = nftnl_udata_get_u32(ud[NFTNL_UDATA_SET_TYPEOF_EXPR]); - ops = expr_ops_by_type(etype); + ops = expr_ops_by_type_u32(etype); + if (!ops || !ops->parse_udata) + return NULL; expr = ops->parse_udata(ud[NFTNL_UDATA_SET_TYPEOF_DATA]); if (!expr) @@ -634,7 +995,7 @@ static struct expr *set_make_key(const struct nftnl_udata *attr) return expr; } -static bool set_udata_key_valid(const struct expr *e, const struct datatype *d, uint32_t len) +static bool set_udata_key_valid(const struct expr *e, uint32_t len) { if (!e) return false; @@ -642,25 +1003,47 @@ static bool set_udata_key_valid(const struct expr *e, const struct datatype *d, return div_round_up(e->len, BITS_PER_BYTE) == len / BITS_PER_BYTE; } +struct setelem_parse_ctx { + struct set *set; + struct nft_cache *cache; + struct list_head stmt_list; +}; + +static int set_elem_parse_expressions(struct nftnl_expr *e, void *data) +{ + struct setelem_parse_ctx *setelem_parse_ctx = data; + struct nft_cache *cache = setelem_parse_ctx->cache; + struct set *set = setelem_parse_ctx->set; + struct stmt *stmt; + + stmt = netlink_parse_set_expr(set, cache, e); + if (stmt) + list_add_tail(&stmt->list, &setelem_parse_ctx->stmt_list); + + return 0; +} + struct set *netlink_delinearize_set(struct netlink_ctx *ctx, const struct nftnl_set *nls) { const struct nftnl_udata *ud[NFTNL_UDATA_SET_MAX + 1] = {}; enum byteorder keybyteorder = BYTEORDER_INVALID; enum byteorder databyteorder = BYTEORDER_INVALID; - const struct datatype *keytype, *datatype = NULL; - struct expr *typeof_expr_key, *typeof_expr_data; + struct setelem_parse_ctx set_parse_ctx; + const struct datatype *datatype = NULL; + const struct datatype *keytype = NULL; + const struct datatype *dtype2 = NULL; + const struct datatype *dtype = NULL; + struct expr *typeof_expr_data = NULL; + struct expr *typeof_expr_key = NULL; + const char *udata, *comment = NULL; uint32_t flags, key, objtype = 0; - const struct datatype *dtype; + uint32_t data_interval = 0; bool automerge = false; - const char *udata; struct set *set; uint32_t ulen; uint32_t klen; - typeof_expr_key = NULL; - typeof_expr_data = NULL; - if (nftnl_set_is_set(nls, NFTNL_SET_USERDATA)) { udata = nftnl_set_get_data(nls, NFTNL_SET_USERDATA, &ulen); if (nftnl_udata_parse(udata, ulen, set_parse_udata_cb, ud) < 0) { @@ -675,11 +1058,14 @@ struct set *netlink_delinearize_set(struct netlink_ctx *ctx, GET_U32_UDATA(keybyteorder, NFTNL_UDATA_SET_KEYBYTEORDER); GET_U32_UDATA(databyteorder, NFTNL_UDATA_SET_DATABYTEORDER); GET_U32_UDATA(automerge, NFTNL_UDATA_SET_MERGE_ELEMENTS); + GET_U32_UDATA(data_interval, NFTNL_UDATA_SET_DATA_INTERVAL); #undef GET_U32_UDATA typeof_expr_key = set_make_key(ud[NFTNL_UDATA_SET_KEY_TYPEOF]); if (ud[NFTNL_UDATA_SET_DATA_TYPEOF]) typeof_expr_data = set_make_key(ud[NFTNL_UDATA_SET_DATA_TYPEOF]); + if (ud[NFTNL_UDATA_SET_COMMENT]) + comment = nftnl_udata_get(ud[NFTNL_UDATA_SET_COMMENT]); } key = nftnl_set_get_u32(nls, NFTNL_SET_KEY_TYPE); @@ -700,8 +1086,8 @@ struct set *netlink_delinearize_set(struct netlink_ctx *ctx, netlink_io_error(ctx, NULL, "Unknown data type in set key %u", data); - datatype_free(keytype); - return NULL; + set = NULL; + goto out; } } @@ -716,18 +1102,48 @@ struct set *netlink_delinearize_set(struct netlink_ctx *ctx, set->handle.table.name = xstrdup(nftnl_set_get_str(nls, NFTNL_SET_TABLE)); set->handle.set.name = xstrdup(nftnl_set_get_str(nls, NFTNL_SET_NAME)); set->automerge = automerge; + if (comment) + set->comment = xstrdup(comment); + + init_list_head(&set_parse_ctx.stmt_list); + + if (nftnl_set_is_set(nls, NFTNL_SET_EXPR)) { + const struct nftnl_expr *nle; + struct stmt *stmt; + + nle = nftnl_set_get(nls, NFTNL_SET_EXPR); + stmt = netlink_parse_set_expr(set, &ctx->nft->cache, nle); + list_add_tail(&stmt->list, &set_parse_ctx.stmt_list); + } else if (nftnl_set_is_set(nls, NFTNL_SET_EXPRESSIONS)) { + set_parse_ctx.cache = &ctx->nft->cache; + set_parse_ctx.set = set; + nftnl_set_expr_foreach(nls, set_elem_parse_expressions, + &set_parse_ctx); + } + list_splice_tail(&set_parse_ctx.stmt_list, &set->stmt_list); + + set->flags = nftnl_set_get_u32(nls, NFTNL_SET_FLAGS); if (datatype) { - dtype = set_datatype_alloc(datatype, databyteorder); + uint32_t dlen; + + dtype2 = set_datatype_alloc(datatype, databyteorder); klen = nftnl_set_get_u32(nls, NFTNL_SET_DATA_LEN) * BITS_PER_BYTE; - if (set_udata_key_valid(typeof_expr_data, dtype, klen)) { - datatype_free(datatype_get(dtype)); + dlen = data_interval ? klen / 2 : klen; + + if (set_udata_key_valid(typeof_expr_data, dlen)) { + typeof_expr_data->len = klen; set->data = typeof_expr_data; + typeof_expr_data = NULL; + } else if (set->flags & NFT_SET_OBJECT) { + set->data = constant_expr_alloc(&netlink_location, + dtype2, + databyteorder, klen, + NULL); } else { - expr_free(typeof_expr_data); set->data = constant_expr_alloc(&netlink_location, - dtype, + dtype2, databyteorder, klen, NULL); @@ -736,28 +1152,23 @@ struct set *netlink_delinearize_set(struct netlink_ctx *ctx, typeof_expr_key = NULL; } - if (dtype != datatype) - datatype_free(datatype); + if (data_interval) + set->data->flags |= EXPR_F_INTERVAL; } dtype = set_datatype_alloc(keytype, keybyteorder); klen = nftnl_set_get_u32(nls, NFTNL_SET_KEY_LEN) * BITS_PER_BYTE; - if (set_udata_key_valid(typeof_expr_key, dtype, klen)) { - datatype_free(datatype_get(dtype)); + if (set_udata_key_valid(typeof_expr_key, klen)) { set->key = typeof_expr_key; + typeof_expr_key = NULL; set->key_typeof_valid = true; } else { - expr_free(typeof_expr_key); set->key = constant_expr_alloc(&netlink_location, dtype, keybyteorder, klen, NULL); } - if (dtype != keytype) - datatype_free(keytype); - - set->flags = nftnl_set_get_u32(nls, NFTNL_SET_FLAGS); set->handle.handle.id = nftnl_set_get_u64(nls, NFTNL_SET_HANDLE); set->objtype = objtype; @@ -773,76 +1184,233 @@ struct set *netlink_delinearize_set(struct netlink_ctx *ctx, if (nftnl_set_is_set(nls, NFTNL_SET_DESC_SIZE)) set->desc.size = nftnl_set_get_u32(nls, NFTNL_SET_DESC_SIZE); + if (nftnl_set_is_set(nls, NFTNL_SET_COUNT)) + set->count = nftnl_set_get_u32(nls, NFTNL_SET_COUNT); + + if (nftnl_set_is_set(nls, NFTNL_SET_DESC_CONCAT)) { + uint32_t len = NFT_REG32_COUNT; + const uint8_t *data; + + data = nftnl_set_get_data(nls, NFTNL_SET_DESC_CONCAT, &len); + if (data) { + memcpy(set->desc.field_len, data, len); + set->desc.field_count = len; + } + } + +out: + expr_free(typeof_expr_data); + expr_free(typeof_expr_key); + datatype_free(datatype); + datatype_free(keytype); + datatype_free(dtype2); + datatype_free(dtype); return set; } -static int list_set_cb(struct nftnl_set *nls, void *arg) +void alloc_setelem_cache(const struct expr *set, struct nftnl_set *nls) { - struct netlink_ctx *ctx = arg; - struct set *set; + struct nftnl_set_elem *nlse; + const struct expr *expr; - set = netlink_delinearize_set(ctx, nls); - if (set == NULL) - return -1; - list_add_tail(&set->list, &ctx->list); - return 0; + list_for_each_entry(expr, &expr_set(set)->expressions, list) { + nlse = alloc_nftnl_setelem(set, expr); + nftnl_set_elem_add(nls, nlse); + } } -int netlink_list_sets(struct netlink_ctx *ctx, const struct handle *h) +static bool range_expr_is_prefix(const struct expr *range, uint32_t *prefix_len) { - struct nftnl_set_list *set_cache; - int err; + const struct expr *right = range->right; + const struct expr *left = range->left; + uint32_t len = left->len; + unsigned long n1, n2; + uint32_t plen; + mpz_t bitmask; - set_cache = mnl_nft_set_dump(ctx, h->family, h->table.name); - if (set_cache == NULL) { - if (errno == EINTR) - return -1; + mpz_init2(bitmask, left->len); + mpz_xor(bitmask, left->value, right->value); - return 0; + n1 = mpz_scan0(bitmask, 0); + if (n1 == ULONG_MAX) + goto not_a_prefix; + + n2 = mpz_scan1(bitmask, n1 + 1); + if (n2 < len) + goto not_a_prefix; + + plen = len - n1; + + if (mpz_scan1(left->value, 0) < len - plen) + goto not_a_prefix; + + mpz_clear(bitmask); + *prefix_len = plen; + + return true; + +not_a_prefix: + mpz_clear(bitmask); + + return false; +} + +struct expr *range_expr_to_prefix(struct expr *range) +{ + struct expr *prefix; + uint32_t prefix_len; + + if (range_expr_is_prefix(range, &prefix_len)) { + prefix = prefix_expr_alloc(&range->location, + expr_get(range->left), + prefix_len); + expr_free(range); + return prefix; } - ctx->data = h; - err = nftnl_set_list_foreach(set_cache, list_set_cb, ctx); - nftnl_set_list_free(set_cache); - return err; + return range; } -void alloc_setelem_cache(const struct expr *set, struct nftnl_set *nls) +static struct expr *range_expr_reduce(struct expr *range) { - struct nftnl_set_elem *nlse; - const struct expr *expr; + struct expr *expr; - list_for_each_entry(expr, &set->expressions, list) { - nlse = alloc_nftnl_setelem(set, expr); - nftnl_set_elem_add(nls, nlse); + if (!mpz_cmp(range->left->value, range->right->value)) { + expr = expr_get(range->left); + expr_free(range); + return expr; } + + if (range->left->dtype->type != TYPE_IPADDR && + range->left->dtype->type != TYPE_IP6ADDR) + return range; + + return range_expr_to_prefix(range); } -static struct expr *netlink_parse_concat_elem(const struct datatype *dtype, - struct expr *data) +static struct expr *netlink_parse_interval_elem(const struct set *set, + struct expr *expr) +{ + unsigned int len = netlink_padded_len(expr->len) / BITS_PER_BYTE; + const struct datatype *dtype = set->data->dtype; + struct expr *range, *left, *right; + char data[NFT_MAX_EXPR_LEN_BYTES]; + + if (len > sizeof(data)) + BUG("Value export of %u bytes would overflow", len); + + memset(data, 0, sizeof(data)); + + mpz_export_data(data, expr->value, dtype->byteorder, len); + left = constant_expr_alloc(&internal_location, dtype, + dtype->byteorder, + (len / 2) * BITS_PER_BYTE, &data[0]); + right = constant_expr_alloc(&internal_location, dtype, + dtype->byteorder, + (len / 2) * BITS_PER_BYTE, &data[len / 2]); + range = range_expr_alloc(&expr->location, left, right); + expr_free(expr); + + return range_expr_to_prefix(range); +} + +static struct expr *concat_elem_expr(const struct set *set, struct expr *key, + const struct datatype *dtype, + struct expr *data, int *off) { const struct datatype *subtype; - struct expr *concat, *expr; + unsigned int sub_length; + struct expr *expr; + + if (key) { + (*off)--; + sub_length = round_up(key->len, BITS_PER_BYTE); + + expr = constant_expr_splice(data, sub_length); + expr->dtype = datatype_get(key->dtype); + expr->byteorder = key->byteorder; + expr->len = key->len; + } else { + subtype = concat_subtype_lookup(dtype->type, --(*off)); + sub_length = round_up(subtype->size, BITS_PER_BYTE); + expr = constant_expr_splice(data, sub_length); + expr->dtype = subtype; + expr->byteorder = subtype->byteorder; + } + + if (expr_basetype(expr)->type == TYPE_STRING || + (!(set->flags & NFT_SET_INTERVAL) && + expr->byteorder == BYTEORDER_HOST_ENDIAN)) + mpz_switch_byteorder(expr->value, expr->len / BITS_PER_BYTE); + + if (expr->dtype->basetype != NULL && + expr->dtype->basetype->type == TYPE_BITMASK) + expr = bitmask_expr_to_binops(expr); + + data->len -= netlink_padding_len(sub_length); + + return expr; +} + +static struct expr *netlink_parse_concat_elem_key(const struct set *set, + struct expr *data) +{ + const struct datatype *dtype = set->key->dtype; + struct expr *concat, *expr, *n = NULL; int off = dtype->subtypes; + if (set->key->etype == EXPR_CONCAT) + n = list_first_entry(&expr_concat(set->key)->expressions, struct expr, list); + concat = concat_expr_alloc(&data->location); while (off > 0) { - subtype = concat_subtype_lookup(dtype->type, --off); + expr = concat_elem_expr(set, n, dtype, data, &off); + compound_expr_add(concat, expr); + if (set->key->etype == EXPR_CONCAT) + n = list_next_entry(n, list); + } - expr = constant_expr_splice(data, subtype->size); - expr->dtype = subtype; - expr->byteorder = subtype->byteorder; + expr_free(data); + + return concat; +} - if (expr->byteorder == BYTEORDER_HOST_ENDIAN) - mpz_switch_byteorder(expr->value, expr->len / BITS_PER_BYTE); +static struct expr *netlink_parse_concat_elem(const struct set *set, + struct expr *data) +{ + const struct datatype *dtype = set->data->dtype; + struct expr *concat, *expr, *left, *range; + struct list_head expressions; + int off = dtype->subtypes; - if (expr->dtype->basetype != NULL && - expr->dtype->basetype->type == TYPE_BITMASK) - expr = bitmask_expr_to_binops(expr); + init_list_head(&expressions); - compound_expr_add(concat, expr); - data->len -= netlink_padding_len(expr->len); + concat = concat_expr_alloc(&data->location); + while (off > 0) { + expr = concat_elem_expr(set, NULL, dtype, data, &off); + list_add_tail(&expr->list, &expressions); } + + if (set->data->flags & EXPR_F_INTERVAL) { + assert(!list_empty(&expressions)); + + off = dtype->subtypes; + + while (off > 0) { + left = list_first_entry(&expressions, struct expr, list); + + expr = concat_elem_expr(set, NULL, dtype, data, &off); + list_del(&left->list); + + range = range_expr_alloc(&data->location, left, expr); + range = range_expr_reduce(range); + compound_expr_add(concat, range); + } + assert(list_empty(&expressions)); + } else { + list_splice_tail(&expressions, &expr_concat(concat)->expressions); + } + expr_free(data); return concat; @@ -885,50 +1453,90 @@ static void set_elem_parse_udata(struct nftnl_set_elem *nlse, if (ud[NFTNL_UDATA_SET_ELEM_COMMENT]) expr->comment = xstrdup(nftnl_udata_get(ud[NFTNL_UDATA_SET_ELEM_COMMENT])); - if (ud[NFTNL_UDATA_SET_ELEM_FLAGS]) - expr->elem_flags = + if (ud[NFTNL_UDATA_SET_ELEM_FLAGS]) { + uint32_t elem_flags; + + elem_flags = nftnl_udata_get_u32(ud[NFTNL_UDATA_SET_ELEM_FLAGS]); + if (elem_flags & NFTNL_SET_ELEM_F_INTERVAL_OPEN) + expr->flags |= EXPR_F_INTERVAL_OPEN; + } } -int netlink_delinearize_setelem(struct nftnl_set_elem *nlse, - struct set *set, struct nft_cache *cache) +int netlink_delinearize_setelem(struct netlink_ctx *ctx, + struct nftnl_set_elem *nlse, + struct set *set) { + struct setelem_parse_ctx setelem_parse_ctx = { + .set = set, + .cache = &ctx->nft->cache, + }; struct nft_data_delinearize nld; struct expr *expr, *key, *data; uint32_t flags = 0; - nld.value = - nftnl_set_elem_get(nlse, NFTNL_SET_ELEM_KEY, &nld.len); + init_list_head(&setelem_parse_ctx.stmt_list); + + if (nftnl_set_elem_is_set(nlse, NFTNL_SET_ELEM_KEY)) + nld.value = nftnl_set_elem_get(nlse, NFTNL_SET_ELEM_KEY, &nld.len); if (nftnl_set_elem_is_set(nlse, NFTNL_SET_ELEM_FLAGS)) flags = nftnl_set_elem_get_u32(nlse, NFTNL_SET_ELEM_FLAGS); - key = netlink_alloc_value(&netlink_location, &nld); - datatype_set(key, set->key->dtype); - key->byteorder = set->key->byteorder; - if (set->key->dtype->subtypes) - key = netlink_parse_concat_elem(set->key->dtype, key); - - if (!(set->flags & NFT_SET_INTERVAL) && - key->byteorder == BYTEORDER_HOST_ENDIAN) - mpz_switch_byteorder(key->value, key->len / BITS_PER_BYTE); - - if (key->dtype->basetype != NULL && - key->dtype->basetype->type == TYPE_BITMASK) - key = bitmask_expr_to_binops(key); +key_end: + if (nftnl_set_elem_is_set(nlse, NFTNL_SET_ELEM_KEY)) { + key = netlink_alloc_value(&netlink_location, &nld); + datatype_set(key, set->key->dtype); + key->byteorder = set->key->byteorder; + if (set->key->dtype->subtypes) + key = netlink_parse_concat_elem_key(set, key); + + if (!(set->flags & NFT_SET_INTERVAL) && + key->byteorder == BYTEORDER_HOST_ENDIAN) + mpz_switch_byteorder(key->value, key->len / BITS_PER_BYTE); + + if (key->dtype->basetype != NULL && + key->dtype->basetype->type == TYPE_BITMASK) + key = bitmask_expr_to_binops(key); + } else if (flags & NFT_SET_ELEM_CATCHALL) { + key = set_elem_catchall_expr_alloc(&netlink_location); + datatype_set(key, set->key->dtype); + key->byteorder = set->key->byteorder; + key->len = set->key->len; + } else { + netlink_io_error(ctx, NULL, + "Unexpected set element with no key"); + return 0; + } expr = set_elem_expr_alloc(&netlink_location, key); - if (nftnl_set_elem_is_set(nlse, NFTNL_SET_ELEM_TIMEOUT)) + expr->flags |= EXPR_F_KERNEL; + + if (nftnl_set_elem_is_set(nlse, NFTNL_SET_ELEM_TIMEOUT)) { expr->timeout = nftnl_set_elem_get_u64(nlse, NFTNL_SET_ELEM_TIMEOUT); + if (expr->timeout == 0) + expr->timeout = NFT_NEVER_TIMEOUT; + } + if (nftnl_set_elem_is_set(nlse, NFTNL_SET_ELEM_EXPIRATION)) expr->expiration = nftnl_set_elem_get_u64(nlse, NFTNL_SET_ELEM_EXPIRATION); - if (nftnl_set_elem_is_set(nlse, NFTNL_SET_ELEM_USERDATA)) + if (nftnl_set_elem_is_set(nlse, NFTNL_SET_ELEM_USERDATA)) { set_elem_parse_udata(nlse, expr); + if (expr->comment) + set->elem_has_comment = true; + } if (nftnl_set_elem_is_set(nlse, NFTNL_SET_ELEM_EXPR)) { const struct nftnl_expr *nle; + struct stmt *stmt; nle = nftnl_set_elem_get(nlse, NFTNL_SET_ELEM_EXPR, NULL); - expr->stmt = netlink_parse_set_expr(set, cache, nle); + stmt = netlink_parse_set_expr(set, &ctx->nft->cache, nle); + list_add_tail(&stmt->list, &setelem_parse_ctx.stmt_list); + } else if (nftnl_set_elem_is_set(nlse, NFTNL_SET_ELEM_EXPRESSIONS)) { + nftnl_set_elem_expr_foreach(nlse, set_elem_parse_expressions, + &setelem_parse_ctx); } + list_splice_tail_init(&setelem_parse_ctx.stmt_list, &expr->stmt_list); + if (flags & NFT_SET_ELEM_INTERVAL_END) { expr->flags |= EXPR_F_INTERVAL_END; if (mpz_cmp_ui(set->key->value, 0) == 0) @@ -950,8 +1558,18 @@ int netlink_delinearize_setelem(struct nftnl_set_elem *nlse, data = netlink_alloc_data(&netlink_location, &nld, set->data->dtype->type == TYPE_VERDICT ? NFT_REG_VERDICT : NFT_REG_1); - datatype_set(data, set->data->dtype); + + if (set->data->dtype->is_typeof) + datatype_set(data, set->data->dtype->basetype); + else + datatype_set(data, set->data->dtype); data->byteorder = set->data->byteorder; + + if (set->data->dtype->subtypes) { + data = netlink_parse_concat_elem(set, data); + } else if (set->data->flags & EXPR_F_INTERVAL) + data = netlink_parse_interval_elem(set, data); + if (data->byteorder == BYTEORDER_HOST_ENDIAN) mpz_switch_byteorder(data->value, data->len / BITS_PER_BYTE); @@ -973,17 +1591,62 @@ int netlink_delinearize_setelem(struct nftnl_set_elem *nlse, } out: compound_expr_add(set->init, expr); + + if (!(flags & NFT_SET_ELEM_INTERVAL_END) && + nftnl_set_elem_is_set(nlse, NFTNL_SET_ELEM_KEY_END)) { + flags |= NFT_SET_ELEM_INTERVAL_END; + nld.value = nftnl_set_elem_get(nlse, NFTNL_SET_ELEM_KEY_END, + &nld.len); + goto key_end; + } + return 0; } static int list_setelem_cb(struct nftnl_set_elem *nlse, void *arg) { struct netlink_ctx *ctx = arg; - return netlink_delinearize_setelem(nlse, ctx->set, &ctx->nft->cache); + return netlink_delinearize_setelem(ctx, nlse, ctx->set); +} + +static int list_setelem_debug_cb(struct nftnl_set_elem *nlse, void *arg) +{ + int r; + + r = list_setelem_cb(nlse, arg); + if (r == 0) { + struct netlink_ctx *ctx = arg; + FILE *fp = ctx->nft->output.output_fp; + + fprintf(fp, "\t"); + nftnl_set_elem_fprintf(fp, nlse, 0, 0); + fprintf(fp, "\n"); + } + + return r; +} + +static int list_setelements(struct nftnl_set *s, struct netlink_ctx *ctx) +{ + FILE *fp = ctx->nft->output.output_fp; + + if (fp && (ctx->nft->debug_mask & NFT_DEBUG_NETLINK)) { + const char *table, *name; + uint32_t family = nftnl_set_get_u32(s, NFTNL_SET_FAMILY); + + table = nftnl_set_get_str(s, NFTNL_SET_TABLE); + name = nftnl_set_get_str(s, NFTNL_SET_NAME); + + fprintf(fp, "%s %s @%s\n", family2str(family), table, name); + + return nftnl_set_elem_foreach(s, list_setelem_debug_cb, ctx); + } + + return nftnl_set_elem_foreach(s, list_setelem_cb, ctx); } int netlink_list_setelems(struct netlink_ctx *ctx, const struct handle *h, - struct set *set) + struct set *set, bool reset) { struct nftnl_set *nls; int err; @@ -998,7 +1661,7 @@ int netlink_list_setelems(struct netlink_ctx *ctx, const struct handle *h, if (h->handle.id) nftnl_set_set_u64(nls, NFTNL_SET_HANDLE, h->handle.id); - err = mnl_nft_setelem_get(ctx, nls); + err = mnl_nft_setelem_get(ctx, nls, reset); if (err < 0) { nftnl_set_free(nls); if (errno == EINTR) @@ -1009,25 +1672,27 @@ int netlink_list_setelems(struct netlink_ctx *ctx, const struct handle *h, ctx->set = set; set->init = set_expr_alloc(&internal_location, set); - nftnl_set_elem_foreach(nls, list_setelem_cb, ctx); + list_setelements(nls, ctx); - if (!(set->flags & NFT_SET_INTERVAL)) - list_expr_sort(&ctx->set->init->expressions); + if (set->flags & NFT_SET_INTERVAL && set->desc.field_count > 1) + concat_range_aggregate(set->init); + else if (set->flags & NFT_SET_INTERVAL) + interval_map_decompose(set->init); + else + list_expr_sort(&expr_set(ctx->set->init)->expressions); nftnl_set_free(nls); ctx->set = NULL; - if (set->flags & NFT_SET_INTERVAL) - interval_map_decompose(set->init); - return 0; } int netlink_get_setelem(struct netlink_ctx *ctx, const struct handle *h, - const struct location *loc, struct table *table, - struct set *set, struct expr *init) + const struct location *loc, struct set *cache_set, + struct set *set, struct expr *init, bool reset) { struct nftnl_set *nls, *nls_out = NULL; + int err = 0; nls = nftnl_set_alloc(); if (nls == NULL) @@ -1043,26 +1708,28 @@ int netlink_get_setelem(struct netlink_ctx *ctx, const struct handle *h, netlink_dump_set(nls, ctx); - nls_out = mnl_nft_setelem_get_one(ctx, nls); - if (!nls_out) + nls_out = mnl_nft_setelem_get_one(ctx, nls, reset); + if (!nls_out) { + nftnl_set_free(nls); return -1; + } ctx->set = set; set->init = set_expr_alloc(loc, set); - nftnl_set_elem_foreach(nls_out, list_setelem_cb, ctx); + list_setelements(nls_out, ctx); - if (!(set->flags & NFT_SET_INTERVAL)) - list_expr_sort(&ctx->set->init->expressions); + if (set->flags & NFT_SET_INTERVAL && set->desc.field_count > 1) + concat_range_aggregate(set->init); + else if (set->flags & NFT_SET_INTERVAL) + err = get_set_decompose(cache_set, set); + else + list_expr_sort(&expr_set(ctx->set->init)->expressions); nftnl_set_free(nls); nftnl_set_free(nls_out); ctx->set = NULL; - if (set->flags & NFT_SET_INTERVAL && - get_set_decompose(table, set) < 0) - return -1; - - return 0; + return err; } void netlink_dump_obj(struct nftnl_obj *nln, struct netlink_ctx *ctx) @@ -1076,11 +1743,33 @@ void netlink_dump_obj(struct nftnl_obj *nln, struct netlink_ctx *ctx) fprintf(fp, "\n"); } +static int obj_parse_udata_cb(const struct nftnl_udata *attr, void *data) +{ + unsigned char *value = nftnl_udata_get(attr); + uint8_t type = nftnl_udata_type(attr); + const struct nftnl_udata **tb = data; + uint8_t len = nftnl_udata_len(attr); + + switch (type) { + case NFTNL_UDATA_OBJ_COMMENT: + if (value[len - 1] != '\0') + return -1; + break; + default: + return 0; + } + tb[type] = attr; + return 0; +} + struct obj *netlink_delinearize_obj(struct netlink_ctx *ctx, struct nftnl_obj *nlo) { + const struct nftnl_udata *ud[NFTNL_UDATA_OBJ_MAX + 1] = {}; + const char *udata; struct obj *obj; uint32_t type; + uint32_t ulen; obj = obj_alloc(&netlink_location); obj->handle.family = nftnl_obj_get_u32(nlo, NFTNL_OBJ_FAMILY); @@ -1090,6 +1779,16 @@ struct obj *netlink_delinearize_obj(struct netlink_ctx *ctx, xstrdup(nftnl_obj_get_str(nlo, NFTNL_OBJ_NAME)); obj->handle.handle.id = nftnl_obj_get_u64(nlo, NFTNL_OBJ_HANDLE); + if (nftnl_obj_is_set(nlo, NFTNL_OBJ_USERDATA)) { + udata = nftnl_obj_get_data(nlo, NFTNL_OBJ_USERDATA, &ulen); + if (nftnl_udata_parse(udata, ulen, obj_parse_udata_cb, ud) < 0) { + netlink_io_error(ctx, NULL, "Cannot parse userdata"); + obj_free(obj); + return NULL; + } + if (ud[NFTNL_UDATA_OBJ_COMMENT]) + obj->comment = xstrdup(nftnl_udata_get(ud[NFTNL_UDATA_OBJ_COMMENT])); + } type = nftnl_obj_get_u32(nlo, NFTNL_OBJ_TYPE); switch (type) { @@ -1118,11 +1817,13 @@ struct obj *netlink_delinearize_obj(struct netlink_ctx *ctx, obj->ct_helper.l4proto = nftnl_obj_get_u8(nlo, NFTNL_OBJ_CT_HELPER_L4PROTO); break; case NFT_OBJECT_CT_TIMEOUT: + init_list_head(&obj->ct_timeout.timeout_list); obj->ct_timeout.l3proto = nftnl_obj_get_u16(nlo, NFTNL_OBJ_CT_TIMEOUT_L3PROTO); obj->ct_timeout.l4proto = nftnl_obj_get_u8(nlo, NFTNL_OBJ_CT_TIMEOUT_L4PROTO); - memcpy(obj->ct_timeout.timeout, - nftnl_obj_get(nlo, NFTNL_OBJ_CT_TIMEOUT_ARRAY), - NFTNL_CTTIMEOUT_ARRAY_MAX * sizeof(uint32_t)); + if (nftnl_obj_is_set(nlo, NFTNL_OBJ_CT_TIMEOUT_ARRAY)) + memcpy(obj->ct_timeout.timeout, + nftnl_obj_get(nlo, NFTNL_OBJ_CT_TIMEOUT_ARRAY), + NFTNL_CTTIMEOUT_ARRAY_MAX * sizeof(uint32_t)); break; case NFT_OBJECT_LIMIT: obj->limit.rate = @@ -1156,6 +1857,10 @@ struct obj *netlink_delinearize_obj(struct netlink_ctx *ctx, obj->synproxy.flags = nftnl_obj_get_u32(nlo, NFTNL_OBJ_SYNPROXY_FLAGS); break; + default: + netlink_io_error(ctx, NULL, "Unknown object type %u", type); + obj_free(obj); + return NULL; } obj->type = type; @@ -1174,55 +1879,7 @@ void netlink_dump_flowtable(struct nftnl_flowtable *flo, fprintf(fp, "\n"); } -static int list_obj_cb(struct nftnl_obj *nls, void *arg) -{ - struct netlink_ctx *ctx = arg; - struct obj *obj; - - obj = netlink_delinearize_obj(ctx, nls); - if (obj == NULL) - return -1; - list_add_tail(&obj->list, &ctx->list); - return 0; -} - -int netlink_list_objs(struct netlink_ctx *ctx, const struct handle *h) -{ - struct nftnl_obj_list *obj_cache; - int err; - - obj_cache = mnl_nft_obj_dump(ctx, h->family, - h->table.name, NULL, 0, true, false); - if (obj_cache == NULL) { - if (errno == EINTR) - return -1; - - return 0; - } - - err = nftnl_obj_list_foreach(obj_cache, list_obj_cb, ctx); - nftnl_obj_list_free(obj_cache); - return err; -} - -int netlink_reset_objs(struct netlink_ctx *ctx, const struct cmd *cmd, - uint32_t type, bool dump) -{ - const struct handle *h = &cmd->handle; - struct nftnl_obj_list *obj_cache; - int err; - - obj_cache = mnl_nft_obj_dump(ctx, h->family, - h->table.name, h->obj.name, type, dump, true); - if (obj_cache == NULL) - return -1; - - err = nftnl_obj_list_foreach(obj_cache, list_obj_cb, ctx); - nftnl_obj_list_free(obj_cache); - return err; -} - -static struct flowtable * +struct flowtable * netlink_delinearize_flowtable(struct netlink_ctx *ctx, struct nftnl_flowtable *nlo) { @@ -1239,26 +1896,38 @@ netlink_delinearize_flowtable(struct netlink_ctx *ctx, xstrdup(nftnl_flowtable_get_str(nlo, NFTNL_FLOWTABLE_NAME)); flowtable->handle.handle.id = nftnl_flowtable_get_u64(nlo, NFTNL_FLOWTABLE_HANDLE); + if (nftnl_flowtable_is_set(nlo, NFTNL_FLOWTABLE_FLAGS)) + flowtable->flags = nftnl_flowtable_get_u32(nlo, NFTNL_FLOWTABLE_FLAGS); dev_array = nftnl_flowtable_get(nlo, NFTNL_FLOWTABLE_DEVICES); - while (dev_array[len]) + while (dev_array && dev_array[len]) len++; - flowtable->dev_array = calloc(1, len * sizeof(char *)); + if (len) + flowtable->dev_array = xmalloc(len * sizeof(char *)); for (i = 0; i < len; i++) flowtable->dev_array[i] = xstrdup(dev_array[i]); flowtable->dev_array_len = len; - priority = nftnl_flowtable_get_u32(nlo, NFTNL_FLOWTABLE_PRIO); - flowtable->priority.expr = + if (flowtable->dev_array_len) { + qsort(flowtable->dev_array, flowtable->dev_array_len, + sizeof(char *), qsort_device_cmp); + } + + if (nftnl_flowtable_is_set(nlo, NFTNL_FLOWTABLE_PRIO)) { + priority = nftnl_flowtable_get_u32(nlo, NFTNL_FLOWTABLE_PRIO); + flowtable->priority.expr = constant_expr_alloc(&netlink_location, &integer_type, BYTEORDER_HOST_ENDIAN, sizeof(int) * BITS_PER_BYTE, &priority); - flowtable->hooknum = + } + flowtable->hook.num = nftnl_flowtable_get_u32(nlo, NFTNL_FLOWTABLE_HOOKNUM); + flowtable->flags = + nftnl_flowtable_get_u32(nlo, NFTNL_FLOWTABLE_FLAGS); return flowtable; } @@ -1280,7 +1949,8 @@ int netlink_list_flowtables(struct netlink_ctx *ctx, const struct handle *h) struct nftnl_flowtable_list *flowtable_cache; int err; - flowtable_cache = mnl_nft_flowtable_dump(ctx, h->family, h->table.name); + flowtable_cache = mnl_nft_flowtable_dump(ctx, h->family, + h->table.name, NULL); if (flowtable_cache == NULL) { if (errno == EINTR) return -1; @@ -1292,321 +1962,3 @@ int netlink_list_flowtables(struct netlink_ctx *ctx, const struct handle *h) nftnl_flowtable_list_free(flowtable_cache); return err; } - -static void trace_print_hdr(const struct nftnl_trace *nlt, - struct output_ctx *octx) -{ - nft_print(octx, "trace id %08x %s ", - nftnl_trace_get_u32(nlt, NFTNL_TRACE_ID), - family2str(nftnl_trace_get_u32(nlt, NFTNL_TRACE_FAMILY))); - if (nftnl_trace_is_set(nlt, NFTNL_TRACE_TABLE)) - nft_print(octx, "%s ", - nftnl_trace_get_str(nlt, NFTNL_TRACE_TABLE)); - if (nftnl_trace_is_set(nlt, NFTNL_TRACE_CHAIN)) - nft_print(octx, "%s ", - nftnl_trace_get_str(nlt, NFTNL_TRACE_CHAIN)); -} - -static void trace_print_expr(const struct nftnl_trace *nlt, unsigned int attr, - struct expr *lhs, struct output_ctx *octx) -{ - struct expr *rhs, *rel; - const void *data; - uint32_t len; - - data = nftnl_trace_get_data(nlt, attr, &len); - rhs = constant_expr_alloc(&netlink_location, - lhs->dtype, lhs->byteorder, - len * BITS_PER_BYTE, data); - rel = relational_expr_alloc(&netlink_location, OP_EQ, lhs, rhs); - - expr_print(rel, octx); - nft_print(octx, " "); - expr_free(rel); -} - -static void trace_print_verdict(const struct nftnl_trace *nlt, - struct output_ctx *octx) -{ - struct expr *chain_expr = NULL; - const char *chain = NULL; - unsigned int verdict; - struct expr *expr; - - verdict = nftnl_trace_get_u32(nlt, NFTNL_TRACE_VERDICT); - if (nftnl_trace_is_set(nlt, NFTNL_TRACE_JUMP_TARGET)) { - chain = xstrdup(nftnl_trace_get_str(nlt, NFTNL_TRACE_JUMP_TARGET)); - chain_expr = constant_expr_alloc(&netlink_location, - &string_type, - BYTEORDER_HOST_ENDIAN, - strlen(chain) * BITS_PER_BYTE, - chain); - } - expr = verdict_expr_alloc(&netlink_location, verdict, chain_expr); - - nft_print(octx, "verdict "); - expr_print(expr, octx); - expr_free(expr); -} - -static void trace_print_policy(const struct nftnl_trace *nlt, - struct output_ctx *octx) -{ - unsigned int policy; - struct expr *expr; - - policy = nftnl_trace_get_u32(nlt, NFTNL_TRACE_POLICY); - - expr = verdict_expr_alloc(&netlink_location, policy, NULL); - - nft_print(octx, "policy "); - expr_print(expr, octx); - expr_free(expr); -} - -static void trace_print_rule(const struct nftnl_trace *nlt, - struct output_ctx *octx, struct nft_cache *cache) -{ - const struct table *table; - uint64_t rule_handle; - struct chain *chain; - struct rule *rule; - struct handle h; - - h.family = nftnl_trace_get_u32(nlt, NFTNL_TRACE_FAMILY); - h.table.name = nftnl_trace_get_str(nlt, NFTNL_TRACE_TABLE); - h.chain.name = nftnl_trace_get_str(nlt, NFTNL_TRACE_CHAIN); - - if (!h.table.name) - return; - - table = table_lookup(&h, cache); - if (!table) - return; - - chain = chain_lookup(table, &h); - if (!chain) - return; - - rule_handle = nftnl_trace_get_u64(nlt, NFTNL_TRACE_RULE_HANDLE); - rule = rule_lookup(chain, rule_handle); - if (!rule) - return; - - trace_print_hdr(nlt, octx); - nft_print(octx, "rule "); - rule_print(rule, octx); - nft_print(octx, " ("); - trace_print_verdict(nlt, octx); - nft_print(octx, ")\n"); -} - -static void trace_gen_stmts(struct list_head *stmts, - struct proto_ctx *ctx, struct payload_dep_ctx *pctx, - const struct nftnl_trace *nlt, unsigned int attr, - enum proto_bases base) -{ - struct list_head unordered = LIST_HEAD_INIT(unordered); - struct list_head list; - struct expr *rel, *lhs, *rhs, *tmp, *nexpr; - struct stmt *stmt; - const struct proto_desc *desc; - const void *hdr; - uint32_t hlen; - unsigned int n; - bool stacked; - - if (!nftnl_trace_is_set(nlt, attr)) - return; - hdr = nftnl_trace_get_data(nlt, attr, &hlen); - - lhs = payload_expr_alloc(&netlink_location, NULL, 0); - payload_init_raw(lhs, base, 0, hlen * BITS_PER_BYTE); - rhs = constant_expr_alloc(&netlink_location, - &invalid_type, BYTEORDER_INVALID, - hlen * BITS_PER_BYTE, hdr); - -restart: - init_list_head(&list); - payload_expr_expand(&list, lhs, ctx); - expr_free(lhs); - - desc = NULL; - list_for_each_entry_safe(lhs, nexpr, &list, list) { - if (desc && desc != ctx->protocol[base].desc) { - /* Chained protocols */ - lhs->payload.offset = 0; - if (ctx->protocol[base].desc == NULL) - break; - goto restart; - } - - tmp = constant_expr_splice(rhs, lhs->len); - expr_set_type(tmp, lhs->dtype, lhs->byteorder); - if (tmp->byteorder == BYTEORDER_HOST_ENDIAN) - mpz_switch_byteorder(tmp->value, tmp->len / BITS_PER_BYTE); - - /* Skip unknown and filtered expressions */ - desc = lhs->payload.desc; - if (lhs->dtype == &invalid_type || - desc->checksum_key == payload_hdr_field(lhs) || - desc->format.filter & (1 << payload_hdr_field(lhs))) { - expr_free(lhs); - expr_free(tmp); - continue; - } - - rel = relational_expr_alloc(&lhs->location, OP_EQ, lhs, tmp); - stmt = expr_stmt_alloc(&rel->location, rel); - list_add_tail(&stmt->list, &unordered); - - desc = ctx->protocol[base].desc; - relational_expr_pctx_update(ctx, rel); - } - - expr_free(rhs); - - n = 0; -next: - list_for_each_entry(stmt, &unordered, list) { - rel = stmt->expr; - lhs = rel->left; - - /* Move statements to result list in defined order */ - desc = lhs->payload.desc; - if (desc->format.order[n] && - desc->format.order[n] != payload_hdr_field(lhs)) - continue; - - list_move_tail(&stmt->list, stmts); - n++; - - stacked = payload_is_stacked(desc, rel); - - if (lhs->flags & EXPR_F_PROTOCOL && - pctx->pbase == PROTO_BASE_INVALID) { - payload_dependency_store(pctx, stmt, base - stacked); - } else { - payload_dependency_kill(pctx, lhs, ctx->family); - if (lhs->flags & EXPR_F_PROTOCOL) - payload_dependency_store(pctx, stmt, base - stacked); - } - - goto next; - } -} - -static void trace_print_packet(const struct nftnl_trace *nlt, - struct output_ctx *octx) -{ - struct list_head stmts = LIST_HEAD_INIT(stmts); - const struct proto_desc *ll_desc; - struct payload_dep_ctx pctx = {}; - struct proto_ctx ctx; - uint16_t dev_type; - uint32_t nfproto; - struct stmt *stmt, *next; - - trace_print_hdr(nlt, octx); - - nft_print(octx, "packet: "); - if (nftnl_trace_is_set(nlt, NFTNL_TRACE_IIF)) - trace_print_expr(nlt, NFTNL_TRACE_IIF, - meta_expr_alloc(&netlink_location, - NFT_META_IIF), octx); - if (nftnl_trace_is_set(nlt, NFTNL_TRACE_OIF)) - trace_print_expr(nlt, NFTNL_TRACE_OIF, - meta_expr_alloc(&netlink_location, - NFT_META_OIF), octx); - - proto_ctx_init(&ctx, nftnl_trace_get_u32(nlt, NFTNL_TRACE_FAMILY), 0); - ll_desc = ctx.protocol[PROTO_BASE_LL_HDR].desc; - if ((ll_desc == &proto_inet || ll_desc == &proto_netdev) && - nftnl_trace_is_set(nlt, NFTNL_TRACE_NFPROTO)) { - nfproto = nftnl_trace_get_u32(nlt, NFTNL_TRACE_NFPROTO); - - proto_ctx_update(&ctx, PROTO_BASE_LL_HDR, &netlink_location, NULL); - proto_ctx_update(&ctx, PROTO_BASE_NETWORK_HDR, &netlink_location, - proto_find_upper(ll_desc, nfproto)); - } - if (ctx.protocol[PROTO_BASE_LL_HDR].desc == NULL && - nftnl_trace_is_set(nlt, NFTNL_TRACE_IIFTYPE)) { - dev_type = nftnl_trace_get_u16(nlt, NFTNL_TRACE_IIFTYPE); - proto_ctx_update(&ctx, PROTO_BASE_LL_HDR, &netlink_location, - proto_dev_desc(dev_type)); - } - - trace_gen_stmts(&stmts, &ctx, &pctx, nlt, NFTNL_TRACE_LL_HEADER, - PROTO_BASE_LL_HDR); - trace_gen_stmts(&stmts, &ctx, &pctx, nlt, NFTNL_TRACE_NETWORK_HEADER, - PROTO_BASE_NETWORK_HDR); - trace_gen_stmts(&stmts, &ctx, &pctx, nlt, NFTNL_TRACE_TRANSPORT_HEADER, - PROTO_BASE_TRANSPORT_HDR); - - list_for_each_entry_safe(stmt, next, &stmts, list) { - stmt_print(stmt, octx); - nft_print(octx, " "); - stmt_free(stmt); - } - nft_print(octx, "\n"); -} - -int netlink_events_trace_cb(const struct nlmsghdr *nlh, int type, - struct netlink_mon_handler *monh) -{ - struct nftnl_trace *nlt; - - assert(type == NFT_MSG_TRACE); - - nlt = nftnl_trace_alloc(); - if (!nlt) - memory_allocation_error(); - - if (nftnl_trace_nlmsg_parse(nlh, nlt) < 0) - netlink_abi_error(); - - switch (nftnl_trace_get_u32(nlt, NFTNL_TRACE_TYPE)) { - case NFT_TRACETYPE_RULE: - if (nftnl_trace_is_set(nlt, NFTNL_TRACE_LL_HEADER) || - nftnl_trace_is_set(nlt, NFTNL_TRACE_NETWORK_HEADER)) - trace_print_packet(nlt, &monh->ctx->nft->output); - - if (nftnl_trace_is_set(nlt, NFTNL_TRACE_RULE_HANDLE)) - trace_print_rule(nlt, &monh->ctx->nft->output, - &monh->ctx->nft->cache); - break; - case NFT_TRACETYPE_POLICY: - trace_print_hdr(nlt, &monh->ctx->nft->output); - - if (nftnl_trace_is_set(nlt, NFTNL_TRACE_POLICY)) { - trace_print_policy(nlt, &monh->ctx->nft->output); - nft_mon_print(monh, " "); - } - - if (nftnl_trace_is_set(nlt, NFTNL_TRACE_MARK)) - trace_print_expr(nlt, NFTNL_TRACE_MARK, - meta_expr_alloc(&netlink_location, - NFT_META_MARK), - &monh->ctx->nft->output); - nft_mon_print(monh, "\n"); - break; - case NFT_TRACETYPE_RETURN: - trace_print_hdr(nlt, &monh->ctx->nft->output); - - if (nftnl_trace_is_set(nlt, NFTNL_TRACE_VERDICT)) { - trace_print_verdict(nlt, &monh->ctx->nft->output); - nft_mon_print(monh, " "); - } - - if (nftnl_trace_is_set(nlt, NFTNL_TRACE_MARK)) - trace_print_expr(nlt, NFTNL_TRACE_MARK, - meta_expr_alloc(&netlink_location, - NFT_META_MARK), - &monh->ctx->nft->output); - nft_mon_print(monh, "\n"); - break; - } - - nftnl_trace_free(nlt); - return MNL_CB_OK; -} diff --git a/src/netlink_delinearize.c b/src/netlink_delinearize.c index 88dbd5a8..b4d4a3da 100644 --- a/src/netlink_delinearize.c +++ b/src/netlink_delinearize.c @@ -9,12 +9,12 @@ * Development of this code funded by Astaro AG (http://www.astaro.com/) */ -#include <stdlib.h> -#include <stdbool.h> -#include <string.h> +#include <nft.h> + #include <limits.h> #include <linux/netfilter/nf_tables.h> #include <arpa/inet.h> +#include <linux/netfilter/nf_nat.h> #include <linux/netfilter.h> #include <net/ethernet.h> #include <netlink.h> @@ -26,8 +26,19 @@ #include <erec.h> #include <sys/socket.h> #include <libnftnl/udata.h> +#include <cache.h> #include <xt.h> +struct dl_proto_ctx *dl_proto_ctx(struct rule_pp_ctx *ctx) +{ + return ctx->dl; +} + +static struct dl_proto_ctx *dl_proto_ctx_outer(struct rule_pp_ctx *ctx) +{ + return &ctx->_dl[0]; +} + static int netlink_parse_expr(const struct nftnl_expr *nle, struct netlink_parse_ctx *ctx); @@ -70,8 +81,7 @@ static void netlink_set_register(struct netlink_parse_ctx *ctx, return; } - if (ctx->registers[reg] != NULL) - expr_free(ctx->registers[reg]); + expr_free(ctx->registers[reg]); ctx->registers[reg] = expr; } @@ -98,7 +108,7 @@ static void netlink_release_registers(struct netlink_parse_ctx *ctx) { int i; - for (i = 0; i < MAX_REGS; i++) + for (i = 0; i <= MAX_REGS; i++) expr_free(ctx->registers[i]); } @@ -132,6 +142,50 @@ err: return NULL; } +static struct expr *netlink_parse_concat_key(struct netlink_parse_ctx *ctx, + const struct location *loc, + unsigned int reg, + const struct expr *key) +{ + uint32_t type = key->dtype->type; + unsigned int n, len = key->len; + struct expr *concat, *expr; + unsigned int consumed; + + concat = concat_expr_alloc(loc); + n = div_round_up(fls(type), TYPE_BITS); + + while (len > 0) { + const struct datatype *i; + + expr = netlink_get_register(ctx, loc, reg); + if (expr == NULL) { + netlink_error(ctx, loc, + "Concat expression size mismatch"); + goto err; + } + + if (n > 0 && concat_subtype_id(type, --n)) { + i = concat_subtype_lookup(type, n); + + expr_set_type(expr, i, i->byteorder); + } + + compound_expr_add(concat, expr); + + consumed = netlink_padded_len(expr->len); + assert(consumed > 0); + len -= consumed; + reg += netlink_register_space(expr->len); + } + + return concat; + +err: + expr_free(concat); + return NULL; +} + static struct expr *netlink_parse_concat_data(struct netlink_parse_ctx *ctx, const struct location *loc, unsigned int reg, @@ -154,6 +208,7 @@ static struct expr *netlink_parse_concat_data(struct netlink_parse_ctx *ctx, len -= netlink_padded_len(expr->len); reg += netlink_register_space(expr->len); + expr_free(expr); } return concat; @@ -162,6 +217,31 @@ err: return NULL; } +static void netlink_parse_chain_verdict(struct netlink_parse_ctx *ctx, + const struct location *loc, + struct expr *expr, + enum nft_verdicts verdict) +{ + char chain_name[NFT_CHAIN_MAXNAMELEN] = {}; + struct chain *chain; + + expr_chain_export(expr->chain, chain_name); + chain = chain_binding_lookup(ctx->table, chain_name); + + /* Special case: 'nft list chain x y' needs to pull in implicit chains */ + if (!chain && !strncmp(chain_name, "__chain", strlen("__chain"))) { + nft_chain_cache_update(ctx->nlctx, ctx->table, chain_name); + chain = chain_binding_lookup(ctx->table, chain_name); + } + + if (chain) { + ctx->stmt = chain_stmt_alloc(loc, chain, verdict); + expr_free(expr); + } else { + ctx->stmt = verdict_stmt_alloc(loc, expr); + } +} + static void netlink_parse_immediate(struct netlink_parse_ctx *ctx, const struct location *loc, const struct nftnl_expr *nle) @@ -171,7 +251,7 @@ static void netlink_parse_immediate(struct netlink_parse_ctx *ctx, struct expr *expr; if (nftnl_expr_is_set(nle, NFTNL_EXPR_IMM_VERDICT)) { - nld.verdict = nftnl_expr_get_u32(nle, NFTNL_EXPR_IMM_VERDICT); + nld.verdict = nftnl_expr_get_u32(nle, NFTNL_EXPR_IMM_VERDICT); if (nftnl_expr_is_set(nle, NFTNL_EXPR_IMM_CHAIN)) { nld.chain = nftnl_expr_get(nle, NFTNL_EXPR_IMM_CHAIN, &nld.len); @@ -181,12 +261,23 @@ static void netlink_parse_immediate(struct netlink_parse_ctx *ctx, } dreg = netlink_parse_register(nle, NFTNL_EXPR_IMM_DREG); - expr = netlink_alloc_data(loc, &nld, dreg); - if (dreg == NFT_REG_VERDICT) - ctx->stmt = verdict_stmt_alloc(loc, expr); - else + + if (dreg == NFT_REG_VERDICT) { + switch (expr->verdict) { + case NFT_JUMP: + netlink_parse_chain_verdict(ctx, loc, expr, NFT_JUMP); + break; + case NFT_GOTO: + netlink_parse_chain_verdict(ctx, loc, expr, NFT_GOTO); + break; + default: + ctx->stmt = verdict_stmt_alloc(loc, expr); + break; + } + } else { netlink_set_register(ctx, dreg, expr); + } } static void netlink_parse_xfrm(struct netlink_parse_ctx *ctx, @@ -291,8 +382,9 @@ static void netlink_parse_cmp(struct netlink_parse_ctx *ctx, if (left->len > right->len && expr_basetype(left) != &string_type) { - netlink_error(ctx, loc, "Relational expression size mismatch"); - goto err_free; + mpz_lshift_ui(right->value, left->len - right->len); + right = prefix_expr_alloc(loc, right, right->len); + right->prefix->len = left->len; } else if (left->len > 0 && left->len < right->len) { expr_free(left); left = netlink_parse_concat_expr(ctx, loc, sreg, right->len); @@ -324,7 +416,7 @@ static void netlink_parse_lookup(struct netlink_parse_ctx *ctx, uint32_t flag; name = nftnl_expr_get_str(nle, NFTNL_EXPR_LOOKUP_SET); - set = set_lookup(ctx->table, name); + set = set_cache_find(ctx->table, name); if (set == NULL) return netlink_error(ctx, loc, "Unknown set '%s' in lookup expression", @@ -363,22 +455,17 @@ static void netlink_parse_lookup(struct netlink_parse_ctx *ctx, ctx->stmt = expr_stmt_alloc(loc, expr); } -static void netlink_parse_bitwise(struct netlink_parse_ctx *ctx, - const struct location *loc, - const struct nftnl_expr *nle) +static struct expr * +netlink_parse_bitwise_mask_xor(struct netlink_parse_ctx *ctx, + const struct location *loc, + const struct nftnl_expr *nle, + enum nft_registers sreg, + struct expr *left) { struct nft_data_delinearize nld; - enum nft_registers sreg, dreg; - struct expr *expr, *left, *mask, *xor, *or; + struct expr *expr, *mask, *xor, *or; mpz_t m, x, o; - sreg = netlink_parse_register(nle, NFTNL_EXPR_BITWISE_SREG); - left = netlink_get_register(ctx, loc, sreg); - if (left == NULL) - return netlink_error(ctx, loc, - "Bitwise expression has no left " - "hand side"); - expr = left; nld.value = nftnl_expr_get(nle, NFTNL_EXPR_BITWISE_MASK, &nld.len); @@ -400,7 +487,7 @@ static void netlink_parse_bitwise(struct netlink_parse_ctx *ctx, mpz_ior(m, m, o); } - if (left->len > 0 && mpz_scan0(m, 0) == left->len) { + if (left->len > 0 && mpz_scan0(m, 0) >= left->len) { /* mask encompasses the entire value */ expr_free(mask); } else { @@ -430,6 +517,98 @@ static void netlink_parse_bitwise(struct netlink_parse_ctx *ctx, mpz_clear(x); mpz_clear(o); + return expr; +} + +static struct expr *netlink_parse_bitwise_bool(struct netlink_parse_ctx *ctx, + const struct location *loc, + const struct nftnl_expr *nle, + enum nft_bitwise_ops op, + enum nft_registers sreg, + struct expr *left) +{ + enum nft_registers sreg2; + struct expr *right, *expr; + + sreg2 = netlink_parse_register(nle, NFTNL_EXPR_BITWISE_SREG2); + right = netlink_get_register(ctx, loc, sreg2); + if (right == NULL) { + netlink_error(ctx, loc, + "Bitwise expression has no right-hand expression"); + return NULL; + } + + expr = binop_expr_alloc(loc, + op == NFT_BITWISE_XOR ? OP_XOR : + op == NFT_BITWISE_AND ? OP_AND : OP_OR, + left, right); + + if (left->len > 0) + expr->len = left->len; + + return expr; +} + +static struct expr *netlink_parse_bitwise_shift(struct netlink_parse_ctx *ctx, + const struct location *loc, + const struct nftnl_expr *nle, + enum nft_bitwise_ops op, + enum nft_registers sreg, + struct expr *left) +{ + struct nft_data_delinearize nld; + struct expr *expr, *right; + + nld.value = nftnl_expr_get(nle, NFTNL_EXPR_BITWISE_DATA, &nld.len); + right = netlink_alloc_value(loc, &nld); + right->byteorder = BYTEORDER_HOST_ENDIAN; + + expr = binop_expr_alloc(loc, + op == NFT_BITWISE_LSHIFT ? OP_LSHIFT : OP_RSHIFT, + left, right); + expr->len = nftnl_expr_get_u32(nle, NFTNL_EXPR_BITWISE_LEN) * BITS_PER_BYTE; + + return expr; +} + +static void netlink_parse_bitwise(struct netlink_parse_ctx *ctx, + const struct location *loc, + const struct nftnl_expr *nle) +{ + enum nft_registers sreg, dreg; + struct expr *expr, *left; + enum nft_bitwise_ops op; + + sreg = netlink_parse_register(nle, NFTNL_EXPR_BITWISE_SREG); + left = netlink_get_register(ctx, loc, sreg); + if (left == NULL) + return netlink_error(ctx, loc, + "Bitwise expression has no left " + "hand side"); + + op = nftnl_expr_get_u32(nle, NFTNL_EXPR_BITWISE_OP); + + switch (op) { + case NFT_BITWISE_MASK_XOR: + expr = netlink_parse_bitwise_mask_xor(ctx, loc, nle, sreg, + left); + break; + case NFT_BITWISE_XOR: + case NFT_BITWISE_AND: + case NFT_BITWISE_OR: + expr = netlink_parse_bitwise_bool(ctx, loc, nle, op, + sreg, left); + break; + case NFT_BITWISE_LSHIFT: + case NFT_BITWISE_RSHIFT: + expr = netlink_parse_bitwise_shift(ctx, loc, nle, op, + sreg, left); + break; + default: + return netlink_error(ctx, loc, + "Invalid bitwise operation %u", op); + } + dreg = netlink_parse_register(nle, NFTNL_EXPR_BITWISE_DREG); netlink_set_register(ctx, dreg, expr); } @@ -438,6 +617,7 @@ static void netlink_parse_byteorder(struct netlink_parse_ctx *ctx, const struct location *loc, const struct nftnl_expr *nle) { + uint32_t opval = nftnl_expr_get_u32(nle, NFTNL_EXPR_BYTEORDER_OP); enum nft_registers sreg, dreg; struct expr *expr, *arg; enum ops op; @@ -449,7 +629,7 @@ static void netlink_parse_byteorder(struct netlink_parse_ctx *ctx, "Byteorder expression has no left " "hand side"); - switch (nftnl_expr_get_u32(nle, NFTNL_EXPR_BYTEORDER_OP)) { + switch (opval) { case NFT_BYTEORDER_NTOH: op = OP_NTOH; break; @@ -457,8 +637,9 @@ static void netlink_parse_byteorder(struct netlink_parse_ctx *ctx, op = OP_HTON; break; default: - BUG("invalid byteorder operation %u\n", - nftnl_expr_get_u32(nle, NFTNL_EXPR_BYTEORDER_OP)); + expr_free(arg); + return netlink_error(ctx, loc, + "Invalid byteorder operation %u", opval); } expr = unary_expr_alloc(loc, op, arg); @@ -477,6 +658,10 @@ static void netlink_parse_payload_expr(struct netlink_parse_ctx *ctx, struct expr *expr; base = nftnl_expr_get_u32(nle, NFTNL_EXPR_PAYLOAD_BASE) + 1; + + if (base == NFT_PAYLOAD_TUN_HEADER + 1) + base = NFT_PAYLOAD_INNER_HEADER + 1; + offset = nftnl_expr_get_u32(nle, NFTNL_EXPR_PAYLOAD_OFFSET) * BITS_PER_BYTE; len = nftnl_expr_get_u32(nle, NFTNL_EXPR_PAYLOAD_LEN) * BITS_PER_BYTE; @@ -484,9 +669,82 @@ static void netlink_parse_payload_expr(struct netlink_parse_ctx *ctx, payload_init_raw(expr, base, offset, len); dreg = netlink_parse_register(nle, NFTNL_EXPR_PAYLOAD_DREG); + + if (ctx->inner) + ctx->inner_reg = dreg; + netlink_set_register(ctx, dreg, expr); } +static void netlink_parse_inner(struct netlink_parse_ctx *ctx, + const struct location *loc, + const struct nftnl_expr *nle) +{ + const struct proto_desc *inner_desc; + const struct nftnl_expr *inner_nle; + uint32_t hdrsize, flags, type; + struct expr *expr; + + hdrsize = nftnl_expr_get_u32(nle, NFTNL_EXPR_INNER_HDRSIZE); + type = nftnl_expr_get_u32(nle, NFTNL_EXPR_INNER_TYPE); + flags = nftnl_expr_get_u32(nle, NFTNL_EXPR_INNER_FLAGS); + + inner_nle = nftnl_expr_get(nle, NFTNL_EXPR_INNER_EXPR, NULL); + if (!inner_nle) { + netlink_error(ctx, loc, "Could not parse inner expression"); + return; + } + + inner_desc = proto_find_inner(type, hdrsize, flags); + + ctx->inner = true; + if (netlink_parse_expr(inner_nle, ctx) < 0) { + ctx->inner = false; + return; + } + ctx->inner = false; + + expr = netlink_get_register(ctx, loc, ctx->inner_reg); + assert(expr); + + if (expr->etype == EXPR_PAYLOAD && + expr->payload.base == PROTO_BASE_INNER_HDR) { + const struct proto_hdr_template *tmpl; + unsigned int i; + + for (i = 1; i < array_size(inner_desc->templates); i++) { + tmpl = &inner_desc->templates[i]; + + if (tmpl->len == 0) + return; + + if (tmpl->offset != expr->payload.offset || + tmpl->len != expr->len) + continue; + + expr->payload.desc = inner_desc; + expr->payload.tmpl = tmpl; + break; + } + } + + switch (expr->etype) { + case EXPR_PAYLOAD: + expr->payload.inner_desc = inner_desc; + break; + case EXPR_META: + expr->meta.inner_desc = inner_desc; + break; + default: + netlink_error(ctx, loc, "Unsupported inner expression type %s", + expr_ops(expr)->name); + expr_free(expr); + return; + } + + netlink_set_register(ctx, ctx->inner_reg, expr); +} + static void netlink_parse_payload_stmt(struct netlink_parse_ctx *ctx, const struct location *loc, const struct nftnl_expr *nle) @@ -510,8 +768,7 @@ static void netlink_parse_payload_stmt(struct netlink_parse_ctx *ctx, payload_init_raw(expr, base, offset, len); stmt = payload_stmt_alloc(loc, expr, val); - - list_add_tail(&stmt->list, &ctx->rule->stmts); + rule_stmt_append(ctx->rule, stmt); } static void netlink_parse_payload(struct netlink_parse_ctx *ctx, @@ -554,7 +811,7 @@ static void netlink_parse_exthdr(struct netlink_parse_ctx *ctx, sreg = netlink_parse_register(nle, NFTNL_EXPR_EXTHDR_SREG); val = netlink_get_register(ctx, loc, sreg); if (val == NULL) { - xfree(expr); + expr_free(expr); return netlink_error(ctx, loc, "exthdr statement has no expression"); } @@ -562,7 +819,11 @@ static void netlink_parse_exthdr(struct netlink_parse_ctx *ctx, expr_set_type(val, expr->dtype, expr->byteorder); stmt = exthdr_stmt_alloc(loc, expr, val); - list_add_tail(&stmt->list, &ctx->rule->stmts); + rule_stmt_append(ctx->rule, stmt); + } else { + struct stmt *stmt = optstrip_stmt_alloc(loc, expr); + + rule_stmt_append(ctx->rule, stmt); } } @@ -596,7 +857,7 @@ static void netlink_parse_hash(struct netlink_parse_ctx *ctx, len = nftnl_expr_get_u32(nle, NFTNL_EXPR_HASH_LEN) * BITS_PER_BYTE; if (hexpr->len < len) { - xfree(hexpr); + expr_free(hexpr); hexpr = netlink_parse_concat_expr(ctx, loc, sreg, len); if (hexpr == NULL) goto out_err; @@ -608,7 +869,7 @@ static void netlink_parse_hash(struct netlink_parse_ctx *ctx, netlink_set_register(ctx, dreg, expr); return; out_err: - xfree(expr); + expr_free(expr); } static void netlink_parse_fib(struct netlink_parse_ctx *ctx, @@ -640,6 +901,9 @@ static void netlink_parse_meta_expr(struct netlink_parse_ctx *ctx, expr = meta_expr_alloc(loc, key); dreg = netlink_parse_register(nle, NFTNL_EXPR_META_DREG); + if (ctx->inner) + ctx->inner_reg = dreg; + netlink_set_register(ctx, dreg, expr); } @@ -648,11 +912,12 @@ static void netlink_parse_socket(struct netlink_parse_ctx *ctx, const struct nftnl_expr *nle) { enum nft_registers dreg; - uint32_t key; + uint32_t key, level; struct expr * expr; key = nftnl_expr_get_u32(nle, NFTNL_EXPR_SOCKET_KEY); - expr = socket_expr_alloc(loc, key); + level = nftnl_expr_get_u32(nle, NFTNL_EXPR_SOCKET_LEVEL); + expr = socket_expr_alloc(loc, key, level); dreg = netlink_parse_register(nle, NFTNL_EXPR_SOCKET_DREG); netlink_set_register(ctx, dreg, expr); @@ -692,7 +957,9 @@ static void netlink_parse_meta_stmt(struct netlink_parse_ctx *ctx, key = nftnl_expr_get_u32(nle, NFTNL_EXPR_META_KEY); stmt = meta_stmt_alloc(loc, key, expr); - expr_set_type(expr, stmt->meta.tmpl->dtype, stmt->meta.tmpl->byteorder); + + if (stmt->meta.tmpl) + expr_set_type(expr, stmt->meta.tmpl->dtype, stmt->meta.tmpl->byteorder); ctx->stmt = stmt; } @@ -726,8 +993,8 @@ static void netlink_parse_numgen(struct netlink_parse_ctx *ctx, const struct location *loc, const struct nftnl_expr *nle) { - enum nft_registers dreg; uint32_t type, until, offset; + enum nft_registers dreg; struct expr *expr; type = nftnl_expr_get_u32(nle, NFTNL_EXPR_NG_TYPE); @@ -839,6 +1106,19 @@ static void netlink_parse_counter(struct netlink_parse_ctx *ctx, ctx->stmt = stmt; } +static void netlink_parse_last(struct netlink_parse_ctx *ctx, + const struct location *loc, + const struct nftnl_expr *nle) +{ + struct stmt *stmt; + + stmt = last_stmt_alloc(loc); + stmt->last.used = nftnl_expr_get_u64(nle, NFTNL_EXPR_LAST_MSECS); + stmt->last.set = nftnl_expr_get_u32(nle, NFTNL_EXPR_LAST_SET); + + ctx->stmt = stmt; +} + static void netlink_parse_log(struct netlink_parse_ctx *ctx, const struct location *loc, const struct nftnl_expr *nle) @@ -927,6 +1207,85 @@ static void netlink_parse_reject(struct netlink_parse_ctx *ctx, ctx->stmt = stmt; } +static bool is_nat_addr_map(const struct expr *addr, uint8_t family, + struct stmt *stmt) +{ + const struct expr *mappings, *data; + const struct set *set; + + if (!addr || + expr_ops(addr)->type != EXPR_MAP) + return false; + + mappings = addr->right; + if (expr_ops(mappings)->type != EXPR_SET_REF) + return false; + + set = mappings->set; + data = set->data; + + if (!(data->flags & EXPR_F_INTERVAL)) + return false; + + stmt->nat.family = family; + + /* if we're dealing with an address:address map, + * the length will be bit_sizeof(addr) + 32 (one register). + */ + switch (family) { + case NFPROTO_IPV4: + if (data->len == 32 + 32) { + stmt->nat.type_flags |= STMT_NAT_F_INTERVAL; + return true; + } else if (data->len == 32 + 32 + 32 + 32) { + stmt->nat.type_flags |= STMT_NAT_F_INTERVAL | + STMT_NAT_F_CONCAT; + return true; + } + break; + case NFPROTO_IPV6: + if (data->len == 128 + 128) { + stmt->nat.type_flags |= STMT_NAT_F_INTERVAL; + return true; + } else if (data->len == 128 + 32 + 128 + 32) { + stmt->nat.type_flags |= STMT_NAT_F_INTERVAL | + STMT_NAT_F_CONCAT; + return true; + } + } + + return false; +} + +static bool is_nat_proto_map(const struct expr *addr, uint8_t family) +{ + const struct expr *mappings, *data; + const struct set *set; + + if (!addr || + expr_ops(addr)->type != EXPR_MAP) + return false; + + mappings = addr->right; + if (expr_ops(mappings)->type != EXPR_SET_REF) + return false; + + set = mappings->set; + data = set->data; + + /* if we're dealing with an address:inet_service map, + * the length will be bit_sizeof(addr) + 32 (one register). + */ + switch (family) { + case NFPROTO_IPV4: + return data->len == 32 + 32; + case NFPROTO_IPV6: + return data->len == 128 + 32; + } + + return false; +} + static void netlink_parse_nat(struct netlink_parse_ctx *ctx, const struct location *loc, const struct nftnl_expr *nle) @@ -947,6 +1306,10 @@ static void netlink_parse_nat(struct netlink_parse_ctx *ctx, if (nftnl_expr_is_set(nle, NFTNL_EXPR_NAT_FLAGS)) stmt->nat.flags = nftnl_expr_get_u32(nle, NFTNL_EXPR_NAT_FLAGS); + if (stmt->nat.flags & NF_NAT_RANGE_NETMAP) + stmt->nat.type_flags |= STMT_NAT_F_PREFIX; + + addr = NULL; reg1 = netlink_parse_register(nle, NFTNL_EXPR_NAT_REG_ADDR_MIN); if (reg1) { addr = netlink_get_register(ctx, loc, reg1); @@ -964,6 +1327,12 @@ static void netlink_parse_nat(struct netlink_parse_ctx *ctx, stmt->nat.addr = addr; } + if (is_nat_addr_map(addr, family, stmt)) { + stmt->nat.family = family; + ctx->stmt = stmt; + return; + } + reg2 = netlink_parse_register(nle, NFTNL_EXPR_NAT_REG_ADDR_MAX); if (reg2 && reg2 != reg1) { addr = netlink_get_register(ctx, loc, reg2); @@ -978,11 +1347,20 @@ static void netlink_parse_nat(struct netlink_parse_ctx *ctx, else expr_set_type(addr, &ip6addr_type, BYTEORDER_BIG_ENDIAN); - if (stmt->nat.addr != NULL) + if (stmt->nat.addr != NULL) { addr = range_expr_alloc(loc, stmt->nat.addr, addr); + addr = range_expr_to_prefix(addr); + } stmt->nat.addr = addr; } + if (is_nat_proto_map(addr, family)) { + stmt->nat.family = family; + stmt->nat.type_flags |= STMT_NAT_F_CONCAT; + ctx->stmt = stmt; + return; + } + reg1 = netlink_parse_register(nle, NFTNL_EXPR_NAT_REG_PROTO_MIN); if (reg1) { proto = netlink_get_register(ctx, loc, reg1); @@ -1014,7 +1392,7 @@ static void netlink_parse_nat(struct netlink_parse_ctx *ctx, ctx->stmt = stmt; return; out_err: - xfree(stmt); + stmt_free(stmt); } static void netlink_parse_synproxy(struct netlink_parse_ctx *ctx, @@ -1078,7 +1456,7 @@ static void netlink_parse_tproxy(struct netlink_parse_ctx *ctx, ctx->stmt = stmt; return; err: - xfree(stmt); + stmt_free(stmt); } static void netlink_parse_masq(struct netlink_parse_ctx *ctx, @@ -1125,7 +1503,7 @@ static void netlink_parse_masq(struct netlink_parse_ctx *ctx, ctx->stmt = stmt; return; out_err: - xfree(stmt); + stmt_free(stmt); } static void netlink_parse_redir(struct netlink_parse_ctx *ctx, @@ -1176,7 +1554,7 @@ static void netlink_parse_redir(struct netlink_parse_ctx *ctx, ctx->stmt = stmt; return; out_err: - xfree(stmt); + stmt_free(stmt); } static void netlink_parse_dup(struct netlink_parse_ctx *ctx, @@ -1229,7 +1607,7 @@ static void netlink_parse_dup(struct netlink_parse_ctx *ctx, ctx->stmt = stmt; return; out_err: - xfree(stmt); + stmt_free(stmt); } static void netlink_parse_fwd(struct netlink_parse_ctx *ctx, @@ -1291,49 +1669,88 @@ static void netlink_parse_fwd(struct netlink_parse_ctx *ctx, ctx->stmt = stmt; return; out_err: - xfree(stmt); + stmt_free(stmt); } static void netlink_parse_queue(struct netlink_parse_ctx *ctx, const struct location *loc, const struct nftnl_expr *nle) { - struct expr *expr, *high; - struct stmt *stmt; - uint16_t num, total; + struct expr *expr; + uint16_t flags; + + if (nftnl_expr_is_set(nle, NFTNL_EXPR_QUEUE_SREG_QNUM)) { + enum nft_registers reg = netlink_parse_register(nle, NFTNL_EXPR_QUEUE_SREG_QNUM); + + expr = netlink_get_register(ctx, loc, reg); + if (!expr) { + netlink_error(ctx, loc, "queue statement has no sreg expression"); + return; + } + } else { + uint16_t total = nftnl_expr_get_u16(nle, NFTNL_EXPR_QUEUE_TOTAL); + uint16_t num = nftnl_expr_get_u16(nle, NFTNL_EXPR_QUEUE_NUM); - num = nftnl_expr_get_u16(nle, NFTNL_EXPR_QUEUE_NUM); - total = nftnl_expr_get_u16(nle, NFTNL_EXPR_QUEUE_TOTAL); + expr = constant_expr_alloc(loc, &integer_type, + BYTEORDER_HOST_ENDIAN, 16, &num); - expr = constant_expr_alloc(loc, &integer_type, - BYTEORDER_HOST_ENDIAN, 16, &num); - if (total > 1) { - total += num - 1; - high = constant_expr_alloc(loc, &integer_type, + if (total > 1) { + struct expr *high; + + total += num - 1; + high = constant_expr_alloc(loc, &integer_type, BYTEORDER_HOST_ENDIAN, 16, &total); - expr = range_expr_alloc(loc, expr, high); + expr = range_expr_alloc(loc, expr, high); + } } - stmt = queue_stmt_alloc(loc); - stmt->queue.queue = expr; - stmt->queue.flags = nftnl_expr_get_u16(nle, NFTNL_EXPR_QUEUE_FLAGS); + flags = nftnl_expr_get_u16(nle, NFTNL_EXPR_QUEUE_FLAGS); + ctx->stmt = queue_stmt_alloc(loc, expr, flags); +} - ctx->stmt = stmt; +struct dynset_parse_ctx { + struct netlink_parse_ctx *nlctx; + const struct location *loc; + struct list_head stmt_list; +}; + +static int dynset_parse_expressions(struct nftnl_expr *e, void *data) +{ + struct dynset_parse_ctx *dynset_parse_ctx = data; + struct netlink_parse_ctx *ctx = dynset_parse_ctx->nlctx; + const struct location *loc = dynset_parse_ctx->loc; + struct stmt *stmt; + + if (netlink_parse_expr(e, ctx) < 0 || !ctx->stmt) { + netlink_error(ctx, loc, "Could not parse dynset stmt"); + return -1; + } + stmt = ctx->stmt; + + list_add_tail(&stmt->list, &dynset_parse_ctx->stmt_list); + + return 0; } static void netlink_parse_dynset(struct netlink_parse_ctx *ctx, const struct location *loc, const struct nftnl_expr *nle) { + struct dynset_parse_ctx dynset_parse_ctx = { + .nlctx = ctx, + .loc = loc, + }; struct expr *expr, *expr_data = NULL; enum nft_registers sreg, sreg_data; + struct stmt *stmt, *dstmt, *next; const struct nftnl_expr *dnle; - struct stmt *stmt, *dstmt; struct set *set; const char *name; + init_list_head(&dynset_parse_ctx.stmt_list); + name = nftnl_expr_get_str(nle, NFTNL_EXPR_DYNSET_SET_NAME); - set = set_lookup(ctx->table, name); + set = set_cache_find(ctx->table, name); if (set == NULL) return netlink_error(ctx, loc, "Unknown set '%s' in dynset statement", @@ -1347,58 +1764,85 @@ static void netlink_parse_dynset(struct netlink_parse_ctx *ctx, if (expr->len < set->key->len) { expr_free(expr); - expr = netlink_parse_concat_expr(ctx, loc, sreg, set->key->len); + expr = netlink_parse_concat_key(ctx, loc, sreg, set->key); if (expr == NULL) return; + } else if (expr->dtype == &invalid_type) { + expr_set_type(expr, datatype_get(set->key->dtype), set->key->byteorder); } expr = set_elem_expr_alloc(&expr->location, expr); expr->timeout = nftnl_expr_get_u64(nle, NFTNL_EXPR_DYNSET_TIMEOUT); - dstmt = NULL; - dnle = nftnl_expr_get(nle, NFTNL_EXPR_DYNSET_EXPR, NULL); - if (dnle != NULL) { - if (netlink_parse_expr(dnle, ctx) < 0) - goto out_err; - if (ctx->stmt == NULL) { - netlink_error(ctx, loc, "Could not parse dynset stmt"); - goto out_err; + if (nftnl_expr_is_set(nle, NFTNL_EXPR_DYNSET_EXPR)) { + dstmt = NULL; + dnle = nftnl_expr_get(nle, NFTNL_EXPR_DYNSET_EXPR, NULL); + if (dnle != NULL) { + if (netlink_parse_expr(dnle, ctx) < 0) + goto out_err; + if (ctx->stmt == NULL) { + netlink_error(ctx, loc, + "Could not parse dynset stmt"); + goto out_err; + } + dstmt = ctx->stmt; + list_add_tail(&dstmt->list, + &dynset_parse_ctx.stmt_list); } - dstmt = ctx->stmt; + } else if (nftnl_expr_is_set(nle, NFTNL_EXPR_DYNSET_EXPRESSIONS)) { + if (nftnl_expr_expr_foreach(nle, dynset_parse_expressions, + &dynset_parse_ctx) < 0) + goto out_err; } if (nftnl_expr_is_set(nle, NFTNL_EXPR_DYNSET_SREG_DATA)) { sreg_data = netlink_parse_register(nle, NFTNL_EXPR_DYNSET_SREG_DATA); expr_data = netlink_get_register(ctx, loc, sreg_data); + + if (expr_data && expr_data->len < set->data->len) { + expr_free(expr_data); + expr_data = netlink_parse_concat_expr(ctx, loc, sreg_data, set->data->len); + if (expr_data == NULL) + netlink_error(ctx, loc, + "Could not parse dynset map data expressions"); + } } if (expr_data != NULL) { + expr_set_type(expr_data, set->data->dtype, set->data->byteorder); stmt = map_stmt_alloc(loc); stmt->map.set = set_ref_expr_alloc(loc, set); stmt->map.key = expr; stmt->map.data = expr_data; - stmt->map.stmt = dstmt; stmt->map.op = nftnl_expr_get_u32(nle, NFTNL_EXPR_DYNSET_OP); + list_splice_tail(&dynset_parse_ctx.stmt_list, + &stmt->map.stmt_list); } else { - if (dstmt != NULL && set->flags & NFT_SET_ANONYMOUS) { + if (!list_empty(&dynset_parse_ctx.stmt_list) && + set_is_anonymous(set->flags)) { stmt = meter_stmt_alloc(loc); stmt->meter.set = set_ref_expr_alloc(loc, set); stmt->meter.key = expr; - stmt->meter.stmt = dstmt; + stmt->meter.stmt = list_first_entry(&dynset_parse_ctx.stmt_list, + struct stmt, list); stmt->meter.size = set->desc.size; } else { stmt = set_stmt_alloc(loc); stmt->set.set = set_ref_expr_alloc(loc, set); stmt->set.op = nftnl_expr_get_u32(nle, NFTNL_EXPR_DYNSET_OP); stmt->set.key = expr; - stmt->set.stmt = dstmt; + list_splice_tail(&dynset_parse_ctx.stmt_list, + &stmt->set.stmt_list); } } ctx->stmt = stmt; return; out_err: - xfree(expr); + list_for_each_entry_safe(dstmt, next, &dynset_parse_ctx.stmt_list, list) + stmt_free(dstmt); + + expr_free(expr); } static void netlink_parse_objref(struct netlink_parse_ctx *ctx, @@ -1425,7 +1869,7 @@ static void netlink_parse_objref(struct netlink_parse_ctx *ctx, struct set *set; name = nftnl_expr_get_str(nle, NFTNL_EXPR_OBJREF_SET_NAME); - set = set_lookup(ctx->table, name); + set = set_cache_find(ctx->table, name); if (set == NULL) return netlink_error(ctx, loc, "Unknown set '%s' in objref expression", @@ -1460,18 +1904,21 @@ static void netlink_parse_objref(struct netlink_parse_ctx *ctx, ctx->stmt = stmt; } -static const struct { +struct expr_handler { const char *name; void (*parse)(struct netlink_parse_ctx *ctx, const struct location *loc, const struct nftnl_expr *nle); -} netlink_parsers[] = { +}; + +static const struct expr_handler netlink_parsers[] = { { .name = "immediate", .parse = netlink_parse_immediate }, { .name = "cmp", .parse = netlink_parse_cmp }, { .name = "lookup", .parse = netlink_parse_lookup }, { .name = "bitwise", .parse = netlink_parse_bitwise }, { .name = "byteorder", .parse = netlink_parse_byteorder }, { .name = "payload", .parse = netlink_parse_payload }, + { .name = "inner", .parse = netlink_parse_inner }, { .name = "exthdr", .parse = netlink_parse_exthdr }, { .name = "meta", .parse = netlink_parse_meta }, { .name = "socket", .parse = netlink_parse_socket }, @@ -1480,6 +1927,7 @@ static const struct { { .name = "ct", .parse = netlink_parse_ct }, { .name = "connlimit", .parse = netlink_parse_connlimit }, { .name = "counter", .parse = netlink_parse_counter }, + { .name = "last", .parse = netlink_parse_last }, { .name = "log", .parse = netlink_parse_log }, { .name = "limit", .parse = netlink_parse_limit }, { .name = "range", .parse = netlink_parse_range }, @@ -1520,11 +1968,13 @@ static int netlink_parse_expr(const struct nftnl_expr *nle, for (i = 0; i < array_size(netlink_parsers); i++) { if (strcmp(type, netlink_parsers[i].name)) continue; + netlink_parsers[i].parse(ctx, &loc, nle); + return 0; } - netlink_error(ctx, &loc, "unknown expression type '%s'", type); + return 0; } @@ -1537,7 +1987,7 @@ static int netlink_parse_rule_expr(struct nftnl_expr *nle, void *arg) if (err < 0) return err; if (ctx->stmt != NULL) { - list_add_tail(&ctx->stmt->list, &ctx->rule->stmts); + rule_stmt_append(ctx->rule, ctx->stmt); ctx->stmt = NULL; } return 0; @@ -1548,16 +1998,55 @@ struct stmt *netlink_parse_set_expr(const struct set *set, const struct nftnl_expr *nle) { struct netlink_parse_ctx ctx, *pctx = &ctx; + struct handle h = {}; - pctx->rule = rule_alloc(&netlink_location, &set->handle); - pctx->table = table_lookup(&set->handle, cache); + handle_merge(&h, &set->handle); + pctx->rule = rule_alloc(&netlink_location, &h); + pctx->table = table_cache_find(&cache->table_cache, + set->handle.table.name, + set->handle.family); assert(pctx->table != NULL); if (netlink_parse_expr(nle, pctx) < 0) return NULL; + + init_list_head(&pctx->rule->stmts); + rule_free(pctx->rule); + return pctx->stmt; } +static bool meta_outer_may_dependency_kill(struct rule_pp_ctx *ctx, + const struct expr *expr) +{ + struct dl_proto_ctx *dl_outer = dl_proto_ctx_outer(ctx); + struct stmt *stmt = dl_outer->pdctx.pdeps[expr->payload.inner_desc->base]; + struct expr *dep; + uint8_t l4proto; + + if (!stmt) + return false; + + dep = stmt->expr; + + if (dep->left->meta.key != NFT_META_L4PROTO) + return false; + + l4proto = mpz_get_uint8(dep->right->value); + + switch (l4proto) { + case IPPROTO_GRE: + if (expr->payload.inner_desc == &proto_gre || + expr->payload.inner_desc == &proto_gretap) + return true; + break; + default: + break; + } + + return false; +} + static void expr_postprocess(struct rule_pp_ctx *ctx, struct expr **exprp); static void payload_match_expand(struct rule_pp_ctx *ctx, @@ -1566,21 +2055,27 @@ static void payload_match_expand(struct rule_pp_ctx *ctx, { struct expr *left = payload, *right = expr->right, *tmp; struct list_head list = LIST_HEAD_INIT(list); - struct stmt *nstmt; - struct expr *nexpr = NULL; + struct dl_proto_ctx *dl = dl_proto_ctx(ctx); enum proto_bases base = left->payload.base; - bool stacked; + struct expr *nexpr = NULL; + struct stmt *nstmt; - payload_expr_expand(&list, left, &ctx->pctx); + payload_expr_expand(&list, left, &dl->pctx); list_for_each_entry(left, &list, list) { tmp = constant_expr_splice(right, left->len); expr_set_type(tmp, left->dtype, left->byteorder); + if (left->payload.tmpl && (left->len < left->payload.tmpl->len)) { + mpz_lshift_ui(tmp->value, left->payload.tmpl->len - left->len); + tmp->len = left->payload.tmpl->len; + tmp = prefix_expr_alloc(&tmp->location, tmp, left->len); + } + nexpr = relational_expr_alloc(&expr->location, expr->op, left, tmp); if (expr->op == OP_EQ) - relational_expr_pctx_update(&ctx->pctx, nexpr); + relational_expr_pctx_update(&dl->pctx, nexpr); nstmt = expr_stmt_alloc(&ctx->stmt->location, nexpr); list_add_tail(&nstmt->list, &ctx->stmt->list); @@ -1589,36 +2084,96 @@ static void payload_match_expand(struct rule_pp_ctx *ctx, assert(left->payload.base); assert(base == left->payload.base); - stacked = payload_is_stacked(ctx->pctx.protocol[base].desc, nexpr); + if (expr->left->payload.inner_desc) { + if (expr->left->payload.inner_desc == expr->left->payload.desc) { + nexpr->left->payload.desc = expr->left->payload.desc; + nexpr->left->payload.tmpl = expr->left->payload.tmpl; + } + nexpr->left->payload.inner_desc = expr->left->payload.inner_desc; + + if (meta_outer_may_dependency_kill(ctx, expr->left)) { + struct dl_proto_ctx *dl_outer = dl_proto_ctx_outer(ctx); + + payload_dependency_release(&dl_outer->pdctx, expr->left->payload.inner_desc->base); + } + } + + if (payload_is_stacked(dl->pctx.protocol[base].desc, nexpr)) + base--; /* Remember the first payload protocol expression to * kill it later on if made redundant by a higher layer * payload expression. */ - if (ctx->pdctx.pbase == PROTO_BASE_INVALID && - expr->op == OP_EQ && - left->flags & EXPR_F_PROTOCOL) { - payload_dependency_store(&ctx->pdctx, nstmt, base - stacked); - } else { - payload_dependency_kill(&ctx->pdctx, nexpr->left, - ctx->pctx.family); - if (expr->op == OP_EQ && left->flags & EXPR_F_PROTOCOL) - payload_dependency_store(&ctx->pdctx, nstmt, base - stacked); - } + payload_dependency_kill(&dl->pdctx, nexpr->left, + dl->pctx.family); + expr_set_type(tmp, nexpr->left->dtype, nexpr->byteorder); + if (expr->op == OP_EQ && left->flags & EXPR_F_PROTOCOL) + payload_dependency_store(&dl->pdctx, nstmt, base); } list_del(&ctx->stmt->list); stmt_free(ctx->stmt); ctx->stmt = NULL; } +static void payload_icmp_check(struct rule_pp_ctx *rctx, struct expr *expr, const struct expr *value) +{ + struct dl_proto_ctx *dl = dl_proto_ctx(rctx); + const struct proto_hdr_template *tmpl; + const struct proto_desc *desc; + uint8_t icmp_type; + unsigned int i; + + assert(expr->etype == EXPR_PAYLOAD); + assert(value->etype == EXPR_VALUE); + + if (expr->payload.base != PROTO_BASE_TRANSPORT_HDR) + return; + + /* icmp(v6) type is 8 bit, if value is smaller or larger, this is not + * a protocol dependency. + */ + if (expr->len != 8 || value->len != 8 || dl->pctx.th_dep.icmp.type) + return; + + desc = dl->pctx.protocol[expr->payload.base].desc; + if (desc == NULL) + return; + + /* not icmp? ignore. */ + if (desc != &proto_icmp && desc != &proto_icmp6) + return; + + assert(desc->base == expr->payload.base); + + icmp_type = mpz_get_uint8(value->value); + + for (i = 1; i < array_size(desc->templates); i++) { + tmpl = &desc->templates[i]; + + if (tmpl->len == 0) + return; + + if (tmpl->offset != expr->payload.offset || + tmpl->len != expr->len) + continue; + + /* Matches but doesn't load a protocol key -> ignore. */ + if (desc->protocol_key != i) + return; + + expr->payload.desc = desc; + expr->payload.tmpl = tmpl; + dl->pctx.th_dep.icmp.type = icmp_type; + return; + } +} + static void payload_match_postprocess(struct rule_pp_ctx *ctx, struct expr *expr, struct expr *payload) { - enum proto_bases base = payload->payload.base; - - assert(payload->payload.offset >= ctx->pctx.protocol[base].offset); - payload->payload.offset -= ctx->pctx.protocol[base].offset; + struct dl_proto_ctx *dl = dl_proto_ctx(ctx); switch (expr->op) { case OP_EQ: @@ -1626,84 +2181,172 @@ static void payload_match_postprocess(struct rule_pp_ctx *ctx, if (expr->right->etype == EXPR_VALUE) { payload_match_expand(ctx, expr, payload); break; + } else if (expr->right->etype == EXPR_SET_REF) { + struct set *set = expr->right->set; + + if (set_is_anonymous(set->flags) && + set->init && + !list_empty(&expr_set(set->init)->expressions)) { + struct expr *elem; + + elem = list_first_entry(&expr_set(set->init)->expressions, struct expr, list); + + if (elem->etype == EXPR_SET_ELEM && + elem->key->etype == EXPR_VALUE) + payload_icmp_check(ctx, payload, elem->key); + } } /* Fall through */ default: - payload_expr_complete(payload, &ctx->pctx); + payload_expr_complete(payload, &dl->pctx); expr_set_type(expr->right, payload->dtype, payload->byteorder); - payload_dependency_kill(&ctx->pdctx, payload, ctx->pctx.family); + payload_dependency_kill(&dl->pdctx, payload, dl->pctx.family); break; } } -/* We have seen a protocol key expression that restricts matching at the network - * base, leave it in place since this is meaninful in bridge, inet and netdev - * families. Exceptions are ICMP and ICMPv6 where this code assumes that can - * only happen with IPv4 and IPv6. - */ -static bool meta_may_dependency_kill(struct payload_dep_ctx *ctx, - unsigned int family, - const struct expr *expr) +static uint8_t ether_type_to_nfproto(uint16_t l3proto) { - struct expr *dep = ctx->pdep->expr; - uint16_t l3proto; - uint8_t l4proto; - - if (ctx->pbase != PROTO_BASE_NETWORK_HDR) - return true; - - switch (family) { - case NFPROTO_INET: - case NFPROTO_NETDEV: - case NFPROTO_BRIDGE: - break; + switch(l3proto) { + case ETH_P_IP: + return NFPROTO_IPV4; + case ETH_P_IPV6: + return NFPROTO_IPV6; default: - return true; + break; } - if (expr->left->meta.key != NFT_META_L4PROTO) - return true; + return NFPROTO_UNSPEC; +} - l3proto = mpz_get_uint16(dep->right->value); +static bool __meta_dependency_may_kill(const struct expr *dep, uint8_t *nfproto) +{ + uint16_t l3proto; switch (dep->left->etype) { case EXPR_META: - if (dep->left->meta.key != NFT_META_NFPROTO) + switch (dep->left->meta.key) { + case NFT_META_NFPROTO: + *nfproto = mpz_get_uint8(dep->right->value); + break; + case NFT_META_PROTOCOL: + l3proto = mpz_get_uint16(dep->right->value); + *nfproto = ether_type_to_nfproto(l3proto); + break; + default: return true; + } break; case EXPR_PAYLOAD: if (dep->left->payload.base != PROTO_BASE_LL_HDR) return true; - switch(l3proto) { - case ETH_P_IP: - l3proto = NFPROTO_IPV4; - break; - case ETH_P_IPV6: - l3proto = NFPROTO_IPV6; - break; + if (dep->left->dtype != ðertype_type) + return true; + + l3proto = mpz_get_uint16(dep->right->value); + *nfproto = ether_type_to_nfproto(l3proto); + break; + default: + return true; + } + + return false; +} + +static bool ct_may_dependency_kill(unsigned int meta_nfproto, + const struct expr *ct) +{ + assert(ct->etype == EXPR_CT); + + switch (ct->ct.key) { + case NFT_CT_DST: + case NFT_CT_SRC: + switch (ct->len) { + case 32: + return meta_nfproto == NFPROTO_IPV4; + case 128: + return meta_nfproto == NFPROTO_IPV6; default: break; } - break; + return false; + case NFT_CT_DST_IP: + case NFT_CT_SRC_IP: + return meta_nfproto == NFPROTO_IPV4; + case NFT_CT_DST_IP6: + case NFT_CT_SRC_IP6: + return meta_nfproto == NFPROTO_IPV6; default: break; } - l4proto = mpz_get_uint8(expr->right->value); + return false; +} + +static bool meta_may_dependency_kill(uint8_t nfproto, const struct expr *meta, const struct expr *v) +{ + uint8_t l4proto; + + if (meta->meta.key != NFT_META_L4PROTO) + return true; + + if (v->etype != EXPR_VALUE || v->len != 8) + return false; + + l4proto = mpz_get_uint8(v->value); switch (l4proto) { case IPPROTO_ICMP: + return nfproto == NFPROTO_IPV4; case IPPROTO_ICMPV6: + return nfproto == NFPROTO_IPV6; + default: break; + } + + return false; +} + +/* We have seen a protocol key expression that restricts matching at the network + * base, leave it in place since this is meaningful in bridge, inet and netdev + * families. Exceptions are ICMP and ICMPv6 where this code assumes that can + * only happen with IPv4 and IPv6. + */ +static bool ct_meta_may_dependency_kill(struct payload_dep_ctx *ctx, + unsigned int family, + const struct expr *expr) +{ + struct expr *dep = payload_dependency_get(ctx, PROTO_BASE_NETWORK_HDR); + uint8_t nfproto = NFPROTO_UNSPEC; + + if (!dep) + return true; + + if (__meta_dependency_may_kill(dep, &nfproto)) + return true; + + switch (family) { + case NFPROTO_INET: + case NFPROTO_NETDEV: + case NFPROTO_BRIDGE: + break; + case NFPROTO_IPV4: + case NFPROTO_IPV6: + return family == nfproto; default: - return false; + return true; } - if ((l3proto == NFPROTO_IPV4 && l4proto == IPPROTO_ICMPV6) || - (l3proto == NFPROTO_IPV6 && l4proto == IPPROTO_ICMP)) - return false; + switch (expr->left->etype) { + case EXPR_META: + return meta_may_dependency_kill(nfproto, expr->left, expr->right); + case EXPR_CT: + return ct_may_dependency_kill(nfproto, expr->left); + default: + break; + } return true; } @@ -1712,6 +2355,7 @@ static void ct_meta_common_postprocess(struct rule_pp_ctx *ctx, const struct expr *expr, enum proto_bases base) { + struct dl_proto_ctx *dl = dl_proto_ctx(ctx); const struct expr *left = expr->left; struct expr *right = expr->right; @@ -1725,18 +2369,16 @@ static void ct_meta_common_postprocess(struct rule_pp_ctx *ctx, expr->right->etype == EXPR_SET_REF) break; - relational_expr_pctx_update(&ctx->pctx, expr); + relational_expr_pctx_update(&dl->pctx, expr); + + if (base < PROTO_BASE_TRANSPORT_HDR) { + if (payload_dependency_exists(&dl->pdctx, base) && + ct_meta_may_dependency_kill(&dl->pdctx, + dl->pctx.family, expr)) + payload_dependency_release(&dl->pdctx, base); - if (ctx->pdctx.pbase == PROTO_BASE_INVALID && - left->flags & EXPR_F_PROTOCOL) { - payload_dependency_store(&ctx->pdctx, ctx->stmt, base); - } else if (ctx->pdctx.pbase < PROTO_BASE_TRANSPORT_HDR) { - if (payload_dependency_exists(&ctx->pdctx, base) && - meta_may_dependency_kill(&ctx->pdctx, - ctx->pctx.family, expr)) - payload_dependency_release(&ctx->pdctx); if (left->flags & EXPR_F_PROTOCOL) - payload_dependency_store(&ctx->pdctx, ctx->stmt, base); + payload_dependency_store(&dl->pdctx, ctx->stmt, base); } break; default: @@ -1821,8 +2463,8 @@ static void binop_adjust_one(const struct expr *binop, struct expr *value, } } -static void __binop_adjust(const struct expr *binop, struct expr *right, - unsigned int shift) +static void binop_adjust(const struct expr *binop, struct expr *right, + unsigned int shift) { struct expr *i; @@ -1831,7 +2473,10 @@ static void __binop_adjust(const struct expr *binop, struct expr *right, binop_adjust_one(binop, right, shift); break; case EXPR_SET_REF: - list_for_each_entry(i, &right->set->init->expressions, list) { + if (!set_is_anonymous(right->set->flags)) + break; + + list_for_each_entry(i, &expr_set(right->set->init)->expressions, list) { switch (i->key->etype) { case EXPR_VALUE: binop_adjust_one(binop, i->key, shift); @@ -1841,7 +2486,7 @@ static void __binop_adjust(const struct expr *binop, struct expr *right, binop_adjust_one(binop, i->key->right, shift); break; case EXPR_SET_ELEM: - __binop_adjust(binop, i->key->key, shift); + binop_adjust(binop, i->key->key, shift); break; default: BUG("unknown expression type %s\n", expr_name(i->key)); @@ -1858,22 +2503,24 @@ static void __binop_adjust(const struct expr *binop, struct expr *right, } } -static void binop_adjust(struct expr *expr, unsigned int shift) +static bool __binop_postprocess(struct rule_pp_ctx *ctx, + struct expr *expr, + struct expr *left, + struct expr *mask, + struct expr **expr_binop) { - __binop_adjust(expr->left, expr->right, shift); -} - -static void binop_postprocess(struct rule_pp_ctx *ctx, struct expr *expr) -{ - struct expr *binop = expr->left; - struct expr *left = binop->left; - struct expr *mask = binop->right; + struct dl_proto_ctx *dl = dl_proto_ctx(ctx); + struct expr *binop = *expr_binop; unsigned int shift; + assert(binop->etype == EXPR_BINOP); + if ((left->etype == EXPR_PAYLOAD && - payload_expr_trim(left, mask, &ctx->pctx, &shift)) || + payload_expr_trim(left, mask, &dl->pctx, &shift)) || (left->etype == EXPR_EXTHDR && exthdr_find_template(left, mask, &shift))) { + struct expr *right = NULL; + /* mask is implicit, binop needs to be removed. * * Fix all values of the expression according to the mask @@ -1883,59 +2530,116 @@ static void binop_postprocess(struct rule_pp_ctx *ctx, struct expr *expr) * Finally, convert the expression to 1) by replacing * the binop with the binop payload/exthdr expression. */ - binop_adjust(expr, shift); + switch (expr->etype) { + case EXPR_BINOP: + case EXPR_RELATIONAL: + right = expr->right; + binop_adjust(binop, right, shift); + break; + case EXPR_MAP: + right = expr->mappings; + binop_adjust(binop, right, shift); + break; + default: + break; + } - assert(expr->left->etype == EXPR_BINOP); assert(binop->left == left); - expr->left = expr_get(left); - expr_free(binop); + *expr_binop = expr_get(left); + if (left->etype == EXPR_PAYLOAD) payload_match_postprocess(ctx, expr, left); - else if (left->etype == EXPR_EXTHDR) - expr_set_type(expr->right, left->dtype, left->byteorder); + else if (left->etype == EXPR_EXTHDR && right) + expr_set_type(right, left->dtype, left->byteorder); + + expr_free(binop); + return true; + } else if (left->etype == EXPR_PAYLOAD && + expr->right->etype == EXPR_VALUE && + payload_expr_trim_force(left, mask, &shift)) { + mpz_rshift_ui(expr->right->value, shift); + *expr_binop = expr_get(left); + expr_free(binop); + return true; } + + return false; +} + +static bool binop_postprocess(struct rule_pp_ctx *ctx, struct expr *expr, + struct expr **expr_binop) +{ + struct expr *binop = *expr_binop; + struct expr *left = binop->left; + struct expr *mask = binop->right; + + return __binop_postprocess(ctx, expr, left, mask, expr_binop); } static void map_binop_postprocess(struct rule_pp_ctx *ctx, struct expr *expr) { - struct expr *binop = expr->left; + struct expr *binop = expr->map; if (binop->op != OP_AND) return; if (binop->left->etype == EXPR_PAYLOAD && binop->right->etype == EXPR_VALUE) - binop_postprocess(ctx, expr); + binop_postprocess(ctx, expr, &expr->map); } -static void relational_binop_postprocess(struct rule_pp_ctx *ctx, struct expr *expr) +static bool is_shift_by_zero(const struct expr *binop) { - struct expr *binop = expr->left, *value = expr->right; + struct expr *rhs; + + if (binop->op != OP_RSHIFT && binop->op != OP_LSHIFT) + return false; + + rhs = binop->right; + if (rhs->etype != EXPR_VALUE || rhs->len > 64) + return false; - if (binop->op == OP_AND && expr->op == OP_NEQ && - value->dtype->basetype && - value->dtype->basetype->type == TYPE_BITMASK && - !mpz_cmp_ui(value->value, 0)) { + return mpz_get_uint64(rhs->value) == 0; +} + +static void relational_binop_postprocess(struct rule_pp_ctx *ctx, + struct expr **exprp) +{ + struct expr *expr = *exprp, *binop = expr->left, *right = expr->right; + + if (binop->op == OP_AND && (expr->op == OP_NEQ || expr->op == OP_EQ) && + right->dtype->basetype && + right->dtype->basetype->type == TYPE_BITMASK && + right->etype == EXPR_VALUE && + !mpz_cmp_ui(right->value, 0)) { /* Flag comparison: data & flags != 0 * * Split the flags into a list of flag values and convert the * op to OP_EQ. */ - expr_free(value); + expr_free(right); expr->left = expr_get(binop->left); expr->right = binop_tree_to_list(NULL, binop->right); - expr->op = OP_IMPLICIT; - + switch (expr->op) { + case OP_NEQ: + expr->op = OP_IMPLICIT; + break; + case OP_EQ: + expr->op = OP_NEG; + break; + default: + BUG("unknown operation type %d\n", expr->op); + } expr_free(binop); - } else if (binop->left->dtype->flags & DTYPE_F_PREFIX && - binop->op == OP_AND && + } else if (datatype_prefix_notation(binop->left->dtype) && + binop->op == OP_AND && expr->right->etype == EXPR_VALUE && expr_mask_is_prefix(binop->right)) { expr->left = expr_get(binop->left); expr->right = prefix_expr_alloc(&expr->location, - expr_get(value), + expr_get(right), expr_mask_to_prefix(binop->right)); - expr_free(value); + expr_free(right); expr_free(binop); } else if (binop->op == OP_AND && binop->right->etype == EXPR_VALUE) { @@ -1962,10 +2666,68 @@ static void relational_binop_postprocess(struct rule_pp_ctx *ctx, struct expr *e * payload_expr_trim will figure out if the mask is needed to match * templates. */ - binop_postprocess(ctx, expr); + binop_postprocess(ctx, expr, &expr->left); + } else if (binop->op == OP_RSHIFT && binop->left->op == OP_AND && + binop->right->etype == EXPR_VALUE && binop->left->right->etype == EXPR_VALUE) { + /* Handle 'ip version @s4' and similar, i.e. set lookups where the lhs needs + * fixups to mask out unwanted bits AND a shift. + */ + + binop_postprocess(ctx, binop, &binop->left); + if (is_shift_by_zero(binop)) { + struct expr *lhs = binop->left; + + expr_get(lhs); + expr_free(binop); + expr->left = lhs; + } } } +static bool payload_binop_postprocess(struct rule_pp_ctx *ctx, + struct expr **exprp) +{ + struct expr *expr = *exprp; + + if (expr->op != OP_RSHIFT) + return false; + + if (expr->left->etype == EXPR_UNARY) { + /* + * If the payload value was originally in a different byte-order + * from the payload expression, there will be a byte-order + * conversion to remove. + */ + struct expr *left = expr_get(expr->left->arg); + expr_free(expr->left); + expr->left = left; + } + + if (expr->left->etype != EXPR_BINOP || expr->left->op != OP_AND) + return false; + + switch (expr->left->left->etype) { + case EXPR_EXTHDR: + break; + case EXPR_PAYLOAD: + break; + default: + return false; + } + + expr_postprocess(ctx, &expr->left->left); + + expr_set_type(expr->right, &integer_type, + BYTEORDER_HOST_ENDIAN); + expr_postprocess(ctx, &expr->right); + + binop_postprocess(ctx, expr, &expr->left); + *exprp = expr_get(expr->left); + expr_free(expr); + + return true; +} + static struct expr *string_wildcard_expr_alloc(struct location *loc, const struct expr *mask, const struct expr *expr) @@ -1983,56 +2745,33 @@ static struct expr *string_wildcard_expr_alloc(struct location *loc, expr->len + BITS_PER_BYTE, data); } -static void escaped_string_wildcard_expr_alloc(struct expr **exprp, - unsigned int len) -{ - struct expr *expr = *exprp, *tmp; - char data[len + 3]; - int pos; - - mpz_export_data(data, expr->value, BYTEORDER_HOST_ENDIAN, len); - pos = div_round_up(len, BITS_PER_BYTE); - data[pos - 1] = '\\'; - data[pos] = '*'; - - tmp = constant_expr_alloc(&expr->location, expr->dtype, - BYTEORDER_HOST_ENDIAN, - expr->len + BITS_PER_BYTE, data); - expr_free(expr); - *exprp = tmp; -} - /* This calculates the string length and checks if it is nul-terminated, this * function is quite a hack :) */ static bool __expr_postprocess_string(struct expr **exprp) { struct expr *expr = *exprp; - unsigned int len = expr->len; - bool nulterminated = false; - mpz_t tmp; - - mpz_init(tmp); - while (len >= BITS_PER_BYTE) { - mpz_bitmask(tmp, BITS_PER_BYTE); - mpz_lshift_ui(tmp, len - BITS_PER_BYTE); - mpz_and(tmp, tmp, expr->value); - if (mpz_cmp_ui(tmp, 0)) - break; - else - nulterminated = true; - len -= BITS_PER_BYTE; - } + unsigned int len = div_round_up(expr->len, BITS_PER_BYTE); + char data[len + 1]; - mpz_rshift_ui(tmp, len - BITS_PER_BYTE); + mpz_export_data(data, expr->value, BYTEORDER_HOST_ENDIAN, len); - if (nulterminated && - mpz_cmp_ui(tmp, '*') == 0) - escaped_string_wildcard_expr_alloc(exprp, len); + if (data[len - 1] != '\0') + return false; - mpz_clear(tmp); + len = strlen(data); + if (len && data[len - 1] == '*') { + data[len - 1] = '\\'; + data[len] = '*'; + data[len + 1] = '\0'; + expr = constant_expr_alloc(&expr->location, expr->dtype, + BYTEORDER_HOST_ENDIAN, + (len + 2) * BITS_PER_BYTE, data); + expr_free(*exprp); + *exprp = expr; + } - return nulterminated; + return true; } static struct expr *expr_postprocess_string(struct expr *expr) @@ -2046,18 +2785,64 @@ static struct expr *expr_postprocess_string(struct expr *expr) mask = constant_expr_alloc(&expr->location, &integer_type, BYTEORDER_HOST_ENDIAN, expr->len + BITS_PER_BYTE, NULL); + mpz_clear(mask->value); mpz_init_bitmask(mask->value, expr->len); out = string_wildcard_expr_alloc(&expr->location, mask, expr); + expr_free(expr); expr_free(mask); return out; } +static void expr_postprocess_value(struct rule_pp_ctx *ctx, struct expr **exprp) +{ + bool interval = (ctx->set && ctx->set->flags & NFT_SET_INTERVAL); + struct expr *expr = *exprp; + + // FIXME + if (expr->byteorder == BYTEORDER_HOST_ENDIAN && !interval) + mpz_switch_byteorder(expr->value, expr->len / BITS_PER_BYTE); + + if (expr_basetype(expr)->type == TYPE_STRING) + *exprp = expr_postprocess_string(expr); + + expr = *exprp; + if (expr->dtype->basetype != NULL && + expr->dtype->basetype->type == TYPE_BITMASK) + *exprp = bitmask_expr_to_binops(expr); +} + +static void expr_postprocess_concat(struct rule_pp_ctx *ctx, struct expr **exprp) +{ + struct expr *i, *n, *expr = *exprp; + unsigned int type = expr->dtype->type, ntype = 0; + int off = expr->dtype->subtypes; + const struct datatype *dtype; + LIST_HEAD(tmp); + + assert(expr->etype == EXPR_CONCAT); + + ctx->flags |= RULE_PP_IN_CONCATENATION; + list_for_each_entry_safe(i, n, &expr_concat(expr)->expressions, list) { + if (type) { + dtype = concat_subtype_lookup(type, --off); + expr_set_type(i, dtype, dtype->byteorder); + } + list_del(&i->list); + expr_postprocess(ctx, &i); + list_add_tail(&i->list, &tmp); + + ntype = concat_subtype_add(ntype, i->dtype->type); + } + ctx->flags &= ~RULE_PP_IN_CONCATENATION; + list_splice(&tmp, &expr_concat(expr)->expressions); + __datatype_set(expr, concat_type_alloc(ntype)); +} + static void expr_postprocess(struct rule_pp_ctx *ctx, struct expr **exprp) { + struct dl_proto_ctx *dl = dl_proto_ctx(ctx); struct expr *expr = *exprp, *i; - //pr_debug("%s len %u\n", expr->ops->name, expr->len); - switch (expr->etype) { case EXPR_MAP: switch (expr->map->etype) { @@ -2076,44 +2861,110 @@ static void expr_postprocess(struct rule_pp_ctx *ctx, struct expr **exprp) expr_postprocess(ctx, &expr->right); break; case EXPR_SET: - list_for_each_entry(i, &expr->expressions, list) + list_for_each_entry(i, &expr_set(expr)->expressions, list) expr_postprocess(ctx, &i); break; - case EXPR_CONCAT: { - unsigned int type = expr->dtype->type, ntype = 0; - int off = expr->dtype->subtypes; - const struct datatype *dtype; - - list_for_each_entry(i, &expr->expressions, list) { - if (type) { - dtype = concat_subtype_lookup(type, --off); - expr_set_type(i, dtype, dtype->byteorder); - } - expr_postprocess(ctx, &i); - - ntype = concat_subtype_add(ntype, i->dtype->type); - } - datatype_set(expr, concat_type_alloc(ntype)); + case EXPR_CONCAT: + expr_postprocess_concat(ctx, exprp); break; - } case EXPR_UNARY: expr_postprocess(ctx, &expr->arg); expr_set_type(expr, expr->arg->dtype, !expr->arg->byteorder); break; case EXPR_BINOP: + if (payload_binop_postprocess(ctx, exprp)) + break; + expr_postprocess(ctx, &expr->left); - expr_set_type(expr->right, expr->left->dtype, - expr->left->byteorder); + switch (expr->op) { + case OP_LSHIFT: + case OP_RSHIFT: + expr_set_type(expr->right, &integer_type, + BYTEORDER_HOST_ENDIAN); + break; + case OP_AND: + if (expr->right->len > expr->left->len) { + expr_set_type(expr->right, expr->left->dtype, + BYTEORDER_HOST_ENDIAN); + } else { + expr_set_type(expr->right, expr->left->dtype, + expr->left->byteorder); + } + + /* Do not process OP_AND in ordinary rule context. + * + * Removal needs to be performed as part of the relational + * operation because the RHS constant might need to be adjusted + * (shifted). + * + * This is different in set element context or concatenations: + * There is no relational operation (eq, neq and so on), thus + * it needs to be processed right away. + */ + if ((ctx->flags & RULE_PP_REMOVE_OP_AND) && + expr->left->etype == EXPR_PAYLOAD && + expr->right->etype == EXPR_VALUE) { + __binop_postprocess(ctx, expr, expr->left, expr->right, exprp); + return; + } + break; + default: + if (expr->right->len > expr->left->len) { + expr_set_type(expr->right, expr->left->dtype, + BYTEORDER_HOST_ENDIAN); + } else { + expr_set_type(expr->right, expr->left->dtype, + expr->left->byteorder); + } + } expr_postprocess(ctx, &expr->right); - expr_set_type(expr, expr->left->dtype, - expr->left->byteorder); + switch (expr->op) { + case OP_LSHIFT: + case OP_RSHIFT: + expr_set_type(expr, &xinteger_type, + BYTEORDER_HOST_ENDIAN); + break; + default: + expr_set_type(expr, expr->left->dtype, + expr->left->byteorder); + } + break; case EXPR_RELATIONAL: switch (expr->left->etype) { case EXPR_PAYLOAD: payload_match_postprocess(ctx, expr, expr->left); return; + case EXPR_CONCAT: + if (expr->right->etype == EXPR_SET_REF) { + assert(expr->left->dtype == &invalid_type); + assert(expr->right->dtype != &invalid_type); + + datatype_set(expr->left, expr->right->dtype); + } + ctx->set = expr->right->set; + expr_postprocess(ctx, &expr->left); + ctx->set = NULL; + break; + case EXPR_UNARY: + if (lhs_is_meta_hour(expr->left->arg) && + expr->right->etype == EXPR_RANGE) { + struct expr *range = expr->right; + + /* Cross-day range needs to be reversed. + * Kernel handles time in UTC. Therefore, + * 03:00-14:00 AEDT (Sidney, Australia) time + * is a cross-day range. + */ + if (mpz_cmp(range->left->value, + range->right->value) <= 0 && + expr->op == OP_NEQ) { + range_expr_swap_values(range); + expr->op = OP_IMPLICIT; + } + } + /* fallthrough */ default: expr_postprocess(ctx, &expr->left); break; @@ -2130,39 +2981,40 @@ static void expr_postprocess(struct rule_pp_ctx *ctx, struct expr **exprp) meta_match_postprocess(ctx, expr); break; case EXPR_BINOP: - relational_binop_postprocess(ctx, expr); + relational_binop_postprocess(ctx, exprp); break; default: break; } break; case EXPR_PAYLOAD: - payload_expr_complete(expr, &ctx->pctx); - payload_dependency_kill(&ctx->pdctx, expr, ctx->pctx.family); + payload_expr_complete(expr, &dl->pctx); + if (expr->payload.inner_desc) { + if (meta_outer_may_dependency_kill(ctx, expr)) { + struct dl_proto_ctx *dl_outer = dl_proto_ctx_outer(ctx); + + payload_dependency_release(&dl_outer->pdctx, expr->payload.inner_desc->base); + } + } + payload_dependency_kill(&dl->pdctx, expr, dl->pctx.family); break; case EXPR_VALUE: - // FIXME - if (expr->byteorder == BYTEORDER_HOST_ENDIAN) - mpz_switch_byteorder(expr->value, expr->len / BITS_PER_BYTE); - - if (expr_basetype(expr)->type == TYPE_STRING) - *exprp = expr_postprocess_string(expr); - - expr = *exprp; - if (expr->dtype->basetype != NULL && - expr->dtype->basetype->type == TYPE_BITMASK) - *exprp = bitmask_expr_to_binops(expr); - + expr_postprocess_value(ctx, exprp); break; case EXPR_RANGE: expr_postprocess(ctx, &expr->left); expr_postprocess(ctx, &expr->right); break; + case EXPR_PREFIX: + expr_postprocess(ctx, &expr->prefix); + break; case EXPR_SET_ELEM: + ctx->flags |= RULE_PP_IN_SET_ELEM; expr_postprocess(ctx, &expr->key); + ctx->flags &= ~RULE_PP_IN_SET_ELEM; break; case EXPR_EXTHDR: - exthdr_dependency_kill(&ctx->pdctx, expr, ctx->pctx.family); + exthdr_dependency_kill(&dl->pdctx, expr, dl->pctx.family); break; case EXPR_SET_REF: case EXPR_META: @@ -2179,7 +3031,7 @@ static void expr_postprocess(struct rule_pp_ctx *ctx, struct expr **exprp) expr_postprocess(ctx, &expr->hash.expr); break; case EXPR_CT: - ct_expr_update_type(&ctx->pctx, expr); + ct_expr_update_type(&dl->pctx, expr); break; default: BUG("unknown expression type %s\n", expr_name(expr)); @@ -2188,48 +3040,35 @@ static void expr_postprocess(struct rule_pp_ctx *ctx, struct expr **exprp) static void stmt_reject_postprocess(struct rule_pp_ctx *rctx) { + struct dl_proto_ctx *dl = dl_proto_ctx(rctx); const struct proto_desc *desc, *base; struct stmt *stmt = rctx->stmt; int protocol; - switch (rctx->pctx.family) { + switch (dl->pctx.family) { case NFPROTO_IPV4: - stmt->reject.family = rctx->pctx.family; - datatype_set(stmt->reject.expr, &icmp_code_type); + stmt->reject.family = dl->pctx.family; + datatype_set(stmt->reject.expr, &reject_icmp_code_type); if (stmt->reject.type == NFT_REJECT_TCP_RST && - payload_dependency_exists(&rctx->pdctx, + payload_dependency_exists(&dl->pdctx, PROTO_BASE_TRANSPORT_HDR)) - payload_dependency_release(&rctx->pdctx); + payload_dependency_release(&dl->pdctx, + PROTO_BASE_TRANSPORT_HDR); break; case NFPROTO_IPV6: - stmt->reject.family = rctx->pctx.family; - datatype_set(stmt->reject.expr, &icmpv6_code_type); + stmt->reject.family = dl->pctx.family; + datatype_set(stmt->reject.expr, &reject_icmpv6_code_type); if (stmt->reject.type == NFT_REJECT_TCP_RST && - payload_dependency_exists(&rctx->pdctx, + payload_dependency_exists(&dl->pdctx, PROTO_BASE_TRANSPORT_HDR)) - payload_dependency_release(&rctx->pdctx); + payload_dependency_release(&dl->pdctx, + PROTO_BASE_TRANSPORT_HDR); break; case NFPROTO_INET: - if (stmt->reject.type == NFT_REJECT_ICMPX_UNREACH) { - datatype_set(stmt->reject.expr, &icmpx_code_type); - break; - } - base = rctx->pctx.protocol[PROTO_BASE_LL_HDR].desc; - desc = rctx->pctx.protocol[PROTO_BASE_NETWORK_HDR].desc; - protocol = proto_find_num(base, desc); - switch (protocol) { - case NFPROTO_IPV4: - datatype_set(stmt->reject.expr, &icmp_code_type); - break; - case NFPROTO_IPV6: - datatype_set(stmt->reject.expr, &icmpv6_code_type); - break; - } - stmt->reject.family = protocol; - break; case NFPROTO_BRIDGE: + case NFPROTO_NETDEV: if (stmt->reject.type == NFT_REJECT_ICMPX_UNREACH) { - datatype_set(stmt->reject.expr, &icmpx_code_type); + datatype_set(stmt->reject.expr, &reject_icmpx_code_type); break; } @@ -2238,25 +3077,27 @@ static void stmt_reject_postprocess(struct rule_pp_ctx *rctx) */ stmt->reject.verbose_print = 1; - base = rctx->pctx.protocol[PROTO_BASE_LL_HDR].desc; - desc = rctx->pctx.protocol[PROTO_BASE_NETWORK_HDR].desc; + base = dl->pctx.protocol[PROTO_BASE_LL_HDR].desc; + desc = dl->pctx.protocol[PROTO_BASE_NETWORK_HDR].desc; protocol = proto_find_num(base, desc); switch (protocol) { - case __constant_htons(ETH_P_IP): + case NFPROTO_IPV4: /* INET */ + case __constant_htons(ETH_P_IP): /* BRIDGE, NETDEV */ stmt->reject.family = NFPROTO_IPV4; - datatype_set(stmt->reject.expr, &icmp_code_type); + datatype_set(stmt->reject.expr, &reject_icmp_code_type); break; - case __constant_htons(ETH_P_IPV6): + case NFPROTO_IPV6: /* INET */ + case __constant_htons(ETH_P_IPV6): /* BRIDGE, NETDEV */ stmt->reject.family = NFPROTO_IPV6; - datatype_set(stmt->reject.expr, &icmpv6_code_type); + datatype_set(stmt->reject.expr, &reject_icmpv6_code_type); break; default: break; } - if (payload_dependency_exists(&rctx->pdctx, PROTO_BASE_NETWORK_HDR)) - payload_dependency_release(&rctx->pdctx); - + if (payload_dependency_exists(&dl->pdctx, PROTO_BASE_NETWORK_HDR)) + payload_dependency_release(&dl->pdctx, + PROTO_BASE_NETWORK_HDR); break; default: break; @@ -2299,23 +3140,24 @@ static bool expr_may_merge_range(struct expr *expr, struct expr *prev, static void expr_postprocess_range(struct rule_pp_ctx *ctx, enum ops op) { + struct dl_proto_ctx *dl = dl_proto_ctx(ctx); struct stmt *nstmt, *stmt = ctx->stmt; struct expr *nexpr, *rel; - nexpr = range_expr_alloc(&ctx->pdctx.prev->location, - expr_clone(ctx->pdctx.prev->expr->right), + nexpr = range_expr_alloc(&dl->pdctx.prev->location, + expr_clone(dl->pdctx.prev->expr->right), expr_clone(stmt->expr->right)); expr_set_type(nexpr, stmt->expr->right->dtype, stmt->expr->right->byteorder); - rel = relational_expr_alloc(&ctx->pdctx.prev->location, op, + rel = relational_expr_alloc(&dl->pdctx.prev->location, op, expr_clone(stmt->expr->left), nexpr); nstmt = expr_stmt_alloc(&stmt->location, rel); list_add_tail(&nstmt->list, &stmt->list); - list_del(&ctx->pdctx.prev->list); - stmt_free(ctx->pdctx.prev); + list_del(&dl->pdctx.prev->list); + stmt_free(dl->pdctx.prev); list_del(&stmt->list); stmt_free(stmt); @@ -2324,26 +3166,28 @@ static void expr_postprocess_range(struct rule_pp_ctx *ctx, enum ops op) static void stmt_expr_postprocess(struct rule_pp_ctx *ctx) { + struct dl_proto_ctx *dl = dl_proto_ctx(ctx); enum ops op; expr_postprocess(ctx, &ctx->stmt->expr); - if (ctx->pdctx.prev && ctx->stmt && - ctx->stmt->ops->type == ctx->pdctx.prev->ops->type && - expr_may_merge_range(ctx->stmt->expr, ctx->pdctx.prev->expr, &op)) + if (dl->pdctx.prev && ctx->stmt && + ctx->stmt->type == dl->pdctx.prev->type && + expr_may_merge_range(ctx->stmt->expr, dl->pdctx.prev->expr, &op)) expr_postprocess_range(ctx, op); } static void stmt_payload_binop_pp(struct rule_pp_ctx *ctx, struct expr *binop) { + struct dl_proto_ctx *dl = dl_proto_ctx(ctx); struct expr *payload = binop->left; struct expr *mask = binop->right; unsigned int shift; assert(payload->etype == EXPR_PAYLOAD); - if (payload_expr_trim(payload, mask, &ctx->pctx, &shift)) { - __binop_adjust(binop, mask, shift); - payload_expr_complete(payload, &ctx->pctx); + if (payload_expr_trim(payload, mask, &dl->pctx, &shift)) { + binop_adjust(binop, mask, shift); + payload_expr_complete(payload, &dl->pctx); expr_set_type(mask, payload->dtype, payload->byteorder); } @@ -2361,7 +3205,7 @@ static void stmt_payload_binop_pp(struct rule_pp_ctx *ctx, struct expr *binop) * the original payload expression because it has an odd size or * a non-byte divisible offset/length. * - * Of that was the case, the 'value' expression is not a value but + * If that was the case, the 'value' expression is not a value but * a binop expression with a munged payload expression on the left * and a mask to clear the real payload offset/length. * @@ -2391,7 +3235,8 @@ static void stmt_payload_binop_pp(struct rule_pp_ctx *ctx, struct expr *binop) * decoding changed '(payload & mask) ^ bits_to_set' into * 'payload | bits_to_set', discarding the redundant "& 0xfff...". */ -static void stmt_payload_binop_postprocess(struct rule_pp_ctx *ctx) +static void stmt_payload_binop_postprocess(struct rule_pp_ctx *ctx, + const struct proto_ctx *pctx) { struct expr *expr, *binop, *payload, *value, *mask; struct stmt *stmt = ctx->stmt; @@ -2404,6 +3249,7 @@ static void stmt_payload_binop_postprocess(struct rule_pp_ctx *ctx) switch (expr->left->etype) { case EXPR_BINOP: {/* I? */ + unsigned int shift = 0; mpz_t tmp; if (expr->op != OP_OR) @@ -2437,13 +3283,18 @@ static void stmt_payload_binop_postprocess(struct rule_pp_ctx *ctx) mpz_set(mask->value, bitmask); mpz_clear(bitmask); - binop_postprocess(ctx, expr); - if (!payload_is_known(payload)) { + if (!binop_postprocess(ctx, expr, &expr->left) && + !payload_is_known(payload) && + !payload_expr_trim_force(payload, + mask, &shift)) { mpz_set(mask->value, tmp); mpz_clear(tmp); return; } + if (shift) + mpz_rshift_ui(value->value, shift); + mpz_clear(tmp); expr_free(stmt->payload.expr); stmt->payload.expr = expr_get(payload); @@ -2452,40 +3303,67 @@ static void stmt_payload_binop_postprocess(struct rule_pp_ctx *ctx) break; } case EXPR_PAYLOAD: /* II? */ - value = expr->right; - if (value->etype != EXPR_VALUE) - return; + payload = expr->left; + mask = expr->right; - switch (expr->op) { - case OP_AND: /* IIa */ - payload = expr->left; - mpz_init_bitmask(bitmask, payload->len); - mpz_xor(bitmask, bitmask, value->value); - mpz_set(value->value, bitmask); - break; - case OP_OR: /* IIb */ - break; - default: /* No idea */ + if (mask->etype != EXPR_VALUE) return; - } - stmt_payload_binop_pp(ctx, expr); - if (!payload_is_known(expr->left)) + if (!payload_expr_cmp(stmt->payload.expr, payload)) return; - expr_free(stmt->payload.expr); - switch (expr->op) { - case OP_AND: - /* Mask was used to match payload, i.e. - * user asked to set zero value. + case OP_AND: { /* IIa */ + unsigned int shift_unused; + mpz_t tmp; + + if (stmt_payload_expr_trim(stmt, pctx)) + return; + + mpz_init(tmp); + mpz_set(tmp, mask->value); + + mpz_init_bitmask(bitmask, payload->len); + mpz_xor(bitmask, bitmask, mask->value); + mpz_set(mask->value, bitmask); + mpz_clear(bitmask); + + stmt_payload_binop_pp(ctx, expr); + if (!payload_is_known(expr->left) && + !payload_expr_trim_force(expr->left, mask, &shift_unused)) { + mpz_set(mask->value, tmp); + mpz_clear(tmp); + return; + } + + mpz_clear(tmp); + + /* Mask was used to match payload, i.e. user asked to + * clear the payload expression. + * The "mask" value becomes new stmt->payload.value + * so set this to 0. + * Also the reason why &shift_unused is ignored. */ - mpz_set_ui(value->value, 0); + mpz_set_ui(mask->value, 0); break; - default: + } + case OP_OR: /* IIb */ + stmt_payload_binop_pp(ctx, expr); + if (stmt_payload_expr_trim(stmt, pctx)) + return; + if (!payload_is_known(expr->left)) + return; break; + case OP_XOR: + if (stmt_payload_expr_trim(stmt, pctx)) + return; + + return; + default: /* No idea what to do */ + return; } + expr_free(stmt->payload.expr); stmt->payload.expr = expr_get(expr->left); stmt->payload.val = expr_get(expr->right); expr_free(expr); @@ -2497,20 +3375,34 @@ static void stmt_payload_binop_postprocess(struct rule_pp_ctx *ctx) static void stmt_payload_postprocess(struct rule_pp_ctx *ctx) { + struct dl_proto_ctx *dl = dl_proto_ctx(ctx); struct stmt *stmt = ctx->stmt; + payload_expr_complete(stmt->payload.expr, &dl->pctx); + if (!payload_is_known(stmt->payload.expr)) + stmt_payload_binop_postprocess(ctx, &dl->pctx); + expr_postprocess(ctx, &stmt->payload.expr); expr_set_type(stmt->payload.val, stmt->payload.expr->dtype, stmt->payload.expr->byteorder); - if (!payload_is_known(stmt->payload.expr)) - stmt_payload_binop_postprocess(ctx); - expr_postprocess(ctx, &stmt->payload.val); } +static void stmt_queue_postprocess(struct rule_pp_ctx *ctx) +{ + struct stmt *stmt = ctx->stmt; + struct expr *e = stmt->queue.queue; + + if (e == NULL || e->etype == EXPR_VALUE || + e->etype == EXPR_RANGE) + return; + + expr_postprocess(ctx, &stmt->queue.queue); +} + /* * We can only remove payload dependencies if they occur without * a statement with side effects in between. @@ -2532,18 +3424,75 @@ rule_maybe_reset_payload_deps(struct payload_dep_ctx *pdctx, enum stmt_types t) payload_dependency_reset(pdctx); } +static bool has_inner_desc(const struct expr *expr) +{ + struct expr *i; + + switch (expr->etype) { + case EXPR_BINOP: + return has_inner_desc(expr->left); + case EXPR_CONCAT: + list_for_each_entry(i, &expr_concat(expr)->expressions, list) { + if (has_inner_desc(i)) + return true; + } + break; + case EXPR_META: + return expr->meta.inner_desc; + case EXPR_PAYLOAD: + return expr->payload.inner_desc; + case EXPR_SET_ELEM: + return has_inner_desc(expr->key); + default: + break; + } + + return false; +} + +static struct dl_proto_ctx *rule_update_dl_proto_ctx(struct rule_pp_ctx *rctx) +{ + const struct stmt *stmt = rctx->stmt; + bool inner = false; + + switch (stmt->type) { + case STMT_EXPRESSION: + if (has_inner_desc(stmt->expr->left)) + inner = true; + break; + case STMT_SET: + if (has_inner_desc(stmt->set.key)) + inner = true; + break; + default: + break; + } + + if (inner) + rctx->dl = &rctx->_dl[1]; + else + rctx->dl = &rctx->_dl[0]; + + return rctx->dl; +} + static void rule_parse_postprocess(struct netlink_parse_ctx *ctx, struct rule *rule) { - struct rule_pp_ctx rctx; struct stmt *stmt, *next; + struct dl_proto_ctx *dl; + struct rule_pp_ctx rctx; + struct expr *expr; memset(&rctx, 0, sizeof(rctx)); - proto_ctx_init(&rctx.pctx, rule->handle.family, ctx->debug_mask); + proto_ctx_init(&rctx._dl[0].pctx, rule->handle.family, ctx->debug_mask, false); + /* use NFPROTO_BRIDGE to set up proto_eth as base protocol. */ + proto_ctx_init(&rctx._dl[1].pctx, NFPROTO_BRIDGE, ctx->debug_mask, true); list_for_each_entry_safe(stmt, next, &rule->stmts, list) { - enum stmt_types type = stmt->ops->type; + enum stmt_types type = stmt->type; rctx.stmt = stmt; + dl = rule_update_dl_proto_ctx(&rctx); switch (type) { case STMT_EXPRESSION: @@ -2564,24 +3513,24 @@ static void rule_parse_postprocess(struct netlink_parse_ctx *ctx, struct rule *r expr_postprocess(&rctx, &stmt->ct.expr); if (stmt->ct.expr->etype == EXPR_BINOP && - stmt->ct.key == NFT_CT_EVENTMASK) - stmt->ct.expr = binop_tree_to_list(NULL, - stmt->ct.expr); + stmt->ct.key == NFT_CT_EVENTMASK) { + expr = binop_tree_to_list(NULL, stmt->ct.expr); + expr_free(stmt->ct.expr); + stmt->ct.expr = expr; + } } break; case STMT_NAT: if (stmt->nat.addr != NULL) expr_postprocess(&rctx, &stmt->nat.addr); - if (stmt->nat.proto != NULL) { - payload_dependency_reset(&rctx.pdctx); + if (stmt->nat.proto != NULL) expr_postprocess(&rctx, &stmt->nat.proto); - } break; case STMT_TPROXY: if (stmt->tproxy.addr) expr_postprocess(&rctx, &stmt->tproxy.addr); if (stmt->tproxy.port) { - payload_dependency_reset(&rctx.pdctx); + payload_dependency_reset(&dl->pdctx); expr_postprocess(&rctx, &stmt->tproxy.port); } break; @@ -2612,13 +3561,16 @@ static void rule_parse_postprocess(struct netlink_parse_ctx *ctx, struct rule *r case STMT_OBJREF: expr_postprocess(&rctx, &stmt->objref.expr); break; + case STMT_QUEUE: + stmt_queue_postprocess(&rctx); + break; default: break; } - rctx.pdctx.prev = rctx.stmt; + dl->pdctx.prev = rctx.stmt; - rule_maybe_reset_payload_deps(&rctx.pdctx, type); + rule_maybe_reset_payload_deps(&dl->pdctx, type); } } @@ -2670,6 +3622,7 @@ struct rule *netlink_delinearize_rule(struct netlink_ctx *ctx, memset(&_ctx, 0, sizeof(_ctx)); _ctx.msgs = ctx->msgs; _ctx.debug_mask = ctx->nft->debug_mask; + _ctx.nlctx = ctx; memset(&h, 0, sizeof(h)); h.family = nftnl_rule_get_u32(nlr, NFTNL_RULE_FAMILY); @@ -2681,8 +3634,12 @@ struct rule *netlink_delinearize_rule(struct netlink_ctx *ctx, h.position.id = nftnl_rule_get_u64(nlr, NFTNL_RULE_POSITION); pctx->rule = rule_alloc(&netlink_location, &h); - pctx->table = table_lookup(&h, &ctx->nft->cache); - assert(pctx->table != NULL); + pctx->table = table_cache_find(&ctx->nft->cache.table_cache, + h.table.name, h.family); + if (!pctx->table) { + errno = ENOENT; + return NULL; + } pctx->rule->comment = nftnl_rule_get_comment(nlr); diff --git a/src/netlink_linearize.c b/src/netlink_linearize.c index cb1b7fe1..8ac33d34 100644 --- a/src/netlink_linearize.c +++ b/src/netlink_linearize.c @@ -9,9 +9,11 @@ * Development of this code funded by Astaro AG (http://www.astaro.com/) */ +#include <nft.h> + #include <linux/netfilter/nf_tables.h> +#include <linux/netfilter/nf_log.h> -#include <string.h> #include <rule.h> #include <statement.h> #include <expression.h> @@ -23,11 +25,34 @@ #include <linux/netfilter.h> #include <libnftnl/udata.h> +struct nft_expr_loc *nft_expr_loc_find(const struct nftnl_expr *nle, + struct netlink_linearize_ctx *ctx) +{ + struct nft_expr_loc *eloc; + uint32_t hash; + + hash = (uint64_t)nle % NFT_EXPR_LOC_HSIZE; + list_for_each_entry(eloc, &ctx->expr_loc_htable[hash], hlist) { + if (eloc->nle == nle) + return eloc; + } + + return NULL; +} -struct netlink_linearize_ctx { - struct nftnl_rule *nlr; - unsigned int reg_low; -}; +static void nft_expr_loc_add(const struct nftnl_expr *nle, + const struct location *loc, + struct netlink_linearize_ctx *ctx) +{ + struct nft_expr_loc *eloc; + uint32_t hash; + + eloc = xmalloc(sizeof(*eloc)); + eloc->nle = nle; + eloc->loc = loc; + hash = (uint64_t)nle % NFT_EXPR_LOC_HSIZE; + list_add_tail(&eloc->hlist, &ctx->expr_loc_htable[hash]); +} static void netlink_put_register(struct nftnl_expr *nle, uint32_t attr, uint32_t reg) @@ -98,12 +123,20 @@ static void netlink_gen_concat(struct netlink_linearize_ctx *ctx, { const struct expr *i; - list_for_each_entry(i, &expr->expressions, list) { + list_for_each_entry(i, &expr_concat(expr)->expressions, list) { netlink_gen_expr(ctx, i, dreg); dreg += netlink_register_space(i->len); } } +static void nft_rule_add_expr(struct netlink_linearize_ctx *ctx, + struct nftnl_expr *nle, + const struct location *loc) +{ + nft_expr_loc_add(nle, loc, ctx); + nftnl_rule_add_expr(ctx->nlr, nle); +} + static void netlink_gen_fib(struct netlink_linearize_ctx *ctx, const struct expr *expr, enum nft_registers dreg) @@ -115,7 +148,7 @@ static void netlink_gen_fib(struct netlink_linearize_ctx *ctx, nftnl_expr_set_u32(nle, NFTNL_EXPR_FIB_RESULT, expr->fib.result); nftnl_expr_set_u32(nle, NFTNL_EXPR_FIB_FLAGS, expr->fib.flags); - nftnl_rule_add_expr(ctx->nlr, nle); + nft_rule_add_expr(ctx, nle, &expr->location); } static void netlink_gen_hash(struct netlink_linearize_ctx *ctx, @@ -143,12 +176,11 @@ static void netlink_gen_hash(struct netlink_linearize_ctx *ctx, nftnl_expr_set_u32(nle, NFTNL_EXPR_HASH_SEED, expr->hash.seed); nftnl_expr_set_u32(nle, NFTNL_EXPR_HASH_OFFSET, expr->hash.offset); nftnl_expr_set_u32(nle, NFTNL_EXPR_HASH_TYPE, expr->hash.type); - nftnl_rule_add_expr(ctx->nlr, nle); + nft_rule_add_expr(ctx, nle, &expr->location); } -static void netlink_gen_payload(struct netlink_linearize_ctx *ctx, - const struct expr *expr, - enum nft_registers dreg) +static struct nftnl_expr * +__netlink_gen_payload(const struct expr *expr, enum nft_registers dreg) { struct nftnl_expr *nle; @@ -161,26 +193,92 @@ static void netlink_gen_payload(struct netlink_linearize_ctx *ctx, nftnl_expr_set_u32(nle, NFTNL_EXPR_PAYLOAD_LEN, div_round_up(expr->len, BITS_PER_BYTE)); - nftnl_rule_add_expr(ctx->nlr, nle); + return nle; +} + +static struct nftnl_expr * +__netlink_gen_meta(const struct expr *expr, enum nft_registers dreg) +{ + struct nftnl_expr *nle; + + nle = alloc_nft_expr("meta"); + netlink_put_register(nle, NFTNL_EXPR_META_DREG, dreg); + nftnl_expr_set_u32(nle, NFTNL_EXPR_META_KEY, expr->meta.key); + + return nle; +} + +static struct nftnl_expr *netlink_gen_inner_expr(const struct expr *expr, + enum nft_registers dreg) +{ + struct expr *_expr = (struct expr *)expr; + struct nftnl_expr *nle; + + switch (expr->etype) { + case EXPR_PAYLOAD: + if (expr->payload.base == NFT_PAYLOAD_INNER_HEADER + 1) + _expr->payload.base = NFT_PAYLOAD_TUN_HEADER + 1; + + nle = __netlink_gen_payload(expr, dreg); + break; + case EXPR_META: + nle = __netlink_gen_meta(expr, dreg); + break; + default: + assert(0); + break; + } + + return nle; +} + +static void netlink_gen_inner(struct netlink_linearize_ctx *ctx, + const struct expr *expr, + enum nft_registers dreg, + const struct proto_desc *desc) +{ + struct nftnl_expr *nle; + + nle = alloc_nft_expr("inner"); + nftnl_expr_set_u32(nle, NFTNL_EXPR_INNER_HDRSIZE, desc->inner.hdrsize); + nftnl_expr_set_u32(nle, NFTNL_EXPR_INNER_FLAGS, desc->inner.flags); + nftnl_expr_set_u32(nle, NFTNL_EXPR_INNER_TYPE, desc->inner.type); + nftnl_expr_set(nle, NFTNL_EXPR_INNER_EXPR, netlink_gen_inner_expr(expr, dreg), 0); + nft_rule_add_expr(ctx, nle, &expr->location); +} + +static void netlink_gen_payload(struct netlink_linearize_ctx *ctx, + const struct expr *expr, + enum nft_registers dreg) +{ + struct nftnl_expr *nle; + + if (expr->payload.inner_desc) { + netlink_gen_inner(ctx, expr, dreg, expr->payload.inner_desc); + return; + } + + nle = __netlink_gen_payload(expr, dreg); + nft_rule_add_expr(ctx, nle, &expr->location); } static void netlink_gen_exthdr(struct netlink_linearize_ctx *ctx, const struct expr *expr, enum nft_registers dreg) { - unsigned int offset = expr->exthdr.tmpl->offset + expr->exthdr.offset; + unsigned int offset = expr->exthdr.offset; struct nftnl_expr *nle; nle = alloc_nft_expr("exthdr"); netlink_put_register(nle, NFTNL_EXPR_EXTHDR_DREG, dreg); nftnl_expr_set_u8(nle, NFTNL_EXPR_EXTHDR_TYPE, - expr->exthdr.desc->type); + expr->exthdr.raw_type); nftnl_expr_set_u32(nle, NFTNL_EXPR_EXTHDR_OFFSET, offset / BITS_PER_BYTE); nftnl_expr_set_u32(nle, NFTNL_EXPR_EXTHDR_LEN, div_round_up(expr->len, BITS_PER_BYTE)); nftnl_expr_set_u32(nle, NFTNL_EXPR_EXTHDR_OP, expr->exthdr.op); nftnl_expr_set_u32(nle, NFTNL_EXPR_EXTHDR_FLAGS, expr->exthdr.flags); - nftnl_rule_add_expr(ctx->nlr, nle); + nft_rule_add_expr(ctx, nle, &expr->location); } static void netlink_gen_meta(struct netlink_linearize_ctx *ctx, @@ -189,10 +287,13 @@ static void netlink_gen_meta(struct netlink_linearize_ctx *ctx, { struct nftnl_expr *nle; - nle = alloc_nft_expr("meta"); - netlink_put_register(nle, NFTNL_EXPR_META_DREG, dreg); - nftnl_expr_set_u32(nle, NFTNL_EXPR_META_KEY, expr->meta.key); - nftnl_rule_add_expr(ctx->nlr, nle); + if (expr->meta.inner_desc) { + netlink_gen_inner(ctx, expr, dreg, expr->meta.inner_desc); + return; + } + + nle = __netlink_gen_meta(expr, dreg); + nft_rule_add_expr(ctx, nle, &expr->location); } static void netlink_gen_rt(struct netlink_linearize_ctx *ctx, @@ -204,7 +305,7 @@ static void netlink_gen_rt(struct netlink_linearize_ctx *ctx, nle = alloc_nft_expr("rt"); netlink_put_register(nle, NFTNL_EXPR_RT_DREG, dreg); nftnl_expr_set_u32(nle, NFTNL_EXPR_RT_KEY, expr->rt.key); - nftnl_rule_add_expr(ctx->nlr, nle); + nft_rule_add_expr(ctx, nle, &expr->location); } static void netlink_gen_socket(struct netlink_linearize_ctx *ctx, @@ -216,7 +317,8 @@ static void netlink_gen_socket(struct netlink_linearize_ctx *ctx, nle = alloc_nft_expr("socket"); netlink_put_register(nle, NFTNL_EXPR_SOCKET_DREG, dreg); nftnl_expr_set_u32(nle, NFTNL_EXPR_SOCKET_KEY, expr->socket.key); - nftnl_rule_add_expr(ctx->nlr, nle); + nftnl_expr_set_u32(nle, NFTNL_EXPR_SOCKET_LEVEL, expr->socket.level); + nft_rule_add_expr(ctx, nle, &expr->location); } static void netlink_gen_osf(struct netlink_linearize_ctx *ctx, @@ -229,7 +331,7 @@ static void netlink_gen_osf(struct netlink_linearize_ctx *ctx, netlink_put_register(nle, NFTNL_EXPR_OSF_DREG, dreg); nftnl_expr_set_u8(nle, NFTNL_EXPR_OSF_TTL, expr->osf.ttl); nftnl_expr_set_u32(nle, NFTNL_EXPR_OSF_FLAGS, expr->osf.flags); - nftnl_rule_add_expr(ctx->nlr, nle); + nft_rule_add_expr(ctx, nle, &expr->location); } static void netlink_gen_numgen(struct netlink_linearize_ctx *ctx, @@ -243,7 +345,7 @@ static void netlink_gen_numgen(struct netlink_linearize_ctx *ctx, nftnl_expr_set_u32(nle, NFTNL_EXPR_NG_TYPE, expr->numgen.type); nftnl_expr_set_u32(nle, NFTNL_EXPR_NG_MODULUS, expr->numgen.mod); nftnl_expr_set_u32(nle, NFTNL_EXPR_NG_OFFSET, expr->numgen.offset); - nftnl_rule_add_expr(ctx->nlr, nle); + nft_rule_add_expr(ctx, nle, &expr->location); } static void netlink_gen_ct(struct netlink_linearize_ctx *ctx, @@ -259,7 +361,7 @@ static void netlink_gen_ct(struct netlink_linearize_ctx *ctx, nftnl_expr_set_u8(nle, NFTNL_EXPR_CT_DIR, expr->ct.direction); - nftnl_rule_add_expr(ctx->nlr, nle); + nft_rule_add_expr(ctx, nle, &expr->location); } static void netlink_gen_map(struct netlink_linearize_ctx *ctx, @@ -297,7 +399,7 @@ static void netlink_gen_map(struct netlink_linearize_ctx *ctx, if (dreg == NFT_REG_VERDICT) release_register(ctx, expr->map); - nftnl_rule_add_expr(ctx->nlr, nle); + nft_rule_add_expr(ctx, nle, &expr->location); } static void netlink_gen_lookup(struct netlink_linearize_ctx *ctx, @@ -323,7 +425,7 @@ static void netlink_gen_lookup(struct netlink_linearize_ctx *ctx, nftnl_expr_set_u32(nle, NFTNL_EXPR_LOOKUP_FLAGS, NFT_LOOKUP_F_INV); release_register(ctx, expr->left); - nftnl_rule_add_expr(ctx->nlr, nle); + nft_rule_add_expr(ctx, nle, &expr->location); } static enum nft_cmp_ops netlink_gen_cmp_op(enum ops op) @@ -358,7 +460,8 @@ static struct expr *netlink_gen_prefix(struct netlink_linearize_ctx *ctx, mpz_init(mask); mpz_prefixmask(mask, expr->right->len, expr->right->prefix_len); netlink_gen_raw_data(mask, expr->right->byteorder, - expr->right->len / BITS_PER_BYTE, &nld); + div_round_up(expr->right->len, BITS_PER_BYTE), + &nld); mpz_clear(mask); zero.len = nld.len; @@ -369,7 +472,7 @@ static struct expr *netlink_gen_prefix(struct netlink_linearize_ctx *ctx, nftnl_expr_set_u32(nle, NFTNL_EXPR_BITWISE_LEN, nld.len); nftnl_expr_set(nle, NFTNL_EXPR_BITWISE_MASK, &nld.value, nld.len); nftnl_expr_set(nle, NFTNL_EXPR_BITWISE_XOR, &zero.value, zero.len); - nftnl_rule_add_expr(ctx->nlr, nle); + nft_rule_add_expr(ctx, nle, &expr->location); return expr->right->prefix; } @@ -390,34 +493,18 @@ static void netlink_gen_range(struct netlink_linearize_ctx *ctx, switch (expr->op) { case OP_NEQ: + case OP_EQ: + case OP_IMPLICIT: nle = alloc_nft_expr("range"); netlink_put_register(nle, NFTNL_EXPR_RANGE_SREG, sreg); - nftnl_expr_set_u32(nle, NFTNL_EXPR_RANGE_OP, NFT_RANGE_NEQ); + nftnl_expr_set_u32(nle, NFTNL_EXPR_RANGE_OP, netlink_gen_cmp_op(expr->op)); netlink_gen_data(range->left, &nld); nftnl_expr_set(nle, NFTNL_EXPR_RANGE_FROM_DATA, nld.value, nld.len); netlink_gen_data(range->right, &nld); nftnl_expr_set(nle, NFTNL_EXPR_RANGE_TO_DATA, nld.value, nld.len); - nftnl_rule_add_expr(ctx->nlr, nle); - break; - case OP_EQ: - case OP_IMPLICIT: - nle = alloc_nft_expr("cmp"); - netlink_put_register(nle, NFTNL_EXPR_CMP_SREG, sreg); - nftnl_expr_set_u32(nle, NFTNL_EXPR_CMP_OP, - netlink_gen_cmp_op(OP_GTE)); - netlink_gen_data(range->left, &nld); - nftnl_expr_set(nle, NFTNL_EXPR_CMP_DATA, nld.value, nld.len); - nftnl_rule_add_expr(ctx->nlr, nle); - - nle = alloc_nft_expr("cmp"); - netlink_put_register(nle, NFTNL_EXPR_CMP_SREG, sreg); - nftnl_expr_set_u32(nle, NFTNL_EXPR_CMP_OP, - netlink_gen_cmp_op(OP_LTE)); - netlink_gen_data(range->right, &nld); - nftnl_expr_set(nle, NFTNL_EXPR_CMP_DATA, nld.value, nld.len); - nftnl_rule_add_expr(ctx->nlr, nle); + nft_rule_add_expr(ctx, nle, &expr->location); break; default: BUG("invalid range operation %u\n", expr->op); @@ -448,19 +535,31 @@ static void netlink_gen_flagcmp(struct netlink_linearize_ctx *ctx, netlink_gen_raw_data(zero, expr->right->byteorder, len, &nld); netlink_gen_data(expr->right, &nld2); - nle = alloc_nft_expr("bitwise"); - netlink_put_register(nle, NFTNL_EXPR_BITWISE_SREG, sreg); - netlink_put_register(nle, NFTNL_EXPR_BITWISE_DREG, sreg); - nftnl_expr_set_u32(nle, NFTNL_EXPR_BITWISE_LEN, len); - nftnl_expr_set(nle, NFTNL_EXPR_BITWISE_MASK, &nld2.value, nld2.len); - nftnl_expr_set(nle, NFTNL_EXPR_BITWISE_XOR, &nld.value, nld.len); - nftnl_rule_add_expr(ctx->nlr, nle); + if (expr->left->etype == EXPR_BINOP) { + nle = alloc_nft_expr("cmp"); + netlink_put_register(nle, NFTNL_EXPR_CMP_SREG, sreg); + nftnl_expr_set_u32(nle, NFTNL_EXPR_CMP_OP, NFT_CMP_EQ); + nftnl_expr_set(nle, NFTNL_EXPR_CMP_DATA, nld2.value, nld2.len); + nft_rule_add_expr(ctx, nle, &expr->location); + } else { + nle = alloc_nft_expr("bitwise"); + netlink_put_register(nle, NFTNL_EXPR_BITWISE_SREG, sreg); + netlink_put_register(nle, NFTNL_EXPR_BITWISE_DREG, sreg); + nftnl_expr_set_u32(nle, NFTNL_EXPR_BITWISE_LEN, len); + nftnl_expr_set(nle, NFTNL_EXPR_BITWISE_MASK, &nld2.value, nld2.len); + nftnl_expr_set(nle, NFTNL_EXPR_BITWISE_XOR, &nld.value, nld.len); + nft_rule_add_expr(ctx, nle, &expr->location); - nle = alloc_nft_expr("cmp"); - netlink_put_register(nle, NFTNL_EXPR_CMP_SREG, sreg); - nftnl_expr_set_u32(nle, NFTNL_EXPR_CMP_OP, NFT_CMP_NEQ); - nftnl_expr_set(nle, NFTNL_EXPR_CMP_DATA, nld.value, nld.len); - nftnl_rule_add_expr(ctx->nlr, nle); + nle = alloc_nft_expr("cmp"); + netlink_put_register(nle, NFTNL_EXPR_CMP_SREG, sreg); + if (expr->op == OP_NEG) + nftnl_expr_set_u32(nle, NFTNL_EXPR_CMP_OP, NFT_CMP_EQ); + else + nftnl_expr_set_u32(nle, NFTNL_EXPR_CMP_OP, NFT_CMP_NEQ); + + nftnl_expr_set(nle, NFTNL_EXPR_CMP_DATA, nld.value, nld.len); + nft_rule_add_expr(ctx, nle, &expr->location); + } mpz_clear(zero); release_register(ctx, expr->left); @@ -486,6 +585,7 @@ static void netlink_gen_relational(struct netlink_linearize_ctx *ctx, case OP_GT: case OP_LTE: case OP_GTE: + case OP_NEG: break; default: BUG("invalid relational operation %u\n", expr->op); @@ -501,7 +601,10 @@ static void netlink_gen_relational(struct netlink_linearize_ctx *ctx, return netlink_gen_flagcmp(ctx, expr, dreg); case EXPR_PREFIX: sreg = get_register(ctx, expr->left); - if (expr_basetype(expr->left)->type != TYPE_STRING) { + if (expr_basetype(expr->left)->type != TYPE_STRING && + (expr->right->byteorder != BYTEORDER_BIG_ENDIAN || + !expr->right->prefix_len || + expr->right->prefix_len % BITS_PER_BYTE)) { len = div_round_up(expr->right->len, BITS_PER_BYTE); netlink_gen_expr(ctx, expr->left, sreg); right = netlink_gen_prefix(ctx, expr, sreg); @@ -513,7 +616,7 @@ static void netlink_gen_relational(struct netlink_linearize_ctx *ctx, } break; default: - if (expr->op == OP_IMPLICIT && + if ((expr->op == OP_IMPLICIT || expr->op == OP_NEG) && expr->right->dtype->basetype != NULL && expr->right->dtype->basetype->type == TYPE_BITMASK) return netlink_gen_flagcmp(ctx, expr, dreg); @@ -533,7 +636,7 @@ static void netlink_gen_relational(struct netlink_linearize_ctx *ctx, nftnl_expr_set(nle, NFTNL_EXPR_CMP_DATA, nld.value, len); release_register(ctx, expr->left); - nftnl_rule_add_expr(ctx->nlr, nle); + nft_rule_add_expr(ctx, nle, &expr->location); } static void combine_binop(mpz_t mask, mpz_t xor, const mpz_t m, const mpz_t x) @@ -545,14 +648,41 @@ static void combine_binop(mpz_t mask, mpz_t xor, const mpz_t m, const mpz_t x) mpz_and(mask, mask, m); } -static void netlink_gen_binop(struct netlink_linearize_ctx *ctx, - const struct expr *expr, - enum nft_registers dreg) +static void netlink_gen_bitwise_shift(struct netlink_linearize_ctx *ctx, + const struct expr *expr, + enum nft_registers dreg) +{ + enum nft_bitwise_ops op = expr->op == OP_LSHIFT ? + NFT_BITWISE_LSHIFT : NFT_BITWISE_RSHIFT; + unsigned int len = div_round_up(expr->len, BITS_PER_BYTE); + struct nft_data_linearize nld; + struct nftnl_expr *nle; + + netlink_gen_expr(ctx, expr->left, dreg); + + nle = alloc_nft_expr("bitwise"); + netlink_put_register(nle, NFTNL_EXPR_BITWISE_SREG, dreg); + netlink_put_register(nle, NFTNL_EXPR_BITWISE_DREG, dreg); + nftnl_expr_set_u32(nle, NFTNL_EXPR_BITWISE_OP, op); + nftnl_expr_set_u32(nle, NFTNL_EXPR_BITWISE_LEN, len); + + netlink_gen_raw_data(expr->right->value, expr->right->byteorder, + sizeof(uint32_t), &nld); + + nftnl_expr_set(nle, NFTNL_EXPR_BITWISE_DATA, nld.value, + nld.len); + + nft_rule_add_expr(ctx, nle, &expr->location); +} + +static void netlink_gen_bitwise_mask_xor(struct netlink_linearize_ctx *ctx, + const struct expr *expr, + enum nft_registers dreg) { + struct expr *binops[NFT_MAX_EXPR_RECURSION]; struct nftnl_expr *nle; struct nft_data_linearize nld; struct expr *left, *i; - struct expr *binops[16]; mpz_t mask, xor, val, tmp; unsigned int len; int n = 0; @@ -562,17 +692,20 @@ static void netlink_gen_binop(struct netlink_linearize_ctx *ctx, mpz_init(val); mpz_init(tmp); - binops[n++] = left = (void *)expr; - while (left->etype == EXPR_BINOP && left->left != NULL) + binops[n++] = left = (struct expr *) expr; + while (left->etype == EXPR_BINOP && left->left != NULL && expr_is_constant(left->right) && + (left->op == OP_AND || left->op == OP_OR || left->op == OP_XOR)) { + if (n == array_size(binops)) + BUG("NFT_MAX_EXPR_RECURSION limit reached"); binops[n++] = left = left->left; - n--; + } - netlink_gen_expr(ctx, binops[n--], dreg); + netlink_gen_expr(ctx, binops[--n], dreg); mpz_bitmask(mask, expr->len); mpz_set_ui(xor, 0); - for (; n >= 0; n--) { - i = binops[n]; + while (n > 0) { + i = binops[--n]; mpz_set(val, i->right->value); switch (i->op) { @@ -598,6 +731,7 @@ static void netlink_gen_binop(struct netlink_linearize_ctx *ctx, nle = alloc_nft_expr("bitwise"); netlink_put_register(nle, NFTNL_EXPR_BITWISE_SREG, dreg); netlink_put_register(nle, NFTNL_EXPR_BITWISE_DREG, dreg); + nftnl_expr_set_u32(nle, NFTNL_EXPR_BITWISE_OP, NFT_BITWISE_MASK_XOR); nftnl_expr_set_u32(nle, NFTNL_EXPR_BITWISE_LEN, len); netlink_gen_raw_data(mask, expr->byteorder, len, &nld); @@ -610,9 +744,66 @@ static void netlink_gen_binop(struct netlink_linearize_ctx *ctx, mpz_clear(xor); mpz_clear(mask); + nft_rule_add_expr(ctx, nle, &expr->location); +} + +static void netlink_gen_bitwise_bool(struct netlink_linearize_ctx *ctx, + const struct expr *expr, + enum nft_registers dreg) +{ + enum nft_registers sreg2; + struct nftnl_expr *nle; + unsigned int len; + + nle = alloc_nft_expr("bitwise"); + + switch (expr->op) { + case OP_XOR: + nftnl_expr_set_u32(nle, NFTNL_EXPR_BITWISE_OP, NFT_BITWISE_XOR); + break; + case OP_AND: + nftnl_expr_set_u32(nle, NFTNL_EXPR_BITWISE_OP, NFT_BITWISE_AND); + break; + case OP_OR: + nftnl_expr_set_u32(nle, NFTNL_EXPR_BITWISE_OP, NFT_BITWISE_OR); + break; + default: + BUG("invalid binary operation %u\n", expr->op); + } + + netlink_gen_expr(ctx, expr->left, dreg); + netlink_put_register(nle, NFTNL_EXPR_BITWISE_SREG, dreg); + netlink_put_register(nle, NFTNL_EXPR_BITWISE_DREG, dreg); + + sreg2 = get_register(ctx, expr->right); + netlink_gen_expr(ctx, expr->right, sreg2); + netlink_put_register(nle, NFTNL_EXPR_BITWISE_SREG2, sreg2); + release_register(ctx, expr->right); + + len = div_round_up(expr->len, BITS_PER_BYTE); + nftnl_expr_set_u32(nle, NFTNL_EXPR_BITWISE_LEN, len); + nftnl_rule_add_expr(ctx->nlr, nle); } +static void netlink_gen_binop(struct netlink_linearize_ctx *ctx, + const struct expr *expr, + enum nft_registers dreg) +{ + switch(expr->op) { + case OP_LSHIFT: + case OP_RSHIFT: + netlink_gen_bitwise_shift(ctx, expr, dreg); + break; + default: + if (expr_is_constant(expr->right)) + netlink_gen_bitwise_mask_xor(ctx, expr, dreg); + else + netlink_gen_bitwise_bool(ctx, expr, dreg); + break; + } +} + static enum nft_byteorder_ops netlink_gen_unary_op(enum ops op) { switch (op) { @@ -632,6 +823,8 @@ static void netlink_gen_unary(struct netlink_linearize_ctx *ctx, struct nftnl_expr *nle; int byte_size; + assert(div_round_up(expr->arg->len, BITS_PER_BYTE) != 1); + if ((expr->arg->len % 64) == 0) byte_size = 8; else if ((expr->arg->len % 32) == 0) @@ -645,20 +838,21 @@ static void netlink_gen_unary(struct netlink_linearize_ctx *ctx, netlink_put_register(nle, NFTNL_EXPR_BYTEORDER_SREG, dreg); netlink_put_register(nle, NFTNL_EXPR_BYTEORDER_DREG, dreg); nftnl_expr_set_u32(nle, NFTNL_EXPR_BYTEORDER_LEN, - expr->len / BITS_PER_BYTE); + div_round_up(expr->len, BITS_PER_BYTE)); nftnl_expr_set_u32(nle, NFTNL_EXPR_BYTEORDER_SIZE, byte_size); nftnl_expr_set_u32(nle, NFTNL_EXPR_BYTEORDER_OP, netlink_gen_unary_op(expr->op)); - nftnl_rule_add_expr(ctx->nlr, nle); + nft_rule_add_expr(ctx, nle, &expr->location); } static void netlink_gen_immediate(struct netlink_linearize_ctx *ctx, const struct expr *expr, enum nft_registers dreg) { - struct nftnl_expr *nle; + const struct location *loc = &expr->location; struct nft_data_linearize nld; + struct nftnl_expr *nle; nle = alloc_nft_expr("immediate"); netlink_put_register(nle, NFTNL_EXPR_IMM_DREG, dreg); @@ -668,17 +862,20 @@ static void netlink_gen_immediate(struct netlink_linearize_ctx *ctx, nftnl_expr_set(nle, NFTNL_EXPR_IMM_DATA, nld.value, nld.len); break; case EXPR_VERDICT: - if ((expr->chain != NULL) && - !nftnl_expr_is_set(nle, NFTNL_EXPR_IMM_CHAIN)) { + if (expr->chain) { nftnl_expr_set_str(nle, NFTNL_EXPR_IMM_CHAIN, nld.chain); + loc = &expr->chain->location; + } else if (expr->chain_id) { + nftnl_expr_set_u32(nle, NFTNL_EXPR_IMM_CHAIN_ID, + nld.chain_id); } nftnl_expr_set_u32(nle, NFTNL_EXPR_IMM_VERDICT, nld.verdict); break; default: break; } - nftnl_rule_add_expr(ctx->nlr, nle); + nft_rule_add_expr(ctx, nle, loc); } static void netlink_gen_xfrm(struct netlink_linearize_ctx *ctx, @@ -692,7 +889,7 @@ static void netlink_gen_xfrm(struct netlink_linearize_ctx *ctx, nftnl_expr_set_u32(nle, NFTNL_EXPR_XFRM_KEY, expr->xfrm.key); nftnl_expr_set_u8(nle, NFTNL_EXPR_XFRM_DIR, expr->xfrm.direction); nftnl_expr_set_u32(nle, NFTNL_EXPR_XFRM_SPNUM, expr->xfrm.spnum); - nftnl_rule_add_expr(ctx->nlr, nle); + nft_rule_add_expr(ctx, nle, &expr->location); } static void netlink_gen_expr(struct netlink_linearize_ctx *ctx, @@ -775,12 +972,10 @@ static void netlink_gen_objref_stmt(struct netlink_linearize_ctx *ctx, default: BUG("unsupported expression %u\n", expr->etype); } - nftnl_rule_add_expr(ctx->nlr, nle); + nft_rule_add_expr(ctx, nle, &expr->location); } -static struct nftnl_expr * -netlink_gen_connlimit_stmt(struct netlink_linearize_ctx *ctx, - const struct stmt *stmt) +static struct nftnl_expr *netlink_gen_connlimit_stmt(const struct stmt *stmt) { struct nftnl_expr *nle; @@ -793,9 +988,7 @@ netlink_gen_connlimit_stmt(struct netlink_linearize_ctx *ctx, return nle; } -static struct nftnl_expr * -netlink_gen_counter_stmt(struct netlink_linearize_ctx *ctx, - const struct stmt *stmt) +static struct nftnl_expr *netlink_gen_counter_stmt(const struct stmt *stmt) { struct nftnl_expr *nle; @@ -812,9 +1005,7 @@ netlink_gen_counter_stmt(struct netlink_linearize_ctx *ctx, return nle; } -static struct nftnl_expr * -netlink_gen_limit_stmt(struct netlink_linearize_ctx *ctx, - const struct stmt *stmt) +static struct nftnl_expr *netlink_gen_limit_stmt(const struct stmt *stmt) { struct nftnl_expr *nle; @@ -830,9 +1021,7 @@ netlink_gen_limit_stmt(struct netlink_linearize_ctx *ctx, return nle; } -static struct nftnl_expr * -netlink_gen_quota_stmt(struct netlink_linearize_ctx *ctx, - const struct stmt *stmt) +static struct nftnl_expr *netlink_gen_quota_stmt(const struct stmt *stmt) { struct nftnl_expr *nle; @@ -844,21 +1033,32 @@ netlink_gen_quota_stmt(struct netlink_linearize_ctx *ctx, return nle; } -static struct nftnl_expr * -netlink_gen_stmt_stateful(struct netlink_linearize_ctx *ctx, - const struct stmt *stmt) +static struct nftnl_expr *netlink_gen_last_stmt(const struct stmt *stmt) { - switch (stmt->ops->type) { + struct nftnl_expr *nle; + + nle = alloc_nft_expr("last"); + nftnl_expr_set_u32(nle, NFTNL_EXPR_LAST_SET, stmt->last.set); + nftnl_expr_set_u64(nle, NFTNL_EXPR_LAST_MSECS, stmt->last.used); + + return nle; +} + +struct nftnl_expr *netlink_gen_stmt_stateful(const struct stmt *stmt) +{ + switch (stmt->type) { case STMT_CONNLIMIT: - return netlink_gen_connlimit_stmt(ctx, stmt); + return netlink_gen_connlimit_stmt(stmt); case STMT_COUNTER: - return netlink_gen_counter_stmt(ctx, stmt); + return netlink_gen_counter_stmt(stmt); case STMT_LIMIT: - return netlink_gen_limit_stmt(ctx, stmt); + return netlink_gen_limit_stmt(stmt); case STMT_QUOTA: - return netlink_gen_quota_stmt(ctx, stmt); + return netlink_gen_quota_stmt(stmt); + case STMT_LAST: + return netlink_gen_last_stmt(stmt); default: - BUG("unknown stateful statement type %s\n", stmt->ops->name); + BUG("unknown stateful statement type %d\n", stmt->type); } } @@ -894,17 +1094,17 @@ static void netlink_gen_exthdr_stmt(struct netlink_linearize_ctx *ctx, expr = stmt->exthdr.expr; - offset = expr->exthdr.tmpl->offset + expr->exthdr.offset; + offset = expr->exthdr.offset; nle = alloc_nft_expr("exthdr"); netlink_put_register(nle, NFTNL_EXPR_EXTHDR_SREG, sreg); nftnl_expr_set_u8(nle, NFTNL_EXPR_EXTHDR_TYPE, - expr->exthdr.desc->type); + expr->exthdr.raw_type); nftnl_expr_set_u32(nle, NFTNL_EXPR_EXTHDR_OFFSET, offset / BITS_PER_BYTE); nftnl_expr_set_u32(nle, NFTNL_EXPR_EXTHDR_LEN, div_round_up(expr->len, BITS_PER_BYTE)); nftnl_expr_set_u32(nle, NFTNL_EXPR_EXTHDR_OP, expr->exthdr.op); - nftnl_rule_add_expr(ctx->nlr, nle); + nft_rule_add_expr(ctx, nle, &expr->location); } static void netlink_gen_payload_stmt(struct netlink_linearize_ctx *ctx, @@ -937,16 +1137,19 @@ static void netlink_gen_payload_stmt(struct netlink_linearize_ctx *ctx, expr->len / BITS_PER_BYTE); if (csum_off) { nftnl_expr_set_u32(nle, NFTNL_EXPR_PAYLOAD_CSUM_TYPE, - NFT_PAYLOAD_CSUM_INET); + desc->checksum_type); nftnl_expr_set_u32(nle, NFTNL_EXPR_PAYLOAD_CSUM_OFFSET, csum_off / BITS_PER_BYTE); } - if (expr->payload.base == PROTO_BASE_NETWORK_HDR && desc && - payload_needs_l4csum_update_pseudohdr(expr, desc)) + if ((expr->payload.base == PROTO_BASE_NETWORK_HDR && desc && + payload_needs_l4csum_update_pseudohdr(expr, desc)) || + (expr->payload.base == PROTO_BASE_TRANSPORT_HDR && desc && + desc == &proto_udp) || + expr->payload.base == PROTO_BASE_INNER_HDR) nftnl_expr_set_u32(nle, NFTNL_EXPR_PAYLOAD_FLAGS, NFT_PAYLOAD_L4CSUM_PSEUDOHDR); - nftnl_rule_add_expr(ctx->nlr, nle); + nft_rule_add_expr(ctx, nle, &expr->location); } static void netlink_gen_meta_stmt(struct netlink_linearize_ctx *ctx, @@ -962,7 +1165,7 @@ static void netlink_gen_meta_stmt(struct netlink_linearize_ctx *ctx, nle = alloc_nft_expr("meta"); netlink_put_register(nle, NFTNL_EXPR_META_SREG, sreg); nftnl_expr_set_u32(nle, NFTNL_EXPR_META_KEY, stmt->meta.key); - nftnl_rule_add_expr(ctx->nlr, nle); + nft_rule_add_expr(ctx, nle, &stmt->location); } static void netlink_gen_log_stmt(struct netlink_linearize_ctx *ctx, @@ -971,10 +1174,9 @@ static void netlink_gen_log_stmt(struct netlink_linearize_ctx *ctx, struct nftnl_expr *nle; nle = alloc_nft_expr("log"); - if (stmt->log.prefix != NULL) { - nftnl_expr_set_str(nle, NFTNL_EXPR_LOG_PREFIX, - stmt->log.prefix); - } + if (stmt->log.prefix != NULL) + nftnl_expr_set_str(nle, NFTNL_EXPR_LOG_PREFIX, stmt->log.prefix); + if (stmt->log.flags & STMT_LOG_GROUP) { nftnl_expr_set_u16(nle, NFTNL_EXPR_LOG_GROUP, stmt->log.group); if (stmt->log.flags & STMT_LOG_SNAPLEN) @@ -991,7 +1193,7 @@ static void netlink_gen_log_stmt(struct netlink_linearize_ctx *ctx, nftnl_expr_set_u32(nle, NFTNL_EXPR_LOG_FLAGS, stmt->log.logflags); } - nftnl_rule_add_expr(ctx->nlr, nle); + nft_rule_add_expr(ctx, nle, &stmt->location); } static void netlink_gen_reject_stmt(struct netlink_linearize_ctx *ctx, @@ -1005,7 +1207,18 @@ static void netlink_gen_reject_stmt(struct netlink_linearize_ctx *ctx, nftnl_expr_set_u8(nle, NFTNL_EXPR_REJECT_CODE, stmt->reject.icmp_code); - nftnl_rule_add_expr(ctx->nlr, nle); + nft_rule_add_expr(ctx, nle, &stmt->location); +} + +static unsigned int nat_addrlen(uint8_t family) +{ + switch (family) { + case NFPROTO_IPV4: return 32; + case NFPROTO_IPV6: return 128; + } + + BUG("invalid nat family %u\n", family); + return 0; } static void netlink_gen_nat_stmt(struct netlink_linearize_ctx *ctx, @@ -1014,8 +1227,8 @@ static void netlink_gen_nat_stmt(struct netlink_linearize_ctx *ctx, struct nftnl_expr *nle; enum nft_registers amin_reg, amax_reg; enum nft_registers pmin_reg, pmax_reg; + uint8_t family = 0; int registers = 0; - int family; int nftnl_flag_attr; int nftnl_reg_pmin, nftnl_reg_pmax; @@ -1072,8 +1285,46 @@ static void netlink_gen_nat_stmt(struct netlink_linearize_ctx *ctx, netlink_gen_expr(ctx, stmt->nat.addr, amin_reg); netlink_put_register(nle, NFTNL_EXPR_NAT_REG_ADDR_MIN, amin_reg); + if (stmt->nat.addr->etype == EXPR_MAP && + stmt->nat.addr->mappings->set->data->flags & EXPR_F_INTERVAL) { + amin_reg += netlink_register_space(nat_addrlen(family)); + if (stmt->nat.type_flags & STMT_NAT_F_CONCAT) { + netlink_put_register(nle, nftnl_reg_pmin, + amin_reg); + } else { + netlink_put_register(nle, NFTNL_EXPR_NAT_REG_ADDR_MAX, + amin_reg); + } + } } + if (stmt->nat.type_flags & STMT_NAT_F_CONCAT) { + /* nat_stmt evaluation step doesn't allow + * STMT_NAT_F_CONCAT && stmt->nat.proto. + */ + assert(stmt->nat.proto == NULL); + + pmin_reg = amin_reg; + + if (stmt->nat.type_flags & STMT_NAT_F_INTERVAL) { + pmin_reg += netlink_register_space(nat_addrlen(family)); + netlink_put_register(nle, NFTNL_EXPR_NAT_REG_ADDR_MAX, + pmin_reg); + } + + /* if STMT_NAT_F_CONCAT is set, the mapped type is a + * concatenation of 'addr . inet_service'. + * The map lookup will then return the + * concatenated value, so we need to skip + * the address and use the register that + * will hold the inet_service part. + */ + pmin_reg += netlink_register_space(nat_addrlen(family)); + if (stmt->nat.type_flags & STMT_NAT_F_INTERVAL) + netlink_put_register(nle, nftnl_reg_pmax, pmin_reg); + else + netlink_put_register(nle, nftnl_reg_pmin, pmin_reg); + } } if (stmt->nat.proto) { @@ -1099,7 +1350,7 @@ static void netlink_gen_nat_stmt(struct netlink_linearize_ctx *ctx, registers--; } - nftnl_rule_add_expr(ctx->nlr, nle); + nft_rule_add_expr(ctx, nle, &stmt->location); } static void netlink_gen_tproxy_stmt(struct netlink_linearize_ctx *ctx, @@ -1138,7 +1389,7 @@ static void netlink_gen_tproxy_stmt(struct netlink_linearize_ctx *ctx, registers--; } - nftnl_rule_add_expr(ctx->nlr, nle); + nft_rule_add_expr(ctx, nle, &stmt->location); } static void netlink_gen_synproxy_stmt(struct netlink_linearize_ctx *ctx, @@ -1153,7 +1404,7 @@ static void netlink_gen_synproxy_stmt(struct netlink_linearize_ctx *ctx, nftnl_expr_set_u32(nle, NFTNL_EXPR_SYNPROXY_FLAGS, stmt->synproxy.flags); - nftnl_rule_add_expr(ctx->nlr, nle); + nft_rule_add_expr(ctx, nle, &stmt->location); } static void netlink_gen_dup_stmt(struct netlink_linearize_ctx *ctx, @@ -1184,7 +1435,7 @@ static void netlink_gen_dup_stmt(struct netlink_linearize_ctx *ctx, if (stmt->dup.to != NULL) release_register(ctx, stmt->dup.to); - nftnl_rule_add_expr(ctx->nlr, nle); + nft_rule_add_expr(ctx, nle, &stmt->location); } static void netlink_gen_fwd_stmt(struct netlink_linearize_ctx *ctx, @@ -1211,39 +1462,69 @@ static void netlink_gen_fwd_stmt(struct netlink_linearize_ctx *ctx, nftnl_expr_set_u32(nle, NFTNL_EXPR_FWD_NFPROTO, stmt->fwd.family); - nftnl_rule_add_expr(ctx->nlr, nle); + nft_rule_add_expr(ctx, nle, &stmt->location); +} + +static void netlink_gen_optstrip_stmt(struct netlink_linearize_ctx *ctx, + const struct stmt *stmt) +{ + struct nftnl_expr *nle = alloc_nft_expr("exthdr"); + struct expr *expr = stmt->optstrip.expr; + + nftnl_expr_set_u8(nle, NFTNL_EXPR_EXTHDR_TYPE, + expr->exthdr.raw_type); + nftnl_expr_set_u32(nle, NFTNL_EXPR_EXTHDR_OP, expr->exthdr.op); + nft_rule_add_expr(ctx, nle, &expr->location); } static void netlink_gen_queue_stmt(struct netlink_linearize_ctx *ctx, const struct stmt *stmt) { + enum nft_registers sreg = 0; struct nftnl_expr *nle; uint16_t total_queues; + struct expr *expr; mpz_t low, high; mpz_init2(low, 16); mpz_init2(high, 16); - if (stmt->queue.queue != NULL) { - range_expr_value_low(low, stmt->queue.queue); - range_expr_value_high(high, stmt->queue.queue); + + expr = stmt->queue.queue; + + if (expr) { + if (expr->etype == EXPR_RANGE || expr->etype == EXPR_VALUE) { + range_expr_value_low(low, stmt->queue.queue); + range_expr_value_high(high, stmt->queue.queue); + } else { + sreg = get_register(ctx, expr); + netlink_gen_expr(ctx, expr, sreg); + release_register(ctx, expr); + } } + total_queues = mpz_get_uint16(high) - mpz_get_uint16(low) + 1; nle = alloc_nft_expr("queue"); - nftnl_expr_set_u16(nle, NFTNL_EXPR_QUEUE_NUM, mpz_get_uint16(low)); - nftnl_expr_set_u16(nle, NFTNL_EXPR_QUEUE_TOTAL, total_queues); + + if (sreg) { + netlink_put_register(nle, NFTNL_EXPR_QUEUE_SREG_QNUM, sreg); + } else { + nftnl_expr_set_u16(nle, NFTNL_EXPR_QUEUE_NUM, mpz_get_uint16(low)); + nftnl_expr_set_u16(nle, NFTNL_EXPR_QUEUE_TOTAL, total_queues); + } + if (stmt->queue.flags) nftnl_expr_set_u16(nle, NFTNL_EXPR_QUEUE_FLAGS, stmt->queue.flags); - nftnl_rule_add_expr(ctx->nlr, nle); + nft_rule_add_expr(ctx, nle, &stmt->location); mpz_clear(low); mpz_clear(high); } static void netlink_gen_ct_stmt(struct netlink_linearize_ctx *ctx, - const struct stmt *stmt) + const struct stmt *stmt) { struct nftnl_expr *nle; enum nft_registers sreg; @@ -1259,7 +1540,7 @@ static void netlink_gen_ct_stmt(struct netlink_linearize_ctx *ctx, nftnl_expr_set_u8(nle, NFTNL_EXPR_CT_DIR, stmt->ct.direction); - nftnl_rule_add_expr(ctx->nlr, nle); + nft_rule_add_expr(ctx, nle, &stmt->location); } static void netlink_gen_notrack_stmt(struct netlink_linearize_ctx *ctx, @@ -1268,7 +1549,7 @@ static void netlink_gen_notrack_stmt(struct netlink_linearize_ctx *ctx, struct nftnl_expr *nle; nle = alloc_nft_expr("notrack"); - nftnl_rule_add_expr(ctx->nlr, nle); + nft_rule_add_expr(ctx, nle, &stmt->location); } static void netlink_gen_flow_offload_stmt(struct netlink_linearize_ctx *ctx, @@ -1279,15 +1560,17 @@ static void netlink_gen_flow_offload_stmt(struct netlink_linearize_ctx *ctx, nle = alloc_nft_expr("flow_offload"); nftnl_expr_set_str(nle, NFTNL_EXPR_FLOW_TABLE_NAME, stmt->flow.table_name); - nftnl_rule_add_expr(ctx->nlr, nle); + nft_rule_add_expr(ctx, nle, &stmt->location); } static void netlink_gen_set_stmt(struct netlink_linearize_ctx *ctx, const struct stmt *stmt) { struct set *set = stmt->meter.set->set; - struct nftnl_expr *nle; enum nft_registers sreg_key; + struct nftnl_expr *nle; + int num_stmts = 0; + struct stmt *this; sreg_key = get_register(ctx, stmt->set.key->key); netlink_gen_expr(ctx, stmt->set.key->key, sreg_key); @@ -1301,11 +1584,24 @@ static void netlink_gen_set_stmt(struct netlink_linearize_ctx *ctx, nftnl_expr_set_u32(nle, NFTNL_EXPR_DYNSET_OP, stmt->set.op); nftnl_expr_set_str(nle, NFTNL_EXPR_DYNSET_SET_NAME, set->handle.set.name); nftnl_expr_set_u32(nle, NFTNL_EXPR_DYNSET_SET_ID, set->handle.set_id); - nftnl_rule_add_expr(ctx->nlr, nle); + nft_rule_add_expr(ctx, nle, &stmt->location); + + list_for_each_entry(this, &stmt->set.stmt_list, list) + num_stmts++; - if (stmt->set.stmt) - nftnl_expr_set(nle, NFTNL_EXPR_DYNSET_EXPR, - netlink_gen_stmt_stateful(ctx, stmt->set.stmt), 0); + if (num_stmts == 1) { + list_for_each_entry(this, &stmt->set.stmt_list, list) { + nftnl_expr_set(nle, NFTNL_EXPR_DYNSET_EXPR, + netlink_gen_stmt_stateful(this), 0); + } + } else if (num_stmts > 1) { + list_for_each_entry(this, &stmt->set.stmt_list, list) { + nftnl_expr_add_expr(nle, NFTNL_EXPR_DYNSET_EXPRESSIONS, + netlink_gen_stmt_stateful(this)); + } + nftnl_expr_set_u32(nle, NFTNL_EXPR_DYNSET_FLAGS, + NFT_DYNSET_F_EXPR); + } } static void netlink_gen_map_stmt(struct netlink_linearize_ctx *ctx, @@ -1315,15 +1611,17 @@ static void netlink_gen_map_stmt(struct netlink_linearize_ctx *ctx, enum nft_registers sreg_data; enum nft_registers sreg_key; struct nftnl_expr *nle; + int num_stmts = 0; + struct stmt *this; - sreg_key = get_register(ctx, stmt->map.key); - netlink_gen_expr(ctx, stmt->map.key, sreg_key); + sreg_key = get_register(ctx, stmt->map.key->key); + netlink_gen_expr(ctx, stmt->map.key->key, sreg_key); - sreg_data = get_register(ctx, stmt->map.data); - netlink_gen_expr(ctx, stmt->map.data, sreg_data); + sreg_data = get_register(ctx, stmt->map.data->key); + netlink_gen_expr(ctx, stmt->map.data->key, sreg_data); - release_register(ctx, stmt->map.key); - release_register(ctx, stmt->map.data); + release_register(ctx, stmt->map.key->key); + release_register(ctx, stmt->map.data->key); nle = alloc_nft_expr("dynset"); netlink_put_register(nle, NFTNL_EXPR_DYNSET_SREG_KEY, sreg_key); @@ -1332,12 +1630,26 @@ static void netlink_gen_map_stmt(struct netlink_linearize_ctx *ctx, nftnl_expr_set_u32(nle, NFTNL_EXPR_DYNSET_OP, stmt->map.op); nftnl_expr_set_str(nle, NFTNL_EXPR_DYNSET_SET_NAME, set->handle.set.name); nftnl_expr_set_u32(nle, NFTNL_EXPR_DYNSET_SET_ID, set->handle.set_id); + nft_rule_add_expr(ctx, nle, &stmt->location); + + if (stmt->map.key->timeout > 0) + nftnl_expr_set_u64(nle, NFTNL_EXPR_DYNSET_TIMEOUT, + stmt->map.key->timeout); - if (stmt->map.stmt) - nftnl_expr_set(nle, NFTNL_EXPR_DYNSET_EXPR, - netlink_gen_stmt_stateful(ctx, stmt->map.stmt), 0); + list_for_each_entry(this, &stmt->map.stmt_list, list) + num_stmts++; - nftnl_rule_add_expr(ctx->nlr, nle); + if (num_stmts == 1) { + list_for_each_entry(this, &stmt->map.stmt_list, list) { + nftnl_expr_set(nle, NFTNL_EXPR_DYNSET_EXPR, + netlink_gen_stmt_stateful(this), 0); + } + } else if (num_stmts > 1) { + list_for_each_entry(this, &stmt->map.stmt_list, list) { + nftnl_expr_add_expr(nle, NFTNL_EXPR_DYNSET_EXPRESSIONS, + netlink_gen_stmt_stateful(this)); + } + } } static void netlink_gen_meter_stmt(struct netlink_linearize_ctx *ctx, @@ -1367,8 +1679,14 @@ static void netlink_gen_meter_stmt(struct netlink_linearize_ctx *ctx, nftnl_expr_set_str(nle, NFTNL_EXPR_DYNSET_SET_NAME, set->handle.set.name); nftnl_expr_set_u32(nle, NFTNL_EXPR_DYNSET_SET_ID, set->handle.set_id); nftnl_expr_set(nle, NFTNL_EXPR_DYNSET_EXPR, - netlink_gen_stmt_stateful(ctx, stmt->meter.stmt), 0); - nftnl_rule_add_expr(ctx->nlr, nle); + netlink_gen_stmt_stateful(stmt->meter.stmt), 0); + nft_rule_add_expr(ctx, nle, &stmt->location); +} + +static void netlink_gen_chain_stmt(struct netlink_linearize_ctx *ctx, + const struct stmt *stmt) +{ + return netlink_gen_expr(ctx, stmt->chain.expr, NFT_REG_VERDICT); } static void netlink_gen_stmt(struct netlink_linearize_ctx *ctx, @@ -1376,7 +1694,7 @@ static void netlink_gen_stmt(struct netlink_linearize_ctx *ctx, { struct nftnl_expr *nle; - switch (stmt->ops->type) { + switch (stmt->type) { case STMT_EXPRESSION: return netlink_gen_expr(ctx, stmt->expr, NFT_REG_VERDICT); case STMT_VERDICT: @@ -1413,8 +1731,9 @@ static void netlink_gen_stmt(struct netlink_linearize_ctx *ctx, case STMT_COUNTER: case STMT_LIMIT: case STMT_QUOTA: - nle = netlink_gen_stmt_stateful(ctx, stmt); - nftnl_rule_add_expr(ctx->nlr, nle); + case STMT_LAST: + nle = netlink_gen_stmt_stateful(stmt); + nft_rule_add_expr(ctx, nle, &stmt->location); break; case STMT_NOTRACK: return netlink_gen_notrack_stmt(ctx, stmt); @@ -1424,23 +1743,49 @@ static void netlink_gen_stmt(struct netlink_linearize_ctx *ctx, return netlink_gen_objref_stmt(ctx, stmt); case STMT_MAP: return netlink_gen_map_stmt(ctx, stmt); + case STMT_CHAIN: + return netlink_gen_chain_stmt(ctx, stmt); + case STMT_OPTSTRIP: + return netlink_gen_optstrip_stmt(ctx, stmt); default: - BUG("unknown statement type %s\n", stmt->ops->name); + BUG("unknown statement type %d\n", stmt->type); } } -void netlink_linearize_rule(struct netlink_ctx *ctx, struct nftnl_rule *nlr, - const struct rule *rule) +void netlink_linearize_init(struct netlink_linearize_ctx *lctx, + struct nftnl_rule *nlr) { - struct netlink_linearize_ctx lctx; - const struct stmt *stmt; + int i; + + memset(lctx, 0, sizeof(*lctx)); + lctx->reg_low = NFT_REG_1; + lctx->nlr = nlr; + lctx->expr_loc_htable = + xmalloc(sizeof(struct list_head) * NFT_EXPR_LOC_HSIZE); + for (i = 0; i < NFT_EXPR_LOC_HSIZE; i++) + init_list_head(&lctx->expr_loc_htable[i]); +} - memset(&lctx, 0, sizeof(lctx)); - lctx.reg_low = NFT_REG_1; - lctx.nlr = nlr; +void netlink_linearize_fini(struct netlink_linearize_ctx *lctx) +{ + struct nft_expr_loc *eloc, *next; + int i; + + for (i = 0; i < NFT_EXPR_LOC_HSIZE; i++) { + list_for_each_entry_safe(eloc, next, &lctx->expr_loc_htable[i], hlist) + free(eloc); + } + free(lctx->expr_loc_htable); +} + +void netlink_linearize_rule(struct netlink_ctx *ctx, + const struct rule *rule, + struct netlink_linearize_ctx *lctx) +{ + const struct stmt *stmt; list_for_each_entry(stmt, &rule->stmts, list) - netlink_gen_stmt(&lctx, stmt); + netlink_gen_stmt(lctx, stmt); if (rule->comment) { struct nftnl_udata_buf *udata; @@ -1452,12 +1797,23 @@ void netlink_linearize_rule(struct netlink_ctx *ctx, struct nftnl_rule *nlr, if (!nftnl_udata_put_strz(udata, NFTNL_UDATA_RULE_COMMENT, rule->comment)) memory_allocation_error(); - nftnl_rule_set_data(nlr, NFTNL_RULE_USERDATA, + nftnl_rule_set_data(lctx->nlr, NFTNL_RULE_USERDATA, nftnl_udata_buf_data(udata), nftnl_udata_buf_len(udata)); nftnl_udata_buf_free(udata); } - netlink_dump_rule(nlr, ctx); + if (ctx->nft->debug_mask & NFT_DEBUG_NETLINK) { + nftnl_rule_set_str(lctx->nlr, NFTNL_RULE_TABLE, + rule->handle.table.name); + if (rule->handle.chain.name) + nftnl_rule_set_str(lctx->nlr, NFTNL_RULE_CHAIN, + rule->handle.chain.name); + + netlink_dump_rule(lctx->nlr, ctx); + + nftnl_rule_unset(lctx->nlr, NFTNL_RULE_CHAIN); + nftnl_rule_unset(lctx->nlr, NFTNL_RULE_TABLE); + } } diff --git a/src/nfnl_osf.c b/src/nfnl_osf.c index 08e978de..20a1bfe7 100644 --- a/src/nfnl_osf.c +++ b/src/nfnl_osf.c @@ -19,12 +19,12 @@ * Based on iptables/utils/nfnl_osf.c. */ +#include <nft.h> + #include <sys/time.h> #include <ctype.h> #include <errno.h> -#include <stdlib.h> -#include <string.h> #include <time.h> #include <netinet/ip.h> diff --git a/src/nftutils.c b/src/nftutils.c new file mode 100644 index 00000000..ca178aa0 --- /dev/null +++ b/src/nftutils.c @@ -0,0 +1,100 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include <nft.h> + +#include "nftutils.h" + +#include <netdb.h> + +/* Buffer size used for getprotobynumber_r() and similar. The manual comments + * that a buffer of 1024 should be sufficient "for most applications"(??), so + * let's double it. It still fits reasonably on the stack, so no need to + * choose a smaller one. */ +#define NETDB_BUFSIZE 2048 + +bool nft_getprotobynumber(int proto, char *out_name, size_t name_len) +{ + const struct protoent *result; + +#if HAVE_DECL_GETPROTOBYNUMBER_R + struct protoent result_buf; + char buf[NETDB_BUFSIZE]; + int r; + + r = getprotobynumber_r(proto, + &result_buf, + buf, + sizeof(buf), + (struct protoent **) &result); + if (r != 0 || result != &result_buf) + result = NULL; +#else + result = getprotobynumber(proto); +#endif + + if (!result) + return false; + + if (strlen(result->p_name) >= name_len) + return false; + strcpy(out_name, result->p_name); + return true; +} + +int nft_getprotobyname(const char *name) +{ + const struct protoent *result; + +#if HAVE_DECL_GETPROTOBYNAME_R + struct protoent result_buf; + char buf[NETDB_BUFSIZE]; + int r; + + r = getprotobyname_r(name, + &result_buf, + buf, + sizeof(buf), + (struct protoent **) &result); + if (r != 0 || result != &result_buf) + result = NULL; +#else + result = getprotobyname(name); +#endif + + if (!result) + return -1; + + if (result->p_proto < 0 || result->p_proto > UINT8_MAX) + return -1; + return (uint8_t) result->p_proto; +} + +bool nft_getservbyport(int port, const char *proto, char *out_name, size_t name_len) +{ + const struct servent *result; + +#if HAVE_DECL_GETSERVBYPORT_R + struct servent result_buf; + char buf[NETDB_BUFSIZE]; + int r; + + r = getservbyport_r(port, + proto, + &result_buf, + buf, + sizeof(buf), + (struct servent**) &result); + if (r != 0 || result != &result_buf) + result = NULL; +#else + result = getservbyport(port, proto); +#endif + + if (!result) + return false; + + if (strlen(result->s_name) >= name_len) + return false; + strcpy(out_name, result->s_name); + return true; +} diff --git a/src/nftutils.h b/src/nftutils.h new file mode 100644 index 00000000..7db56f42 --- /dev/null +++ b/src/nftutils.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef NFTUTILS_H +#define NFTUTILS_H + +#include <stddef.h> + +/* The maximum buffer size for (struct protoent).p_name. It is excessively large, + * while still reasonably fitting on the stack. Arbitrarily chosen. */ +#define NFT_PROTONAME_MAXSIZE 1024 + +bool nft_getprotobynumber(int number, char *out_name, size_t name_len); +int nft_getprotobyname(const char *name); + +/* The maximum buffer size for (struct servent).s_name. It is excessively large, + * while still reasonably fitting on the stack. Arbitrarily chosen. */ +#define NFT_SERVNAME_MAXSIZE 1024 + +bool nft_getservbyport(int port, const char *proto, char *out_name, size_t name_len); + +#endif /* NFTUTILS_H */ diff --git a/src/numgen.c b/src/numgen.c index ea2b2626..3029fa58 100644 --- a/src/numgen.c +++ b/src/numgen.c @@ -4,10 +4,12 @@ * Copyright (c) 2016 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 as - * published by the Free Software Foundation. + * it under the terms of the GNU General Public License version 2 (or any + * later) as published by the Free Software Foundation. */ +#include <nft.h> + #include <nftables.h> #include <expression.h> #include <datatype.h> diff --git a/src/optimize.c b/src/optimize.c new file mode 100644 index 00000000..40756cec --- /dev/null +++ b/src/optimize.c @@ -0,0 +1,1504 @@ +/* + * Copyright (c) 2021 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 PET Fund established by NLnet (https://nlnet.nl) + * with support from the European Commission's Next Generation Internet + * programme. + */ + +#include <nft.h> + +#include <errno.h> +#include <inttypes.h> +#include <nftables.h> +#include <parser.h> +#include <expression.h> +#include <statement.h> +#include <utils.h> +#include <erec.h> +#include <linux/netfilter.h> + +#define MAX_STMTS 32 + +struct optimize_ctx { + struct stmt *stmt[MAX_STMTS]; + uint32_t num_stmts; + + struct stmt ***stmt_matrix; + struct rule **rule; + uint32_t num_rules; +}; + +static bool __expr_cmp(const struct expr *expr_a, const struct expr *expr_b) +{ + if (expr_a->etype != expr_b->etype) + return false; + if (expr_a->len != expr_b->len) + return false; + + switch (expr_a->etype) { + case EXPR_PAYLOAD: + if (expr_a->payload.base != expr_b->payload.base) + return false; + if (expr_a->payload.offset != expr_b->payload.offset) + return false; + if (expr_a->payload.desc != expr_b->payload.desc) + return false; + if (expr_a->payload.inner_desc != expr_b->payload.inner_desc) + return false; + if (expr_a->payload.tmpl != expr_b->payload.tmpl) + return false; + break; + case EXPR_EXTHDR: + if (expr_a->exthdr.desc != expr_b->exthdr.desc) + return false; + if (expr_a->exthdr.tmpl != expr_b->exthdr.tmpl) + return false; + break; + case EXPR_META: + if (expr_a->meta.key != expr_b->meta.key) + return false; + if (expr_a->meta.base != expr_b->meta.base) + return false; + if (expr_a->meta.inner_desc != expr_b->meta.inner_desc) + return false; + break; + case EXPR_CT: + if (expr_a->ct.key != expr_b->ct.key) + return false; + if (expr_a->ct.base != expr_b->ct.base) + return false; + if (expr_a->ct.direction != expr_b->ct.direction) + return false; + if (expr_a->ct.nfproto != expr_b->ct.nfproto) + return false; + break; + case EXPR_RT: + if (expr_a->rt.key != expr_b->rt.key) + return false; + break; + case EXPR_SOCKET: + if (expr_a->socket.key != expr_b->socket.key) + return false; + if (expr_a->socket.level != expr_b->socket.level) + return false; + break; + case EXPR_OSF: + if (expr_a->osf.ttl != expr_b->osf.ttl) + return false; + if (expr_a->osf.flags != expr_b->osf.flags) + return false; + break; + case EXPR_XFRM: + if (expr_a->xfrm.key != expr_b->xfrm.key) + return false; + if (expr_a->xfrm.direction != expr_b->xfrm.direction) + return false; + break; + case EXPR_FIB: + if (expr_a->fib.flags != expr_b->fib.flags) + return false; + if (expr_a->fib.result != expr_b->fib.result) + return false; + break; + case EXPR_NUMGEN: + if (expr_a->numgen.type != expr_b->numgen.type) + return false; + if (expr_a->numgen.mod != expr_b->numgen.mod) + return false; + if (expr_a->numgen.offset != expr_b->numgen.offset) + return false; + break; + case EXPR_HASH: + if (expr_a->hash.mod != expr_b->hash.mod) + return false; + if (expr_a->hash.seed_set != expr_b->hash.seed_set) + return false; + if (expr_a->hash.seed != expr_b->hash.seed) + return false; + if (expr_a->hash.offset != expr_b->hash.offset) + return false; + if (expr_a->hash.type != expr_b->hash.type) + return false; + break; + case EXPR_BINOP: + if (!__expr_cmp(expr_a->left, expr_b->left)) + return false; + + return __expr_cmp(expr_a->right, expr_b->right); + case EXPR_SYMBOL: + if (expr_a->symtype != expr_b->symtype) + return false; + if (expr_a->symtype != SYMBOL_VALUE) + return false; + + return !strcmp(expr_a->identifier, expr_b->identifier); + case EXPR_VALUE: + return !mpz_cmp(expr_a->value, expr_b->value); + default: + return false; + } + + return true; +} + +static bool is_bitmask(const struct expr *expr) +{ + switch (expr->etype) { + case EXPR_BINOP: + if (expr->op == OP_OR && + !is_bitmask(expr->left)) + return false; + + return is_bitmask(expr->right); + case EXPR_VALUE: + case EXPR_SYMBOL: + return true; + default: + break; + } + + return false; +} + +static bool stmt_expr_supported(const struct expr *expr) +{ + switch (expr->right->etype) { + case EXPR_SYMBOL: + case EXPR_RANGE_SYMBOL: + case EXPR_RANGE: + case EXPR_RANGE_VALUE: + case EXPR_PREFIX: + case EXPR_SET: + case EXPR_LIST: + case EXPR_VALUE: + return true; + case EXPR_BINOP: + if (is_bitmask(expr->right)) + return true; + break; + default: + break; + } + + return false; +} + +static bool expr_symbol_set(const struct expr *expr) +{ + return expr->right->etype == EXPR_SYMBOL && + expr->right->symtype == SYMBOL_SET; +} + +static bool __stmt_type_eq(const struct stmt *stmt_a, const struct stmt *stmt_b, + bool fully_compare) +{ + struct expr *expr_a, *expr_b; + + if (stmt_a->type != stmt_b->type) + return false; + + switch (stmt_a->type) { + case STMT_EXPRESSION: + expr_a = stmt_a->expr; + expr_b = stmt_b->expr; + + if (expr_a->op != expr_b->op) + return false; + if (expr_a->op != OP_IMPLICIT && expr_a->op != OP_EQ) + return false; + + if (fully_compare) { + if (!stmt_expr_supported(expr_a) || + !stmt_expr_supported(expr_b)) + return false; + + if (expr_symbol_set(expr_a) || + expr_symbol_set(expr_b)) + return false; + } + + return __expr_cmp(expr_a->left, expr_b->left); + case STMT_COUNTER: + case STMT_NOTRACK: + break; + case STMT_VERDICT: + if (!fully_compare) + break; + + expr_a = stmt_a->expr; + expr_b = stmt_b->expr; + + if (expr_a->etype != expr_b->etype) + return false; + + if (expr_a->etype == EXPR_MAP && + expr_b->etype == EXPR_MAP) + return __expr_cmp(expr_a->map, expr_b->map); + break; + case STMT_LOG: + if (stmt_a->log.snaplen != stmt_b->log.snaplen || + stmt_a->log.group != stmt_b->log.group || + stmt_a->log.qthreshold != stmt_b->log.qthreshold || + stmt_a->log.level != stmt_b->log.level || + stmt_a->log.logflags != stmt_b->log.logflags || + stmt_a->log.flags != stmt_b->log.flags) + return false; + + if (!!stmt_a->log.prefix ^ !!stmt_b->log.prefix) + return false; + + if (!stmt_a->log.prefix) + return true; + + if (strcmp(stmt_a->log.prefix, stmt_b->log.prefix)) + return false; + break; + case STMT_REJECT: + if (stmt_a->reject.family != stmt_b->reject.family || + stmt_a->reject.type != stmt_b->reject.type || + stmt_a->reject.icmp_code != stmt_b->reject.icmp_code) + return false; + + if (!!stmt_a->reject.expr ^ !!stmt_b->reject.expr) + return false; + + if (!stmt_a->reject.expr) + return true; + + if (!__expr_cmp(stmt_a->reject.expr, stmt_b->reject.expr)) + return false; + break; + case STMT_NAT: + if (stmt_a->nat.type != stmt_b->nat.type || + stmt_a->nat.flags != stmt_b->nat.flags || + stmt_a->nat.family != stmt_b->nat.family || + stmt_a->nat.type_flags != stmt_b->nat.type_flags) + return false; + + switch (stmt_a->nat.type) { + case NFT_NAT_SNAT: + case NFT_NAT_DNAT: + if ((stmt_a->nat.addr && + stmt_a->nat.addr->etype != EXPR_SYMBOL && + stmt_a->nat.addr->etype != EXPR_RANGE) || + (stmt_b->nat.addr && + stmt_b->nat.addr->etype != EXPR_SYMBOL && + stmt_b->nat.addr->etype != EXPR_RANGE) || + (stmt_a->nat.proto && + stmt_a->nat.proto->etype != EXPR_SYMBOL && + stmt_a->nat.proto->etype != EXPR_RANGE) || + (stmt_b->nat.proto && + stmt_b->nat.proto->etype != EXPR_SYMBOL && + stmt_b->nat.proto->etype != EXPR_RANGE)) + return false; + break; + case NFT_NAT_MASQ: + break; + case NFT_NAT_REDIR: + if ((stmt_a->nat.proto && + stmt_a->nat.proto->etype != EXPR_SYMBOL && + stmt_a->nat.proto->etype != EXPR_RANGE) || + (stmt_b->nat.proto && + stmt_b->nat.proto->etype != EXPR_SYMBOL && + stmt_b->nat.proto->etype != EXPR_RANGE)) + return false; + + /* it should be possible to infer implicit redirections + * such as: + * + * tcp dport 1234 redirect + * tcp dport 3456 redirect to :7890 + * merge: + * redirect to tcp dport map { 1234 : 1234, 3456 : 7890 } + * + * currently not implemented. + */ + if (fully_compare && + stmt_a->nat.type == NFT_NAT_REDIR && + stmt_b->nat.type == NFT_NAT_REDIR && + (!!stmt_a->nat.proto ^ !!stmt_b->nat.proto)) + return false; + + break; + default: + assert(0); + } + + return true; + default: + /* ... Merging anything else is yet unsupported. */ + return false; + } + + return true; +} + +static bool expr_verdict_eq(const struct expr *expr_a, const struct expr *expr_b) +{ + if (expr_a->verdict != expr_b->verdict) + return false; + if (expr_a->chain && expr_b->chain) { + if (expr_a->chain->etype != expr_b->chain->etype) + return false; + if (expr_a->chain->etype == EXPR_VALUE && + strcmp(expr_a->chain->identifier, expr_b->chain->identifier)) + return false; + } else if (expr_a->chain || expr_b->chain) { + return false; + } + + return true; +} + +static bool stmt_verdict_eq(const struct stmt *stmt_a, const struct stmt *stmt_b) +{ + struct expr *expr_a, *expr_b; + + assert (stmt_a->type == STMT_VERDICT); + + expr_a = stmt_a->expr; + expr_b = stmt_b->expr; + if (expr_a->etype == EXPR_VERDICT && + expr_b->etype == EXPR_VERDICT) + return expr_verdict_eq(expr_a, expr_b); + + if (expr_a->etype == EXPR_MAP && + expr_b->etype == EXPR_MAP) + return __expr_cmp(expr_a->map, expr_b->map); + + return false; +} + +static bool stmt_type_find(struct optimize_ctx *ctx, const struct stmt *stmt) +{ + bool unsupported_exists = false; + uint32_t i; + + for (i = 0; i < ctx->num_stmts; i++) { + if (ctx->stmt[i]->type == STMT_INVALID) + unsupported_exists = true; + + if (__stmt_type_eq(stmt, ctx->stmt[i], false)) + return true; + } + + switch (stmt->type) { + case STMT_EXPRESSION: + case STMT_VERDICT: + case STMT_COUNTER: + case STMT_NOTRACK: + case STMT_LOG: + case STMT_NAT: + case STMT_REJECT: + break; + default: + /* add unsupported statement only once to statement matrix. */ + if (unsupported_exists) + return true; + break; + } + + return false; +} + +static int rule_collect_stmts(struct optimize_ctx *ctx, struct rule *rule) +{ + const struct stmt_ops *ops; + struct stmt *stmt, *clone; + + list_for_each_entry(stmt, &rule->stmts, list) { + if (stmt_type_find(ctx, stmt)) + continue; + + /* No refcounter available in statement objects, clone it to + * to store in the array of selectors. + */ + ops = stmt_ops(stmt); + clone = stmt_alloc(&internal_location, ops); + switch (stmt->type) { + case STMT_EXPRESSION: + if (stmt->expr->op != OP_IMPLICIT && + stmt->expr->op != OP_EQ) { + clone->type = STMT_INVALID; + break; + } + if (stmt->expr->left->etype == EXPR_CONCAT) { + clone->type = STMT_INVALID; + break; + } + /* fall-through */ + case STMT_VERDICT: + clone->expr = expr_get(stmt->expr); + break; + case STMT_COUNTER: + case STMT_NOTRACK: + break; + case STMT_LOG: + memcpy(&clone->log, &stmt->log, sizeof(clone->log)); + if (stmt->log.prefix) + clone->log.prefix = xstrdup(stmt->log.prefix); + break; + case STMT_NAT: + if ((stmt->nat.addr && + (stmt->nat.addr->etype == EXPR_MAP || + stmt->nat.addr->etype == EXPR_VARIABLE)) || + (stmt->nat.proto && + (stmt->nat.proto->etype == EXPR_MAP || + stmt->nat.proto->etype == EXPR_VARIABLE))) { + clone->type = STMT_INVALID; + break; + } + clone->nat.type = stmt->nat.type; + clone->nat.family = stmt->nat.family; + if (stmt->nat.addr) + clone->nat.addr = expr_clone(stmt->nat.addr); + if (stmt->nat.proto) + clone->nat.proto = expr_clone(stmt->nat.proto); + clone->nat.flags = stmt->nat.flags; + clone->nat.type_flags = stmt->nat.type_flags; + break; + case STMT_REJECT: + if (stmt->reject.expr) + clone->reject.expr = expr_get(stmt->reject.expr); + clone->reject.type = stmt->reject.type; + clone->reject.icmp_code = stmt->reject.icmp_code; + clone->reject.family = stmt->reject.family; + break; + default: + clone->type = STMT_INVALID; + break; + } + + ctx->stmt[ctx->num_stmts++] = clone; + if (ctx->num_stmts >= MAX_STMTS) + return -1; + } + + return 0; +} + +static int unsupported_in_stmt_matrix(const struct optimize_ctx *ctx) +{ + uint32_t i; + + for (i = 0; i < ctx->num_stmts; i++) { + if (ctx->stmt[i]->type == STMT_INVALID) + return i; + } + /* this should not happen. */ + return -1; +} + +static int cmd_stmt_find_in_stmt_matrix(struct optimize_ctx *ctx, struct stmt *stmt) +{ + uint32_t i; + + for (i = 0; i < ctx->num_stmts; i++) { + if (__stmt_type_eq(stmt, ctx->stmt[i], false)) + return i; + } + + return -1; +} + +static struct stmt unsupported_stmt = { + .type = STMT_INVALID, +}; + +static void rule_build_stmt_matrix_stmts(struct optimize_ctx *ctx, + struct rule *rule, uint32_t *i) +{ + struct stmt *stmt; + int k; + + list_for_each_entry(stmt, &rule->stmts, list) { + k = cmd_stmt_find_in_stmt_matrix(ctx, stmt); + if (k < 0) { + k = unsupported_in_stmt_matrix(ctx); + assert(k >= 0); + ctx->stmt_matrix[*i][k] = &unsupported_stmt; + continue; + } + ctx->stmt_matrix[*i][k] = stmt; + } + ctx->rule[(*i)++] = rule; +} + +static int stmt_verdict_find(const struct optimize_ctx *ctx) +{ + uint32_t i; + + for (i = 0; i < ctx->num_stmts; i++) { + if (ctx->stmt[i]->type != STMT_VERDICT) + continue; + + return i; + } + + return -1; +} + +struct merge { + /* interval of rules to be merged */ + uint32_t rule_from; + uint32_t num_rules; + /* statements to be merged (index relative to statement matrix) */ + uint32_t stmt[MAX_STMTS]; + uint32_t num_stmts; + /* merge has been invalidated */ + bool skip; +}; + +static void merge_expr_stmts(const struct optimize_ctx *ctx, + uint32_t from, uint32_t to, + const struct merge *merge, + struct stmt *stmt_a) +{ + struct expr *expr_a, *expr_b, *set, *elem; + struct stmt *stmt_b; + uint32_t i; + + set = set_expr_alloc(&internal_location, NULL); + expr_set(set)->set_flags |= NFT_SET_ANONYMOUS; + + expr_a = stmt_a->expr->right; + elem = set_elem_expr_alloc(&internal_location, expr_get(expr_a)); + compound_expr_add(set, elem); + + for (i = from + 1; i <= to; i++) { + stmt_b = ctx->stmt_matrix[i][merge->stmt[0]]; + expr_b = stmt_b->expr->right; + elem = set_elem_expr_alloc(&internal_location, expr_get(expr_b)); + compound_expr_add(set, elem); + } + + expr_free(stmt_a->expr->right); + stmt_a->expr->right = set; +} + +static void merge_vmap(const struct optimize_ctx *ctx, + struct stmt *stmt_a, const struct stmt *stmt_b) +{ + struct expr *mappings, *mapping, *expr; + + mappings = stmt_b->expr->mappings; + list_for_each_entry(expr, &expr_set(mappings)->expressions, list) { + mapping = expr_clone(expr); + compound_expr_add(stmt_a->expr->mappings, mapping); + } +} + +static void merge_verdict_stmts(const struct optimize_ctx *ctx, + uint32_t from, uint32_t to, + const struct merge *merge, + struct stmt *stmt_a) +{ + struct stmt *stmt_b; + uint32_t i; + + for (i = from + 1; i <= to; i++) { + stmt_b = ctx->stmt_matrix[i][merge->stmt[0]]; + switch (stmt_b->type) { + case STMT_VERDICT: + switch (stmt_b->expr->etype) { + case EXPR_MAP: + merge_vmap(ctx, stmt_a, stmt_b); + break; + default: + assert(0); + } + break; + default: + assert(0); + break; + } + } +} + +static void merge_stmts(const struct optimize_ctx *ctx, + uint32_t from, uint32_t to, const struct merge *merge) +{ + struct stmt *stmt_a = ctx->stmt_matrix[from][merge->stmt[0]]; + + switch (stmt_a->type) { + case STMT_EXPRESSION: + merge_expr_stmts(ctx, from, to, merge, stmt_a); + break; + case STMT_VERDICT: + merge_verdict_stmts(ctx, from, to, merge, stmt_a); + break; + default: + assert(0); + } +} + +static void __merge_concat(const struct optimize_ctx *ctx, uint32_t i, + const struct merge *merge, struct list_head *concat_list) +{ + struct expr *concat, *next, *expr, *concat_clone, *clone; + LIST_HEAD(pending_list); + struct stmt *stmt_a; + uint32_t k; + + concat = concat_expr_alloc(&internal_location); + list_add(&concat->list, concat_list); + + for (k = 0; k < merge->num_stmts; k++) { + list_for_each_entry_safe(concat, next, concat_list, list) { + stmt_a = ctx->stmt_matrix[i][merge->stmt[k]]; + switch (stmt_a->expr->right->etype) { + case EXPR_SET: + list_for_each_entry(expr, &expr_set(stmt_a->expr->right)->expressions, list) { + concat_clone = expr_clone(concat); + clone = expr_clone(expr->key); + compound_expr_add(concat_clone, clone); + list_add_tail(&concat_clone->list, &pending_list); + } + list_del(&concat->list); + expr_free(concat); + break; + case EXPR_SYMBOL: + case EXPR_VALUE: + case EXPR_PREFIX: + case EXPR_RANGE_SYMBOL: + case EXPR_RANGE: + case EXPR_RANGE_VALUE: + clone = expr_clone(stmt_a->expr->right); + compound_expr_add(concat, clone); + break; + case EXPR_LIST: + list_for_each_entry(expr, &expr_list(stmt_a->expr->right)->expressions, list) { + concat_clone = expr_clone(concat); + clone = expr_clone(expr); + compound_expr_add(concat_clone, clone); + list_add_tail(&concat_clone->list, &pending_list); + } + list_del(&concat->list); + expr_free(concat); + break; + default: + assert(0); + break; + } + } + list_splice_init(&pending_list, concat_list); + } +} + +static void __merge_concat_stmts(const struct optimize_ctx *ctx, uint32_t i, + const struct merge *merge, struct expr *set) +{ + struct expr *concat, *next, *elem; + LIST_HEAD(concat_list); + + __merge_concat(ctx, i, merge, &concat_list); + + list_for_each_entry_safe(concat, next, &concat_list, list) { + list_del(&concat->list); + elem = set_elem_expr_alloc(&internal_location, concat); + compound_expr_add(set, elem); + } +} + +static void merge_concat_stmts(const struct optimize_ctx *ctx, + uint32_t from, uint32_t to, + const struct merge *merge) +{ + struct stmt *stmt, *stmt_a; + struct expr *concat, *set; + uint32_t i, k; + + stmt = ctx->stmt_matrix[from][merge->stmt[0]]; + /* build concatenation of selectors, eg. ifname . ip daddr . tcp dport */ + concat = concat_expr_alloc(&internal_location); + + for (k = 0; k < merge->num_stmts; k++) { + stmt_a = ctx->stmt_matrix[from][merge->stmt[k]]; + compound_expr_add(concat, expr_get(stmt_a->expr->left)); + } + expr_free(stmt->expr->left); + stmt->expr->left = concat; + + /* build set data contenation, eg. { eth0 . 1.1.1.1 . 22 } */ + set = set_expr_alloc(&internal_location, NULL); + expr_set(set)->set_flags |= NFT_SET_ANONYMOUS; + + for (i = from; i <= to; i++) + __merge_concat_stmts(ctx, i, merge, set); + + expr_free(stmt->expr->right); + stmt->expr->right = set; + + for (k = 1; k < merge->num_stmts; k++) { + stmt_a = ctx->stmt_matrix[from][merge->stmt[k]]; + list_del(&stmt_a->list); + stmt_free(stmt_a); + } +} + +static void build_verdict_map(struct expr *expr, struct stmt *verdict, + struct expr *set, struct stmt *counter) +{ + struct expr *item, *elem, *mapping; + struct stmt *counter_elem; + + switch (expr->etype) { + case EXPR_LIST: + list_for_each_entry(item, &expr_list(expr)->expressions, list) { + elem = set_elem_expr_alloc(&internal_location, expr_get(item)); + if (counter) { + counter_elem = counter_stmt_alloc(&counter->location); + list_add_tail(&counter_elem->list, &elem->stmt_list); + } + + mapping = mapping_expr_alloc(&internal_location, elem, + expr_get(verdict->expr)); + compound_expr_add(set, mapping); + } + stmt_free(counter); + break; + case EXPR_SET: + list_for_each_entry(item, &expr_set(expr)->expressions, list) { + elem = set_elem_expr_alloc(&internal_location, expr_get(item->key)); + if (counter) { + counter_elem = counter_stmt_alloc(&counter->location); + list_add_tail(&counter_elem->list, &elem->stmt_list); + } + + mapping = mapping_expr_alloc(&internal_location, elem, + expr_get(verdict->expr)); + compound_expr_add(set, mapping); + } + stmt_free(counter); + break; + case EXPR_PREFIX: + case EXPR_RANGE_SYMBOL: + case EXPR_RANGE: + case EXPR_RANGE_VALUE: + case EXPR_VALUE: + case EXPR_SYMBOL: + case EXPR_CONCAT: + elem = set_elem_expr_alloc(&internal_location, expr_get(expr)); + if (counter) + list_add_tail(&counter->list, &elem->stmt_list); + + mapping = mapping_expr_alloc(&internal_location, elem, + expr_get(verdict->expr)); + compound_expr_add(set, mapping); + break; + default: + assert(0); + break; + } +} + +static void remove_counter(const struct optimize_ctx *ctx, uint32_t from) +{ + struct stmt *stmt; + uint32_t i; + + /* remove counter statement */ + for (i = 0; i < ctx->num_stmts; i++) { + stmt = ctx->stmt_matrix[from][i]; + if (!stmt) + continue; + + if (stmt->type == STMT_COUNTER) { + list_del(&stmt->list); + stmt_free(stmt); + } + } +} + +static struct stmt *zap_counter(const struct optimize_ctx *ctx, uint32_t from) +{ + struct stmt *stmt; + uint32_t i; + + /* remove counter statement */ + for (i = 0; i < ctx->num_stmts; i++) { + stmt = ctx->stmt_matrix[from][i]; + if (!stmt) + continue; + + if (stmt->type == STMT_COUNTER) { + list_del(&stmt->list); + return stmt; + } + } + + return NULL; +} + +static void merge_stmts_vmap(const struct optimize_ctx *ctx, + uint32_t from, uint32_t to, + const struct merge *merge) +{ + struct stmt *stmt_a = ctx->stmt_matrix[from][merge->stmt[0]]; + struct stmt *stmt_b, *verdict_a, *verdict_b, *stmt; + struct expr *expr_a, *expr_b, *expr, *left, *set; + struct stmt *counter; + uint32_t i; + int k; + + k = stmt_verdict_find(ctx); + assert(k >= 0); + + set = set_expr_alloc(&internal_location, NULL); + expr_set(set)->set_flags |= NFT_SET_ANONYMOUS; + + expr_a = stmt_a->expr->right; + verdict_a = ctx->stmt_matrix[from][k]; + counter = zap_counter(ctx, from); + build_verdict_map(expr_a, verdict_a, set, counter); + + for (i = from + 1; i <= to; i++) { + stmt_b = ctx->stmt_matrix[i][merge->stmt[0]]; + expr_b = stmt_b->expr->right; + verdict_b = ctx->stmt_matrix[i][k]; + counter = zap_counter(ctx, i); + build_verdict_map(expr_b, verdict_b, set, counter); + } + + left = expr_get(stmt_a->expr->left); + expr = map_expr_alloc(&internal_location, left, set); + stmt = verdict_stmt_alloc(&internal_location, expr); + + list_add(&stmt->list, &stmt_a->list); + list_del(&stmt_a->list); + stmt_free(stmt_a); + list_del(&verdict_a->list); + stmt_free(verdict_a); +} + +static void __merge_concat_stmts_vmap(const struct optimize_ctx *ctx, + uint32_t i, const struct merge *merge, + struct expr *set, struct stmt *verdict) +{ + struct expr *concat, *next, *elem, *mapping; + struct stmt *counter, *counter_elem; + LIST_HEAD(concat_list); + + counter = zap_counter(ctx, i); + __merge_concat(ctx, i, merge, &concat_list); + + list_for_each_entry_safe(concat, next, &concat_list, list) { + list_del(&concat->list); + elem = set_elem_expr_alloc(&internal_location, concat); + if (counter) { + counter_elem = counter_stmt_alloc(&counter->location); + list_add_tail(&counter_elem->list, &elem->stmt_list); + } + + mapping = mapping_expr_alloc(&internal_location, elem, + expr_get(verdict->expr)); + compound_expr_add(set, mapping); + } + stmt_free(counter); +} + +static void merge_concat_stmts_vmap(const struct optimize_ctx *ctx, + uint32_t from, uint32_t to, + const struct merge *merge) +{ + struct stmt *orig_stmt = ctx->stmt_matrix[from][merge->stmt[0]]; + struct stmt *stmt, *stmt_a, *verdict; + struct expr *concat_a, *expr, *set; + uint32_t i; + int k; + + k = stmt_verdict_find(ctx); + assert(k >= 0); + + /* build concatenation of selectors, eg. ifname . ip daddr . tcp dport */ + concat_a = concat_expr_alloc(&internal_location); + for (i = 0; i < merge->num_stmts; i++) { + stmt_a = ctx->stmt_matrix[from][merge->stmt[i]]; + compound_expr_add(concat_a, expr_get(stmt_a->expr->left)); + } + + /* build set data contenation, eg. { eth0 . 1.1.1.1 . 22 : accept } */ + set = set_expr_alloc(&internal_location, NULL); + expr_set(set)->set_flags |= NFT_SET_ANONYMOUS; + + for (i = from; i <= to; i++) { + verdict = ctx->stmt_matrix[i][k]; + __merge_concat_stmts_vmap(ctx, i, merge, set, verdict); + } + + expr = map_expr_alloc(&internal_location, concat_a, set); + stmt = verdict_stmt_alloc(&internal_location, expr); + + list_add(&stmt->list, &orig_stmt->list); + list_del(&orig_stmt->list); + stmt_free(orig_stmt); + + for (i = 1; i < merge->num_stmts; i++) { + stmt_a = ctx->stmt_matrix[from][merge->stmt[i]]; + list_del(&stmt_a->list); + stmt_free(stmt_a); + } + + verdict = ctx->stmt_matrix[from][k]; + list_del(&verdict->list); + stmt_free(verdict); +} + +static bool stmt_verdict_cmp(const struct optimize_ctx *ctx, + uint32_t from, uint32_t to) +{ + struct stmt *stmt_a, *stmt_b; + uint32_t i; + int k; + + k = stmt_verdict_find(ctx); + if (k < 0) + return true; + + for (i = from; i + 1 <= to; i++) { + stmt_a = ctx->stmt_matrix[i][k]; + stmt_b = ctx->stmt_matrix[i + 1][k]; + if (!stmt_a && !stmt_b) + continue; + if (!stmt_a || !stmt_b) + return false; + if (!stmt_verdict_eq(stmt_a, stmt_b)) + return false; + } + + return true; +} + +static int stmt_nat_type(const struct optimize_ctx *ctx, int from, + enum nft_nat_etypes *nat_type) +{ + uint32_t j; + + for (j = 0; j < ctx->num_stmts; j++) { + if (!ctx->stmt_matrix[from][j]) + continue; + + if (ctx->stmt_matrix[from][j]->type == STMT_NAT) { + *nat_type = ctx->stmt_matrix[from][j]->nat.type; + return 0; + } + } + + return -1; +} + +static int stmt_nat_find(const struct optimize_ctx *ctx, int from) +{ + enum nft_nat_etypes nat_type; + uint32_t i; + + if (stmt_nat_type(ctx, from, &nat_type) < 0) + return -1; + + for (i = 0; i < ctx->num_stmts; i++) { + if (ctx->stmt[i]->type != STMT_NAT || + ctx->stmt[i]->nat.type != nat_type) + continue; + + return i; + } + + return -1; +} + +static struct expr *stmt_nat_expr(struct stmt *nat_stmt) +{ + struct expr *nat_expr; + + assert(nat_stmt->type == STMT_NAT); + + if (nat_stmt->nat.proto) { + if (nat_stmt->nat.addr) { + nat_expr = concat_expr_alloc(&internal_location); + compound_expr_add(nat_expr, expr_get(nat_stmt->nat.addr)); + compound_expr_add(nat_expr, expr_get(nat_stmt->nat.proto)); + } else { + nat_expr = expr_get(nat_stmt->nat.proto); + } + expr_free(nat_stmt->nat.proto); + nat_stmt->nat.proto = NULL; + } else { + nat_expr = expr_get(nat_stmt->nat.addr); + } + + assert(nat_expr); + + return nat_expr; +} + +static void merge_nat(const struct optimize_ctx *ctx, + uint32_t from, uint32_t to, + const struct merge *merge) +{ + struct expr *expr, *set, *elem, *nat_expr, *mapping, *left; + int k, family = NFPROTO_UNSPEC; + struct stmt *stmt, *nat_stmt; + uint32_t i; + + k = stmt_nat_find(ctx, from); + assert(k >= 0); + + set = set_expr_alloc(&internal_location, NULL); + expr_set(set)->set_flags |= NFT_SET_ANONYMOUS; + + for (i = from; i <= to; i++) { + stmt = ctx->stmt_matrix[i][merge->stmt[0]]; + expr = stmt->expr->right; + + nat_stmt = ctx->stmt_matrix[i][k]; + nat_expr = stmt_nat_expr(nat_stmt); + + elem = set_elem_expr_alloc(&internal_location, expr_get(expr)); + mapping = mapping_expr_alloc(&internal_location, elem, nat_expr); + compound_expr_add(set, mapping); + } + + stmt = ctx->stmt_matrix[from][merge->stmt[0]]; + left = expr_get(stmt->expr->left); + if (left->etype == EXPR_PAYLOAD) { + if (left->payload.desc == &proto_ip) + family = NFPROTO_IPV4; + else if (left->payload.desc == &proto_ip6) + family = NFPROTO_IPV6; + } + expr = map_expr_alloc(&internal_location, left, set); + + nat_stmt = ctx->stmt_matrix[from][k]; + if (nat_stmt->nat.family == NFPROTO_UNSPEC) + nat_stmt->nat.family = family; + + expr_free(nat_stmt->nat.addr); + if (nat_stmt->nat.type == NFT_NAT_REDIR) + nat_stmt->nat.proto = expr; + else + nat_stmt->nat.addr = expr; + + remove_counter(ctx, from); + list_del(&stmt->list); + stmt_free(stmt); +} + +static void merge_concat_nat(const struct optimize_ctx *ctx, + uint32_t from, uint32_t to, + const struct merge *merge) +{ + struct expr *expr, *set, *elem, *nat_expr, *mapping, *left, *concat; + int k, family = NFPROTO_UNSPEC; + struct stmt *stmt, *nat_stmt; + uint32_t i, j; + + k = stmt_nat_find(ctx, from); + assert(k >= 0); + + set = set_expr_alloc(&internal_location, NULL); + expr_set(set)->set_flags |= NFT_SET_ANONYMOUS; + + for (i = from; i <= to; i++) { + + concat = concat_expr_alloc(&internal_location); + for (j = 0; j < merge->num_stmts; j++) { + stmt = ctx->stmt_matrix[i][merge->stmt[j]]; + expr = stmt->expr->right; + compound_expr_add(concat, expr_get(expr)); + } + + nat_stmt = ctx->stmt_matrix[i][k]; + nat_expr = stmt_nat_expr(nat_stmt); + + elem = set_elem_expr_alloc(&internal_location, concat); + mapping = mapping_expr_alloc(&internal_location, elem, nat_expr); + compound_expr_add(set, mapping); + } + + concat = concat_expr_alloc(&internal_location); + for (j = 0; j < merge->num_stmts; j++) { + stmt = ctx->stmt_matrix[from][merge->stmt[j]]; + left = stmt->expr->left; + if (left->etype == EXPR_PAYLOAD) { + if (left->payload.desc == &proto_ip) + family = NFPROTO_IPV4; + else if (left->payload.desc == &proto_ip6) + family = NFPROTO_IPV6; + } + compound_expr_add(concat, expr_get(left)); + } + expr = map_expr_alloc(&internal_location, concat, set); + + nat_stmt = ctx->stmt_matrix[from][k]; + if (nat_stmt->nat.family == NFPROTO_UNSPEC) + nat_stmt->nat.family = family; + + expr_free(nat_stmt->nat.addr); + nat_stmt->nat.addr = expr; + + remove_counter(ctx, from); + for (j = 0; j < merge->num_stmts; j++) { + stmt = ctx->stmt_matrix[from][merge->stmt[j]]; + list_del(&stmt->list); + stmt_free(stmt); + } +} + +static void rule_optimize_print(struct output_ctx *octx, + const struct rule *rule) +{ + const struct location *loc = &rule->location; + const struct input_descriptor *indesc = loc->indesc; + const char *line = ""; + char buf[1024]; + + switch (indesc->type) { + case INDESC_BUFFER: + case INDESC_CLI: + line = indesc->data; + *strchrnul(line, '\n') = '\0'; + break; + case INDESC_STDIN: + line = indesc->data; + line += loc->line_offset; + *strchrnul(line, '\n') = '\0'; + break; + case INDESC_FILE: + line = line_location(indesc, loc, buf, sizeof(buf)); + break; + case INDESC_INTERNAL: + case INDESC_NETLINK: + break; + default: + BUG("invalid input descriptor type %u\n", indesc->type); + } + + print_location(octx->error_fp, indesc, loc); + fprintf(octx->error_fp, "%s\n", line); +} + +enum { + MERGE_BY_VERDICT, + MERGE_BY_NAT_MAP, + MERGE_BY_NAT, +}; + +static uint32_t merge_stmt_type(const struct optimize_ctx *ctx, + uint32_t from, uint32_t to) +{ + const struct stmt *stmt; + uint32_t i, j; + + for (i = from; i <= to; i++) { + for (j = 0; j < ctx->num_stmts; j++) { + stmt = ctx->stmt_matrix[i][j]; + if (!stmt) + continue; + if (stmt->type == STMT_NAT) { + if ((stmt->nat.type == NFT_NAT_REDIR && + !stmt->nat.proto) || + stmt->nat.type == NFT_NAT_MASQ) + return MERGE_BY_NAT; + + return MERGE_BY_NAT_MAP; + } + } + } + + /* merge by verdict, even if no verdict is specified. */ + return MERGE_BY_VERDICT; +} + +static void merge_rules(const struct optimize_ctx *ctx, + uint32_t from, uint32_t to, + const struct merge *merge, + struct output_ctx *octx) +{ + uint32_t merge_type; + bool same_verdict; + uint32_t i; + + merge_type = merge_stmt_type(ctx, from, to); + + switch (merge_type) { + case MERGE_BY_VERDICT: + same_verdict = stmt_verdict_cmp(ctx, from, to); + if (merge->num_stmts > 1) { + if (same_verdict) + merge_concat_stmts(ctx, from, to, merge); + else + merge_concat_stmts_vmap(ctx, from, to, merge); + } else { + if (same_verdict) + merge_stmts(ctx, from, to, merge); + else + merge_stmts_vmap(ctx, from, to, merge); + } + break; + case MERGE_BY_NAT_MAP: + if (merge->num_stmts > 1) + merge_concat_nat(ctx, from, to, merge); + else + merge_nat(ctx, from, to, merge); + break; + case MERGE_BY_NAT: + if (merge->num_stmts > 1) + merge_concat_stmts(ctx, from, to, merge); + else + merge_stmts(ctx, from, to, merge); + break; + default: + assert(0); + } + + if (ctx->rule[from]->comment) { + free_const(ctx->rule[from]->comment); + ctx->rule[from]->comment = NULL; + } + + octx->flags |= NFT_CTX_OUTPUT_STATELESS; + + fprintf(octx->error_fp, "Merging:\n"); + rule_optimize_print(octx, ctx->rule[from]); + + for (i = from + 1; i <= to; i++) { + rule_optimize_print(octx, ctx->rule[i]); + list_del(&ctx->rule[i]->list); + rule_free(ctx->rule[i]); + } + + fprintf(octx->error_fp, "into:\n\t"); + rule_print(ctx->rule[from], octx); + fprintf(octx->error_fp, "\n"); + + octx->flags &= ~NFT_CTX_OUTPUT_STATELESS; +} + +static bool stmt_type_eq(const struct stmt *stmt_a, const struct stmt *stmt_b) +{ + if (!stmt_a && !stmt_b) + return true; + else if (!stmt_a) + return false; + else if (!stmt_b) + return false; + + return __stmt_type_eq(stmt_a, stmt_b, true); +} + +static bool stmt_is_mergeable(const struct stmt *stmt) +{ + if (!stmt) + return false; + + switch (stmt->type) { + case STMT_VERDICT: + if (stmt->expr->etype == EXPR_MAP) + return true; + break; + case STMT_EXPRESSION: + case STMT_NAT: + return true; + default: + break; + } + + return false; +} + +static bool rules_eq(const struct optimize_ctx *ctx, int i, int j) +{ + uint32_t k, mergeable = 0; + + for (k = 0; k < ctx->num_stmts; k++) { + if (stmt_is_mergeable(ctx->stmt_matrix[i][k])) + mergeable++; + + if (!stmt_type_eq(ctx->stmt_matrix[i][k], ctx->stmt_matrix[j][k])) + return false; + } + + if (mergeable == 0) + return false; + + return true; +} + +static int chain_optimize(struct nft_ctx *nft, struct list_head *rules) +{ + struct optimize_ctx *ctx; + uint32_t num_merges = 0; + struct merge *merge; + uint32_t i, j, m, k; + struct rule *rule; + int ret; + + ctx = xzalloc(sizeof(*ctx)); + + /* Step 1: collect statements in rules */ + list_for_each_entry(rule, rules, list) { + ret = rule_collect_stmts(ctx, rule); + if (ret < 0) + goto err; + + ctx->num_rules++; + } + + ctx->rule = xzalloc(sizeof(*ctx->rule) * ctx->num_rules); + ctx->stmt_matrix = xzalloc(sizeof(*ctx->stmt_matrix) * ctx->num_rules); + for (i = 0; i < ctx->num_rules; i++) + ctx->stmt_matrix[i] = xzalloc_array(MAX_STMTS, + sizeof(**ctx->stmt_matrix)); + + merge = xzalloc(sizeof(*merge) * ctx->num_rules); + + /* Step 2: Build matrix of statements */ + i = 0; + list_for_each_entry(rule, rules, list) + rule_build_stmt_matrix_stmts(ctx, rule, &i); + + /* Step 3: Look for common selectors for possible rule mergers */ + for (i = 0; i < ctx->num_rules; i++) { + for (j = i + 1; j < ctx->num_rules; j++) { + if (!rules_eq(ctx, i, j)) { + if (merge[num_merges].num_rules > 0) + num_merges++; + + i = j - 1; + break; + } + if (merge[num_merges].num_rules > 0) { + merge[num_merges].num_rules++; + } else { + merge[num_merges].rule_from = i; + merge[num_merges].num_rules = 2; + } + } + if (j == ctx->num_rules && merge[num_merges].num_rules > 0) { + num_merges++; + break; + } + } + + /* Step 4: Invalidate merge in case of duplicated keys in set/map. */ + for (k = 0; k < num_merges; k++) { + uint32_t r1, r2; + + i = merge[k].rule_from; + + for (r1 = i; r1 < i + merge[k].num_rules; r1++) { + for (r2 = r1 + 1; r2 < i + merge[k].num_rules; r2++) { + bool match_same_value = true, match_seen = false; + + for (m = 0; m < ctx->num_stmts; m++) { + if (!ctx->stmt_matrix[r1][m]) + continue; + + switch (ctx->stmt_matrix[r1][m]->type) { + case STMT_EXPRESSION: + match_seen = true; + if (!__expr_cmp(ctx->stmt_matrix[r1][m]->expr->right, + ctx->stmt_matrix[r2][m]->expr->right)) + match_same_value = false; + break; + default: + break; + } + } + if (match_seen && match_same_value) + merge[k].skip = true; + } + } + } + + /* Step 5: Infer how to merge the candidate rules */ + for (k = 0; k < num_merges; k++) { + if (merge[k].skip) + continue; + + i = merge[k].rule_from; + + for (m = 0; m < ctx->num_stmts; m++) { + if (!ctx->stmt_matrix[i][m]) + continue; + switch (ctx->stmt_matrix[i][m]->type) { + case STMT_EXPRESSION: + merge[k].stmt[merge[k].num_stmts++] = m; + break; + case STMT_VERDICT: + if (ctx->stmt_matrix[i][m]->expr->etype == EXPR_MAP) + merge[k].stmt[merge[k].num_stmts++] = m; + break; + default: + break; + } + } + + j = merge[k].num_rules - 1; + merge_rules(ctx, i, i + j, &merge[k], &nft->output); + } + ret = 0; + for (i = 0; i < ctx->num_rules; i++) + free(ctx->stmt_matrix[i]); + + free(ctx->stmt_matrix); + free(merge); +err: + for (i = 0; i < ctx->num_stmts; i++) + stmt_free(ctx->stmt[i]); + + free(ctx->rule); + free(ctx); + + return ret; +} + +static int cmd_optimize(struct nft_ctx *nft, struct cmd *cmd) +{ + struct table *table; + struct chain *chain; + int ret = 0; + + switch (cmd->obj) { + case CMD_OBJ_TABLE: + table = cmd->table; + if (!table) + break; + + list_for_each_entry(chain, &table->chains, list) { + if (chain->flags & CHAIN_F_HW_OFFLOAD) + continue; + + chain_optimize(nft, &chain->rules); + } + break; + default: + break; + } + + return ret; +} + +int nft_optimize(struct nft_ctx *nft, struct list_head *cmds) +{ + struct cmd *cmd; + int ret = 0; + + list_for_each_entry(cmd, cmds, list) { + switch (cmd->op) { + case CMD_ADD: + ret = cmd_optimize(nft, cmd); + break; + default: + break; + } + } + + return ret; +} @@ -1,7 +1,16 @@ +/* + * Copyright (c) 2018 Fernando Fernandez Mancera <ffmancera@riseup.net> + * + * 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. + */ + +#include <nft.h> + #include <nftables.h> #include <expression.h> #include <utils.h> -#include <string.h> #include <osf.h> #include <json.h> diff --git a/src/owner.c b/src/owner.c new file mode 100644 index 00000000..65eaad3e --- /dev/null +++ b/src/owner.c @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2021 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. + */ + +#include <nft.h> + +#include <stdio.h> +#include <unistd.h> +#include <sys/time.h> +#include <time.h> +#include <inttypes.h> +#include <dirent.h> + +#include <netlink.h> +#include <owner.h> + +static char *pid2name(pid_t pid) +{ + char procname[256], *prog; + FILE *fp; + int ret; + + ret = snprintf(procname, sizeof(procname), "/proc/%lu/stat", (unsigned long)pid); + if (ret < 0 || ret > (int)sizeof(procname)) + return NULL; + + fp = fopen(procname, "r"); + if (!fp) + return NULL; + + ret = fscanf(fp, "%*u (%m[^)]", &prog); + + fclose(fp); + + if (ret == 1) + return prog; + + return NULL; +} + +static char *portid2name(pid_t pid, uint32_t portid, unsigned long inode) +{ + const struct dirent *ent; + char procname[256]; + DIR *dir; + int ret; + + ret = snprintf(procname, sizeof(procname), "/proc/%lu/fd/", (unsigned long)pid); + if (ret < 0 || ret >= (int)sizeof(procname)) + return NULL; + + dir = opendir(procname); + if (!dir) + return NULL; + + for (;;) { + unsigned long ino; + char tmp[128]; + ssize_t rl; + + ent = readdir(dir); + if (!ent) + break; + + if (ent->d_type != DT_LNK) + continue; + + ret = snprintf(procname, sizeof(procname), "/proc/%d/fd/%s", + pid, ent->d_name); + if (ret < 0 || ret >= (int)sizeof(procname)) + continue; + + rl = readlink(procname, tmp, sizeof(tmp)); + if (rl <= 0 || rl >= (ssize_t)sizeof(tmp)) + continue; + + tmp[rl] = 0; + + ret = sscanf(tmp, "socket:[%lu]", &ino); + if (ret == 1 && ino == inode) { + closedir(dir); + return pid2name(pid); + } + } + + closedir(dir); + return NULL; +} + +static char *name_by_portid(uint32_t portid, unsigned long inode) +{ + const struct dirent *ent; + char *prog; + DIR *dir; + + /* Many netlink users use their process ID to allocate the first port id. */ + prog = portid2name(portid, portid, inode); + if (prog) + return prog; + + /* no luck, search harder. */ + dir = opendir("/proc"); + if (!dir) + return NULL; + + for (;;) { + unsigned long pid; + char *end; + + ent = readdir(dir); + if (!ent) + break; + + if (ent->d_type != DT_DIR) + continue; + + pid = strtoul(ent->d_name, &end, 10); + if (pid <= 1 || *end) + continue; + + if (pid == portid) /* already tried */ + continue; + + prog = portid2name(pid, portid, inode); + if (prog) + break; + } + + closedir(dir); + return prog; +} + +char *get_progname(uint32_t portid) +{ + FILE *fp = fopen("/proc/net/netlink", "r"); + uint32_t portid_check; + unsigned long inode; + int ret, prot; + + if (!fp) + return NULL; + + for (;;) { + char line[256]; + + if (!fgets(line, sizeof(line), fp)) + break; + + ret = sscanf(line, "%*x %d %u %*x %*d %*d %*x %*d %*u %lu\n", + &prot, &portid_check, &inode); + + if (ret == EOF) + break; + + if (ret == 3 && portid_check == portid && prot == NETLINK_NETFILTER) { + static uint32_t last_portid; + static uint32_t last_inode; + static char *last_program; + char *prog; + + fclose(fp); + + if (last_portid == portid && last_inode == inode) + return last_program; + + prog = name_by_portid(portid, inode); + + free(last_program); + last_program = prog; + last_portid = portid; + last_inode = inode; + return prog; + } + } + + fclose(fp); + return NULL; +} diff --git a/src/parser_bison.y b/src/parser_bison.y index 799f7a30..5b84331f 100644 --- a/src/parser_bison.y +++ b/src/parser_bison.y @@ -9,11 +9,14 @@ */ %{ +#include <nft.h> +#include <ctype.h> #include <stddef.h> #include <stdio.h> #include <inttypes.h> #include <syslog.h> +#include <net/if.h> #include <netinet/ip.h> #include <netinet/tcp.h> #include <netinet/if_ether.h> @@ -32,12 +35,14 @@ #include <libnftnl/udata.h> #include <rule.h> +#include <cmd.h> #include <statement.h> #include <expression.h> #include <headers.h> #include <utils.h> #include <parser.h> #include <erec.h> +#include <sctp_chunk.h> #include "parser_bison.h" @@ -63,16 +68,26 @@ static struct scope *current_scope(const struct parser_state *state) return state->scopes[state->scope]; } -static void open_scope(struct parser_state *state, struct scope *scope) +static int open_scope(struct parser_state *state, struct scope *scope) { - assert(state->scope < array_size(state->scopes) - 1); + if (state->scope >= array_size(state->scopes) - 1) { + state->scope_err = true; + return -1; + } + scope_init(scope, current_scope(state)); state->scopes[++state->scope] = scope; + + return 0; } static void close_scope(struct parser_state *state) { - assert(state->scope > 0); + if (state->scope_err || state->scope == 0) { + state->scope_err = false; + return; + } + state->scope--; } @@ -87,17 +102,14 @@ static void location_update(struct location *loc, struct location *rhs, int n) { if (n) { loc->indesc = rhs[n].indesc; - loc->token_offset = rhs[1].token_offset; loc->line_offset = rhs[1].line_offset; loc->first_line = rhs[1].first_line; loc->first_column = rhs[1].first_column; - loc->last_line = rhs[n].last_line; loc->last_column = rhs[n].last_column; } else { loc->indesc = rhs[0].indesc; - loc->token_offset = rhs[0].token_offset; loc->line_offset = rhs[0].line_offset; - loc->first_line = loc->last_line = rhs[0].last_line; + loc->first_line = rhs[0].first_line; loc->first_column = loc->last_column = rhs[0].last_column; } } @@ -121,6 +133,62 @@ static struct expr *handle_concat_expr(const struct location *loc, return expr; } +static bool already_set(const void *attr, const struct location *loc, + struct parser_state *state) +{ + if (!attr) + return false; + + erec_queue(error(loc, "You can only specify this once. This statement is duplicated."), + state->msgs); + return true; +} + +static struct expr *ifname_expr_alloc(const struct location *location, + struct list_head *queue, + const char *name) +{ + size_t length = strlen(name); + struct expr *expr; + + if (length == 0) { + free_const(name); + erec_queue(error(location, "empty interface name"), queue); + return NULL; + } + + if (length >= IFNAMSIZ) { + free_const(name); + erec_queue(error(location, "interface name too long"), queue); + return NULL; + } + + expr = constant_expr_alloc(location, &ifname_type, BYTEORDER_HOST_ENDIAN, + length * BITS_PER_BYTE, name); + + free_const(name); + + return expr; +} + +static void timeout_state_free(struct timeout_state *s) +{ + free_const(s->timeout_str); + free(s); +} + +static void timeout_states_free(struct list_head *list) +{ + struct timeout_state *ts, *next; + + list_for_each_entry_safe(ts, next, list, head) { + list_del(&ts->head); + timeout_state_free(ts); + } + + free(list); +} + #define YYLLOC_DEFAULT(Current, Rhs, N) location_update(&Current, Rhs, N) #define symbol_value(loc, str) \ @@ -173,11 +241,17 @@ int nft_lex(void *, void *, void *); struct handle_spec handle_spec; struct position_spec position_spec; struct prio_spec prio_spec; - const struct exthdr_desc *exthdr_desc; + struct limit_rate limit_rate; + struct tcp_kind_field { + uint16_t kind; /* must allow > 255 for SACK1, 2.. hack */ + uint8_t field; + } tcp_kind_field; + struct timeout_state *timeout_state; } %token TOKEN_EOF 0 "end of file" %token JUNK "junk" +%token CRLF "CRLF line terminators" %token NEWLINE "newline" %token COLON "colon" @@ -210,9 +284,12 @@ int nft_lex(void *, void *, void *); %token UNDEFINE "undefine" %token FIB "fib" +%token CHECK "check" %token SOCKET "socket" %token TRANSPARENT "transparent" +%token WILDCARD "wildcard" +%token CGROUPV2 "cgroupv2" %token TPROXY "tproxy" @@ -221,11 +298,11 @@ int nft_lex(void *, void *, void *); %token SYNPROXY "synproxy" %token MSS "mss" %token WSCALE "wscale" -%token SACKPERM "sack-perm" %token TYPEOF "typeof" %token HOOK "hook" +%token HOOKS "hooks" %token DEVICE "device" %token DEVICES "devices" %token TABLE "table" @@ -261,6 +338,8 @@ int nft_lex(void *, void *, void *); %token DESCRIBE "describe" %token IMPORT "import" %token EXPORT "export" +%token DESTROY "destroy" + %token MONITOR "monitor" %token ALL "all" @@ -298,7 +377,7 @@ int nft_lex(void *, void *, void *); %token <string> STRING "string" %token <string> QUOTED_STRING "quoted string" %token <string> ASTERISK_STRING "string with a trailing asterisk" -%destructor { xfree($$); } STRING QUOTED_STRING ASTERISK_STRING +%destructor { free_const($$); } STRING QUOTED_STRING ASTERISK_STRING %token LL_HDR "ll" %token NETWORK_HDR "nh" @@ -314,6 +393,7 @@ int nft_lex(void *, void *, void *); %token VLAN "vlan" %token ID "id" %token CFI "cfi" +%token DEI "dei" %token PCP "pcp" %token ARP "arp" @@ -362,6 +442,7 @@ int nft_lex(void *, void *, void *); %token ICMP6 "icmpv6" %token PPTR "param-problem" %token MAXDELAY "max-delay" +%token TADDR "taddr" %token AH "ah" %token RESERVED "reserved" @@ -373,6 +454,7 @@ int nft_lex(void *, void *, void *); %token FLAGS "flags" %token CPI "cpi" +%token PORT "port" %token UDP "udp" %token SPORT "sport" %token DPORT "dport" @@ -387,25 +469,69 @@ int nft_lex(void *, void *, void *); %token OPTION "option" %token ECHO "echo" %token EOL "eol" -%token MAXSEG "maxseg" -%token NOOP "noop" +%token MPTCP "mptcp" +%token NOP "nop" %token SACK "sack" %token SACK0 "sack0" %token SACK1 "sack1" %token SACK2 "sack2" %token SACK3 "sack3" -%token SACK_PERMITTED "sack-permitted" +%token SACK_PERM "sack-permitted" +%token FASTOPEN "fastopen" +%token MD5SIG "md5sig" %token TIMESTAMP "timestamp" -%token KIND "kind" %token COUNT "count" %token LEFT "left" %token RIGHT "right" %token TSVAL "tsval" %token TSECR "tsecr" +%token SUBTYPE "subtype" %token DCCP "dccp" +%token VXLAN "vxlan" +%token VNI "vni" + +%token GRE "gre" +%token GRETAP "gretap" + +%token GENEVE "geneve" + %token SCTP "sctp" +%token CHUNK "chunk" +%token DATA "data" +%token INIT "init" +%token INIT_ACK "init-ack" +%token HEARTBEAT "heartbeat" +%token HEARTBEAT_ACK "heartbeat-ack" +%token ABORT "abort" +%token SHUTDOWN "shutdown" +%token SHUTDOWN_ACK "shutdown-ack" +%token ERROR "error" +%token COOKIE_ECHO "cookie-echo" +%token COOKIE_ACK "cookie-ack" +%token ECNE "ecne" +%token CWR "cwr" +%token SHUTDOWN_COMPLETE "shutdown-complete" +%token ASCONF_ACK "asconf-ack" +%token FORWARD_TSN "forward-tsn" +%token ASCONF "asconf" +%token TSN "tsn" +%token STREAM "stream" +%token SSN "ssn" +%token PPID "ppid" +%token INIT_TAG "init-tag" +%token A_RWND "a-rwnd" +%token NUM_OSTREAMS "num-outbound-streams" +%token NUM_ISTREAMS "num-inbound-streams" +%token INIT_TSN "initial-tsn" +%token CUM_TSN_ACK "cum-tsn-ack" +%token NUM_GACK_BLOCKS "num-gap-ack-blocks" +%token NUM_DUP_TSNS "num-dup-tsns" +%token LOWEST_TSN "lowest-tsn" +%token SEQNO "seqno" +%token NEW_CUM_TSN "new-cum-tsn" + %token VTAG "vtag" %token RT "rt" @@ -476,6 +602,9 @@ int nft_lex(void *, void *, void *); %token BYTES "bytes" %token AVGPKT "avgpkt" +%token LAST "last" +%token NEVER "never" + %token COUNTERS "counters" %token QUOTAS "quotas" %token LIMITS "limits" @@ -501,9 +630,6 @@ int nft_lex(void *, void *, void *); %token SECMARK "secmark" %token SECMARKS "secmarks" -%token NANOSECOND "nanosecond" -%token MICROSECOND "microsecond" -%token MILLISECOND "millisecond" %token SECOND "second" %token MINUTE "minute" %token HOUR "hour" @@ -555,19 +681,21 @@ int nft_lex(void *, void *, void *); %token EXTHDR "exthdr" %token IPSEC "ipsec" -%token MODE "mode" %token REQID "reqid" %token SPNUM "spnum" -%token TRANSPORT "transport" -%token TUNNEL "tunnel" %token IN "in" %token OUT "out" +%token XT "xt" + +%type <limit_rate> limit_rate_pkts +%type <limit_rate> limit_rate_bytes + %type <string> identifier type_identifier string comment_spec -%destructor { xfree($$); } identifier type_identifier string comment_spec +%destructor { free_const($$); } identifier type_identifier string comment_spec -%type <val> time_spec quota_used +%type <val> time_spec time_spec_or_num_s set_elem_time_spec quota_used %type <expr> data_type_expr data_type_atom_expr %destructor { expr_free($$); } data_type_expr data_type_atom_expr @@ -575,29 +703,50 @@ int nft_lex(void *, void *, void *); %type <cmd> line %destructor { cmd_free($$); } line -%type <cmd> base_cmd add_cmd replace_cmd create_cmd insert_cmd delete_cmd get_cmd list_cmd reset_cmd flush_cmd rename_cmd export_cmd monitor_cmd describe_cmd import_cmd -%destructor { cmd_free($$); } base_cmd add_cmd replace_cmd create_cmd insert_cmd delete_cmd get_cmd list_cmd reset_cmd flush_cmd rename_cmd export_cmd monitor_cmd describe_cmd import_cmd +%type <cmd> base_cmd add_cmd replace_cmd create_cmd insert_cmd delete_cmd get_cmd list_cmd reset_cmd flush_cmd rename_cmd export_cmd monitor_cmd describe_cmd import_cmd destroy_cmd +%destructor { cmd_free($$); } base_cmd add_cmd replace_cmd create_cmd insert_cmd delete_cmd get_cmd list_cmd reset_cmd flush_cmd rename_cmd export_cmd monitor_cmd describe_cmd import_cmd destroy_cmd + +%type <handle> table_spec tableid_spec table_or_id_spec +%destructor { handle_free(&$$); } table_spec tableid_spec table_or_id_spec +%type <handle> chain_spec chainid_spec chain_or_id_spec +%destructor { handle_free(&$$); } chain_spec chainid_spec chain_or_id_spec + +%type <handle> flowtable_spec chain_identifier ruleid_spec handle_spec position_spec rule_position ruleset_spec index_spec +%destructor { handle_free(&$$); } flowtable_spec chain_identifier ruleid_spec handle_spec position_spec rule_position ruleset_spec index_spec +%type <handle> set_spec setid_spec set_or_id_spec +%destructor { handle_free(&$$); } set_spec setid_spec set_or_id_spec +%type <handle> obj_spec objid_spec obj_or_id_spec +%destructor { handle_free(&$$); } obj_spec objid_spec obj_or_id_spec + +%type <handle> set_identifier flowtableid_spec flowtable_identifier obj_identifier +%destructor { handle_free(&$$); } set_identifier flowtableid_spec obj_identifier + +%type <handle> basehook_spec +%destructor { handle_free(&$$); } basehook_spec + +%type <handle> list_cmd_spec_any list_cmd_spec_table +%destructor { handle_free(&$$); } list_cmd_spec_any list_cmd_spec_table -%type <handle> table_spec tableid_spec chain_spec chainid_spec flowtable_spec chain_identifier ruleid_spec handle_spec position_spec rule_position ruleset_spec index_spec -%destructor { handle_free(&$$); } table_spec tableid_spec chain_spec chainid_spec flowtable_spec chain_identifier ruleid_spec handle_spec position_spec rule_position ruleset_spec index_spec -%type <handle> set_spec setid_spec set_identifier flowtableid_spec flowtable_identifier obj_spec objid_spec obj_identifier -%destructor { handle_free(&$$); } set_spec setid_spec set_identifier flowtableid_spec obj_spec objid_spec obj_identifier %type <val> family_spec family_spec_explicit %type <val32> int_num chain_policy %type <prio_spec> extended_prio_spec prio_spec -%type <string> extended_prio_name quota_unit -%destructor { xfree($$); } extended_prio_name quota_unit +%destructor { expr_free($$.expr); } extended_prio_spec prio_spec + +%type <string> extended_prio_name quota_unit basehook_device_name +%destructor { free_const($$); } extended_prio_name quota_unit basehook_device_name %type <expr> dev_spec -%destructor { xfree($$); } dev_spec +%destructor { free($$); } dev_spec %type <table> table_block_alloc table_block %destructor { close_scope(state); table_free($$); } table_block_alloc -%type <chain> chain_block_alloc chain_block +%type <chain> chain_block_alloc chain_block subchain_block %destructor { close_scope(state); chain_free($$); } chain_block_alloc %type <rule> rule rule_alloc %destructor { rule_free($$); } rule +%type <val> table_flags table_flag + %type <val> set_flag_list set_flag %type <val> set_policy_spec @@ -607,6 +756,7 @@ int nft_lex(void *, void *, void *); %type <set> map_block_alloc map_block %destructor { set_free($$); } map_block_alloc +%type <val> map_block_obj_type map_block_obj_typeof map_block_data_interval %type <flowtable> flowtable_block_alloc flowtable_block %destructor { flowtable_free($$); } flowtable_block_alloc @@ -614,12 +764,17 @@ int nft_lex(void *, void *, void *); %type <obj> obj_block_alloc counter_block quota_block ct_helper_block ct_timeout_block ct_expect_block limit_block secmark_block synproxy_block %destructor { obj_free($$); } obj_block_alloc -%type <list> stmt_list -%destructor { stmt_list_free($$); xfree($$); } stmt_list -%type <stmt> stmt match_stmt verdict_stmt -%destructor { stmt_free($$); } stmt match_stmt verdict_stmt -%type <stmt> counter_stmt counter_stmt_alloc stateful_stmt -%destructor { stmt_free($$); } counter_stmt counter_stmt_alloc stateful_stmt +%type <list> stmt_list stateful_stmt_list set_elem_stmt_list +%destructor { stmt_list_free($$); free($$); } stmt_list stateful_stmt_list set_elem_stmt_list +%type <stmt> stmt match_stmt verdict_stmt set_elem_stmt +%destructor { stmt_free($$); } stmt match_stmt verdict_stmt set_elem_stmt +%type <stmt> counter_stmt counter_stmt_alloc stateful_stmt last_stmt +%destructor { stmt_free($$); } counter_stmt counter_stmt_alloc stateful_stmt last_stmt +%type <stmt> limit_stmt_alloc quota_stmt_alloc last_stmt_alloc ct_limit_stmt_alloc +%destructor { stmt_free($$); } limit_stmt_alloc quota_stmt_alloc last_stmt_alloc ct_limit_stmt_alloc +%type <stmt> objref_stmt objref_stmt_counter objref_stmt_limit objref_stmt_quota objref_stmt_ct objref_stmt_synproxy +%destructor { stmt_free($$); } objref_stmt objref_stmt_counter objref_stmt_limit objref_stmt_quota objref_stmt_ct objref_stmt_synproxy + %type <stmt> payload_stmt %destructor { stmt_free($$); } payload_stmt %type <stmt> ct_stmt @@ -631,7 +786,7 @@ int nft_lex(void *, void *, void *); %type <val> level_type log_flags log_flags_tcp log_flag_tcp %type <stmt> limit_stmt quota_stmt connlimit_stmt %destructor { stmt_free($$); } limit_stmt quota_stmt connlimit_stmt -%type <val> limit_burst_pkts limit_burst_bytes limit_mode time_unit quota_mode +%type <val> limit_burst_pkts limit_burst_bytes limit_mode limit_bytes time_unit quota_mode %type <stmt> reject_stmt reject_stmt_alloc %destructor { stmt_free($$); } reject_stmt reject_stmt_alloc %type <stmt> nat_stmt nat_stmt_alloc masq_stmt masq_stmt_alloc redir_stmt redir_stmt_alloc @@ -641,10 +796,14 @@ int nft_lex(void *, void *, void *); %destructor { stmt_free($$); } tproxy_stmt %type <stmt> synproxy_stmt synproxy_stmt_alloc %destructor { stmt_free($$); } synproxy_stmt synproxy_stmt_alloc - - -%type <stmt> queue_stmt queue_stmt_alloc -%destructor { stmt_free($$); } queue_stmt queue_stmt_alloc +%type <stmt> chain_stmt +%destructor { stmt_free($$); } chain_stmt +%type <val> chain_stmt_type + +%type <stmt> queue_stmt queue_stmt_alloc queue_stmt_compat +%destructor { stmt_free($$); } queue_stmt queue_stmt_alloc queue_stmt_compat +%type <expr> queue_stmt_expr_simple queue_stmt_expr queue_expr reject_with_expr +%destructor { expr_free($$); } queue_stmt_expr_simple queue_stmt_expr queue_expr reject_with_expr %type <val> queue_stmt_flags queue_stmt_flag %type <stmt> dup_stmt %destructor { stmt_free($$); } dup_stmt @@ -655,13 +814,13 @@ int nft_lex(void *, void *, void *); %type <val> set_stmt_op %type <stmt> map_stmt %destructor { stmt_free($$); } map_stmt -%type <stmt> meter_stmt meter_stmt_alloc flow_stmt_legacy_alloc -%destructor { stmt_free($$); } meter_stmt meter_stmt_alloc flow_stmt_legacy_alloc +%type <stmt> meter_stmt +%destructor { stmt_free($$); } meter_stmt %type <expr> symbol_expr verdict_expr integer_expr variable_expr chain_expr policy_expr %destructor { expr_free($$); } symbol_expr verdict_expr integer_expr variable_expr chain_expr policy_expr -%type <expr> primary_expr shift_expr and_expr typeof_expr -%destructor { expr_free($$); } primary_expr shift_expr and_expr typeof_expr +%type <expr> primary_expr shift_expr and_expr primary_typeof_expr typeof_expr typeof_data_expr typeof_key_expr typeof_verdict_expr selector_expr +%destructor { expr_free($$); } primary_expr shift_expr and_expr primary_typeof_expr typeof_expr typeof_data_expr typeof_key_expr typeof_verdict_expr selector_expr %type <expr> exclusive_or_expr inclusive_or_expr %destructor { expr_free($$); } exclusive_or_expr inclusive_or_expr %type <expr> basic_expr @@ -679,8 +838,8 @@ int nft_lex(void *, void *, void *); %type <expr> multiton_stmt_expr %destructor { expr_free($$); } multiton_stmt_expr -%type <expr> prefix_stmt_expr range_stmt_expr wildcard_expr -%destructor { expr_free($$); } prefix_stmt_expr range_stmt_expr wildcard_expr +%type <expr> prefix_stmt_expr range_stmt_expr +%destructor { expr_free($$); } prefix_stmt_expr range_stmt_expr %type <expr> primary_stmt_expr basic_stmt_expr %destructor { expr_free($$); } primary_stmt_expr basic_stmt_expr @@ -731,6 +890,8 @@ int nft_lex(void *, void *, void *); %type <expr> payload_expr payload_raw_expr %destructor { expr_free($$); } payload_expr payload_raw_expr %type <val> payload_base_spec +%type <val> payload_raw_len + %type <expr> eth_hdr_expr vlan_hdr_expr %destructor { expr_free($$); } eth_hdr_expr vlan_hdr_expr %type <val> eth_hdr_field vlan_hdr_field @@ -750,9 +911,12 @@ int nft_lex(void *, void *, void *); %type <expr> udp_hdr_expr udplite_hdr_expr %destructor { expr_free($$); } udp_hdr_expr udplite_hdr_expr %type <val> udp_hdr_field udplite_hdr_field -%type <expr> dccp_hdr_expr sctp_hdr_expr -%destructor { expr_free($$); } dccp_hdr_expr sctp_hdr_expr +%type <expr> dccp_hdr_expr sctp_hdr_expr sctp_chunk_alloc +%destructor { expr_free($$); } dccp_hdr_expr sctp_hdr_expr sctp_chunk_alloc %type <val> dccp_hdr_field sctp_hdr_field +%type <val> sctp_chunk_type sctp_chunk_common_field +%type <val> sctp_chunk_data_field sctp_chunk_init_field +%type <val> sctp_chunk_sack_field %type <expr> th_hdr_expr %destructor { expr_free($$); } th_hdr_expr %type <val> th_hdr_field @@ -797,7 +961,7 @@ int nft_lex(void *, void *, void *); %type <val> markup_format %type <string> monitor_event -%destructor { xfree($$); } monitor_event +%destructor { free_const($$); } monitor_event %type <val> monitor_object monitor_format %type <val> synproxy_ts synproxy_sack @@ -805,7 +969,23 @@ int nft_lex(void *, void *, void *); %type <expr> tcp_hdr_expr %destructor { expr_free($$); } tcp_hdr_expr %type <val> tcp_hdr_field -%type <val> tcp_hdr_option_type tcp_hdr_option_field +%type <val> tcp_hdr_option_type +%type <val> tcp_hdr_option_sack +%type <val> tcpopt_field_maxseg tcpopt_field_mptcp tcpopt_field_sack tcpopt_field_tsopt tcpopt_field_window +%type <tcp_kind_field> tcp_hdr_option_kind_and_field + +%type <expr> inner_eth_expr inner_inet_expr inner_expr +%destructor { expr_free($$); } inner_eth_expr inner_inet_expr inner_expr + +%type <expr> vxlan_hdr_expr geneve_hdr_expr gre_hdr_expr gretap_hdr_expr +%destructor { expr_free($$); } vxlan_hdr_expr geneve_hdr_expr gre_hdr_expr gretap_hdr_expr +%type <val> vxlan_hdr_field geneve_hdr_field gre_hdr_field + +%type <stmt> optstrip_stmt +%destructor { stmt_free($$); } optstrip_stmt + +%type <stmt> xt_stmt +%destructor { stmt_free($$); } xt_stmt %type <expr> boolean_expr %destructor { expr_free($$); } boolean_expr @@ -815,15 +995,21 @@ int nft_lex(void *, void *, void *); %destructor { expr_free($$); } exthdr_exists_expr %type <val> exthdr_key -%type <val> ct_l4protoname ct_obj_type +%type <val> ct_l4protoname ct_obj_type ct_cmd_type ct_obj_type_map + +%type <timeout_state> timeout_state +%destructor { timeout_state_free($$); } timeout_state -%type <list> timeout_states timeout_state -%destructor { xfree($$); } timeout_states timeout_state +%type <list> timeout_states +%destructor { timeout_states_free($$); } timeout_states %type <val> xfrm_state_key xfrm_state_proto_key xfrm_dir xfrm_spnum %type <expr> xfrm_expr %destructor { expr_free($$); } xfrm_expr +%type <expr> set_elem_key_expr +%destructor { expr_free($$); } set_elem_key_expr + %% input : /* empty */ @@ -844,13 +1030,69 @@ opt_newline : NEWLINE | /* empty */ ; +close_scope_ah : { scanner_pop_start_cond(nft->scanner, PARSER_SC_EXPR_AH); }; +close_scope_arp : { scanner_pop_start_cond(nft->scanner, PARSER_SC_ARP); }; +close_scope_at : { scanner_pop_start_cond(nft->scanner, PARSER_SC_AT); }; +close_scope_comp : { scanner_pop_start_cond(nft->scanner, PARSER_SC_EXPR_COMP); }; +close_scope_ct : { scanner_pop_start_cond(nft->scanner, PARSER_SC_CT); }; +close_scope_counter : { scanner_pop_start_cond(nft->scanner, PARSER_SC_COUNTER); }; +close_scope_last : { scanner_pop_start_cond(nft->scanner, PARSER_SC_LAST); }; +close_scope_dccp : { scanner_pop_start_cond(nft->scanner, PARSER_SC_EXPR_DCCP); }; +close_scope_destroy : { scanner_pop_start_cond(nft->scanner, PARSER_SC_CMD_DESTROY); }; +close_scope_dst : { scanner_pop_start_cond(nft->scanner, PARSER_SC_EXPR_DST); }; +close_scope_dup : { scanner_pop_start_cond(nft->scanner, PARSER_SC_STMT_DUP); }; +close_scope_esp : { scanner_pop_start_cond(nft->scanner, PARSER_SC_EXPR_ESP); }; +close_scope_eth : { scanner_pop_start_cond(nft->scanner, PARSER_SC_ETH); }; +close_scope_export : { scanner_pop_start_cond(nft->scanner, PARSER_SC_CMD_EXPORT); }; +close_scope_fib : { scanner_pop_start_cond(nft->scanner, PARSER_SC_EXPR_FIB); }; +close_scope_frag : { scanner_pop_start_cond(nft->scanner, PARSER_SC_EXPR_FRAG); }; +close_scope_fwd : { scanner_pop_start_cond(nft->scanner, PARSER_SC_STMT_FWD); }; +close_scope_gre : { scanner_pop_start_cond(nft->scanner, PARSER_SC_GRE); }; +close_scope_hash : { scanner_pop_start_cond(nft->scanner, PARSER_SC_EXPR_HASH); }; +close_scope_hbh : { scanner_pop_start_cond(nft->scanner, PARSER_SC_EXPR_HBH); }; +close_scope_ip : { scanner_pop_start_cond(nft->scanner, PARSER_SC_IP); }; +close_scope_ip6 : { scanner_pop_start_cond(nft->scanner, PARSER_SC_IP6); }; +close_scope_vlan : { scanner_pop_start_cond(nft->scanner, PARSER_SC_VLAN); }; +close_scope_icmp : { scanner_pop_start_cond(nft->scanner, PARSER_SC_ICMP); }; +close_scope_igmp : { scanner_pop_start_cond(nft->scanner, PARSER_SC_IGMP); }; +close_scope_import : { scanner_pop_start_cond(nft->scanner, PARSER_SC_CMD_IMPORT); }; +close_scope_ipsec : { scanner_pop_start_cond(nft->scanner, PARSER_SC_EXPR_IPSEC); }; +close_scope_list : { scanner_pop_start_cond(nft->scanner, PARSER_SC_CMD_LIST); }; +close_scope_limit : { scanner_pop_start_cond(nft->scanner, PARSER_SC_LIMIT); }; +close_scope_meta : { scanner_pop_start_cond(nft->scanner, PARSER_SC_META); }; +close_scope_mh : { scanner_pop_start_cond(nft->scanner, PARSER_SC_EXPR_MH); }; +close_scope_monitor : { scanner_pop_start_cond(nft->scanner, PARSER_SC_CMD_MONITOR); }; +close_scope_nat : { scanner_pop_start_cond(nft->scanner, PARSER_SC_STMT_NAT); }; +close_scope_numgen : { scanner_pop_start_cond(nft->scanner, PARSER_SC_EXPR_NUMGEN); }; +close_scope_osf : { scanner_pop_start_cond(nft->scanner, PARSER_SC_EXPR_OSF); }; +close_scope_policy : { scanner_pop_start_cond(nft->scanner, PARSER_SC_POLICY); }; +close_scope_quota : { scanner_pop_start_cond(nft->scanner, PARSER_SC_QUOTA); }; +close_scope_queue : { scanner_pop_start_cond(nft->scanner, PARSER_SC_EXPR_QUEUE); }; +close_scope_reject : { scanner_pop_start_cond(nft->scanner, PARSER_SC_STMT_REJECT); }; +close_scope_reset : { scanner_pop_start_cond(nft->scanner, PARSER_SC_CMD_RESET); }; +close_scope_rt : { scanner_pop_start_cond(nft->scanner, PARSER_SC_EXPR_RT); }; +close_scope_sctp : { scanner_pop_start_cond(nft->scanner, PARSER_SC_SCTP); }; +close_scope_sctp_chunk : { scanner_pop_start_cond(nft->scanner, PARSER_SC_EXPR_SCTP_CHUNK); }; +close_scope_secmark : { scanner_pop_start_cond(nft->scanner, PARSER_SC_SECMARK); }; +close_scope_socket : { scanner_pop_start_cond(nft->scanner, PARSER_SC_EXPR_SOCKET); } +close_scope_tcp : { scanner_pop_start_cond(nft->scanner, PARSER_SC_TCP); }; +close_scope_tproxy : { scanner_pop_start_cond(nft->scanner, PARSER_SC_STMT_TPROXY); }; +close_scope_type : { scanner_pop_start_cond(nft->scanner, PARSER_SC_TYPE); }; +close_scope_th : { scanner_pop_start_cond(nft->scanner, PARSER_SC_EXPR_TH); }; +close_scope_udp : { scanner_pop_start_cond(nft->scanner, PARSER_SC_EXPR_UDP); }; +close_scope_udplite : { scanner_pop_start_cond(nft->scanner, PARSER_SC_EXPR_UDPLITE); }; + +close_scope_log : { scanner_pop_start_cond(nft->scanner, PARSER_SC_STMT_LOG); } +close_scope_synproxy : { scanner_pop_start_cond(nft->scanner, PARSER_SC_STMT_SYNPROXY); } +close_scope_xt : { scanner_pop_start_cond(nft->scanner, PARSER_SC_XT); } + common_block : INCLUDE QUOTED_STRING stmt_separator { if (scanner_include_file(nft, scanner, $2, &@$) < 0) { - xfree($2); + free_const($2); YYERROR; } - xfree($2); + free_const($2); } | DEFINE identifier '=' initializer_expr stmt_separator { @@ -859,19 +1101,20 @@ common_block : INCLUDE QUOTED_STRING stmt_separator if (symbol_lookup(scope, $2) != NULL) { erec_queue(error(&@2, "redefinition of symbol '%s'", $2), state->msgs); - xfree($2); + expr_free($4); + free_const($2); YYERROR; } symbol_bind(scope, $2, $4); - xfree($2); + free_const($2); } | REDEFINE identifier '=' initializer_expr stmt_separator { struct scope *scope = current_scope(state); symbol_bind(scope, $2, $4); - xfree($2); + free_const($2); } | UNDEFINE identifier stmt_separator { @@ -880,9 +1123,10 @@ common_block : INCLUDE QUOTED_STRING stmt_separator if (symbol_unbind(scope, $2) < 0) { erec_queue(error(&@2, "undefined symbol '%s'", $2), state->msgs); + free_const($2); YYERROR; } - xfree($2); + free_const($2); } | error stmt_separator { @@ -922,14 +1166,15 @@ base_cmd : /* empty */ add_cmd { $$ = $1; } | INSERT insert_cmd { $$ = $2; } | DELETE delete_cmd { $$ = $2; } | GET get_cmd { $$ = $2; } - | LIST list_cmd { $$ = $2; } - | RESET reset_cmd { $$ = $2; } + | LIST list_cmd close_scope_list { $$ = $2; } + | RESET reset_cmd close_scope_reset { $$ = $2; } | FLUSH flush_cmd { $$ = $2; } | RENAME rename_cmd { $$ = $2; } - | IMPORT import_cmd { $$ = $2; } - | EXPORT export_cmd { $$ = $2; } - | MONITOR monitor_cmd { $$ = $2; } + | IMPORT import_cmd close_scope_import { $$ = $2; } + | EXPORT export_cmd close_scope_export { $$ = $2; } + | MONITOR monitor_cmd close_scope_monitor { $$ = $2; } | DESCRIBE describe_cmd { $$ = $2; } + | DESTROY destroy_cmd close_scope_destroy { $$ = $2; } ; add_cmd : TABLE table_spec @@ -979,7 +1224,13 @@ add_cmd : TABLE table_spec } | ELEMENT set_spec set_block_expr { - $$ = cmd_alloc(CMD_ADD, CMD_OBJ_SETELEM, &$2, &@$, $3); + if (nft_cmd_collapse_elems(CMD_ADD, state->cmds, &$2, $3)) { + handle_free(&$2); + expr_free($3); + $$ = NULL; + break; + } + $$ = cmd_alloc(CMD_ADD, CMD_OBJ_ELEMENTS, &$2, &@$, $3); } | FLOWTABLE flowtable_spec flowtable_block_alloc '{' flowtable_block '}' @@ -988,7 +1239,7 @@ add_cmd : TABLE table_spec handle_merge(&$3->handle, &$2); $$ = cmd_alloc(CMD_ADD, CMD_OBJ_FLOWTABLE, &$2, &@$, $5); } - | COUNTER obj_spec + | COUNTER obj_spec close_scope_counter { struct obj *obj; @@ -997,35 +1248,55 @@ add_cmd : TABLE table_spec handle_merge(&obj->handle, &$2); $$ = cmd_alloc(CMD_ADD, CMD_OBJ_COUNTER, &$2, &@$, obj); } - | COUNTER obj_spec counter_obj counter_config + | COUNTER obj_spec counter_obj counter_config close_scope_counter + { + $$ = cmd_alloc(CMD_ADD, CMD_OBJ_COUNTER, &$2, &@$, $3); + } + | COUNTER obj_spec counter_obj '{' counter_block '}' close_scope_counter { $$ = cmd_alloc(CMD_ADD, CMD_OBJ_COUNTER, &$2, &@$, $3); } - | QUOTA obj_spec quota_obj quota_config + | QUOTA obj_spec quota_obj quota_config close_scope_quota { $$ = cmd_alloc(CMD_ADD, CMD_OBJ_QUOTA, &$2, &@$, $3); } - | CT HELPER obj_spec ct_obj_alloc '{' ct_helper_block '}' + | QUOTA obj_spec quota_obj '{' quota_block '}' close_scope_quota + { + $$ = cmd_alloc(CMD_ADD, CMD_OBJ_QUOTA, &$2, &@$, $3); + } + | CT HELPER obj_spec ct_obj_alloc '{' ct_helper_block '}' close_scope_ct { $$ = cmd_alloc_obj_ct(CMD_ADD, NFT_OBJECT_CT_HELPER, &$3, &@$, $4); } - | CT TIMEOUT obj_spec ct_obj_alloc '{' ct_timeout_block '}' + | CT TIMEOUT obj_spec ct_obj_alloc '{' ct_timeout_block '}' close_scope_ct { $$ = cmd_alloc_obj_ct(CMD_ADD, NFT_OBJECT_CT_TIMEOUT, &$3, &@$, $4); } - | CT EXPECTATION obj_spec ct_obj_alloc '{' ct_expect_block '}' + | CT EXPECTATION obj_spec ct_obj_alloc '{' ct_expect_block '}' close_scope_ct { $$ = cmd_alloc_obj_ct(CMD_ADD, NFT_OBJECT_CT_EXPECT, &$3, &@$, $4); } - | LIMIT obj_spec limit_obj limit_config + | LIMIT obj_spec limit_obj limit_config close_scope_limit + { + $$ = cmd_alloc(CMD_ADD, CMD_OBJ_LIMIT, &$2, &@$, $3); + } + | LIMIT obj_spec limit_obj '{' limit_block '}' close_scope_limit { $$ = cmd_alloc(CMD_ADD, CMD_OBJ_LIMIT, &$2, &@$, $3); } - | SECMARK obj_spec secmark_obj secmark_config + | SECMARK obj_spec secmark_obj secmark_config close_scope_secmark { $$ = cmd_alloc(CMD_ADD, CMD_OBJ_SECMARK, &$2, &@$, $3); } - | SYNPROXY obj_spec synproxy_obj synproxy_config + | SECMARK obj_spec secmark_obj '{' secmark_block '}' close_scope_secmark + { + $$ = cmd_alloc(CMD_ADD, CMD_OBJ_SECMARK, &$2, &@$, $3); + } + | SYNPROXY obj_spec synproxy_obj synproxy_config close_scope_synproxy + { + $$ = cmd_alloc(CMD_ADD, CMD_OBJ_SYNPROXY, &$2, &@$, $3); + } + | SYNPROXY obj_spec synproxy_obj '{' synproxy_block '}' close_scope_synproxy { $$ = cmd_alloc(CMD_ADD, CMD_OBJ_SYNPROXY, &$2, &@$, $3); } @@ -1076,7 +1347,13 @@ create_cmd : TABLE table_spec } | ELEMENT set_spec set_block_expr { - $$ = cmd_alloc(CMD_CREATE, CMD_OBJ_SETELEM, &$2, &@$, $3); + if (nft_cmd_collapse_elems(CMD_CREATE, state->cmds, &$2, $3)) { + handle_free(&$2); + expr_free($3); + $$ = NULL; + break; + } + $$ = cmd_alloc(CMD_CREATE, CMD_OBJ_ELEMENTS, &$2, &@$, $3); } | FLOWTABLE flowtable_spec flowtable_block_alloc '{' flowtable_block '}' @@ -1085,7 +1362,7 @@ create_cmd : TABLE table_spec handle_merge(&$3->handle, &$2); $$ = cmd_alloc(CMD_CREATE, CMD_OBJ_FLOWTABLE, &$2, &@$, $5); } - | COUNTER obj_spec + | COUNTER obj_spec close_scope_counter { struct obj *obj; @@ -1094,35 +1371,35 @@ create_cmd : TABLE table_spec handle_merge(&obj->handle, &$2); $$ = cmd_alloc(CMD_CREATE, CMD_OBJ_COUNTER, &$2, &@$, obj); } - | COUNTER obj_spec counter_obj counter_config + | COUNTER obj_spec counter_obj counter_config close_scope_counter { $$ = cmd_alloc(CMD_CREATE, CMD_OBJ_COUNTER, &$2, &@$, $3); } - | QUOTA obj_spec quota_obj quota_config + | QUOTA obj_spec quota_obj quota_config close_scope_quota { $$ = cmd_alloc(CMD_CREATE, CMD_OBJ_QUOTA, &$2, &@$, $3); } - | CT HELPER obj_spec ct_obj_alloc '{' ct_helper_block '}' + | CT HELPER obj_spec ct_obj_alloc '{' ct_helper_block '}' close_scope_ct { $$ = cmd_alloc_obj_ct(CMD_CREATE, NFT_OBJECT_CT_HELPER, &$3, &@$, $4); } - | CT TIMEOUT obj_spec ct_obj_alloc '{' ct_timeout_block '}' + | CT TIMEOUT obj_spec ct_obj_alloc '{' ct_timeout_block '}' close_scope_ct { $$ = cmd_alloc_obj_ct(CMD_CREATE, NFT_OBJECT_CT_TIMEOUT, &$3, &@$, $4); } - | CT EXPECTATION obj_spec ct_obj_alloc '{' ct_expect_block '}' + | CT EXPECTATION obj_spec ct_obj_alloc '{' ct_expect_block '}' close_scope_ct { $$ = cmd_alloc_obj_ct(CMD_CREATE, NFT_OBJECT_CT_EXPECT, &$3, &@$, $4); } - | LIMIT obj_spec limit_obj limit_config + | LIMIT obj_spec limit_obj limit_config close_scope_limit { $$ = cmd_alloc(CMD_CREATE, CMD_OBJ_LIMIT, &$2, &@$, $3); } - | SECMARK obj_spec secmark_obj secmark_config + | SECMARK obj_spec secmark_obj secmark_config close_scope_secmark { $$ = cmd_alloc(CMD_CREATE, CMD_OBJ_SECMARK, &$2, &@$, $3); } - | SYNPROXY obj_spec synproxy_obj synproxy_config + | SYNPROXY obj_spec synproxy_obj synproxy_config close_scope_synproxy { $$ = cmd_alloc(CMD_CREATE, CMD_OBJ_SYNPROXY, &$2, &@$, $3); } @@ -1134,41 +1411,53 @@ insert_cmd : RULE rule_position rule } ; -delete_cmd : TABLE table_spec - { - $$ = cmd_alloc(CMD_DELETE, CMD_OBJ_TABLE, &$2, &@$, NULL); - } - | TABLE tableid_spec +table_or_id_spec : table_spec + | tableid_spec + ; + +chain_or_id_spec : chain_spec + | chainid_spec + ; + +set_or_id_spec : set_spec + | setid_spec + ; + +obj_or_id_spec : obj_spec + | objid_spec + ; + +delete_cmd : TABLE table_or_id_spec { $$ = cmd_alloc(CMD_DELETE, CMD_OBJ_TABLE, &$2, &@$, NULL); } - | CHAIN chain_spec + | CHAIN chain_or_id_spec { $$ = cmd_alloc(CMD_DELETE, CMD_OBJ_CHAIN, &$2, &@$, NULL); } - | CHAIN chainid_spec + | CHAIN chain_spec chain_block_alloc + '{' chain_block '}' { - $$ = cmd_alloc(CMD_DELETE, CMD_OBJ_CHAIN, &$2, &@$, NULL); + $5->location = @5; + handle_merge(&$3->handle, &$2); + close_scope(state); + $$ = cmd_alloc(CMD_DELETE, CMD_OBJ_CHAIN, &$2, &@$, $5); } | RULE ruleid_spec { $$ = cmd_alloc(CMD_DELETE, CMD_OBJ_RULE, &$2, &@$, NULL); } - | SET set_spec - { - $$ = cmd_alloc(CMD_DELETE, CMD_OBJ_SET, &$2, &@$, NULL); - } - | SET setid_spec + | SET set_or_id_spec { $$ = cmd_alloc(CMD_DELETE, CMD_OBJ_SET, &$2, &@$, NULL); } - | MAP set_spec + | MAP set_or_id_spec { $$ = cmd_alloc(CMD_DELETE, CMD_OBJ_SET, &$2, &@$, NULL); } | ELEMENT set_spec set_block_expr { - $$ = cmd_alloc(CMD_DELETE, CMD_OBJ_SETELEM, &$2, &@$, $3); + $$ = cmd_alloc(CMD_DELETE, CMD_OBJ_ELEMENTS, &$2, &@$, $3); } | FLOWTABLE flowtable_spec { @@ -1178,58 +1467,122 @@ delete_cmd : TABLE table_spec { $$ = cmd_alloc(CMD_DELETE, CMD_OBJ_FLOWTABLE, &$2, &@$, NULL); } - | COUNTER obj_spec + | FLOWTABLE flowtable_spec flowtable_block_alloc + '{' flowtable_block '}' { - $$ = cmd_alloc(CMD_DELETE, CMD_OBJ_COUNTER, &$2, &@$, NULL); + $5->location = @5; + handle_merge(&$3->handle, &$2); + $$ = cmd_alloc(CMD_DELETE, CMD_OBJ_FLOWTABLE, &$2, &@$, $5); } - | COUNTER objid_spec + | COUNTER obj_or_id_spec close_scope_counter { $$ = cmd_alloc(CMD_DELETE, CMD_OBJ_COUNTER, &$2, &@$, NULL); } - | QUOTA obj_spec - { - $$ = cmd_alloc(CMD_DELETE, CMD_OBJ_QUOTA, &$2, &@$, NULL); - } - | QUOTA objid_spec + | QUOTA obj_or_id_spec close_scope_quota { $$ = cmd_alloc(CMD_DELETE, CMD_OBJ_QUOTA, &$2, &@$, NULL); } - | CT ct_obj_type obj_spec ct_obj_alloc + | CT ct_obj_type obj_spec ct_obj_alloc close_scope_ct { $$ = cmd_alloc_obj_ct(CMD_DELETE, $2, &$3, &@$, $4); + if ($2 == NFT_OBJECT_CT_TIMEOUT) + init_list_head(&$4->ct_timeout.timeout_list); } - | LIMIT obj_spec + | LIMIT obj_or_id_spec close_scope_limit { $$ = cmd_alloc(CMD_DELETE, CMD_OBJ_LIMIT, &$2, &@$, NULL); } - | LIMIT objid_spec + | SECMARK obj_or_id_spec close_scope_secmark { - $$ = cmd_alloc(CMD_DELETE, CMD_OBJ_LIMIT, &$2, &@$, NULL); + $$ = cmd_alloc(CMD_DELETE, CMD_OBJ_SECMARK, &$2, &@$, NULL); } - | SECMARK obj_spec + | SYNPROXY obj_or_id_spec close_scope_synproxy { - $$ = cmd_alloc(CMD_DELETE, CMD_OBJ_SECMARK, &$2, &@$, NULL); + $$ = cmd_alloc(CMD_DELETE, CMD_OBJ_SYNPROXY, &$2, &@$, NULL); } - | SECMARK objid_spec + ; + +destroy_cmd : TABLE table_or_id_spec { - $$ = cmd_alloc(CMD_DELETE, CMD_OBJ_SECMARK, &$2, &@$, NULL); + $$ = cmd_alloc(CMD_DESTROY, CMD_OBJ_TABLE, &$2, &@$, NULL); } - | SYNPROXY obj_spec + | CHAIN chain_or_id_spec { - $$ = cmd_alloc(CMD_DELETE, CMD_OBJ_SYNPROXY, &$2, &@$, NULL); + $$ = cmd_alloc(CMD_DESTROY, CMD_OBJ_CHAIN, &$2, &@$, NULL); } - | SYNPROXY objid_spec + | RULE ruleid_spec { - $$ = cmd_alloc(CMD_DELETE, CMD_OBJ_SYNPROXY, &$2, &@$, NULL); + $$ = cmd_alloc(CMD_DESTROY, CMD_OBJ_RULE, &$2, &@$, NULL); + } + | SET set_or_id_spec + { + $$ = cmd_alloc(CMD_DESTROY, CMD_OBJ_SET, &$2, &@$, NULL); + } + | MAP set_spec + { + $$ = cmd_alloc(CMD_DESTROY, CMD_OBJ_SET, &$2, &@$, NULL); + } + | ELEMENT set_spec set_block_expr + { + $$ = cmd_alloc(CMD_DESTROY, CMD_OBJ_ELEMENTS, &$2, &@$, $3); + } + | FLOWTABLE flowtable_spec + { + $$ = cmd_alloc(CMD_DESTROY, CMD_OBJ_FLOWTABLE, &$2, &@$, NULL); + } + | FLOWTABLE flowtableid_spec + { + $$ = cmd_alloc(CMD_DESTROY, CMD_OBJ_FLOWTABLE, &$2, &@$, NULL); + } + | FLOWTABLE flowtable_spec flowtable_block_alloc + '{' flowtable_block '}' + { + $5->location = @5; + handle_merge(&$3->handle, &$2); + $$ = cmd_alloc(CMD_DESTROY, CMD_OBJ_FLOWTABLE, &$2, &@$, $5); + } + | COUNTER obj_or_id_spec close_scope_counter + { + $$ = cmd_alloc(CMD_DESTROY, CMD_OBJ_COUNTER, &$2, &@$, NULL); + } + | QUOTA obj_or_id_spec close_scope_quota + { + $$ = cmd_alloc(CMD_DESTROY, CMD_OBJ_QUOTA, &$2, &@$, NULL); + } + | CT ct_obj_type obj_spec ct_obj_alloc close_scope_ct + { + $$ = cmd_alloc_obj_ct(CMD_DESTROY, $2, &$3, &@$, $4); + if ($2 == NFT_OBJECT_CT_TIMEOUT) + init_list_head(&$4->ct_timeout.timeout_list); + } + | LIMIT obj_or_id_spec close_scope_limit + { + $$ = cmd_alloc(CMD_DESTROY, CMD_OBJ_LIMIT, &$2, &@$, NULL); + } + | SECMARK obj_or_id_spec close_scope_secmark + { + $$ = cmd_alloc(CMD_DESTROY, CMD_OBJ_SECMARK, &$2, &@$, NULL); + } + | SYNPROXY obj_or_id_spec close_scope_synproxy + { + $$ = cmd_alloc(CMD_DESTROY, CMD_OBJ_SYNPROXY, &$2, &@$, NULL); } ; + get_cmd : ELEMENT set_spec set_block_expr { - $$ = cmd_alloc(CMD_GET, CMD_OBJ_SETELEM, &$2, &@$, $3); + $$ = cmd_alloc(CMD_GET, CMD_OBJ_ELEMENTS, &$2, &@$, $3); } ; +list_cmd_spec_table : TABLE table_spec { $$ = $2; } + | table_spec + ; +list_cmd_spec_any : list_cmd_spec_table + | ruleset_spec + ; + list_cmd : TABLE table_spec { $$ = cmd_alloc(CMD_LIST, CMD_OBJ_TABLE, &$2, &@$, NULL); @@ -1246,75 +1599,51 @@ list_cmd : TABLE table_spec { $$ = cmd_alloc(CMD_LIST, CMD_OBJ_CHAINS, &$2, &@$, NULL); } - | SETS ruleset_spec + | SETS list_cmd_spec_any { $$ = cmd_alloc(CMD_LIST, CMD_OBJ_SETS, &$2, &@$, NULL); } - | SETS TABLE table_spec - { - $$ = cmd_alloc(CMD_LIST, CMD_OBJ_SETS, &$3, &@$, NULL); - } | SET set_spec { $$ = cmd_alloc(CMD_LIST, CMD_OBJ_SET, &$2, &@$, NULL); } - | COUNTERS ruleset_spec + | COUNTERS list_cmd_spec_any { $$ = cmd_alloc(CMD_LIST, CMD_OBJ_COUNTERS, &$2, &@$, NULL); } - | COUNTERS TABLE table_spec - { - $$ = cmd_alloc(CMD_LIST, CMD_OBJ_COUNTERS, &$3, &@$, NULL); - } - | COUNTER obj_spec + | COUNTER obj_spec close_scope_counter { $$ = cmd_alloc(CMD_LIST, CMD_OBJ_COUNTER, &$2, &@$, NULL); } - | QUOTAS ruleset_spec + | QUOTAS list_cmd_spec_any { $$ = cmd_alloc(CMD_LIST, CMD_OBJ_QUOTAS, &$2, &@$, NULL); } - | QUOTAS TABLE table_spec - { - $$ = cmd_alloc(CMD_LIST, CMD_OBJ_QUOTAS, &$3, &@$, NULL); - } - | QUOTA obj_spec + | QUOTA obj_spec close_scope_quota { $$ = cmd_alloc(CMD_LIST, CMD_OBJ_QUOTA, &$2, &@$, NULL); } - | LIMITS ruleset_spec + | LIMITS list_cmd_spec_any { $$ = cmd_alloc(CMD_LIST, CMD_OBJ_LIMITS, &$2, &@$, NULL); } - | LIMITS TABLE table_spec - { - $$ = cmd_alloc(CMD_LIST, CMD_OBJ_LIMITS, &$3, &@$, NULL); - } - | LIMIT obj_spec + | LIMIT obj_spec close_scope_limit { $$ = cmd_alloc(CMD_LIST, CMD_OBJ_LIMIT, &$2, &@$, NULL); } - | SECMARKS ruleset_spec + | SECMARKS list_cmd_spec_any { $$ = cmd_alloc(CMD_LIST, CMD_OBJ_SECMARKS, &$2, &@$, NULL); } - | SECMARKS TABLE table_spec - { - $$ = cmd_alloc(CMD_LIST, CMD_OBJ_SECMARKS, &$3, &@$, NULL); - } - | SECMARK obj_spec + | SECMARK obj_spec close_scope_secmark { $$ = cmd_alloc(CMD_LIST, CMD_OBJ_SECMARK, &$2, &@$, NULL); } - | SYNPROXYS ruleset_spec + | SYNPROXYS list_cmd_spec_any { $$ = cmd_alloc(CMD_LIST, CMD_OBJ_SYNPROXYS, &$2, &@$, NULL); } - | SYNPROXYS TABLE table_spec - { - $$ = cmd_alloc(CMD_LIST, CMD_OBJ_SYNPROXYS, &$3, &@$, NULL); - } - | SYNPROXY obj_spec + | SYNPROXY obj_spec close_scope_synproxy { $$ = cmd_alloc(CMD_LIST, CMD_OBJ_SYNPROXY, &$2, &@$, NULL); } @@ -1338,7 +1667,7 @@ list_cmd : TABLE table_spec { $$ = cmd_alloc(CMD_LIST, CMD_OBJ_METER, &$2, &@$, NULL); } - | FLOWTABLES ruleset_spec + | FLOWTABLES list_cmd_spec_any { $$ = cmd_alloc(CMD_LIST, CMD_OBJ_FLOWTABLES, &$2, &@$, NULL); } @@ -1346,7 +1675,7 @@ list_cmd : TABLE table_spec { $$ = cmd_alloc(CMD_LIST, CMD_OBJ_FLOWTABLE, &$2, &@$, NULL); } - | MAPS ruleset_spec + | MAPS list_cmd_spec_any { $$ = cmd_alloc(CMD_LIST, CMD_OBJ_MAPS, &$2, &@$, NULL); } @@ -1354,47 +1683,88 @@ list_cmd : TABLE table_spec { $$ = cmd_alloc(CMD_LIST, CMD_OBJ_MAP, &$2, &@$, NULL); } - | CT ct_obj_type obj_spec + | CT ct_obj_type obj_spec close_scope_ct { $$ = cmd_alloc_obj_ct(CMD_LIST, $2, &$3, &@$, NULL); } - | CT HELPERS TABLE table_spec + | CT ct_cmd_type TABLE table_spec close_scope_ct { - $$ = cmd_alloc(CMD_LIST, CMD_OBJ_CT_HELPERS, &$4, &@$, NULL); + $$ = cmd_alloc(CMD_LIST, $2, &$4, &@$, NULL); } - | CT TIMEOUT TABLE table_spec + | HOOKS basehook_spec { - $$ = cmd_alloc(CMD_LIST, CMD_OBJ_CT_TIMEOUT, &$4, &@$, NULL); + $$ = cmd_alloc(CMD_LIST, CMD_OBJ_HOOKS, &$2, &@$, NULL); } - | CT EXPECTATION TABLE table_spec + ; + +basehook_device_name : DEVICE STRING { - $$ = cmd_alloc(CMD_LIST, CMD_OBJ_CT_EXPECT, &$4, &@$, NULL); + $$ = $2; } ; -reset_cmd : COUNTERS ruleset_spec +basehook_spec : ruleset_spec { - $$ = cmd_alloc(CMD_RESET, CMD_OBJ_COUNTERS, &$2, &@$, NULL); + $$ = $1; + } + | ruleset_spec basehook_device_name + { + if ($2) { + $1.obj.name = $2; + $1.obj.location = @2; + } + $$ = $1; } - | COUNTERS TABLE table_spec + ; + +reset_cmd : COUNTERS list_cmd_spec_any { - $$ = cmd_alloc(CMD_RESET, CMD_OBJ_COUNTERS, &$3, &@$, NULL); + $$ = cmd_alloc(CMD_RESET, CMD_OBJ_COUNTERS, &$2, &@$, NULL); } - | COUNTER obj_spec + | COUNTER obj_spec close_scope_counter { $$ = cmd_alloc(CMD_RESET, CMD_OBJ_COUNTER, &$2,&@$, NULL); } - | QUOTAS ruleset_spec + | QUOTAS list_cmd_spec_any { $$ = cmd_alloc(CMD_RESET, CMD_OBJ_QUOTAS, &$2, &@$, NULL); } - | QUOTAS TABLE table_spec + | QUOTA obj_spec close_scope_quota { - $$ = cmd_alloc(CMD_RESET, CMD_OBJ_QUOTAS, &$3, &@$, NULL); + $$ = cmd_alloc(CMD_RESET, CMD_OBJ_QUOTA, &$2, &@$, NULL); } - | QUOTA obj_spec + | RULES ruleset_spec { - $$ = cmd_alloc(CMD_RESET, CMD_OBJ_QUOTA, &$2, &@$, NULL); + $$ = cmd_alloc(CMD_RESET, CMD_OBJ_RULES, &$2, &@$, NULL); + } + | RULES list_cmd_spec_table + { + $$ = cmd_alloc(CMD_RESET, CMD_OBJ_TABLE, &$2, &@$, NULL); + } + | RULES chain_spec + { + $$ = cmd_alloc(CMD_RESET, CMD_OBJ_CHAIN, &$2, &@$, NULL); + } + | RULES CHAIN chain_spec + { + /* alias of previous rule. */ + $$ = cmd_alloc(CMD_RESET, CMD_OBJ_CHAIN, &$3, &@$, NULL); + } + | RULE ruleid_spec + { + $$ = cmd_alloc(CMD_RESET, CMD_OBJ_RULE, &$2, &@$, NULL); + } + | ELEMENT set_spec set_block_expr + { + $$ = cmd_alloc(CMD_RESET, CMD_OBJ_ELEMENTS, &$2, &@$, $3); + } + | SET set_spec + { + $$ = cmd_alloc(CMD_RESET, CMD_OBJ_SET, &$2, &@$, NULL); + } + | MAP set_spec + { + $$ = cmd_alloc(CMD_RESET, CMD_OBJ_MAP, &$2, &@$, NULL); } ; @@ -1506,21 +1876,45 @@ describe_cmd : primary_expr table_block_alloc : /* empty */ { $$ = table_alloc(); - open_scope(state, &$$->scope); + if (open_scope(state, &$$->scope) < 0) { + erec_queue(error(&@$, "too many levels of nesting"), + state->msgs); + state->nerrs++; + } } ; -table_options : FLAGS STRING +table_options : FLAGS table_flags { - if (strcmp($2, "dormant") == 0) { - $<table>0->flags = TABLE_F_DORMANT; - xfree($2); - } else { - erec_queue(error(&@2, "unknown table option %s", $2), + $<table>0->flags |= $2; + } + | comment_spec + { + if (already_set($<table>0->comment, &@$, state)) { + free_const($1); + YYERROR; + } + $<table>0->comment = $1; + } + ; + +table_flags : table_flag + | table_flags COMMA table_flag + { + $$ = $1 | $3; + } + ; +table_flag : STRING + { + $$ = parse_table_flag($1); + if ($$ == 0) { + erec_queue(error(&@1, "unknown table option %s", $1), state->msgs); - xfree($2); + free_const($1); YYERROR; } + + free_const($1); } ; @@ -1572,7 +1966,7 @@ table_block : /* empty */ { $$ = $<table>-1; } } | table_block COUNTER obj_identifier obj_block_alloc '{' counter_block '}' - stmt_separator + stmt_separator close_scope_counter { $4->location = @3; $4->type = NFT_OBJECT_COUNTER; @@ -1583,7 +1977,7 @@ table_block : /* empty */ { $$ = $<table>-1; } } | table_block QUOTA obj_identifier obj_block_alloc '{' quota_block '}' - stmt_separator + stmt_separator close_scope_quota { $4->location = @3; $4->type = NFT_OBJECT_QUOTA; @@ -1592,7 +1986,7 @@ table_block : /* empty */ { $$ = $<table>-1; } list_add_tail(&$4->list, &$1->objs); $$ = $1; } - | table_block CT HELPER obj_identifier obj_block_alloc '{' ct_helper_block '}' stmt_separator + | table_block CT HELPER obj_identifier obj_block_alloc '{' ct_helper_block '}' stmt_separator close_scope_ct { $5->location = @4; $5->type = NFT_OBJECT_CT_HELPER; @@ -1601,7 +1995,7 @@ table_block : /* empty */ { $$ = $<table>-1; } list_add_tail(&$5->list, &$1->objs); $$ = $1; } - | table_block CT TIMEOUT obj_identifier obj_block_alloc '{' ct_timeout_block '}' stmt_separator + | table_block CT TIMEOUT obj_identifier obj_block_alloc '{' ct_timeout_block '}' stmt_separator close_scope_ct { $5->location = @4; $5->type = NFT_OBJECT_CT_TIMEOUT; @@ -1610,7 +2004,7 @@ table_block : /* empty */ { $$ = $<table>-1; } list_add_tail(&$5->list, &$1->objs); $$ = $1; } - | table_block CT EXPECTATION obj_identifier obj_block_alloc '{' ct_expect_block '}' stmt_separator + | table_block CT EXPECTATION obj_identifier obj_block_alloc '{' ct_expect_block '}' stmt_separator close_scope_ct { $5->location = @4; $5->type = NFT_OBJECT_CT_EXPECT; @@ -1621,7 +2015,7 @@ table_block : /* empty */ { $$ = $<table>-1; } } | table_block LIMIT obj_identifier obj_block_alloc '{' limit_block '}' - stmt_separator + stmt_separator close_scope_limit { $4->location = @3; $4->type = NFT_OBJECT_LIMIT; @@ -1632,7 +2026,7 @@ table_block : /* empty */ { $$ = $<table>-1; } } | table_block SECMARK obj_identifier obj_block_alloc '{' secmark_block '}' - stmt_separator + stmt_separator close_scope_secmark { $4->location = @3; $4->type = NFT_OBJECT_SECMARK; @@ -1643,7 +2037,7 @@ table_block : /* empty */ { $$ = $<table>-1; } } | table_block SYNPROXY obj_identifier obj_block_alloc '{' synproxy_block '}' - stmt_separator + stmt_separator close_scope_synproxy { $4->location = @3; $4->type = NFT_OBJECT_SYNPROXY; @@ -1656,8 +2050,12 @@ table_block : /* empty */ { $$ = $<table>-1; } chain_block_alloc : /* empty */ { - $$ = chain_alloc(NULL); - open_scope(state, &$$->scope); + $$ = chain_alloc(); + if (open_scope(state, &$$->scope) < 0) { + erec_queue(error(&@$, "too many levels of nesting"), + state->msgs); + state->nerrs++; + } } ; @@ -1666,14 +2064,94 @@ chain_block : /* empty */ { $$ = $<chain>-1; } | chain_block stmt_separator | chain_block hook_spec stmt_separator | chain_block policy_spec stmt_separator + | chain_block flags_spec stmt_separator | chain_block rule stmt_separator { list_add_tail(&$2->list, &$1->rules); $$ = $1; } + | chain_block DEVICES '=' flowtable_expr stmt_separator + { + if ($$->dev_expr) { + list_splice_init(&expr_list($4)->expressions, &expr_list($$->dev_expr)->expressions); + expr_free($4); + break; + } + $$->dev_expr = $4; + } + | chain_block comment_spec stmt_separator + { + if (already_set($1->comment, &@2, state)) { + free_const($2); + YYERROR; + } + $1->comment = $2; + } + ; + +subchain_block : /* empty */ { $$ = $<chain>-1; } + | subchain_block stmt_separator + | subchain_block rule stmt_separator + { + list_add_tail(&$2->list, &$1->rules); + $$ = $1; + } ; -typeof_expr : primary_expr +typeof_verdict_expr : selector_expr + { + struct expr *e = $1; + + if (expr_ops(e)->build_udata == NULL) { + erec_queue(error(&@1, "map data type '%s' lacks typeof serialization", expr_ops(e)->name), + state->msgs); + expr_free(e); + YYERROR; + } + $$ = e; + } + | typeof_expr DOT selector_expr + { + struct location rhs[] = { + [1] = @2, + [2] = @3, + }; + + $$ = handle_concat_expr(&@$, $$, $1, $3, rhs); + } + ; + +typeof_data_expr : INTERVAL typeof_expr + { + $2->flags |= EXPR_F_INTERVAL; + $$ = $2; + } + | typeof_verdict_expr + { + $$ = $1; + } + | QUEUE + { + $$ = constant_expr_alloc(&@$, &queue_type, BYTEORDER_HOST_ENDIAN, 16, NULL); + } + | STRING + { + struct expr *verdict; + + if (strcmp("verdict", $1) != 0) { + erec_queue(error(&@1, "map data type '%s' lacks typeof serialization", $1), + state->msgs); + free_const($1); + YYERROR; + } + verdict = verdict_expr_alloc(&@1, NF_ACCEPT, NULL); + verdict->flags &= ~EXPR_F_CONSTANT; + $$ = verdict; + free_const($1); + } + ; + +primary_typeof_expr : selector_expr { if (expr_ops($1)->build_udata == NULL) { erec_queue(error(&@1, "primary expression type '%s' lacks typeof serialization", expr_ops($1)->name), @@ -1684,7 +2162,13 @@ typeof_expr : primary_expr $$ = $1; } - | typeof_expr DOT primary_expr + ; + +typeof_expr : primary_typeof_expr + { + $$ = $1; + } + | typeof_expr DOT primary_typeof_expr { struct location rhs[] = { [1] = @2, @@ -1698,22 +2182,25 @@ typeof_expr : primary_expr set_block_alloc : /* empty */ { - $$ = set_alloc(NULL); + $$ = set_alloc(&internal_location); } ; +typeof_key_expr : TYPEOF typeof_expr { $$ = $2; } + | TYPE data_type_expr close_scope_type { $$ = $2; } + ; + set_block : /* empty */ { $$ = $<set>-1; } | set_block common_block | set_block stmt_separator - | set_block TYPE data_type_expr stmt_separator + | set_block typeof_key_expr stmt_separator { - $1->key = $3; - $$ = $1; - } - | set_block TYPEOF typeof_expr stmt_separator - { - $1->key = $3; - datatype_set($1->key, $3->dtype); + if (already_set($1->key, &@2, state)) { + expr_free($2); + YYERROR; + } + + $1->key = $2; $$ = $1; } | set_block FLAGS set_flag_list stmt_separator @@ -1731,8 +2218,18 @@ set_block : /* empty */ { $$ = $<set>-1; } $1->gc_int = $3; $$ = $1; } + | set_block stateful_stmt_list stmt_separator + { + list_splice_tail($2, &$1->stmt_list); + $$ = $1; + free($2); + } | set_block ELEMENTS '=' set_block_expr { + if (already_set($1->init, &@2, state)) { + expr_free($4); + YYERROR; + } $1->init = $4; $$ = $1; } @@ -1742,6 +2239,15 @@ set_block : /* empty */ { $$ = $<set>-1; } $$ = $1; } | set_block set_mechanism stmt_separator + | set_block comment_spec stmt_separator + { + if (already_set($1->comment, &@2, state)) { + free_const($2); + YYERROR; + } + $1->comment = $2; + $$ = $1; + } ; set_block_expr : set_expr @@ -1763,10 +2269,29 @@ set_flag : CONSTANT { $$ = NFT_SET_CONSTANT; } map_block_alloc : /* empty */ { - $$ = set_alloc(NULL); + $$ = set_alloc(&internal_location); } ; +ct_obj_type_map : TIMEOUT { $$ = NFT_OBJECT_CT_TIMEOUT; } + | EXPECTATION { $$ = NFT_OBJECT_CT_EXPECT; } + ; + +map_block_obj_type : COUNTER close_scope_counter { $$ = NFT_OBJECT_COUNTER; } + | QUOTA close_scope_quota { $$ = NFT_OBJECT_QUOTA; } + | LIMIT close_scope_limit { $$ = NFT_OBJECT_LIMIT; } + | SECMARK close_scope_secmark { $$ = NFT_OBJECT_SECMARK; } + | SYNPROXY close_scope_synproxy { $$ = NFT_OBJECT_SYNPROXY; } + ; + +map_block_obj_typeof : map_block_obj_type + | CT ct_obj_type_map close_scope_ct { $$ = $2; } + ; + +map_block_data_interval : INTERVAL { $$ = EXPR_F_INTERVAL; } + | { $$ = 0; } + ; + map_block : /* empty */ { $$ = $<set>-1; } | map_block common_block | map_block stmt_separator @@ -1775,77 +2300,103 @@ map_block : /* empty */ { $$ = $<set>-1; } $1->timeout = $3; $$ = $1; } + | map_block GC_INTERVAL time_spec stmt_separator + { + $1->gc_int = $3; + $$ = $1; + } | map_block TYPE - data_type_expr COLON data_type_expr - stmt_separator + data_type_expr COLON map_block_data_interval data_type_expr + stmt_separator close_scope_type { + if (already_set($1->key, &@2, state)) { + expr_free($3); + expr_free($6); + YYERROR; + } + $1->key = $3; - $1->data = $5; + $1->data = $6; + $1->data->flags |= $5; $1->flags |= NFT_SET_MAP; $$ = $1; } | map_block TYPEOF - typeof_expr COLON typeof_expr + typeof_expr COLON typeof_data_expr stmt_separator { + if (already_set($1->key, &@2, state)) { + expr_free($3); + expr_free($5); + YYERROR; + } + $1->key = $3; - datatype_set($1->key, $3->dtype); - $1->data = $5; - $1->flags |= NFT_SET_MAP; + if ($5->etype == EXPR_CT && $5->ct.key == NFT_CT_HELPER) { + $1->objtype = NFT_OBJECT_CT_HELPER; + $1->flags |= NFT_SET_OBJECT; + expr_free($5); + } else { + $1->data = $5; + $1->flags |= NFT_SET_MAP; + } + $$ = $1; } | map_block TYPE - data_type_expr COLON COUNTER - stmt_separator + data_type_expr COLON map_block_obj_type + stmt_separator close_scope_type { + if (already_set($1->key, &@2, state)) { + expr_free($3); + YYERROR; + } + $1->key = $3; - $1->objtype = NFT_OBJECT_COUNTER; + $1->objtype = $5; $1->flags |= NFT_SET_OBJECT; $$ = $1; } - | map_block TYPE - data_type_expr COLON QUOTA + | map_block TYPEOF + typeof_expr COLON map_block_obj_typeof stmt_separator { $1->key = $3; - $1->objtype = NFT_OBJECT_QUOTA; + $1->objtype = $5; $1->flags |= NFT_SET_OBJECT; $$ = $1; } - | map_block TYPE - data_type_expr COLON LIMIT - stmt_separator + | map_block FLAGS set_flag_list stmt_separator { - $1->key = $3; - $1->objtype = NFT_OBJECT_LIMIT; - $1->flags |= NFT_SET_OBJECT; + $1->flags |= $3; $$ = $1; } - | map_block TYPE - data_type_expr COLON SECMARK - stmt_separator + | map_block stateful_stmt_list stmt_separator { - $1->key = $3; - $1->objtype = NFT_OBJECT_SECMARK; - $1->flags |= NFT_SET_OBJECT; + list_splice_tail($2, &$1->stmt_list); $$ = $1; + free($2); } - | map_block FLAGS set_flag_list stmt_separator + | map_block ELEMENTS '=' set_block_expr { - $1->flags |= $3; + $1->init = $4; $$ = $1; } - | map_block ELEMENTS '=' set_block_expr + | map_block comment_spec stmt_separator { - $1->init = $4; + if (already_set($1->comment, &@2, state)) { + free_const($2); + YYERROR; + } + $1->comment = $2; $$ = $1; } | map_block set_mechanism stmt_separator ; -set_mechanism : POLICY set_policy_spec +set_mechanism : POLICY set_policy_spec close_scope_policy { $<set>0->policy = $2; } @@ -1861,7 +2412,7 @@ set_policy_spec : PERFORMANCE { $$ = NFT_SET_POL_PERFORMANCE; } flowtable_block_alloc : /* empty */ { - $$ = flowtable_alloc(NULL); + $$ = flowtable_alloc(&internal_location); } ; @@ -1870,14 +2421,15 @@ flowtable_block : /* empty */ { $$ = $<flowtable>-1; } | flowtable_block stmt_separator | flowtable_block HOOK STRING prio_spec stmt_separator { - $$->hookstr = chain_hookname_lookup($3); - if ($$->hookstr == NULL) { - erec_queue(error(&@3, "unknown chain hook %s", $3), + $$->hook.loc = @3; + $$->hook.name = chain_hookname_lookup($3); + if ($$->hook.name == NULL) { + erec_queue(error(&@3, "unknown chain hook"), state->msgs); - xfree($3); + free_const($3); YYERROR; } - xfree($3); + free_const($3); $$->priority = $4; } @@ -1885,6 +2437,14 @@ flowtable_block : /* empty */ { $$ = $<flowtable>-1; } { $$->dev_expr = $4; } + | flowtable_block COUNTER close_scope_counter + { + $$->flags |= NFT_FLOWTABLE_COUNTER; + } + | flowtable_block FLAGS OFFLOAD stmt_separator + { + $$->flags |= FLOWTABLE_F_HW_OFFLOAD; + } ; flowtable_expr : '{' flowtable_list_expr '}' @@ -1892,6 +2452,11 @@ flowtable_expr : '{' flowtable_list_expr '}' $2->location = @$; $$ = $2; } + | variable_expr + { + $1->location = @$; + $$ = $1; + } ; flowtable_list_expr : flowtable_expr_member @@ -1907,12 +2472,28 @@ flowtable_list_expr : flowtable_expr_member | flowtable_list_expr COMMA opt_newline ; -flowtable_expr_member : STRING +flowtable_expr_member : QUOTED_STRING { - $$ = symbol_expr_alloc(&@$, SYMBOL_VALUE, - current_scope(state), - $1); - xfree($1); + struct expr *expr = ifname_expr_alloc(&@$, state->msgs, $1); + + if (!expr) + YYERROR; + + $$ = expr; + } + | STRING + { + struct expr *expr = ifname_expr_alloc(&@$, state->msgs, $1); + + if (!expr) + YYERROR; + + $$ = expr; + } + | variable_expr + { + datatype_set($1->sym->expr, &ifname_type); + $$ = $1; } ; @@ -1922,11 +2503,12 @@ data_type_atom_expr : type_identifier if (dtype == NULL) { erec_queue(error(&@1, "unknown datatype %s", $1), state->msgs); + free_const($1); YYERROR; } $$ = constant_expr_alloc(&@1, dtype, dtype->byteorder, dtype->size, NULL); - xfree($1); + free_const($1); } | TIME { @@ -1949,7 +2531,7 @@ data_type_expr : data_type_atom_expr obj_block_alloc : /* empty */ { - $$ = obj_alloc(NULL); + $$ = obj_alloc(&internal_location); } ; @@ -1960,6 +2542,14 @@ counter_block : /* empty */ { $$ = $<obj>-1; } { $$ = $1; } + | counter_block comment_spec + { + if (already_set($<obj>1->comment, &@2, state)) { + free_const($2); + YYERROR; + } + $<obj>1->comment = $2; + } ; quota_block : /* empty */ { $$ = $<obj>-1; } @@ -1969,6 +2559,14 @@ quota_block : /* empty */ { $$ = $<obj>-1; } { $$ = $1; } + | quota_block comment_spec + { + if (already_set($<obj>1->comment, &@2, state)) { + free_const($2); + YYERROR; + } + $<obj>1->comment = $2; + } ; ct_helper_block : /* empty */ { $$ = $<obj>-1; } @@ -1978,15 +2576,36 @@ ct_helper_block : /* empty */ { $$ = $<obj>-1; } { $$ = $1; } + | ct_helper_block comment_spec + { + if (already_set($<obj>1->comment, &@2, state)) { + free_const($2); + YYERROR; + } + $<obj>1->comment = $2; + } ; -ct_timeout_block : /*empty */ { $$ = $<obj>-1; } +ct_timeout_block : /*empty */ + { + $$ = $<obj>-1; + init_list_head(&$$->ct_timeout.timeout_list); + $$->type = NFT_OBJECT_CT_TIMEOUT; + } | ct_timeout_block common_block | ct_timeout_block stmt_separator | ct_timeout_block ct_timeout_config { $$ = $1; } + | ct_timeout_block comment_spec + { + if (already_set($<obj>1->comment, &@2, state)) { + free_const($2); + YYERROR; + } + $<obj>1->comment = $2; + } ; ct_expect_block : /*empty */ { $$ = $<obj>-1; } @@ -1996,6 +2615,14 @@ ct_expect_block : /*empty */ { $$ = $<obj>-1; } { $$ = $1; } + | ct_expect_block comment_spec + { + if (already_set($<obj>1->comment, &@2, state)) { + free_const($2); + YYERROR; + } + $<obj>1->comment = $2; + } ; limit_block : /* empty */ { $$ = $<obj>-1; } @@ -2005,6 +2632,14 @@ limit_block : /* empty */ { $$ = $<obj>-1; } { $$ = $1; } + | limit_block comment_spec + { + if (already_set($<obj>1->comment, &@2, state)) { + free_const($2); + YYERROR; + } + $<obj>1->comment = $2; + } ; secmark_block : /* empty */ { $$ = $<obj>-1; } @@ -2014,6 +2649,14 @@ secmark_block : /* empty */ { $$ = $<obj>-1; } { $$ = $1; } + | secmark_block comment_spec + { + if (already_set($<obj>1->comment, &@2, state)) { + free_const($2); + YYERROR; + } + $<obj>1->comment = $2; + } ; synproxy_block : /* empty */ { $$ = $<obj>-1; } @@ -2023,6 +2666,14 @@ synproxy_block : /* empty */ { $$ = $<obj>-1; } { $$ = $1; } + | synproxy_block comment_spec + { + if (already_set($<obj>1->comment, &@2, state)) { + free_const($2); + YYERROR; + } + $<obj>1->comment = $2; + } ; type_identifier : STRING { $$ = $1; } @@ -2032,30 +2683,38 @@ type_identifier : STRING { $$ = $1; } | CLASSID { $$ = xstrdup("classid"); } ; -hook_spec : TYPE STRING HOOK STRING dev_spec prio_spec +hook_spec : TYPE close_scope_type STRING HOOK STRING dev_spec prio_spec { - const char *chain_type = chain_type_name_lookup($2); + const char *chain_type = chain_type_name_lookup($3); if (chain_type == NULL) { - erec_queue(error(&@2, "unknown chain type %s", $2), + erec_queue(error(&@3, "unknown chain type"), state->msgs); - xfree($2); + free_const($3); + free_const($5); + expr_free($6); + expr_free($7.expr); YYERROR; } - $<chain>0->type = xstrdup(chain_type); - xfree($2); - - $<chain>0->hookstr = chain_hookname_lookup($4); - if ($<chain>0->hookstr == NULL) { - erec_queue(error(&@4, "unknown chain hook %s", $4), + $<chain>0->type.loc = @3; + $<chain>0->type.str = xstrdup(chain_type); + free_const($3); + + $<chain>0->loc = @$; + $<chain>0->hook.loc = @5; + $<chain>0->hook.name = chain_hookname_lookup($5); + if ($<chain>0->hook.name == NULL) { + erec_queue(error(&@5, "unknown chain hook"), state->msgs); - xfree($4); + free_const($5); + expr_free($6); + expr_free($7.expr); YYERROR; } - xfree($4); + free_const($5); - $<chain>0->dev_expr = $5; - $<chain>0->priority = $6; + $<chain>0->dev_expr = $6; + $<chain>0->priority = $7; $<chain>0->flags |= CHAIN_F_BASECHAIN; } ; @@ -2088,7 +2747,6 @@ extended_prio_spec : int_num { struct prio_spec spec = {0}; - datatype_set($1->sym->expr, &priority_type); spec.expr = $1; $$ = spec; } @@ -2100,7 +2758,7 @@ extended_prio_spec : int_num BYTEORDER_HOST_ENDIAN, strlen($1) * BITS_PER_BYTE, $1); - xfree($1); + free_const($1); $$ = spec; } | extended_prio_name PLUS NUM @@ -2113,7 +2771,7 @@ extended_prio_spec : int_num BYTEORDER_HOST_ENDIAN, strlen(str) * BITS_PER_BYTE, str); - xfree($1); + free_const($1); $$ = spec; } | extended_prio_name DASH NUM @@ -2126,6 +2784,7 @@ extended_prio_spec : int_num BYTEORDER_HOST_ENDIAN, strlen(str) * BITS_PER_BYTE, str); + free_const($1); $$ = spec; } ; @@ -2136,15 +2795,21 @@ int_num : NUM { $$ = $1; } dev_spec : DEVICE string { - struct expr *expr; + struct expr *expr = ifname_expr_alloc(&@$, state->msgs, $2); + + if (!expr) + YYERROR; - expr = constant_expr_alloc(&@$, &string_type, - BYTEORDER_HOST_ENDIAN, - strlen($2) * BITS_PER_BYTE, $2); $$ = compound_expr_alloc(&@$, EXPR_LIST); compound_expr_add($$, expr); } + | DEVICE variable_expr + { + datatype_set($2->sym->expr, &ifname_type); + $$ = compound_expr_alloc(&@$, EXPR_LIST); + compound_expr_add($$, $2); + } | DEVICES '=' flowtable_expr { $$ = $3; @@ -2152,7 +2817,13 @@ dev_spec : DEVICE string | /* empty */ { $$ = NULL; } ; -policy_spec : POLICY policy_expr +flags_spec : FLAGS OFFLOAD + { + $<chain>0->flags |= CHAIN_F_HW_OFFLOAD; + } + ; + +policy_spec : POLICY policy_expr close_scope_policy { if ($<chain>0->policy) { erec_queue(error(&@$, "you cannot set chain policy twice"), @@ -2160,7 +2831,8 @@ policy_spec : POLICY policy_expr expr_free($2); YYERROR; } - $<chain>0->policy = $2; + $<chain>0->policy = $2; + $<chain>0->policy->location = @$; } ; @@ -2183,6 +2855,7 @@ chain_policy : ACCEPT { $$ = NF_ACCEPT; } ; identifier : STRING + | LAST { $$ = xstrdup("last"); } ; string : STRING @@ -2196,7 +2869,7 @@ time_spec : STRING uint64_t res; erec = time_parse(&@1, $1, &res); - xfree($1); + free_const($1); if (erec != NULL) { erec_queue(erec, state->msgs); YYERROR; @@ -2205,16 +2878,21 @@ time_spec : STRING } ; +/* compatibility kludge to allow either 60, 60s, 1m, ... */ +time_spec_or_num_s : NUM + | time_spec { $$ = $1 / 1000u; } + ; + family_spec : /* empty */ { $$ = NFPROTO_IPV4; } | family_spec_explicit ; -family_spec_explicit : IP { $$ = NFPROTO_IPV4; } - | IP6 { $$ = NFPROTO_IPV6; } - | INET { $$ = NFPROTO_INET; } - | ARP { $$ = NFPROTO_ARP; } - | BRIDGE { $$ = NFPROTO_BRIDGE; } - | NETDEV { $$ = NFPROTO_NETDEV; } +family_spec_explicit : IP close_scope_ip { $$ = NFPROTO_IPV4; } + | IP6 close_scope_ip6 { $$ = NFPROTO_IPV6; } + | INET { $$ = NFPROTO_INET; } + | ARP close_scope_arp { $$ = NFPROTO_ARP; } + | BRIDGE { $$ = NFPROTO_BRIDGE; } + | NETDEV { $$ = NFPROTO_NETDEV; } ; table_spec : family_spec identifier @@ -2231,7 +2909,7 @@ tableid_spec : family_spec HANDLE NUM memset(&$$, 0, sizeof($$)); $$.family = $1; $$.handle.id = $3; - $$.handle.location = @$; + $$.handle.location = @3; } ; @@ -2246,7 +2924,7 @@ chain_spec : table_spec identifier chainid_spec : table_spec HANDLE NUM { $$ = $1; - $$.handle.location = @$; + $$.handle.location = @3; $$.handle.id = $3; } ; @@ -2270,7 +2948,7 @@ set_spec : table_spec identifier setid_spec : table_spec HANDLE NUM { $$ = $1; - $$.handle.location = @$; + $$.handle.location = @3; $$.handle.id = $3; } ; @@ -2294,7 +2972,7 @@ flowtable_spec : table_spec identifier flowtableid_spec : table_spec HANDLE NUM { $$ = $1; - $$.handle.location = @$; + $$.handle.location = @3; $$.handle.id = $3; } ; @@ -2318,7 +2996,7 @@ obj_spec : table_spec identifier objid_spec : table_spec HANDLE NUM { $$ = $1; - $$.handle.location = @$; + $$.handle.location = @3; $$.handle.id = $3; } ; @@ -2334,7 +3012,7 @@ obj_identifier : identifier handle_spec : HANDLE NUM { memset(&$$, 0, sizeof($$)); - $$.handle.location = @$; + $$.handle.location = @2; $$.handle.id = $2; } ; @@ -2392,6 +3070,7 @@ comment_spec : COMMENT string erec_queue(error(&@2, "comment too long, %d characters maximum allowed", NFTNL_UDATA_COMMENT_MAXLEN), state->msgs); + free_const($2); YYERROR; } $$ = $2; @@ -2428,7 +3107,7 @@ rule_alloc : stmt_list list_for_each_entry(i, $1, list) $$->num_stmts++; list_splice_tail($1, &$$->stmts); - xfree($1); + free($1); } ; @@ -2445,10 +3124,78 @@ stmt_list : stmt } ; -stateful_stmt : counter_stmt - | limit_stmt - | quota_stmt - | connlimit_stmt +stateful_stmt_list : stateful_stmt + { + $$ = xmalloc(sizeof(*$$)); + init_list_head($$); + list_add_tail(&$1->list, $$); + } + | stateful_stmt_list stateful_stmt + { + $$ = $1; + list_add_tail(&$2->list, $1); + } + ; + +objref_stmt_counter : COUNTER NAME stmt_expr close_scope_counter + { + $$ = objref_stmt_alloc(&@$); + $$->objref.type = NFT_OBJECT_COUNTER; + $$->objref.expr = $3; + } + ; + +objref_stmt_limit : LIMIT NAME stmt_expr close_scope_limit + { + $$ = objref_stmt_alloc(&@$); + $$->objref.type = NFT_OBJECT_LIMIT; + $$->objref.expr = $3; + } + ; + +objref_stmt_quota : QUOTA NAME stmt_expr close_scope_quota + { + $$ = objref_stmt_alloc(&@$); + $$->objref.type = NFT_OBJECT_QUOTA; + $$->objref.expr = $3; + } + ; + +objref_stmt_synproxy : SYNPROXY NAME stmt_expr close_scope_synproxy + { + $$ = objref_stmt_alloc(&@$); + $$->objref.type = NFT_OBJECT_SYNPROXY; + $$->objref.expr = $3; + } + ; + +objref_stmt_ct : CT TIMEOUT SET stmt_expr close_scope_ct + { + $$ = objref_stmt_alloc(&@$); + $$->objref.type = NFT_OBJECT_CT_TIMEOUT; + $$->objref.expr = $4; + + } + | CT EXPECTATION SET stmt_expr close_scope_ct + { + $$ = objref_stmt_alloc(&@$); + $$->objref.type = NFT_OBJECT_CT_EXPECT; + $$->objref.expr = $4; + } + ; + +objref_stmt : objref_stmt_counter + | objref_stmt_limit + | objref_stmt_quota + | objref_stmt_synproxy + | objref_stmt_ct + ; + +stateful_stmt : counter_stmt close_scope_counter + | limit_stmt close_scope_limit + | quota_stmt close_scope_quota + | connlimit_stmt close_scope_ct + | last_stmt close_scope_last ; stmt : verdict_stmt @@ -2457,19 +3204,47 @@ stmt : verdict_stmt | payload_stmt | stateful_stmt | meta_stmt - | log_stmt - | reject_stmt - | nat_stmt - | tproxy_stmt + | log_stmt close_scope_log + | reject_stmt close_scope_reject + | nat_stmt close_scope_nat + | tproxy_stmt close_scope_tproxy | queue_stmt | ct_stmt - | masq_stmt - | redir_stmt - | dup_stmt - | fwd_stmt + | masq_stmt close_scope_nat + | redir_stmt close_scope_nat + | dup_stmt close_scope_dup + | fwd_stmt close_scope_fwd | set_stmt | map_stmt - | synproxy_stmt + | synproxy_stmt close_scope_synproxy + | chain_stmt + | optstrip_stmt + | xt_stmt close_scope_xt + | objref_stmt + ; + +xt_stmt : XT STRING string + { + $$ = NULL; + free_const($2); + free_const($3); + erec_queue(error(&@$, "unsupported xtables compat expression, use iptables-nft with this ruleset"), + state->msgs); + YYERROR; + } + ; + +chain_stmt_type : JUMP { $$ = NFT_JUMP; } + | GOTO { $$ = NFT_GOTO; } + ; + +chain_stmt : chain_stmt_type chain_block_alloc '{' subchain_block '}' + { + $2->location = @2; + close_scope(state); + $4->location = @4; + $$ = chain_stmt_alloc(&@$, $4, $1); + } ; verdict_stmt : verdict_expr @@ -2511,20 +3286,31 @@ verdict_map_list_expr : verdict_map_list_member_expr verdict_map_list_member_expr: opt_newline set_elem_expr COLON verdict_expr opt_newline { - $$ = mapping_expr_alloc(&@$, $2, $4); + $$ = mapping_expr_alloc(&@2, $2, $4); } ; -connlimit_stmt : CT COUNT NUM +ct_limit_stmt_alloc : CT COUNT { $$ = connlimit_stmt_alloc(&@$); - $$->connlimit.count = $3; } - | CT COUNT OVER NUM + ; + +connlimit_stmt : ct_limit_stmt_alloc ct_limit_args + ; + +ct_limit_args : NUM { - $$ = connlimit_stmt_alloc(&@$); - $$->connlimit.count = $4; - $$->connlimit.flags = NFT_CONNLIMIT_F_INV; + assert($<stmt>0->type == STMT_CONNLIMIT); + + $<stmt>0->connlimit.count = $1; + } + | OVER NUM + { + assert($<stmt>0->type == STMT_CONNLIMIT); + + $<stmt>0->connlimit.count = $2; + $<stmt>0->connlimit.flags = NFT_CONNLIMIT_F_INV; } ; @@ -2535,12 +3321,6 @@ counter_stmt_alloc : COUNTER { $$ = counter_stmt_alloc(&@$); } - | COUNTER NAME stmt_expr - { - $$ = objref_stmt_alloc(&@$); - $$->objref.type = NFT_OBJECT_COUNTER; - $$->objref.expr = $3; - } ; counter_args : counter_arg @@ -2552,14 +3332,38 @@ counter_args : counter_arg counter_arg : PACKETS NUM { + assert($<stmt>0->type == STMT_COUNTER); $<stmt>0->counter.packets = $2; } | BYTES NUM { + assert($<stmt>0->type == STMT_COUNTER); $<stmt>0->counter.bytes = $2; } ; +last_stmt_alloc : LAST + { + $$ = last_stmt_alloc(&@$); + } + ; + +last_stmt : last_stmt_alloc + | last_stmt_alloc last_args + ; + +last_args : USED NEVER + | USED time_spec + { + struct last_stmt *last; + + assert($<stmt>0->type == STMT_LAST); + last = &$<stmt>0->last; + last->used = $2; + last->set = true; + } + ; + log_stmt : log_stmt_alloc | log_stmt_alloc log_args ; @@ -2579,8 +3383,20 @@ log_args : log_arg log_arg : PREFIX string { - $<stmt>0->log.prefix = $2; - $<stmt>0->log.flags |= STMT_LOG_PREFIX; + struct scope *scope = current_scope(state); + struct error_record *erec; + const char *prefix; + + prefix = str_preprocess(state, &@2, scope, $2, &erec); + if (!prefix) { + erec_queue(erec, state->msgs); + free_const($2); + YYERROR; + } + + free_const($2); + $<stmt>0->log.prefix = prefix; + $<stmt>0->log.flags |= STMT_LOG_PREFIX; } | GROUP NUM { @@ -2631,18 +3447,18 @@ level_type : string else { erec_queue(error(&@1, "invalid log level"), state->msgs); - xfree($1); + free_const($1); YYERROR; } - xfree($1); + free_const($1); } ; -log_flags : TCP log_flags_tcp +log_flags : TCP log_flags_tcp close_scope_tcp { $$ = $2; } - | IP OPTIONS + | IP OPTIONS close_scope_ip { $$ = NF_LOG_IPOPT; } @@ -2650,7 +3466,7 @@ log_flags : TCP log_flags_tcp { $$ = NF_LOG_UID; } - | ETHER + | ETHER close_scope_eth { $$ = NF_LOG_MACDECODE; } @@ -2677,39 +3493,45 @@ log_flag_tcp : SEQUENCE } ; -limit_stmt : LIMIT RATE limit_mode NUM SLASH time_unit limit_burst_pkts - { +limit_stmt_alloc : LIMIT RATE + { $$ = limit_stmt_alloc(&@$); - $$->limit.rate = $4; - $$->limit.unit = $6; - $$->limit.burst = $7; - $$->limit.type = NFT_LIMIT_PKTS; - $$->limit.flags = $3; } - | LIMIT RATE limit_mode NUM STRING limit_burst_bytes - { - struct error_record *erec; - uint64_t rate, unit; + ; - erec = rate_parse(&@$, $5, &rate, &unit); - xfree($5); - if (erec != NULL) { - erec_queue(erec, state->msgs); +limit_stmt : limit_stmt_alloc limit_args + ; + +limit_args : limit_mode limit_rate_pkts limit_burst_pkts + { + struct limit_stmt *limit; + + assert($<stmt>0->type == STMT_LIMIT); + + if ($3 == 0) { + erec_queue(error(&@3, "packet limit burst must be > 0"), + state->msgs); YYERROR; } - - $$ = limit_stmt_alloc(&@$); - $$->limit.rate = rate * $4; - $$->limit.unit = unit; - $$->limit.burst = $6; - $$->limit.type = NFT_LIMIT_PKT_BYTES; - $$->limit.flags = $3; + limit = &$<stmt>0->limit; + limit->rate = $2.rate; + limit->unit = $2.unit; + limit->burst = $3; + limit->type = NFT_LIMIT_PKTS; + limit->flags = $1; } - | LIMIT NAME stmt_expr + | limit_mode limit_rate_bytes limit_burst_bytes { - $$ = objref_stmt_alloc(&@$); - $$->objref.type = NFT_OBJECT_LIMIT; - $$->objref.expr = $3; + struct limit_stmt *limit; + + assert($<stmt>0->type == STMT_LIMIT); + + limit = &$<stmt>0->limit; + limit->rate = $2.rate; + limit->unit = $2.unit; + limit->burst = $3; + limit->type = NFT_LIMIT_PKT_BYTES; + limit->flags = $1; } ; @@ -2729,7 +3551,7 @@ quota_used : /* empty */ { $$ = 0; } uint64_t rate; erec = data_unit_parse(&@$, $3, &rate); - xfree($3); + free_const($3); if (erec != NULL) { erec_queue(erec, state->msgs); YYERROR; @@ -2738,27 +3560,33 @@ quota_used : /* empty */ { $$ = 0; } } ; -quota_stmt : QUOTA quota_mode NUM quota_unit quota_used +quota_stmt_alloc : QUOTA + { + $$ = quota_stmt_alloc(&@$); + } + ; + +quota_stmt : quota_stmt_alloc quota_args + ; + +quota_args : quota_mode NUM quota_unit quota_used { struct error_record *erec; + struct quota_stmt *quota; uint64_t rate; - erec = data_unit_parse(&@$, $4, &rate); - xfree($4); + assert($<stmt>0->type == STMT_QUOTA); + + erec = data_unit_parse(&@$, $3, &rate); + free_const($3); if (erec != NULL) { erec_queue(erec, state->msgs); YYERROR; } - $$ = quota_stmt_alloc(&@$); - $$->quota.bytes = $3 * rate; - $$->quota.used = $5; - $$->quota.flags = $2; - } - | QUOTA NAME stmt_expr - { - $$ = objref_stmt_alloc(&@$); - $$->objref.type = NFT_OBJECT_QUOTA; - $$->objref.expr = $3; + quota = &$<stmt>0->quota; + quota->bytes = $2 * rate; + quota->used = $4; + quota->flags = $1; } ; @@ -2767,24 +3595,55 @@ limit_mode : OVER { $$ = NFT_LIMIT_F_INV; } | /* empty */ { $$ = 0; } ; -limit_burst_pkts : /* empty */ { $$ = 0; } +limit_burst_pkts : /* empty */ { $$ = 5; } | BURST NUM PACKETS { $$ = $2; } ; +limit_rate_pkts : NUM SLASH time_unit + { + $$.rate = $1; + $$.unit = $3; + } + ; + limit_burst_bytes : /* empty */ { $$ = 0; } - | BURST NUM BYTES { $$ = $2; } - | BURST NUM STRING + | BURST limit_bytes { $$ = $2; } + ; + +limit_rate_bytes : NUM STRING + { + struct error_record *erec; + uint64_t rate, unit; + + erec = rate_parse(&@$, $2, &rate, &unit); + free_const($2); + if (erec != NULL) { + erec_queue(erec, state->msgs); + YYERROR; + } + $$.rate = rate * $1; + $$.unit = unit; + } + | limit_bytes SLASH time_unit + { + $$.rate = $1; + $$.unit = $3; + } + ; + +limit_bytes : NUM BYTES { $$ = $1; } + | NUM STRING { struct error_record *erec; uint64_t rate; - erec = data_unit_parse(&@$, $3, &rate); - xfree($3); + erec = data_unit_parse(&@$, $2, &rate); + free_const($2); if (erec != NULL) { erec_queue(erec, state->msgs); YYERROR; } - $$ = $2 * rate; + $$ = $1 * rate; } ; @@ -2804,44 +3663,61 @@ reject_stmt_alloc : _REJECT } ; +reject_with_expr : STRING + { + $$ = symbol_expr_alloc(&@$, SYMBOL_VALUE, + current_scope(state), $1); + free_const($1); + } + | integer_expr { $$ = $1; } + ; + reject_opts : /* empty */ { $<stmt>0->reject.type = -1; $<stmt>0->reject.icmp_code = -1; } - | WITH ICMP TYPE STRING + | WITH ICMP TYPE reject_with_expr close_scope_type close_scope_icmp { $<stmt>0->reject.family = NFPROTO_IPV4; $<stmt>0->reject.type = NFT_REJECT_ICMP_UNREACH; - $<stmt>0->reject.expr = - symbol_expr_alloc(&@$, SYMBOL_VALUE, - current_scope(state), - $4); - datatype_set($<stmt>0->reject.expr, &icmp_code_type); - xfree($4); + $<stmt>0->reject.expr = $4; + datatype_set($<stmt>0->reject.expr, &reject_icmp_code_type); } - | WITH ICMP6 TYPE STRING + | WITH ICMP reject_with_expr + { + $<stmt>0->reject.family = NFPROTO_IPV4; + $<stmt>0->reject.type = NFT_REJECT_ICMP_UNREACH; + $<stmt>0->reject.expr = $3; + datatype_set($<stmt>0->reject.expr, &reject_icmp_code_type); + } + | WITH ICMP6 TYPE reject_with_expr close_scope_type close_scope_icmp { $<stmt>0->reject.family = NFPROTO_IPV6; $<stmt>0->reject.type = NFT_REJECT_ICMP_UNREACH; - $<stmt>0->reject.expr = - symbol_expr_alloc(&@$, SYMBOL_VALUE, - current_scope(state), - $4); - datatype_set($<stmt>0->reject.expr, &icmpv6_code_type); - xfree($4); + $<stmt>0->reject.expr = $4; + datatype_set($<stmt>0->reject.expr, &reject_icmpv6_code_type); } - | WITH ICMPX TYPE STRING + | WITH ICMP6 reject_with_expr + { + $<stmt>0->reject.family = NFPROTO_IPV6; + $<stmt>0->reject.type = NFT_REJECT_ICMP_UNREACH; + $<stmt>0->reject.expr = $3; + datatype_set($<stmt>0->reject.expr, &reject_icmpv6_code_type); + } + | WITH ICMPX TYPE reject_with_expr close_scope_type { $<stmt>0->reject.type = NFT_REJECT_ICMPX_UNREACH; - $<stmt>0->reject.expr = - symbol_expr_alloc(&@$, SYMBOL_VALUE, - current_scope(state), - $4); - datatype_set($<stmt>0->reject.expr, &icmpx_code_type); - xfree($4); + $<stmt>0->reject.expr = $4; + datatype_set($<stmt>0->reject.expr, &reject_icmpx_code_type); } - | WITH TCP RESET + | WITH ICMPX reject_with_expr + { + $<stmt>0->reject.type = NFT_REJECT_ICMPX_UNREACH; + $<stmt>0->reject.expr = $3; + datatype_set($<stmt>0->reject.expr, &reject_icmpx_code_type); + } + | WITH TCP close_scope_tcp RESET close_scope_reset { $<stmt>0->reject.type = NFT_REJECT_TCP_RST; } @@ -2850,8 +3726,8 @@ reject_opts : /* empty */ nat_stmt : nat_stmt_alloc nat_stmt_args ; -nat_stmt_alloc : SNAT { $$ = nat_stmt_alloc(&@$, NFT_NAT_SNAT); } - | DNAT { $$ = nat_stmt_alloc(&@$, NFT_NAT_DNAT); } +nat_stmt_alloc : SNAT { $$ = nat_stmt_alloc(&@$, __NFT_NAT_SNAT); } + | DNAT { $$ = nat_stmt_alloc(&@$, __NFT_NAT_DNAT); } ; tproxy_stmt : TPROXY TO stmt_expr @@ -2902,12 +3778,6 @@ synproxy_stmt_alloc : SYNPROXY { $$ = synproxy_stmt_alloc(&@$); } - | SYNPROXY NAME stmt_expr - { - $$ = objref_stmt_alloc(&@$); - $$->objref.type = NFT_OBJECT_SYNPROXY; - $$->objref.expr = $3; - } ; synproxy_args : synproxy_arg @@ -2931,7 +3801,7 @@ synproxy_arg : MSS NUM { $<stmt>0->synproxy.flags |= NF_SYNPROXY_OPT_TIMESTAMP; } - | SACKPERM + | SACK_PERM { $<stmt>0->synproxy.flags |= NF_SYNPROXY_OPT_SACK_PERM; } @@ -2986,24 +3856,26 @@ synproxy_ts : /* empty */ { $$ = 0; } ; synproxy_sack : /* empty */ { $$ = 0; } - | SACKPERM + | SACK_PERM { $$ = NF_SYNPROXY_OPT_SACK_PERM; } ; -primary_stmt_expr : symbol_expr { $$ = $1; } - | integer_expr { $$ = $1; } - | boolean_expr { $$ = $1; } - | meta_expr { $$ = $1; } - | rt_expr { $$ = $1; } - | ct_expr { $$ = $1; } - | numgen_expr { $$ = $1; } - | hash_expr { $$ = $1; } - | payload_expr { $$ = $1; } - | keyword_expr { $$ = $1; } - | socket_expr { $$ = $1; } - | osf_expr { $$ = $1; } +primary_stmt_expr : symbol_expr { $$ = $1; } + | integer_expr { $$ = $1; } + | boolean_expr { $$ = $1; } + | meta_expr { $$ = $1; } + | rt_expr { $$ = $1; } + | ct_expr { $$ = $1; } + | numgen_expr { $$ = $1; } + | hash_expr { $$ = $1; } + | payload_expr { $$ = $1; } + | keyword_expr { $$ = $1; } + | socket_expr { $$ = $1; } + | fib_expr { $$ = $1; } + | osf_expr { $$ = $1; } + | '(' basic_stmt_expr ')' { $$ = $2; } ; shift_stmt_expr : primary_stmt_expr @@ -3011,7 +3883,7 @@ shift_stmt_expr : primary_stmt_expr { $$ = binop_expr_alloc(&@$, OP_LSHIFT, $1, $3); } - | shift_stmt_expr RSHIFT primary_rhs_expr + | shift_stmt_expr RSHIFT primary_stmt_expr { $$ = binop_expr_alloc(&@$, OP_RSHIFT, $1, $3); } @@ -3076,20 +3948,8 @@ range_stmt_expr : basic_stmt_expr DASH basic_stmt_expr } ; -wildcard_expr : ASTERISK - { - struct expr *expr; - - expr = constant_expr_alloc(&@$, &integer_type, - BYTEORDER_HOST_ENDIAN, - 0, NULL); - $$ = prefix_expr_alloc(&@$, expr, 0); - } - ; - multiton_stmt_expr : prefix_stmt_expr | range_stmt_expr - | wildcard_expr ; stmt_expr : map_stmt_expr @@ -3138,6 +3998,36 @@ nat_stmt_args : stmt_expr { $<stmt>0->nat.flags = $2; } + | nf_key_proto ADDR DOT PORT TO stmt_expr + { + $<stmt>0->nat.family = $1; + $<stmt>0->nat.addr = $6; + $<stmt>0->nat.type_flags = STMT_NAT_F_CONCAT; + } + | nf_key_proto INTERVAL TO stmt_expr + { + $<stmt>0->nat.family = $1; + $<stmt>0->nat.addr = $4; + } + | INTERVAL TO stmt_expr + { + $<stmt>0->nat.addr = $3; + } + | nf_key_proto PREFIX TO stmt_expr + { + $<stmt>0->nat.family = $1; + $<stmt>0->nat.addr = $4; + $<stmt>0->nat.type_flags = + STMT_NAT_F_PREFIX; + $<stmt>0->nat.flags |= NF_NAT_RANGE_NETMAP; + } + | PREFIX TO stmt_expr + { + $<stmt>0->nat.addr = $3; + $<stmt>0->nat.type_flags = + STMT_NAT_F_PREFIX; + $<stmt>0->nat.flags |= NF_NAT_RANGE_NETMAP; + } ; masq_stmt : masq_stmt_alloc masq_stmt_args @@ -3232,13 +4122,28 @@ nf_nat_flag : RANDOM { $$ = NF_NAT_RANGE_PROTO_RANDOM; } | PERSISTENT { $$ = NF_NAT_RANGE_PERSISTENT; } ; -queue_stmt : queue_stmt_alloc +queue_stmt : queue_stmt_compat close_scope_queue + | QUEUE TO queue_stmt_expr close_scope_queue + { + $$ = queue_stmt_alloc(&@$, $3, 0); + } + | QUEUE FLAGS queue_stmt_flags TO queue_stmt_expr close_scope_queue + { + $$ = queue_stmt_alloc(&@$, $5, $3); + } + | QUEUE FLAGS queue_stmt_flags QUEUENUM queue_stmt_expr_simple close_scope_queue + { + $$ = queue_stmt_alloc(&@$, $5, $3); + } + ; + +queue_stmt_compat : queue_stmt_alloc | queue_stmt_alloc queue_stmt_args ; queue_stmt_alloc : QUEUE { - $$ = queue_stmt_alloc(&@$); + $$ = queue_stmt_alloc(&@$, NULL, 0); } ; @@ -3249,7 +4154,7 @@ queue_stmt_args : queue_stmt_arg | queue_stmt_args queue_stmt_arg ; -queue_stmt_arg : QUEUENUM stmt_expr +queue_stmt_arg : QUEUENUM queue_stmt_expr_simple { $<stmt>0->queue.queue = $2; $<stmt>0->queue.queue->location = @$; @@ -3260,6 +4165,24 @@ queue_stmt_arg : QUEUENUM stmt_expr } ; +queue_expr : variable_expr + | integer_expr + ; + +queue_stmt_expr_simple : integer_expr + | variable_expr + | queue_expr DASH queue_expr + { + $$ = range_expr_alloc(&@$, $1, $3); + } + ; + +queue_stmt_expr : numgen_expr + | hash_expr + | map_expr + | queue_stmt_expr_simple + ; + queue_stmt_flags : queue_stmt_flag | queue_stmt_flags COMMA queue_stmt_flag { @@ -3295,13 +4218,14 @@ set_stmt : SET set_stmt_op set_elem_expr_stmt set_ref_expr $$->set.key = $4; $$->set.set = $2; } - | set_stmt_op set_ref_expr '{' set_elem_expr_stmt stateful_stmt '}' + | set_stmt_op set_ref_expr '{' set_elem_expr_stmt stateful_stmt_list '}' { $$ = set_stmt_alloc(&@$); $$->set.op = $1; $$->set.key = $4; $$->set.set = $2; - $$->set.stmt = $5; + list_splice_tail($5, &$$->set.stmt_list); + free($5); } ; @@ -3318,47 +4242,19 @@ map_stmt : set_stmt_op set_ref_expr '{' set_elem_expr_stmt COLON set_elem_expr_ $$->map.data = $6; $$->map.set = $2; } - | set_stmt_op set_ref_expr '{' set_elem_expr_stmt stateful_stmt COLON set_elem_expr_stmt '}' + | set_stmt_op set_ref_expr '{' set_elem_expr_stmt stateful_stmt_list COLON set_elem_expr_stmt '}' { $$ = map_stmt_alloc(&@$); $$->map.op = $1; $$->map.key = $4; $$->map.data = $7; - $$->map.stmt = $5; $$->map.set = $2; + list_splice_tail($5, &$$->map.stmt_list); + free($5); } ; -meter_stmt : flow_stmt_legacy_alloc flow_stmt_opts '{' meter_key_expr stmt '}' - { - $1->meter.key = $4; - $1->meter.stmt = $5; - $$->location = @$; - $$ = $1; - } - | meter_stmt_alloc { $$ = $1; } - ; - -flow_stmt_legacy_alloc : FLOW - { - $$ = meter_stmt_alloc(&@$); - } - ; - -flow_stmt_opts : flow_stmt_opt - { - $<stmt>$ = $<stmt>0; - } - | flow_stmt_opts flow_stmt_opt - ; - -flow_stmt_opt : TABLE identifier - { - $<stmt>0->meter.name = $2; - } - ; - -meter_stmt_alloc : METER identifier '{' meter_key_expr stmt '}' +meter_stmt : METER identifier '{' meter_key_expr stmt '}' { $$ = meter_stmt_alloc(&@$); $$->meter.name = $2; @@ -3394,19 +4290,19 @@ variable_expr : '$' identifier sym = symbol_lookup_fuzzy(scope, $2); if (sym) { erec_queue(error(&@2, "unknown identifier '%s'; " - "did you mean identifier ‘%s’?", + "did you mean identifier '%s’?", $2, sym->identifier), state->msgs); } else { erec_queue(error(&@2, "unknown identifier '%s'", $2), state->msgs); } - xfree($2); + free_const($2); YYERROR; } $$ = variable_expr_alloc(&@$, scope, sym); - xfree($2); + free_const($2); } ; @@ -3416,7 +4312,7 @@ symbol_expr : variable_expr $$ = symbol_expr_alloc(&@$, SYMBOL_VALUE, current_scope(state), $1); - xfree($1); + free_const($1); } ; @@ -3424,12 +4320,12 @@ set_ref_expr : set_ref_symbol_expr | variable_expr ; -set_ref_symbol_expr : AT identifier +set_ref_symbol_expr : AT identifier close_scope_at { $$ = symbol_expr_alloc(&@$, SYMBOL_SET, current_scope(state), $2); - xfree($2); + free_const($2); } ; @@ -3444,9 +4340,7 @@ integer_expr : NUM } ; -primary_expr : symbol_expr { $$ = $1; } - | integer_expr { $$ = $1; } - | payload_expr { $$ = $1; } +selector_expr : payload_expr { $$ = $1; } | exthdr_expr { $$ = $1; } | exthdr_exists_expr { $$ = $1; } | meta_expr { $$ = $1; } @@ -3458,35 +4352,48 @@ primary_expr : symbol_expr { $$ = $1; } | fib_expr { $$ = $1; } | osf_expr { $$ = $1; } | xfrm_expr { $$ = $1; } + ; + +primary_expr : symbol_expr { $$ = $1; } + | integer_expr { $$ = $1; } + | selector_expr { $$ = $1; } | '(' basic_expr ')' { $$ = $2; } ; -fib_expr : FIB fib_tuple fib_result +fib_expr : FIB fib_tuple fib_result close_scope_fib { - if (($2 & (NFTA_FIB_F_SADDR|NFTA_FIB_F_DADDR)) == 0) { + uint32_t flags = $2, result = $3; + + if (result == __NFT_FIB_RESULT_MAX) { + result = NFT_FIB_RESULT_OIF; + flags |= NFTA_FIB_F_PRESENT; + } + + if ((flags & (NFTA_FIB_F_SADDR|NFTA_FIB_F_DADDR)) == 0) { erec_queue(error(&@2, "fib: need either saddr or daddr"), state->msgs); YYERROR; } - if (($2 & (NFTA_FIB_F_SADDR|NFTA_FIB_F_DADDR)) == - (NFTA_FIB_F_SADDR|NFTA_FIB_F_DADDR)) { + if ((flags & (NFTA_FIB_F_SADDR|NFTA_FIB_F_DADDR)) == + (NFTA_FIB_F_SADDR|NFTA_FIB_F_DADDR)) { erec_queue(error(&@2, "fib: saddr and daddr are mutually exclusive"), state->msgs); YYERROR; } - if (($2 & (NFTA_FIB_F_IIF|NFTA_FIB_F_OIF)) == - (NFTA_FIB_F_IIF|NFTA_FIB_F_OIF)) { + if ((flags & (NFTA_FIB_F_IIF|NFTA_FIB_F_OIF)) == + (NFTA_FIB_F_IIF|NFTA_FIB_F_OIF)) { erec_queue(error(&@2, "fib: iif and oif are mutually exclusive"), state->msgs); YYERROR; } - $$ = fib_expr_alloc(&@$, $2, $3); + $$ = fib_expr_alloc(&@$, flags, result); } ; fib_result : OIF { $$ =NFT_FIB_RESULT_OIF; } | OIFNAME { $$ =NFT_FIB_RESULT_OIFNAME; } - | TYPE { $$ =NFT_FIB_RESULT_ADDRTYPE; } + | TYPE close_scope_type { $$ =NFT_FIB_RESULT_ADDRTYPE; } + | CHECK { $$ = __NFT_FIB_RESULT_MAX; } /* actually, NFT_FIB_F_PRESENT. */ ; fib_flag : SADDR { $$ = NFTA_FIB_F_SADDR; } @@ -3503,11 +4410,11 @@ fib_tuple : fib_flag DOT fib_tuple | fib_flag ; -osf_expr : OSF osf_ttl HDRVERSION +osf_expr : OSF osf_ttl HDRVERSION close_scope_osf { $$ = osf_expr_alloc(&@$, $2, NFT_OSF_F_VERSION); } - | OSF osf_ttl NAME + | OSF osf_ttl NAME close_scope_osf { $$ = osf_expr_alloc(&@$, $2, 0); } @@ -3526,8 +4433,10 @@ osf_ttl : /* empty */ else { erec_queue(error(&@2, "invalid ttl option"), state->msgs); + free_const($2); YYERROR; } + free_const($2); } ; @@ -3586,13 +4495,21 @@ prefix_rhs_expr : basic_rhs_expr SLASH NUM range_rhs_expr : basic_rhs_expr DASH basic_rhs_expr { - $$ = range_expr_alloc(&@$, $1, $3); + if ($1->etype == EXPR_SYMBOL && + $1->symtype == SYMBOL_VALUE && + $3->etype == EXPR_SYMBOL && + $3->symtype == SYMBOL_VALUE) { + $$ = symbol_range_expr_alloc(&@$, $1->symtype, $1->scope, $1->identifier, $3->identifier); + expr_free($1); + expr_free($3); + } else { + $$ = range_expr_alloc(&@$, $1, $3); + } } ; multiton_rhs_expr : prefix_rhs_expr | range_rhs_expr - | wildcard_expr ; map_expr : concat_expr MAP rhs_expr @@ -3636,7 +4553,7 @@ set_list_member_expr : opt_newline set_expr opt_newline } | opt_newline set_elem_expr COLON set_rhs_expr opt_newline { - $$ = mapping_expr_alloc(&@$, $2, $4); + $$ = mapping_expr_alloc(&@2, $2, $4); } ; @@ -3655,10 +4572,26 @@ meter_key_expr_alloc : concat_expr ; set_elem_expr : set_elem_expr_alloc - | set_elem_expr_alloc set_elem_options + | set_elem_expr_alloc set_elem_expr_options + | set_elem_expr_alloc set_elem_expr_options set_elem_stmt_list + { + $$ = $1; + list_splice_tail($3, &$$->stmt_list); + free($3); + } + ; + +set_elem_key_expr : set_lhs_expr { $$ = $1; } + | ASTERISK { $$ = set_elem_catchall_expr_alloc(&@1); } ; -set_elem_expr_alloc : set_lhs_expr +set_elem_expr_alloc : set_elem_key_expr set_elem_stmt_list + { + $$ = set_elem_expr_alloc(&@1, $1); + list_splice_tail($2, &$$->stmt_list); + free($2); + } + | set_elem_key_expr { $$ = set_elem_expr_alloc(&@1, $1); } @@ -3671,7 +4604,28 @@ set_elem_options : set_elem_option | set_elem_options set_elem_option ; -set_elem_option : TIMEOUT time_spec +set_elem_time_spec : STRING + { + struct error_record *erec; + uint64_t res; + + if (!strcmp("never", $1)) { + free_const($1); + $$ = NFT_NEVER_TIMEOUT; + break; + } + + erec = time_parse(&@1, $1, &res); + free_const($1); + if (erec != NULL) { + erec_queue(erec, state->msgs); + YYERROR; + } + $$ = res; + } + ; + +set_elem_option : TIMEOUT time_spec { $<expr>0->timeout = $2; } @@ -3681,12 +4635,60 @@ set_elem_option : TIMEOUT time_spec } | comment_spec { + if (already_set($<expr>0->comment, &@1, state)) { + free_const($1); + YYERROR; + } + $<expr>0->comment = $1; + } + ; + +set_elem_expr_options : set_elem_expr_option + { + $<expr>$ = $<expr>0; + } + | set_elem_expr_options set_elem_expr_option + ; + +set_elem_stmt_list : set_elem_stmt + { + $$ = xmalloc(sizeof(*$$)); + init_list_head($$); + list_add_tail(&$1->list, $$); + } + | set_elem_stmt_list set_elem_stmt + { + $$ = $1; + list_add_tail(&$2->list, $1); + } + ; + +set_elem_stmt : counter_stmt close_scope_counter + | limit_stmt close_scope_limit + | connlimit_stmt close_scope_ct + | quota_stmt close_scope_quota + | last_stmt close_scope_last + ; + +set_elem_expr_option : TIMEOUT set_elem_time_spec + { + $<expr>0->timeout = $2; + } + | EXPIRES time_spec + { + $<expr>0->expiration = $2; + } + | comment_spec + { + if (already_set($<expr>0->comment, &@1, state)) { + free_const($1); + YYERROR; + } $<expr>0->comment = $1; } ; set_lhs_expr : concat_rhs_expr - | multiton_rhs_expr ; set_rhs_expr : concat_rhs_expr @@ -3695,6 +4697,16 @@ set_rhs_expr : concat_rhs_expr initializer_expr : rhs_expr | list_rhs_expr + | '{' '}' { $$ = compound_expr_alloc(&@$, EXPR_SET); } + | DASH NUM + { + int32_t num = -$2; + + $$ = constant_expr_alloc(&@$, &integer_type, + BYTEORDER_HOST_ENDIAN, + sizeof(num) * BITS_PER_BYTE, + &num); + } ; counter_config : PACKETS NUM BYTES NUM @@ -3721,7 +4733,7 @@ quota_config : quota_mode NUM quota_unit quota_used uint64_t rate; erec = data_unit_parse(&@$, $3, &rate); - xfree($3); + free_const($3); if (erec != NULL) { erec_queue(erec, state->msgs); YYERROR; @@ -3750,10 +4762,10 @@ secmark_config : string ret = snprintf(secmark->ctx, sizeof(secmark->ctx), "%s", $1); if (ret <= 0 || ret >= (int)sizeof(secmark->ctx)) { erec_queue(error(&@1, "invalid context '%s', max length is %u\n", $1, (int)sizeof(secmark->ctx)), state->msgs); - xfree($1); + free_const($1); YYERROR; } - xfree($1); + free_const($1); } ; @@ -3769,22 +4781,35 @@ ct_obj_type : HELPER { $$ = NFT_OBJECT_CT_HELPER; } | EXPECTATION { $$ = NFT_OBJECT_CT_EXPECT; } ; -ct_l4protoname : TCP { $$ = IPPROTO_TCP; } - | UDP { $$ = IPPROTO_UDP; } +ct_cmd_type : HELPERS { $$ = CMD_OBJ_CT_HELPERS; } + | TIMEOUT { $$ = CMD_OBJ_CT_TIMEOUTS; } + | EXPECTATION { $$ = CMD_OBJ_CT_EXPECTATIONS; } + ; + +ct_l4protoname : TCP close_scope_tcp { $$ = IPPROTO_TCP; } + | UDP close_scope_udp { $$ = IPPROTO_UDP; } ; -ct_helper_config : TYPE QUOTED_STRING PROTOCOL ct_l4protoname stmt_separator +ct_helper_config : TYPE QUOTED_STRING PROTOCOL ct_l4protoname stmt_separator close_scope_type { struct ct_helper *ct; int ret; ct = &$<obj>0->ct_helper; + if (ct->l4proto) { + erec_queue(error(&@2, "You can only specify this once. This statement is already set for %s.", ct->name), state->msgs); + free_const($2); + YYERROR; + } + ret = snprintf(ct->name, sizeof(ct->name), "%s", $2); if (ret <= 0 || ret >= (int)sizeof(ct->name)) { erec_queue(error(&@2, "invalid name '%s', max length is %u\n", $2, (int)sizeof(ct->name)), state->msgs); + free_const($2); YYERROR; } + free_const($2); ct->l4proto = $4; } @@ -3798,17 +4823,16 @@ timeout_states : timeout_state { $$ = xmalloc(sizeof(*$$)); init_list_head($$); - list_add_tail($1, $$); + list_add_tail(&$1->head, $$); } | timeout_states COMMA timeout_state { - list_add_tail($3, $1); + list_add_tail(&$3->head, $1); $$ = $1; } ; -timeout_state : STRING COLON NUM - +timeout_state : STRING COLON time_spec_or_num_s { struct timeout_state *ts; @@ -3817,7 +4841,7 @@ timeout_state : STRING COLON NUM ts->timeout_value = $3; ts->location = @1; init_list_head(&ts->head); - $$ = &ts->head; + $$ = ts; } ; @@ -3829,13 +4853,13 @@ ct_timeout_config : PROTOCOL ct_l4protoname stmt_separator ct = &$<obj>0->ct_timeout; ct->l4proto = l4proto; } - | POLICY '=' '{' timeout_states '}' stmt_separator + | POLICY '=' '{' timeout_states '}' stmt_separator close_scope_policy { struct ct_timeout *ct; ct = &$<obj>0->ct_timeout; - init_list_head(&ct->timeout_list); list_splice_tail($4, &ct->timeout_list); + free($4); } | L3PROTOCOL family_spec_explicit stmt_separator { @@ -3871,33 +4895,25 @@ ct_obj_alloc : /* empty */ } ; -limit_config : RATE limit_mode NUM SLASH time_unit limit_burst_pkts +limit_config : RATE limit_mode limit_rate_pkts limit_burst_pkts { struct limit *limit; limit = &$<obj>0->limit; - limit->rate = $3; - limit->unit = $5; - limit->burst = $6; + limit->rate = $3.rate; + limit->unit = $3.unit; + limit->burst = $4; limit->type = NFT_LIMIT_PKTS; limit->flags = $2; } - | RATE limit_mode NUM STRING limit_burst_bytes + | RATE limit_mode limit_rate_bytes limit_burst_bytes { struct limit *limit; - struct error_record *erec; - uint64_t rate, unit; - - erec = rate_parse(&@$, $4, &rate, &unit); - if (erec != NULL) { - erec_queue(erec, state->msgs); - YYERROR; - } limit = &$<obj>0->limit; - limit->rate = rate * $3; - limit->unit = unit; - limit->burst = $5; + limit->rate = $3.rate; + limit->unit = $3.unit; + limit->burst = $4; limit->type = NFT_LIMIT_PKT_BYTES; limit->flags = $2; } @@ -3918,10 +4934,44 @@ relational_expr : expr /* implicit */ rhs_expr { $$ = relational_expr_alloc(&@$, OP_IMPLICIT, $1, $2); } + | expr /* implicit */ basic_rhs_expr SLASH list_rhs_expr + { + struct expr *mask = list_expr_to_binop($4); + struct expr *binop = binop_expr_alloc(&@$, OP_AND, $1, mask); + + $$ = relational_expr_alloc(&@$, OP_IMPLICIT, binop, $2); + } + | expr /* implicit */ list_rhs_expr SLASH list_rhs_expr + { + struct expr *value = list_expr_to_binop($2); + struct expr *mask = list_expr_to_binop($4); + struct expr *binop = binop_expr_alloc(&@$, OP_AND, $1, mask); + + $$ = relational_expr_alloc(&@$, OP_IMPLICIT, binop, value); + } + | expr relational_op basic_rhs_expr SLASH list_rhs_expr + { + struct expr *mask = list_expr_to_binop($5); + struct expr *binop = binop_expr_alloc(&@$, OP_AND, $1, mask); + + $$ = relational_expr_alloc(&@$, $2, binop, $3); + } + | expr relational_op list_rhs_expr SLASH list_rhs_expr + { + struct expr *value = list_expr_to_binop($3); + struct expr *mask = list_expr_to_binop($5); + struct expr *binop = binop_expr_alloc(&@$, OP_AND, $1, mask); + + $$ = relational_expr_alloc(&@$, $2, binop, value); + } | expr relational_op rhs_expr { $$ = relational_expr_alloc(&@2, $2, $1, $3); } + | expr relational_op list_rhs_expr + { + $$ = relational_expr_alloc(&@2, $2, $1, $3); + } ; list_rhs_expr : basic_rhs_expr COMMA basic_rhs_expr @@ -3939,7 +4989,6 @@ list_rhs_expr : basic_rhs_expr COMMA basic_rhs_expr ; rhs_expr : concat_rhs_expr { $$ = $1; } - | multiton_rhs_expr { $$ = $1; } | set_expr { $$ = $1; } | set_ref_symbol_expr { $$ = $1; } ; @@ -3980,7 +5029,17 @@ basic_rhs_expr : inclusive_or_rhs_expr ; concat_rhs_expr : basic_rhs_expr - | concat_rhs_expr DOT basic_rhs_expr + | multiton_rhs_expr + | concat_rhs_expr DOT multiton_rhs_expr + { + struct location rhs[] = { + [1] = @2, + [2] = @3, + }; + + $$ = handle_concat_expr(&@$, $$, $1, $3, rhs); + } + | concat_rhs_expr DOT basic_rhs_expr { struct location rhs[] = { [1] = @2, @@ -4003,60 +5062,62 @@ boolean_expr : boolean_keys } ; -keyword_expr : ETHER { $$ = symbol_value(&@$, "ether"); } - | IP { $$ = symbol_value(&@$, "ip"); } - | IP6 { $$ = symbol_value(&@$, "ip6"); } - | VLAN { $$ = symbol_value(&@$, "vlan"); } - | ARP { $$ = symbol_value(&@$, "arp"); } - | DNAT { $$ = symbol_value(&@$, "dnat"); } - | SNAT { $$ = symbol_value(&@$, "snat"); } +keyword_expr : ETHER close_scope_eth { $$ = symbol_value(&@$, "ether"); } + | IP close_scope_ip { $$ = symbol_value(&@$, "ip"); } + | IP6 close_scope_ip6 { $$ = symbol_value(&@$, "ip6"); } + | VLAN close_scope_vlan { $$ = symbol_value(&@$, "vlan"); } + | ARP close_scope_arp { $$ = symbol_value(&@$, "arp"); } + | DNAT close_scope_nat { $$ = symbol_value(&@$, "dnat"); } + | SNAT close_scope_nat { $$ = symbol_value(&@$, "snat"); } | ECN { $$ = symbol_value(&@$, "ecn"); } - | RESET { $$ = symbol_value(&@$, "reset"); } + | RESET close_scope_reset { $$ = symbol_value(&@$, "reset"); } + | DESTROY close_scope_destroy { $$ = symbol_value(&@$, "destroy"); } | ORIGINAL { $$ = symbol_value(&@$, "original"); } | REPLY { $$ = symbol_value(&@$, "reply"); } | LABEL { $$ = symbol_value(&@$, "label"); } + | LAST close_scope_last { $$ = symbol_value(&@$, "last"); } ; primary_rhs_expr : symbol_expr { $$ = $1; } | integer_expr { $$ = $1; } | boolean_expr { $$ = $1; } | keyword_expr { $$ = $1; } - | TCP + | TCP close_scope_tcp { uint8_t data = IPPROTO_TCP; $$ = constant_expr_alloc(&@$, &inet_protocol_type, BYTEORDER_HOST_ENDIAN, sizeof(data) * BITS_PER_BYTE, &data); } - | UDP + | UDP close_scope_udp { uint8_t data = IPPROTO_UDP; $$ = constant_expr_alloc(&@$, &inet_protocol_type, BYTEORDER_HOST_ENDIAN, sizeof(data) * BITS_PER_BYTE, &data); } - | UDPLITE + | UDPLITE close_scope_udplite { uint8_t data = IPPROTO_UDPLITE; $$ = constant_expr_alloc(&@$, &inet_protocol_type, BYTEORDER_HOST_ENDIAN, sizeof(data) * BITS_PER_BYTE, &data); } - | ESP + | ESP close_scope_esp { uint8_t data = IPPROTO_ESP; $$ = constant_expr_alloc(&@$, &inet_protocol_type, BYTEORDER_HOST_ENDIAN, sizeof(data) * BITS_PER_BYTE, &data); } - | AH + | AH close_scope_ah { uint8_t data = IPPROTO_AH; $$ = constant_expr_alloc(&@$, &inet_protocol_type, BYTEORDER_HOST_ENDIAN, sizeof(data) * BITS_PER_BYTE, &data); } - | ICMP + | ICMP close_scope_icmp { uint8_t data = IPPROTO_ICMP; $$ = constant_expr_alloc(&@$, &inet_protocol_type, @@ -4070,35 +5131,42 @@ primary_rhs_expr : symbol_expr { $$ = $1; } BYTEORDER_HOST_ENDIAN, sizeof(data) * BITS_PER_BYTE, &data); } - | ICMP6 + | ICMP6 close_scope_icmp { uint8_t data = IPPROTO_ICMPV6; $$ = constant_expr_alloc(&@$, &inet_protocol_type, BYTEORDER_HOST_ENDIAN, sizeof(data) * BITS_PER_BYTE, &data); } - | COMP + | GRE close_scope_gre + { + uint8_t data = IPPROTO_GRE; + $$ = constant_expr_alloc(&@$, &inet_protocol_type, + BYTEORDER_HOST_ENDIAN, + sizeof(data) * BITS_PER_BYTE, &data); + } + | COMP close_scope_comp { uint8_t data = IPPROTO_COMP; $$ = constant_expr_alloc(&@$, &inet_protocol_type, BYTEORDER_HOST_ENDIAN, sizeof(data) * BITS_PER_BYTE, &data); } - | DCCP + | DCCP close_scope_dccp { uint8_t data = IPPROTO_DCCP; $$ = constant_expr_alloc(&@$, &inet_protocol_type, BYTEORDER_HOST_ENDIAN, sizeof(data) * BITS_PER_BYTE, &data); } - | SCTP + | SCTP close_scope_sctp { uint8_t data = IPPROTO_SCTP; $$ = constant_expr_alloc(&@$, &inet_protocol_type, BYTEORDER_HOST_ENDIAN, sizeof(data) * BITS_PER_BYTE, &data); } - | REDIRECT + | REDIRECT close_scope_nat { uint8_t data = ICMP_REDIRECT; $$ = constant_expr_alloc(&@$, &icmp_type_type, @@ -4114,6 +5182,7 @@ relational_op : EQ { $$ = OP_EQ; } | GT { $$ = OP_GT; } | GTE { $$ = OP_GTE; } | LTE { $$ = OP_LTE; } + | NOT { $$ = OP_NEG; } ; verdict_expr : ACCEPT @@ -4149,11 +5218,11 @@ chain_expr : variable_expr BYTEORDER_HOST_ENDIAN, strlen($1) * BITS_PER_BYTE, $1); - xfree($1); + free_const($1); } ; -meta_expr : META meta_key +meta_expr : META meta_key close_scope_meta { $$ = meta_expr_alloc(&@$, $2); } @@ -4161,13 +5230,13 @@ meta_expr : META meta_key { $$ = meta_expr_alloc(&@$, $1); } - | META STRING + | META STRING close_scope_meta { struct error_record *erec; unsigned int key; erec = meta_key_parse(&@$, $2, &key); - xfree($2); + free_const($2); if (erec != NULL) { erec_queue(erec, state->msgs); YYERROR; @@ -4185,7 +5254,7 @@ meta_key_qualified : LENGTH { $$ = NFT_META_LEN; } | PROTOCOL { $$ = NFT_META_PROTOCOL; } | PRIORITY { $$ = NFT_META_PRIORITY; } | RANDOM { $$ = NFT_META_PRANDOM; } - | SECMARK { $$ = NFT_META_SECMARK; } + | SECMARK close_scope_secmark { $$ = NFT_META_SECMARK; } ; meta_key_unqualified : MARK { $$ = NFT_META_MARK; } @@ -4208,13 +5277,13 @@ meta_key_unqualified : MARK { $$ = NFT_META_MARK; } | IIFGROUP { $$ = NFT_META_IIFGROUP; } | OIFGROUP { $$ = NFT_META_OIFGROUP; } | CGROUP { $$ = NFT_META_CGROUP; } - | IPSEC { $$ = NFT_META_SECPATH; } + | IPSEC close_scope_ipsec { $$ = NFT_META_SECPATH; } | TIME { $$ = NFT_META_TIME_NS; } | DAY { $$ = NFT_META_TIME_DAY; } | HOUR { $$ = NFT_META_TIME_HOUR; } ; -meta_stmt : META meta_key SET stmt_expr +meta_stmt : META meta_key SET stmt_expr close_scope_meta { switch ($2) { case NFT_META_SECMARK: @@ -4238,15 +5307,16 @@ meta_stmt : META meta_key SET stmt_expr { $$ = meta_stmt_alloc(&@$, $1, $3); } - | META STRING SET stmt_expr + | META STRING SET stmt_expr close_scope_meta { struct error_record *erec; unsigned int key; erec = meta_key_parse(&@$, $2, &key); - xfree($2); + free_const($2); if (erec != NULL) { erec_queue(erec, state->msgs); + expr_free($4); YYERROR; } @@ -4256,24 +5326,29 @@ meta_stmt : META meta_key SET stmt_expr { $$ = notrack_stmt_alloc(&@$); } - | FLOW OFFLOAD AT string + | FLOW OFFLOAD AT string close_scope_at { $$ = flow_offload_stmt_alloc(&@$, $4); } - | FLOW ADD AT string + | FLOW ADD AT string close_scope_at { $$ = flow_offload_stmt_alloc(&@$, $4); } ; -socket_expr : SOCKET socket_key +socket_expr : SOCKET socket_key close_scope_socket { - $$ = socket_expr_alloc(&@$, $2); + $$ = socket_expr_alloc(&@$, $2, 0); + } + | SOCKET CGROUPV2 LEVEL NUM close_scope_socket + { + $$ = socket_expr_alloc(&@$, NFT_SOCKET_CGROUPV2, $4); } ; socket_key : TRANSPARENT { $$ = NFT_SOCKET_TRANSPARENT; } | MARK { $$ = NFT_SOCKET_MARK; } + | WILDCARD { $$ = NFT_SOCKET_WILDCARD; } ; offset_opt : /* empty */ { $$ = 0; } @@ -4284,7 +5359,7 @@ numgen_type : INC { $$ = NFT_NG_INCREMENTAL; } | RANDOM { $$ = NFT_NG_RANDOM; } ; -numgen_expr : NUMGEN numgen_type MOD NUM offset_opt +numgen_expr : NUMGEN numgen_type MOD NUM offset_opt close_scope_numgen { $$ = numgen_expr_alloc(&@$, $2, $4, $5); } @@ -4306,7 +5381,7 @@ xfrm_state_proto_key : DADDR { $$ = NFT_XFRM_KEY_DADDR_IP4; } | SADDR { $$ = NFT_XFRM_KEY_SADDR_IP4; } ; -xfrm_expr : IPSEC xfrm_dir xfrm_spnum xfrm_state_key +xfrm_expr : IPSEC xfrm_dir xfrm_spnum xfrm_state_key close_scope_ipsec { if ($3 > 255) { erec_queue(error(&@3, "value too large"), state->msgs); @@ -4314,7 +5389,7 @@ xfrm_expr : IPSEC xfrm_dir xfrm_spnum xfrm_state_key } $$ = xfrm_expr_alloc(&@$, $2, $3, $4); } - | IPSEC xfrm_dir xfrm_spnum nf_key_proto xfrm_state_proto_key + | IPSEC xfrm_dir xfrm_spnum nf_key_proto xfrm_state_proto_key close_scope_ipsec { enum nft_xfrm_keys xfrmk = $5; @@ -4341,31 +5416,31 @@ xfrm_expr : IPSEC xfrm_dir xfrm_spnum xfrm_state_key } ; -hash_expr : JHASH expr MOD NUM SEED NUM offset_opt +hash_expr : JHASH expr MOD NUM SEED NUM offset_opt close_scope_hash { $$ = hash_expr_alloc(&@$, $4, true, $6, $7, NFT_HASH_JENKINS); $$->hash.expr = $2; } - | JHASH expr MOD NUM offset_opt + | JHASH expr MOD NUM offset_opt close_scope_hash { $$ = hash_expr_alloc(&@$, $4, false, 0, $5, NFT_HASH_JENKINS); $$->hash.expr = $2; } - | SYMHASH MOD NUM offset_opt + | SYMHASH MOD NUM offset_opt close_scope_hash { $$ = hash_expr_alloc(&@$, $3, false, 0, $4, NFT_HASH_SYM); } ; -nf_key_proto : IP { $$ = NFPROTO_IPV4; } - | IP6 { $$ = NFPROTO_IPV6; } +nf_key_proto : IP close_scope_ip { $$ = NFPROTO_IPV4; } + | IP6 close_scope_ip6 { $$ = NFPROTO_IPV6; } ; -rt_expr : RT rt_key +rt_expr : RT rt_key close_scope_rt { $$ = rt_expr_alloc(&@$, $2, true); } - | RT nf_key_proto rt_key + | RT nf_key_proto rt_key close_scope_rt { enum nft_rt_keys rtk = $3; @@ -4388,18 +5463,18 @@ rt_expr : RT rt_key rt_key : CLASSID { $$ = NFT_RT_CLASSID; } | NEXTHOP { $$ = NFT_RT_NEXTHOP4; } | MTU { $$ = NFT_RT_TCPMSS; } - | IPSEC { $$ = NFT_RT_XFRM; } + | IPSEC close_scope_ipsec { $$ = NFT_RT_XFRM; } ; -ct_expr : CT ct_key +ct_expr : CT ct_key close_scope_ct { $$ = ct_expr_alloc(&@$, $2, -1); } - | CT ct_dir ct_key_dir + | CT ct_dir ct_key_dir close_scope_ct { $$ = ct_expr_alloc(&@$, $3, $2); } - | CT ct_dir ct_key_proto_field + | CT ct_dir ct_key_proto_field close_scope_ct { $$ = ct_expr_alloc(&@$, $3, $2); } @@ -4423,7 +5498,8 @@ ct_key : L3PROTOCOL { $$ = NFT_CT_L3PROTOCOL; } | PROTO_DST { $$ = NFT_CT_PROTO_DST; } | LABEL { $$ = NFT_CT_LABELS; } | EVENT { $$ = NFT_CT_EVENTMASK; } - | SECMARK { $$ = NFT_CT_SECMARK; } + | SECMARK close_scope_secmark { $$ = NFT_CT_SECMARK; } + | ID { $$ = NFT_CT_ID; } | ct_key_dir_optional ; @@ -4436,10 +5512,10 @@ ct_key_dir : SADDR { $$ = NFT_CT_SRC; } | ct_key_dir_optional ; -ct_key_proto_field : IP SADDR { $$ = NFT_CT_SRC_IP; } - | IP DADDR { $$ = NFT_CT_DST_IP; } - | IP6 SADDR { $$ = NFT_CT_SRC_IP6; } - | IP6 DADDR { $$ = NFT_CT_DST_IP6; } +ct_key_proto_field : IP SADDR close_scope_ip { $$ = NFT_CT_SRC_IP; } + | IP DADDR close_scope_ip { $$ = NFT_CT_DST_IP; } + | IP6 SADDR close_scope_ip6 { $$ = NFT_CT_SRC_IP6; } + | IP6 DADDR close_scope_ip6 { $$ = NFT_CT_DST_IP6; } ; ct_key_dir_optional : BYTES { $$ = NFT_CT_BYTES; } @@ -4466,7 +5542,7 @@ list_stmt_expr : symbol_stmt_expr COMMA symbol_stmt_expr } ; -ct_stmt : CT ct_key SET stmt_expr +ct_stmt : CT ct_key SET stmt_expr close_scope_ct { switch ($2) { case NFT_CT_HELPER: @@ -4479,20 +5555,7 @@ ct_stmt : CT ct_key SET stmt_expr break; } } - | CT TIMEOUT SET stmt_expr - { - $$ = objref_stmt_alloc(&@$); - $$->objref.type = NFT_OBJECT_CT_TIMEOUT; - $$->objref.expr = $4; - - } - | CT EXPECTATION SET stmt_expr - { - $$ = objref_stmt_alloc(&@$); - $$->objref.type = NFT_OBJECT_CT_EXPECT; - $$->objref.expr = $4; - } - | CT ct_dir ct_key_dir_optional SET stmt_expr + | CT ct_dir ct_key_dir_optional SET stmt_expr close_scope_ct { $$ = ct_stmt_alloc(&@$, $3, $2, $5); } @@ -4521,13 +5584,35 @@ payload_expr : payload_raw_expr | comp_hdr_expr | udp_hdr_expr | udplite_hdr_expr - | tcp_hdr_expr + | tcp_hdr_expr close_scope_tcp | dccp_hdr_expr | sctp_hdr_expr | th_hdr_expr + | vxlan_hdr_expr + | geneve_hdr_expr + | gre_hdr_expr + | gretap_hdr_expr ; -payload_raw_expr : AT payload_base_spec COMMA NUM COMMA NUM +payload_raw_len : NUM + { + if ($1 > NFT_MAX_EXPR_LEN_BITS) { + erec_queue(error(&@1, "raw payload length %u exceeds upper limit of %u", + $1, NFT_MAX_EXPR_LEN_BITS), + state->msgs); + YYERROR; + } + + if ($1 == 0) { + erec_queue(error(&@1, "raw payload length cannot be 0"), state->msgs); + YYERROR; + } + + $$ = $1; + } + ; + +payload_raw_expr : AT payload_base_spec COMMA NUM COMMA payload_raw_len close_scope_at { $$ = payload_expr_alloc(&@$, NULL, 0); payload_init_raw($$, $2, $4, $6); @@ -4538,10 +5623,21 @@ payload_raw_expr : AT payload_base_spec COMMA NUM COMMA NUM payload_base_spec : LL_HDR { $$ = PROTO_BASE_LL_HDR; } | NETWORK_HDR { $$ = PROTO_BASE_NETWORK_HDR; } - | TRANSPORT_HDR { $$ = PROTO_BASE_TRANSPORT_HDR; } + | TRANSPORT_HDR close_scope_th { $$ = PROTO_BASE_TRANSPORT_HDR; } + | STRING + { + if (!strcmp($1, "ih")) { + $$ = PROTO_BASE_INNER_HDR; + } else { + erec_queue(error(&@1, "unknown raw payload base"), state->msgs); + free_const($1); + YYERROR; + } + free_const($1); + } ; -eth_hdr_expr : ETHER eth_hdr_field +eth_hdr_expr : ETHER eth_hdr_field close_scope_eth { $$ = payload_expr_alloc(&@$, &proto_eth, $2); } @@ -4549,10 +5645,10 @@ eth_hdr_expr : ETHER eth_hdr_field eth_hdr_field : SADDR { $$ = ETHHDR_SADDR; } | DADDR { $$ = ETHHDR_DADDR; } - | TYPE { $$ = ETHHDR_TYPE; } + | TYPE close_scope_type { $$ = ETHHDR_TYPE; } ; -vlan_hdr_expr : VLAN vlan_hdr_field +vlan_hdr_expr : VLAN vlan_hdr_field close_scope_vlan { $$ = payload_expr_alloc(&@$, &proto_vlan, $2); } @@ -4560,11 +5656,12 @@ vlan_hdr_expr : VLAN vlan_hdr_field vlan_hdr_field : ID { $$ = VLANHDR_VID; } | CFI { $$ = VLANHDR_CFI; } + | DEI { $$ = VLANHDR_DEI; } | PCP { $$ = VLANHDR_PCP; } - | TYPE { $$ = VLANHDR_TYPE; } + | TYPE close_scope_type { $$ = VLANHDR_TYPE; } ; -arp_hdr_expr : ARP arp_hdr_field +arp_hdr_expr : ARP arp_hdr_field close_scope_arp { $$ = payload_expr_alloc(&@$, &proto_arp, $2); } @@ -4575,23 +5672,30 @@ arp_hdr_field : HTYPE { $$ = ARPHDR_HRD; } | HLEN { $$ = ARPHDR_HLN; } | PLEN { $$ = ARPHDR_PLN; } | OPERATION { $$ = ARPHDR_OP; } - | SADDR ETHER { $$ = ARPHDR_SADDR_ETHER; } - | DADDR ETHER { $$ = ARPHDR_DADDR_ETHER; } - | SADDR IP { $$ = ARPHDR_SADDR_IP; } - | DADDR IP { $$ = ARPHDR_DADDR_IP; } + | SADDR ETHER close_scope_eth { $$ = ARPHDR_SADDR_ETHER; } + | DADDR ETHER close_scope_eth { $$ = ARPHDR_DADDR_ETHER; } + | SADDR IP close_scope_ip { $$ = ARPHDR_SADDR_IP; } + | DADDR IP close_scope_ip { $$ = ARPHDR_DADDR_IP; } ; -ip_hdr_expr : IP ip_hdr_field +ip_hdr_expr : IP ip_hdr_field close_scope_ip { $$ = payload_expr_alloc(&@$, &proto_ip, $2); } - | IP OPTION ip_option_type ip_option_field + | IP OPTION ip_option_type ip_option_field close_scope_ip { - $$ = ipopt_expr_alloc(&@$, $3, $4, 0); + $$ = ipopt_expr_alloc(&@$, $3, $4); + if (!$$) { + erec_queue(error(&@1, "unknown ip option type/field"), state->msgs); + YYERROR; + } + + if ($4 == IPOPT_FIELD_TYPE) + $$->exthdr.flags = NFT_EXTHDR_F_PRESENT; } - | IP OPTION ip_option_type + | IP OPTION ip_option_type close_scope_ip { - $$ = ipopt_expr_alloc(&@$, $3, IPOPT_FIELD_TYPE, 0); + $$ = ipopt_expr_alloc(&@$, $3, IPOPT_FIELD_TYPE); $$->exthdr.flags = NFT_EXTHDR_F_PRESENT; } ; @@ -4616,20 +5720,20 @@ ip_option_type : LSRR { $$ = IPOPT_LSRR; } | RA { $$ = IPOPT_RA; } ; -ip_option_field : TYPE { $$ = IPOPT_FIELD_TYPE; } +ip_option_field : TYPE close_scope_type { $$ = IPOPT_FIELD_TYPE; } | LENGTH { $$ = IPOPT_FIELD_LENGTH; } | VALUE { $$ = IPOPT_FIELD_VALUE; } | PTR { $$ = IPOPT_FIELD_PTR; } | ADDR { $$ = IPOPT_FIELD_ADDR_0; } ; -icmp_hdr_expr : ICMP icmp_hdr_field +icmp_hdr_expr : ICMP icmp_hdr_field close_scope_icmp { $$ = payload_expr_alloc(&@$, &proto_icmp, $2); } ; -icmp_hdr_field : TYPE { $$ = ICMPHDR_TYPE; } +icmp_hdr_field : TYPE close_scope_type { $$ = ICMPHDR_TYPE; } | CODE { $$ = ICMPHDR_CODE; } | CHECKSUM { $$ = ICMPHDR_CHECKSUM; } | ID { $$ = ICMPHDR_ID; } @@ -4638,19 +5742,19 @@ icmp_hdr_field : TYPE { $$ = ICMPHDR_TYPE; } | MTU { $$ = ICMPHDR_MTU; } ; -igmp_hdr_expr : IGMP igmp_hdr_field +igmp_hdr_expr : IGMP igmp_hdr_field close_scope_igmp { $$ = payload_expr_alloc(&@$, &proto_igmp, $2); } ; -igmp_hdr_field : TYPE { $$ = IGMPHDR_TYPE; } +igmp_hdr_field : TYPE close_scope_type { $$ = IGMPHDR_TYPE; } | CHECKSUM { $$ = IGMPHDR_CHECKSUM; } | MRT { $$ = IGMPHDR_MRT; } | GROUP { $$ = IGMPHDR_GROUP; } ; -ip6_hdr_expr : IP6 ip6_hdr_field +ip6_hdr_expr : IP6 ip6_hdr_field close_scope_ip6 { $$ = payload_expr_alloc(&@$, &proto_ip6, $2); } @@ -4666,13 +5770,13 @@ ip6_hdr_field : HDRVERSION { $$ = IP6HDR_VERSION; } | SADDR { $$ = IP6HDR_SADDR; } | DADDR { $$ = IP6HDR_DADDR; } ; -icmp6_hdr_expr : ICMP6 icmp6_hdr_field +icmp6_hdr_expr : ICMP6 icmp6_hdr_field close_scope_icmp { $$ = payload_expr_alloc(&@$, &proto_icmp6, $2); } ; -icmp6_hdr_field : TYPE { $$ = ICMP6HDR_TYPE; } +icmp6_hdr_field : TYPE close_scope_type { $$ = ICMP6HDR_TYPE; } | CODE { $$ = ICMP6HDR_CODE; } | CHECKSUM { $$ = ICMP6HDR_CHECKSUM; } | PPTR { $$ = ICMP6HDR_PPTR; } @@ -4680,9 +5784,11 @@ icmp6_hdr_field : TYPE { $$ = ICMP6HDR_TYPE; } | ID { $$ = ICMP6HDR_ID; } | SEQUENCE { $$ = ICMP6HDR_SEQ; } | MAXDELAY { $$ = ICMP6HDR_MAXDELAY; } + | TADDR { $$ = ICMP6HDR_TADDR; } + | DADDR { $$ = ICMP6HDR_DADDR; } ; -auth_hdr_expr : AH auth_hdr_field +auth_hdr_expr : AH auth_hdr_field close_scope_ah { $$ = payload_expr_alloc(&@$, &proto_ah, $2); } @@ -4695,7 +5801,7 @@ auth_hdr_field : NEXTHDR { $$ = AHHDR_NEXTHDR; } | SEQUENCE { $$ = AHHDR_SEQUENCE; } ; -esp_hdr_expr : ESP esp_hdr_field +esp_hdr_expr : ESP esp_hdr_field close_scope_esp { $$ = payload_expr_alloc(&@$, &proto_esp, $2); } @@ -4705,7 +5811,7 @@ esp_hdr_field : SPI { $$ = ESPHDR_SPI; } | SEQUENCE { $$ = ESPHDR_SEQUENCE; } ; -comp_hdr_expr : COMP comp_hdr_field +comp_hdr_expr : COMP comp_hdr_field close_scope_comp { $$ = payload_expr_alloc(&@$, &proto_comp, $2); } @@ -4716,7 +5822,7 @@ comp_hdr_field : NEXTHDR { $$ = COMPHDR_NEXTHDR; } | CPI { $$ = COMPHDR_CPI; } ; -udp_hdr_expr : UDP udp_hdr_field +udp_hdr_expr : UDP udp_hdr_field close_scope_udp { $$ = payload_expr_alloc(&@$, &proto_udp, $2); } @@ -4728,7 +5834,7 @@ udp_hdr_field : SPORT { $$ = UDPHDR_SPORT; } | CHECKSUM { $$ = UDPHDR_CHECKSUM; } ; -udplite_hdr_expr : UDPLITE udplite_hdr_field +udplite_hdr_expr : UDPLITE udplite_hdr_field close_scope_udplite { $$ = payload_expr_alloc(&@$, &proto_udplite, $2); } @@ -4744,15 +5850,119 @@ tcp_hdr_expr : TCP tcp_hdr_field { $$ = payload_expr_alloc(&@$, &proto_tcp, $2); } - | TCP OPTION tcp_hdr_option_type tcp_hdr_option_field - { - $$ = tcpopt_expr_alloc(&@$, $3, $4); - } | TCP OPTION tcp_hdr_option_type { - $$ = tcpopt_expr_alloc(&@$, $3, TCPOPTHDR_FIELD_KIND); + $$ = tcpopt_expr_alloc(&@$, $3, TCPOPT_COMMON_KIND); $$->exthdr.flags = NFT_EXTHDR_F_PRESENT; } + | TCP OPTION tcp_hdr_option_kind_and_field + { + $$ = tcpopt_expr_alloc(&@$, $3.kind, $3.field); + if ($$ == NULL) { + erec_queue(error(&@1, "Could not find a tcp option template"), state->msgs); + YYERROR; + } + } + | TCP OPTION AT close_scope_at tcp_hdr_option_type COMMA NUM COMMA payload_raw_len + { + $$ = tcpopt_expr_alloc(&@$, $5, 0); + tcpopt_init_raw($$, $5, $7, $9, 0); + } + ; + +inner_inet_expr : ip_hdr_expr + | icmp_hdr_expr + | igmp_hdr_expr + | ip6_hdr_expr + | icmp6_hdr_expr + | auth_hdr_expr + | esp_hdr_expr + | comp_hdr_expr + | udp_hdr_expr + | udplite_hdr_expr + | tcp_hdr_expr close_scope_tcp + | dccp_hdr_expr + | sctp_hdr_expr + | th_hdr_expr + ; + +inner_eth_expr : eth_hdr_expr + | vlan_hdr_expr + | arp_hdr_expr + ; + +inner_expr : inner_eth_expr + | inner_inet_expr + ; + +vxlan_hdr_expr : VXLAN vxlan_hdr_field + { + struct expr *expr; + + expr = payload_expr_alloc(&@$, &proto_vxlan, $2); + expr->payload.inner_desc = &proto_vxlan; + $$ = expr; + } + | VXLAN inner_expr + { + $$ = $2; + $$->location = @$; + $$->payload.inner_desc = &proto_vxlan; + } + ; + +vxlan_hdr_field : VNI { $$ = VXLANHDR_VNI; } + | FLAGS { $$ = VXLANHDR_FLAGS; } + ; + +geneve_hdr_expr : GENEVE geneve_hdr_field + { + struct expr *expr; + + expr = payload_expr_alloc(&@$, &proto_geneve, $2); + expr->payload.inner_desc = &proto_geneve; + $$ = expr; + } + | GENEVE inner_expr + { + $$ = $2; + $$->location = @$; + $$->payload.inner_desc = &proto_geneve; + } + ; + +geneve_hdr_field : VNI { $$ = GNVHDR_VNI; } + | TYPE { $$ = GNVHDR_TYPE; } + ; + +gre_hdr_expr : GRE gre_hdr_field close_scope_gre + { + $$ = payload_expr_alloc(&@$, &proto_gre, $2); + } + | GRE close_scope_gre inner_inet_expr + { + $$ = $3; + $$->payload.inner_desc = &proto_gre; + } + ; + +gre_hdr_field : HDRVERSION { $$ = GREHDR_VERSION; } + | FLAGS { $$ = GREHDR_FLAGS; } + | PROTOCOL { $$ = GREHDR_PROTOCOL; } + ; + +gretap_hdr_expr : GRETAP close_scope_gre inner_expr + { + $$ = $3; + $$->payload.inner_desc = &proto_gretap; + } + ; + +optstrip_stmt : RESET TCP OPTION tcp_hdr_option_type close_scope_tcp + { + $$ = optstrip_stmt_alloc(&@$, tcpopt_expr_alloc(&@$, + $4, TCPOPT_COMMON_KIND)); + } ; tcp_hdr_field : SPORT { $$ = TCPHDR_SPORT; } @@ -4767,45 +5977,211 @@ tcp_hdr_field : SPORT { $$ = TCPHDR_SPORT; } | URGPTR { $$ = TCPHDR_URGPTR; } ; -tcp_hdr_option_type : EOL { $$ = TCPOPTHDR_EOL; } - | NOOP { $$ = TCPOPTHDR_NOOP; } - | MAXSEG { $$ = TCPOPTHDR_MAXSEG; } - | WINDOW { $$ = TCPOPTHDR_WINDOW; } - | SACK_PERMITTED { $$ = TCPOPTHDR_SACK_PERMITTED; } - | SACK { $$ = TCPOPTHDR_SACK0; } - | SACK0 { $$ = TCPOPTHDR_SACK0; } - | SACK1 { $$ = TCPOPTHDR_SACK1; } - | SACK2 { $$ = TCPOPTHDR_SACK2; } - | SACK3 { $$ = TCPOPTHDR_SACK3; } - | ECHO { $$ = TCPOPTHDR_ECHO; } - | TIMESTAMP { $$ = TCPOPTHDR_TIMESTAMP; } +tcp_hdr_option_kind_and_field : MSS tcpopt_field_maxseg + { + struct tcp_kind_field kind_field = { .kind = TCPOPT_KIND_MAXSEG, .field = $2 }; + $$ = kind_field; + } + | tcp_hdr_option_sack tcpopt_field_sack + { + struct tcp_kind_field kind_field = { .kind = $1, .field = $2 }; + $$ = kind_field; + } + | WINDOW tcpopt_field_window + { + struct tcp_kind_field kind_field = { .kind = TCPOPT_KIND_WINDOW, .field = $2 }; + $$ = kind_field; + } + | TIMESTAMP tcpopt_field_tsopt + { + struct tcp_kind_field kind_field = { .kind = TCPOPT_KIND_TIMESTAMP, .field = $2 }; + $$ = kind_field; + } + | tcp_hdr_option_type LENGTH + { + struct tcp_kind_field kind_field = { .kind = $1, .field = TCPOPT_COMMON_LENGTH }; + $$ = kind_field; + } + | MPTCP tcpopt_field_mptcp + { + struct tcp_kind_field kind_field = { .kind = TCPOPT_KIND_MPTCP, .field = $2 }; + $$ = kind_field; + } + ; + +tcp_hdr_option_sack : SACK { $$ = TCPOPT_KIND_SACK; } + | SACK0 { $$ = TCPOPT_KIND_SACK; } + | SACK1 { $$ = TCPOPT_KIND_SACK1; } + | SACK2 { $$ = TCPOPT_KIND_SACK2; } + | SACK3 { $$ = TCPOPT_KIND_SACK3; } + ; + +tcp_hdr_option_type : ECHO { $$ = TCPOPT_KIND_ECHO; } + | EOL { $$ = TCPOPT_KIND_EOL; } + | FASTOPEN { $$ = TCPOPT_KIND_FASTOPEN; } + | MD5SIG { $$ = TCPOPT_KIND_MD5SIG; } + | MPTCP { $$ = TCPOPT_KIND_MPTCP; } + | MSS { $$ = TCPOPT_KIND_MAXSEG; } + | NOP { $$ = TCPOPT_KIND_NOP; } + | SACK_PERM { $$ = TCPOPT_KIND_SACK_PERMITTED; } + | TIMESTAMP { $$ = TCPOPT_KIND_TIMESTAMP; } + | WINDOW { $$ = TCPOPT_KIND_WINDOW; } + | tcp_hdr_option_sack { $$ = $1; } + | NUM { + if ($1 > 255) { + erec_queue(error(&@1, "value too large"), state->msgs); + YYERROR; + } + $$ = $1; + } + ; + +tcpopt_field_sack : LEFT { $$ = TCPOPT_SACK_LEFT; } + | RIGHT { $$ = TCPOPT_SACK_RIGHT; } ; -tcp_hdr_option_field : KIND { $$ = TCPOPTHDR_FIELD_KIND; } - | LENGTH { $$ = TCPOPTHDR_FIELD_LENGTH; } - | SIZE { $$ = TCPOPTHDR_FIELD_SIZE; } - | COUNT { $$ = TCPOPTHDR_FIELD_COUNT; } - | LEFT { $$ = TCPOPTHDR_FIELD_LEFT; } - | RIGHT { $$ = TCPOPTHDR_FIELD_RIGHT; } - | TSVAL { $$ = TCPOPTHDR_FIELD_TSVAL; } - | TSECR { $$ = TCPOPTHDR_FIELD_TSECR; } +tcpopt_field_window : COUNT { $$ = TCPOPT_WINDOW_COUNT; } ; -dccp_hdr_expr : DCCP dccp_hdr_field +tcpopt_field_tsopt : TSVAL { $$ = TCPOPT_TS_TSVAL; } + | TSECR { $$ = TCPOPT_TS_TSECR; } + ; + +tcpopt_field_maxseg : SIZE { $$ = TCPOPT_MAXSEG_SIZE; } + ; + +tcpopt_field_mptcp : SUBTYPE { $$ = TCPOPT_MPTCP_SUBTYPE; } + ; + +dccp_hdr_expr : DCCP dccp_hdr_field close_scope_dccp { $$ = payload_expr_alloc(&@$, &proto_dccp, $2); } + | DCCP OPTION NUM close_scope_dccp + { + if ($3 > DCCPOPT_TYPE_MAX) { + erec_queue(error(&@1, "value too large"), + state->msgs); + YYERROR; + } + $$ = dccpopt_expr_alloc(&@$, $3); + } ; dccp_hdr_field : SPORT { $$ = DCCPHDR_SPORT; } | DPORT { $$ = DCCPHDR_DPORT; } - | TYPE { $$ = DCCPHDR_TYPE; } + | TYPE close_scope_type { $$ = DCCPHDR_TYPE; } + ; + +sctp_chunk_type : DATA { $$ = SCTP_CHUNK_TYPE_DATA; } + | INIT { $$ = SCTP_CHUNK_TYPE_INIT; } + | INIT_ACK { $$ = SCTP_CHUNK_TYPE_INIT_ACK; } + | SACK { $$ = SCTP_CHUNK_TYPE_SACK; } + | HEARTBEAT { $$ = SCTP_CHUNK_TYPE_HEARTBEAT; } + | HEARTBEAT_ACK { $$ = SCTP_CHUNK_TYPE_HEARTBEAT_ACK; } + | ABORT { $$ = SCTP_CHUNK_TYPE_ABORT; } + | SHUTDOWN { $$ = SCTP_CHUNK_TYPE_SHUTDOWN; } + | SHUTDOWN_ACK { $$ = SCTP_CHUNK_TYPE_SHUTDOWN_ACK; } + | ERROR { $$ = SCTP_CHUNK_TYPE_ERROR; } + | COOKIE_ECHO { $$ = SCTP_CHUNK_TYPE_COOKIE_ECHO; } + | COOKIE_ACK { $$ = SCTP_CHUNK_TYPE_COOKIE_ACK; } + | ECNE { $$ = SCTP_CHUNK_TYPE_ECNE; } + | CWR { $$ = SCTP_CHUNK_TYPE_CWR; } + | SHUTDOWN_COMPLETE { $$ = SCTP_CHUNK_TYPE_SHUTDOWN_COMPLETE; } + | ASCONF_ACK { $$ = SCTP_CHUNK_TYPE_ASCONF_ACK; } + | FORWARD_TSN { $$ = SCTP_CHUNK_TYPE_FORWARD_TSN; } + | ASCONF { $$ = SCTP_CHUNK_TYPE_ASCONF; } + ; + +sctp_chunk_common_field : TYPE close_scope_type { $$ = SCTP_CHUNK_COMMON_TYPE; } + | FLAGS { $$ = SCTP_CHUNK_COMMON_FLAGS; } + | LENGTH { $$ = SCTP_CHUNK_COMMON_LENGTH; } ; -sctp_hdr_expr : SCTP sctp_hdr_field +sctp_chunk_data_field : TSN { $$ = SCTP_CHUNK_DATA_TSN; } + | STREAM { $$ = SCTP_CHUNK_DATA_STREAM; } + | SSN { $$ = SCTP_CHUNK_DATA_SSN; } + | PPID { $$ = SCTP_CHUNK_DATA_PPID; } + ; + +sctp_chunk_init_field : INIT_TAG { $$ = SCTP_CHUNK_INIT_TAG; } + | A_RWND { $$ = SCTP_CHUNK_INIT_RWND; } + | NUM_OSTREAMS { $$ = SCTP_CHUNK_INIT_OSTREAMS; } + | NUM_ISTREAMS { $$ = SCTP_CHUNK_INIT_ISTREAMS; } + | INIT_TSN { $$ = SCTP_CHUNK_INIT_TSN; } + ; + +sctp_chunk_sack_field : CUM_TSN_ACK { $$ = SCTP_CHUNK_SACK_CTSN_ACK; } + | A_RWND { $$ = SCTP_CHUNK_SACK_RWND; } + | NUM_GACK_BLOCKS { $$ = SCTP_CHUNK_SACK_GACK_BLOCKS; } + | NUM_DUP_TSNS { $$ = SCTP_CHUNK_SACK_DUP_TSNS; } + ; + +sctp_chunk_alloc : sctp_chunk_type + { + $$ = sctp_chunk_expr_alloc(&@$, $1, SCTP_CHUNK_COMMON_TYPE); + $$->exthdr.flags = NFT_EXTHDR_F_PRESENT; + } + | sctp_chunk_type sctp_chunk_common_field + { + $$ = sctp_chunk_expr_alloc(&@$, $1, $2); + } + | DATA sctp_chunk_data_field + { + $$ = sctp_chunk_expr_alloc(&@$, SCTP_CHUNK_TYPE_DATA, $2); + } + | INIT sctp_chunk_init_field + { + $$ = sctp_chunk_expr_alloc(&@$, SCTP_CHUNK_TYPE_INIT, $2); + } + | INIT_ACK sctp_chunk_init_field + { + $$ = sctp_chunk_expr_alloc(&@$, SCTP_CHUNK_TYPE_INIT_ACK, $2); + } + | SACK sctp_chunk_sack_field + { + $$ = sctp_chunk_expr_alloc(&@$, SCTP_CHUNK_TYPE_SACK, $2); + } + | SHUTDOWN CUM_TSN_ACK + { + $$ = sctp_chunk_expr_alloc(&@$, SCTP_CHUNK_TYPE_SHUTDOWN, + SCTP_CHUNK_SHUTDOWN_CTSN_ACK); + } + | ECNE LOWEST_TSN + { + $$ = sctp_chunk_expr_alloc(&@$, SCTP_CHUNK_TYPE_ECNE, + SCTP_CHUNK_ECNE_CWR_MIN_TSN); + } + | CWR LOWEST_TSN + { + $$ = sctp_chunk_expr_alloc(&@$, SCTP_CHUNK_TYPE_CWR, + SCTP_CHUNK_ECNE_CWR_MIN_TSN); + } + | ASCONF_ACK SEQNO + { + $$ = sctp_chunk_expr_alloc(&@$, SCTP_CHUNK_TYPE_ASCONF_ACK, + SCTP_CHUNK_ASCONF_SEQNO); + } + | FORWARD_TSN NEW_CUM_TSN + { + $$ = sctp_chunk_expr_alloc(&@$, SCTP_CHUNK_TYPE_FORWARD_TSN, + SCTP_CHUNK_FORWARD_TSN_NCTSN); + } + | ASCONF SEQNO + { + $$ = sctp_chunk_expr_alloc(&@$, SCTP_CHUNK_TYPE_ASCONF, + SCTP_CHUNK_ASCONF_SEQNO); + } + ; + +sctp_hdr_expr : SCTP sctp_hdr_field close_scope_sctp { $$ = payload_expr_alloc(&@$, &proto_sctp, $2); } + | SCTP CHUNK sctp_chunk_alloc close_scope_sctp_chunk close_scope_sctp + { + $$ = $3; + } ; sctp_hdr_field : SPORT { $$ = SCTPHDR_SPORT; } @@ -4814,7 +6190,7 @@ sctp_hdr_field : SPORT { $$ = SCTPHDR_SPORT; } | CHECKSUM { $$ = SCTPHDR_CHECKSUM; } ; -th_hdr_expr : TRANSPORT_HDR th_hdr_field +th_hdr_expr : TRANSPORT_HDR th_hdr_field close_scope_th { $$ = payload_expr_alloc(&@$, &proto_th, $2); if ($$) @@ -4836,7 +6212,7 @@ exthdr_expr : hbh_hdr_expr | mh_hdr_expr ; -hbh_hdr_expr : HBH hbh_hdr_field +hbh_hdr_expr : HBH hbh_hdr_field close_scope_hbh { $$ = exthdr_expr_alloc(&@$, &exthdr_hbh, $2); } @@ -4846,7 +6222,7 @@ hbh_hdr_field : NEXTHDR { $$ = HBHHDR_NEXTHDR; } | HDRLENGTH { $$ = HBHHDR_HDRLENGTH; } ; -rt_hdr_expr : RT rt_hdr_field +rt_hdr_expr : RT rt_hdr_field close_scope_rt { $$ = exthdr_expr_alloc(&@$, &exthdr_rt, $2); } @@ -4854,11 +6230,11 @@ rt_hdr_expr : RT rt_hdr_field rt_hdr_field : NEXTHDR { $$ = RTHDR_NEXTHDR; } | HDRLENGTH { $$ = RTHDR_HDRLENGTH; } - | TYPE { $$ = RTHDR_TYPE; } + | TYPE close_scope_type { $$ = RTHDR_TYPE; } | SEG_LEFT { $$ = RTHDR_SEG_LEFT; } ; -rt0_hdr_expr : RT0 rt0_hdr_field +rt0_hdr_expr : RT0 rt0_hdr_field close_scope_rt { $$ = exthdr_expr_alloc(&@$, &exthdr_rt0, $2); } @@ -4870,7 +6246,7 @@ rt0_hdr_field : ADDR '[' NUM ']' } ; -rt2_hdr_expr : RT2 rt2_hdr_field +rt2_hdr_expr : RT2 rt2_hdr_field close_scope_rt { $$ = exthdr_expr_alloc(&@$, &exthdr_rt2, $2); } @@ -4879,7 +6255,7 @@ rt2_hdr_expr : RT2 rt2_hdr_field rt2_hdr_field : ADDR { $$ = RT2HDR_ADDR; } ; -rt4_hdr_expr : RT4 rt4_hdr_field +rt4_hdr_expr : RT4 rt4_hdr_field close_scope_rt { $$ = exthdr_expr_alloc(&@$, &exthdr_rt4, $2); } @@ -4894,7 +6270,7 @@ rt4_hdr_field : LAST_ENT { $$ = RT4HDR_LASTENT; } } ; -frag_hdr_expr : FRAG frag_hdr_field +frag_hdr_expr : FRAG frag_hdr_field close_scope_frag { $$ = exthdr_expr_alloc(&@$, &exthdr_frag, $2); } @@ -4908,7 +6284,7 @@ frag_hdr_field : NEXTHDR { $$ = FRAGHDR_NEXTHDR; } | ID { $$ = FRAGHDR_ID; } ; -dst_hdr_expr : DST dst_hdr_field +dst_hdr_expr : DST dst_hdr_field close_scope_dst { $$ = exthdr_expr_alloc(&@$, &exthdr_dst, $2); } @@ -4918,7 +6294,7 @@ dst_hdr_field : NEXTHDR { $$ = DSTHDR_NEXTHDR; } | HDRLENGTH { $$ = DSTHDR_HDRLENGTH; } ; -mh_hdr_expr : MH mh_hdr_field +mh_hdr_expr : MH mh_hdr_field close_scope_mh { $$ = exthdr_expr_alloc(&@$, &exthdr_mh, $2); } @@ -4926,7 +6302,7 @@ mh_hdr_expr : MH mh_hdr_field mh_hdr_field : NEXTHDR { $$ = MHHDR_NEXTHDR; } | HDRLENGTH { $$ = MHHDR_HDRLENGTH; } - | TYPE { $$ = MHHDR_TYPE; } + | TYPE close_scope_type { $$ = MHHDR_TYPE; } | RESERVED { $$ = MHHDR_RESERVED; } | CHECKSUM { $$ = MHHDR_CHECKSUM; } ; @@ -4938,18 +6314,18 @@ exthdr_exists_expr : EXTHDR exthdr_key desc = exthdr_find_proto($2); /* Assume that NEXTHDR template is always - * the fist one in list of templates. + * the first one in list of templates. */ $$ = exthdr_expr_alloc(&@$, desc, 1); $$->exthdr.flags = NFT_EXTHDR_F_PRESENT; } ; -exthdr_key : HBH { $$ = IPPROTO_HOPOPTS; } - | RT { $$ = IPPROTO_ROUTING; } - | FRAG { $$ = IPPROTO_FRAGMENT; } - | DST { $$ = IPPROTO_DSTOPTS; } - | MH { $$ = IPPROTO_MH; } +exthdr_key : HBH close_scope_hbh { $$ = IPPROTO_HOPOPTS; } + | RT close_scope_rt { $$ = IPPROTO_ROUTING; } + | FRAG close_scope_frag { $$ = IPPROTO_FRAGMENT; } + | DST close_scope_dst { $$ = IPPROTO_DSTOPTS; } + | MH close_scope_mh { $$ = IPPROTO_MH; } ; %% diff --git a/src/parser_json.c b/src/parser_json.c index 85082cce..bd865de5 100644 --- a/src/parser_json.c +++ b/src/parser_json.c @@ -1,7 +1,14 @@ -#define _GNU_SOURCE +/* + * Copyright (c) Red Hat GmbH. Author: Phil Sutter <phil@nwl.cc> + * + * 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. + */ + +#include <nft.h> + #include <errno.h> -#include <stdint.h> /* needed by gmputil.h */ -#include <string.h> #include <syslog.h> #include <erec.h> @@ -11,6 +18,8 @@ #include <netlink.h> #include <parser.h> #include <rule.h> +#include <cmd.h> +#include <sctp_chunk.h> #include <socket.h> #include <netdb.h> @@ -40,9 +49,10 @@ #define CTX_F_MANGLE (1 << 5) #define CTX_F_SES (1 << 6) /* set_elem_expr_stmt */ #define CTX_F_MAP (1 << 7) /* LHS of map_expr */ +#define CTX_F_CONCAT (1 << 8) /* inside concat_expr */ +#define CTX_F_COLLAPSED (1 << 9) struct json_ctx { - struct input_descriptor indesc; struct nft_ctx *nft; struct list_head *msgs; struct list_head *cmds; @@ -99,19 +109,20 @@ static struct expr *json_parse_primary_expr(struct json_ctx *ctx, json_t *root); static struct expr *json_parse_set_rhs_expr(struct json_ctx *ctx, json_t *root); static struct expr *json_parse_set_elem_expr_stmt(struct json_ctx *ctx, json_t *root); static struct expr *json_parse_map_lhs_expr(struct json_ctx *ctx, json_t *root); +static struct expr *json_parse_concat_elem_expr(struct json_ctx *ctx, json_t *root); static struct stmt *json_parse_stmt(struct json_ctx *ctx, json_t *root); /* parsing helpers */ const struct location *int_loc = &internal_location; +static struct input_descriptor json_indesc; static void json_lib_error(struct json_ctx *ctx, json_error_t *err) { struct location loc = { - .indesc = &ctx->indesc, + .indesc = &json_indesc, .line_offset = err->position - err->column, .first_line = err->line, - .last_line = err->line, .first_column = err->column, /* no information where problematic part ends :( */ .last_column = err->column, @@ -171,8 +182,11 @@ static int json_unpack_stmt(struct json_ctx *ctx, json_t *root, assert(value); if (json_object_size(root) != 1) { + const char *dump = json_dumps(root, 0); + json_error(ctx, "Malformed object (too many properties): '%s'.", - json_dumps(root, 0)); + dump); + free_const(dump); return 1; } @@ -183,6 +197,60 @@ static int json_unpack_stmt(struct json_ctx *ctx, json_t *root, return 1; } +/** + * parse_flags_array - parse JSON property as an array of flags + * + * @ctx: JSON parser context + * @obj: JSON object to extract property from + * @key: name of property containing the flags array + * @flag_parser: Callback parsing a single flag, returns 0 on error + * + * The property value may be a string representing a single flag or an array of + * strings representing a number of flags whose values are ORed together. + * + * @return: Combined flag value, 0 if no such property found or -1 if data is + * malformed or flag parsing failed. + */ +static int parse_flags_array(struct json_ctx *ctx, json_t *obj, const char *key, + unsigned int (*flag_parser)(const char *flag)) +{ + json_t *value = json_object_get(obj, key), *tmp; + size_t index; + int ret = 0; + + if (!value) + return 0; + + if (json_is_string(value)) { + ret = flag_parser(json_string_value(value)); + return ret ?: -1; + } + + if (!json_is_array(value)) { + json_error(ctx, + "Expecting string or array in '%s' property.", key); + return -1; + } + + json_array_foreach(value, index, tmp) { + int flag = 0; + + if (json_is_string(tmp)) + flag = flag_parser(json_string_value(tmp)); + + if (!flag) { + json_error(ctx, + "Invalid flag in '%s' property array at index %zu.", + key, index); + return -1; + } + + ret |= flag; + } + + return ret; +} + static int parse_family(const char *name, uint32_t *family) { unsigned int i; @@ -312,15 +380,6 @@ static struct expr *json_parse_constant(struct json_ctx *ctx, const char *name) return NULL; } -static struct expr *wildcard_expr_alloc(void) -{ - struct expr *expr; - - expr = constant_expr_alloc(int_loc, &integer_type, - BYTEORDER_HOST_ENDIAN, 0, NULL); - return prefix_expr_alloc(int_loc, expr, 0); -} - /* this is a combination of symbol_expr, integer_expr, boolean_expr ... */ static struct expr *json_parse_immediate(struct json_ctx *ctx, json_t *root) { @@ -335,7 +394,7 @@ static struct expr *json_parse_immediate(struct json_ctx *ctx, json_t *root) symtype = SYMBOL_SET; str++; } else if (str[0] == '*' && str[1] == '\0') { - return wildcard_expr_alloc(); + return set_elem_catchall_expr_alloc(int_loc); } else if (is_keyword(str)) { return symbol_expr_alloc(int_loc, SYMBOL_VALUE, NULL, str); @@ -425,13 +484,15 @@ static struct expr *json_parse_socket_expr(struct json_ctx *ctx, keyval = NFT_SOCKET_TRANSPARENT; else if (!strcmp(key, "mark")) keyval = NFT_SOCKET_MARK; + else if (!strcmp(key, "wildcard")) + keyval = NFT_SOCKET_WILDCARD; if (keyval == -1) { json_error(ctx, "Invalid socket key value."); return NULL; } - return socket_expr_alloc(int_loc, keyval); + return socket_expr_alloc(int_loc, keyval, 0); } static int json_parse_payload_field(const struct proto_desc *desc, @@ -454,9 +515,9 @@ static int json_parse_tcp_option_type(const char *name, int *val) { unsigned int i; - for (i = 0; i < array_size(tcpopthdr_protocols); i++) { - if (tcpopthdr_protocols[i] && - !strcmp(tcpopthdr_protocols[i]->name, name)) { + for (i = 0; i < array_size(tcpopt_protocols); i++) { + if (tcpopt_protocols[i] && + !strcmp(tcpopt_protocols[i]->name, name)) { if (val) *val = i; return 0; @@ -464,8 +525,10 @@ static int json_parse_tcp_option_type(const char *name, int *val) } /* special case for sack0 - sack3 */ if (sscanf(name, "sack%u", &i) == 1 && i < 4) { - if (val) - *val = TCPOPTHDR_SACK0 + i; + if (val && i == 0) + *val = TCPOPT_KIND_SACK; + else if (val && i > 0) + *val = TCPOPT_KIND_SACK1 + i - 1; return 0; } return 1; @@ -473,12 +536,40 @@ static int json_parse_tcp_option_type(const char *name, int *val) static int json_parse_tcp_option_field(int type, const char *name, int *val) { + const struct exthdr_desc *desc; + unsigned int block = 0; unsigned int i; - const struct exthdr_desc *desc = tcpopthdr_protocols[type]; + + switch (type) { + case TCPOPT_KIND_SACK1: + type = TCPOPT_KIND_SACK; + block = 1; + break; + case TCPOPT_KIND_SACK2: + type = TCPOPT_KIND_SACK; + block = 2; + break; + case TCPOPT_KIND_SACK3: + type = TCPOPT_KIND_SACK; + block = 3; + break; + } + + if (type < 0 || type >= (int)array_size(tcpopt_protocols)) + return 1; + + desc = tcpopt_protocols[type]; + if (!desc) + return 1; for (i = 0; i < array_size(desc->templates); i++) { if (desc->templates[i].token && !strcmp(desc->templates[i].token, name)) { + if (block) { + block--; + continue; + } + if (val) *val = i; return 0; @@ -507,6 +598,27 @@ static const struct proto_desc *proto_lookup_byname(const char *name) &proto_dccp, &proto_sctp, &proto_th, + &proto_vxlan, + &proto_gre, + &proto_gretap, + &proto_geneve, + }; + unsigned int i; + + for (i = 0; i < array_size(proto_tbl); i++) { + if (!strcmp(proto_tbl[i]->name, name)) + return proto_tbl[i]; + } + return NULL; +} + +static const struct proto_desc *inner_proto_lookup_byname(const char *name) +{ + const struct proto_desc *proto_tbl[] = { + &proto_geneve, + &proto_gre, + &proto_gretap, + &proto_vxlan, }; unsigned int i; @@ -520,7 +632,7 @@ static const struct proto_desc *proto_lookup_byname(const char *name) static struct expr *json_parse_payload_expr(struct json_ctx *ctx, const char *type, json_t *root) { - const char *protocol, *field, *base; + const char *tunnel, *protocol, *field, *base; int offset, len, val; struct expr *expr; @@ -532,15 +644,51 @@ static struct expr *json_parse_payload_expr(struct json_ctx *ctx, val = PROTO_BASE_NETWORK_HDR; } else if (!strcmp(base, "th")) { val = PROTO_BASE_TRANSPORT_HDR; + } else if (!strcmp(base, "ih")) { + val = PROTO_BASE_INNER_HDR; } else { json_error(ctx, "Invalid payload base '%s'.", base); return NULL; } + + if (len <= 0 || len > (int)NFT_MAX_EXPR_LEN_BITS) { + json_error(ctx, "Payload length must be between 0 and %lu, got %d", + NFT_MAX_EXPR_LEN_BITS, len); + return NULL; + } + expr = payload_expr_alloc(int_loc, NULL, 0); payload_init_raw(expr, val, offset, len); expr->byteorder = BYTEORDER_BIG_ENDIAN; expr->payload.is_raw = true; return expr; + } else if (!json_unpack(root, "{s:s, s:s, s:s}", + "tunnel", &tunnel, "protocol", &protocol, "field", &field)) { + const struct proto_desc *proto = proto_lookup_byname(protocol); + const struct proto_desc *inner_proto = inner_proto_lookup_byname(tunnel); + + if (!inner_proto) { + json_error(ctx, "Unknown payload tunnel protocol '%s'.", + tunnel); + return NULL; + } + if (!proto) { + json_error(ctx, "Unknown payload protocol '%s'.", + protocol); + return NULL; + } + if (json_parse_payload_field(proto, field, &val)) { + json_error(ctx, "Unknown %s field '%s'.", + protocol, field); + return NULL; + } + expr = payload_expr_alloc(int_loc, proto, val); + expr->payload.inner_desc = inner_proto; + + if (proto == &proto_th) + expr->payload.is_raw = true; + + return expr; } else if (!json_unpack(root, "{s:s, s:s}", "protocol", &protocol, "field", &field)) { const struct proto_desc *proto = proto_lookup_byname(protocol); @@ -569,30 +717,54 @@ static struct expr *json_parse_payload_expr(struct json_ctx *ctx, static struct expr *json_parse_tcp_option_expr(struct json_ctx *ctx, const char *type, json_t *root) { + int fieldval, kind, offset, len; const char *desc, *field; - int descval, fieldval; struct expr *expr; - if (json_unpack_err(ctx, root, "{s:s}", "name", &desc)) - return NULL; + if (!json_unpack(root, "{s:i, s:i, s:i}", + "base", &kind, "offset", &offset, "len", &len)) { + uint32_t flag = 0; - if (json_parse_tcp_option_type(desc, &descval)) { - json_error(ctx, "Unknown tcp option name '%s'.", desc); - return NULL; - } + if (kind < 0 || kind > 255) + return NULL; - if (json_unpack(root, "{s:s}", "field", &field)) { - expr = tcpopt_expr_alloc(int_loc, descval, - TCPOPTHDR_FIELD_KIND); - expr->exthdr.flags = NFT_EXTHDR_F_PRESENT; + if (len < 0 || len > (int)NFT_MAX_EXPR_LEN_BITS) { + json_error(ctx, "option length must be between 0 and %lu, got %d", + NFT_MAX_EXPR_LEN_BITS, len); + return NULL; + } + expr = tcpopt_expr_alloc(int_loc, kind, + TCPOPT_COMMON_KIND); + + if (offset == TCPOPT_COMMON_KIND && len == 8) + flag = NFT_EXTHDR_F_PRESENT; + + tcpopt_init_raw(expr, kind, offset, len, flag); return expr; + } else if (!json_unpack(root, "{s:s}", "name", &desc)) { + if (json_parse_tcp_option_type(desc, &kind)) { + json_error(ctx, "Unknown tcp option name '%s'.", desc); + return NULL; + } + + if (json_unpack(root, "{s:s}", "field", &field)) { + expr = tcpopt_expr_alloc(int_loc, kind, + TCPOPT_COMMON_KIND); + expr->exthdr.flags = NFT_EXTHDR_F_PRESENT; + return expr; + } + + if (json_parse_tcp_option_field(kind, field, &fieldval)) { + json_error(ctx, "Unknown tcp option field '%s'.", field); + return NULL; + } + + return tcpopt_expr_alloc(int_loc, kind, fieldval); } - if (json_parse_tcp_option_field(descval, field, &fieldval)) { - json_error(ctx, "Unknown tcp option field '%s'.", field); - return NULL; - } - return tcpopt_expr_alloc(int_loc, descval, fieldval); + + json_error(ctx, "Invalid tcp option expression properties."); + return NULL; } static int json_parse_ip_option_type(const char *name, int *val) @@ -627,7 +799,7 @@ static int json_parse_ip_option_field(int type, const char *name, int *val) } static struct expr *json_parse_ip_option_expr(struct json_ctx *ctx, - const char *type, json_t *root) + const char *type, json_t *root) { const char *desc, *field; int descval, fieldval; @@ -643,7 +815,7 @@ static struct expr *json_parse_ip_option_expr(struct json_ctx *ctx, if (json_unpack(root, "{s:s}", "field", &field)) { expr = ipopt_expr_alloc(int_loc, descval, - IPOPT_FIELD_TYPE, 0); + IPOPT_FIELD_TYPE); expr->exthdr.flags = NFT_EXTHDR_F_PRESENT; return expr; @@ -652,7 +824,70 @@ static struct expr *json_parse_ip_option_expr(struct json_ctx *ctx, json_error(ctx, "Unknown ip option field '%s'.", field); return NULL; } - return ipopt_expr_alloc(int_loc, descval, fieldval, 0); + return ipopt_expr_alloc(int_loc, descval, fieldval); +} + +static int json_parse_sctp_chunk_field(const struct exthdr_desc *desc, + const char *name, int *val) +{ + unsigned int i; + + for (i = 0; i < array_size(desc->templates); i++) { + if (desc->templates[i].token && + !strcmp(desc->templates[i].token, name)) { + if (val) + *val = i; + return 0; + } + } + return 1; +} + +static struct expr *json_parse_sctp_chunk_expr(struct json_ctx *ctx, + const char *type, json_t *root) +{ + const struct exthdr_desc *desc; + const char *name, *field; + struct expr *expr; + int fieldval; + + if (json_unpack_err(ctx, root, "{s:s}", "name", &name)) + return NULL; + + desc = sctp_chunk_protocol_find(name); + if (!desc) { + json_error(ctx, "Unknown sctp chunk name '%s'.", name); + return NULL; + } + + if (json_unpack(root, "{s:s}", "field", &field)) { + expr = sctp_chunk_expr_alloc(int_loc, desc->type, + SCTP_CHUNK_COMMON_TYPE); + expr->exthdr.flags = NFT_EXTHDR_F_PRESENT; + + return expr; + } + if (json_parse_sctp_chunk_field(desc, field, &fieldval)) { + json_error(ctx, "Unknown sctp chunk field '%s'.", field); + return NULL; + } + return sctp_chunk_expr_alloc(int_loc, desc->type, fieldval); +} + +static struct expr *json_parse_dccp_option_expr(struct json_ctx *ctx, + const char *type, json_t *root) +{ + int opt_type; + + if (json_unpack_err(ctx, root, "{s:i}", "type", &opt_type)) + return NULL; + + if (opt_type < DCCPOPT_TYPE_MIN || opt_type > DCCPOPT_TYPE_MAX) { + json_error(ctx, "Unknown dccp option type '%d'.", opt_type); + return NULL; + } + + return dccpopt_expr_alloc(int_loc, opt_type); } static const struct exthdr_desc *exthdr_lookup_byname(const char *name) @@ -896,7 +1131,7 @@ static struct expr *json_parse_hash_expr(struct json_ctx *ctx, return hash_expr; } -static int fib_flag_parse(const char *name, int *flags) +static unsigned int fib_flag_parse(const char *name) { const char *fib_flags[] = { "saddr", @@ -908,12 +1143,10 @@ static int fib_flag_parse(const char *name, int *flags) unsigned int i; for (i = 0; i < array_size(fib_flags); i++) { - if (!strcmp(name, fib_flags[i])) { - *flags |= (1 << i); - return 0; - } + if (!strcmp(name, fib_flags[i])) + return 1 << i; } - return 1; + return 0; } static struct expr *json_parse_fib_expr(struct json_ctx *ctx, @@ -924,13 +1157,12 @@ static struct expr *json_parse_fib_expr(struct json_ctx *ctx, [NFT_FIB_RESULT_OIF] = "oif", [NFT_FIB_RESULT_OIFNAME] = "oifname", [NFT_FIB_RESULT_ADDRTYPE] = "type", + [__NFT_FIB_RESULT_MAX] = "check", /* Actually, NFT_FIB_F_PRESENT. */ }; enum nft_fib_result resultval = NFT_FIB_RESULT_UNSPEC; - json_t *flags, *value; const char *result; - unsigned int i; - size_t index; int flagval = 0; + unsigned int i; if (json_unpack_err(ctx, root, "{s:s}", "result", &result)) return NULL; @@ -941,39 +1173,21 @@ static struct expr *json_parse_fib_expr(struct json_ctx *ctx, break; } } - if (resultval == NFT_FIB_RESULT_UNSPEC) { + switch (resultval) { + case NFT_FIB_RESULT_UNSPEC: json_error(ctx, "Invalid fib result '%s'.", result); return NULL; + case __NFT_FIB_RESULT_MAX: + resultval = NFT_FIB_RESULT_OIF; + flagval = NFTA_FIB_F_PRESENT; + break; + default: + break; } - if (!json_unpack(root, "{s:o}", "flags", &flags)) { - const char *flag; - - if (json_is_string(flags)) { - flag = json_string_value(flags); - - if (fib_flag_parse(flag, &flagval)) { - json_error(ctx, "Invalid fib flag '%s'.", flag); - return NULL; - } - } else if (!json_is_array(flags)) { - json_error(ctx, "Unexpected object type in fib tuple."); - return NULL; - } - - json_array_foreach(flags, index, value) { - if (!json_is_string(value)) { - json_error(ctx, "Unexpected object type in fib flags array at index %zd.", index); - return NULL; - } - flag = json_string_value(value); - - if (fib_flag_parse(flag, &flagval)) { - json_error(ctx, "Invalid fib flag '%s'.", flag); - return NULL; - } - } - } + flagval |= parse_flags_array(ctx, root, "flags", fib_flag_parse); + if (flagval < 0) + return NULL; /* sanity checks from fib_expr in parser_bison.y */ @@ -983,13 +1197,13 @@ static struct expr *json_parse_fib_expr(struct json_ctx *ctx, } if ((flagval & (NFTA_FIB_F_SADDR|NFTA_FIB_F_DADDR)) == - (NFTA_FIB_F_SADDR|NFTA_FIB_F_DADDR)) { + (NFTA_FIB_F_SADDR|NFTA_FIB_F_DADDR)) { json_error(ctx, "fib: saddr and daddr are mutually exclusive"); return NULL; } if ((flagval & (NFTA_FIB_F_IIF|NFTA_FIB_F_OIF)) == - (NFTA_FIB_F_IIF|NFTA_FIB_F_OIF)) { + (NFTA_FIB_F_IIF|NFTA_FIB_F_OIF)) { json_error(ctx, "fib: iif and oif are mutually exclusive"); return NULL; } @@ -1027,6 +1241,32 @@ static struct expr *json_parse_binop_expr(struct json_ctx *ctx, return NULL; } + if (json_array_size(root) > 2) { + left = json_parse_primary_expr(ctx, json_array_get(root, 0)); + if (!left) { + json_error(ctx, "Failed to parse LHS of binop expression."); + return NULL; + } + right = json_parse_primary_expr(ctx, json_array_get(root, 1)); + if (!right) { + json_error(ctx, "Failed to parse RHS of binop expression."); + expr_free(left); + return NULL; + } + left = binop_expr_alloc(int_loc, thisop, left, right); + for (i = 2; i < json_array_size(root); i++) { + jright = json_array_get(root, i); + right = json_parse_primary_expr(ctx, jright); + if (!right) { + json_error(ctx, "Failed to parse RHS of binop expression."); + expr_free(left); + return NULL; + } + left = binop_expr_alloc(int_loc, thisop, left, right); + } + return left; + } + if (json_unpack_err(ctx, root, "[o, o!]", &jleft, &jright)) return NULL; @@ -1035,7 +1275,7 @@ static struct expr *json_parse_binop_expr(struct json_ctx *ctx, json_error(ctx, "Failed to parse LHS of binop expression."); return NULL; } - right = json_parse_primary_expr(ctx, jright); + right = json_parse_rhs_expr(ctx, jright); if (!right) { json_error(ctx, "Failed to parse RHS of binop expression."); expr_free(left); @@ -1044,6 +1284,17 @@ static struct expr *json_parse_binop_expr(struct json_ctx *ctx, return binop_expr_alloc(int_loc, thisop, left, right); } +static struct expr *json_check_concat_expr(struct json_ctx *ctx, struct expr *e) +{ + if (expr_concat(e)->size >= 2) + return e; + + json_error(ctx, "Concatenation with %d elements is illegal", + expr_concat(e)->size); + expr_free(e); + return NULL; +} + static struct expr *json_parse_concat_expr(struct json_ctx *ctx, const char *type, json_t *root) { @@ -1058,7 +1309,7 @@ static struct expr *json_parse_concat_expr(struct json_ctx *ctx, } json_array_foreach(root, index, value) { - tmp = json_parse_primary_expr(ctx, value); + tmp = json_parse_concat_elem_expr(ctx, value); if (!tmp) { json_error(ctx, "Parsing expr at index %zd failed.", index); expr_free(expr); @@ -1077,7 +1328,7 @@ static struct expr *json_parse_concat_expr(struct json_ctx *ctx, } compound_expr_add(expr, tmp); } - return expr; + return expr ? json_check_concat_expr(ctx, expr) : NULL; } static struct expr *json_parse_prefix_expr(struct json_ctx *ctx, @@ -1116,6 +1367,7 @@ static struct expr *json_parse_range_expr(struct json_ctx *ctx, expr_high = json_parse_primary_expr(ctx, high); if (!expr_high) { json_error(ctx, "Invalid high value in range expression."); + expr_free(expr_low); return NULL; } return range_expr_alloc(int_loc, expr_low, expr_high); @@ -1152,9 +1404,13 @@ static struct expr *json_parse_verdict_expr(struct json_ctx *ctx, if (strcmp(type, verdict_tbl[i].name)) continue; - if (verdict_tbl[i].need_chain && - json_unpack_err(ctx, root, "{s:s}", "target", &chain)) - return NULL; + if (verdict_tbl[i].need_chain) { + if (json_unpack_err(ctx, root, "{s:s}", "target", &chain)) + return NULL; + + if (!chain || chain[0] == '\0') + return NULL; + } return verdict_expr_alloc(int_loc, verdict_tbl[i].verdict, json_alloc_chain_expr(chain)); @@ -1354,28 +1610,30 @@ static struct expr *json_parse_expr(struct json_ctx *ctx, json_t *root) { "set", json_parse_set_expr, CTX_F_RHS | CTX_F_STMT }, /* allow this as stmt expr because that allows set references */ { "map", json_parse_map_expr, CTX_F_STMT | CTX_F_PRIMARY | CTX_F_SET_RHS }, /* below three are multiton_rhs_expr */ - { "prefix", json_parse_prefix_expr, CTX_F_RHS | CTX_F_STMT }, - { "range", json_parse_range_expr, CTX_F_RHS | CTX_F_STMT }, - { "payload", json_parse_payload_expr, CTX_F_STMT | CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_MANGLE | CTX_F_SES | CTX_F_MAP }, - { "exthdr", json_parse_exthdr_expr, CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_SES | CTX_F_MAP }, - { "tcp option", json_parse_tcp_option_expr, CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_MANGLE | CTX_F_SES }, - { "ip option", json_parse_ip_option_expr, CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_MANGLE | CTX_F_SES }, - { "meta", json_parse_meta_expr, CTX_F_STMT | CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_MANGLE | CTX_F_SES | CTX_F_MAP }, - { "osf", json_parse_osf_expr, CTX_F_STMT | CTX_F_PRIMARY | CTX_F_MAP }, - { "ipsec", json_parse_xfrm_expr, CTX_F_PRIMARY | CTX_F_MAP }, - { "socket", json_parse_socket_expr, CTX_F_PRIMARY }, - { "rt", json_parse_rt_expr, CTX_F_STMT | CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_SES | CTX_F_MAP }, - { "ct", json_parse_ct_expr, CTX_F_STMT | CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_MANGLE | CTX_F_SES | CTX_F_MAP }, - { "numgen", json_parse_numgen_expr, CTX_F_STMT | CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_SES | CTX_F_MAP }, + { "prefix", json_parse_prefix_expr, CTX_F_RHS | CTX_F_SET_RHS | CTX_F_STMT | CTX_F_CONCAT }, + { "range", json_parse_range_expr, CTX_F_RHS | CTX_F_SET_RHS | CTX_F_STMT | CTX_F_CONCAT }, + { "payload", json_parse_payload_expr, CTX_F_STMT | CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_MANGLE | CTX_F_SES | CTX_F_MAP | CTX_F_CONCAT }, + { "exthdr", json_parse_exthdr_expr, CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_SES | CTX_F_MAP | CTX_F_CONCAT }, + { "tcp option", json_parse_tcp_option_expr, CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_MANGLE | CTX_F_SES | CTX_F_CONCAT }, + { "ip option", json_parse_ip_option_expr, CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_MANGLE | CTX_F_SES | CTX_F_CONCAT }, + { "sctp chunk", json_parse_sctp_chunk_expr, CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_MANGLE | CTX_F_SES | CTX_F_CONCAT }, + { "dccp option", json_parse_dccp_option_expr, CTX_F_PRIMARY }, + { "meta", json_parse_meta_expr, CTX_F_RHS | CTX_F_STMT | CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_MANGLE | CTX_F_SES | CTX_F_MAP | CTX_F_CONCAT }, + { "osf", json_parse_osf_expr, CTX_F_STMT | CTX_F_PRIMARY | CTX_F_MAP | CTX_F_CONCAT }, + { "ipsec", json_parse_xfrm_expr, CTX_F_PRIMARY | CTX_F_MAP | CTX_F_CONCAT }, + { "socket", json_parse_socket_expr, CTX_F_PRIMARY | CTX_F_CONCAT }, + { "rt", json_parse_rt_expr, CTX_F_STMT | CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_SES | CTX_F_MAP | CTX_F_CONCAT }, + { "ct", json_parse_ct_expr, CTX_F_RHS | CTX_F_STMT | CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_MANGLE | CTX_F_SES | CTX_F_MAP | CTX_F_CONCAT }, + { "numgen", json_parse_numgen_expr, CTX_F_STMT | CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_SES | CTX_F_MAP | CTX_F_CONCAT }, /* below two are hash expr */ - { "jhash", json_parse_hash_expr, CTX_F_STMT | CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_SES | CTX_F_MAP }, - { "symhash", json_parse_hash_expr, CTX_F_STMT | CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_SES | CTX_F_MAP }, - { "fib", json_parse_fib_expr, CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_SES | CTX_F_MAP }, - { "|", json_parse_binop_expr, CTX_F_RHS | CTX_F_STMT | CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_SES | CTX_F_MAP }, - { "^", json_parse_binop_expr, CTX_F_RHS | CTX_F_STMT | CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_SES | CTX_F_MAP }, - { "&", json_parse_binop_expr, CTX_F_RHS | CTX_F_STMT | CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_SES | CTX_F_MAP }, - { ">>", json_parse_binop_expr, CTX_F_RHS | CTX_F_STMT | CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_SES | CTX_F_MAP }, - { "<<", json_parse_binop_expr, CTX_F_RHS | CTX_F_STMT | CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_SES | CTX_F_MAP }, + { "jhash", json_parse_hash_expr, CTX_F_STMT | CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_SES | CTX_F_MAP | CTX_F_CONCAT }, + { "symhash", json_parse_hash_expr, CTX_F_STMT | CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_SES | CTX_F_MAP | CTX_F_CONCAT }, + { "fib", json_parse_fib_expr, CTX_F_STMT | CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_SES | CTX_F_MAP | CTX_F_CONCAT }, + { "|", json_parse_binop_expr, CTX_F_RHS | CTX_F_STMT | CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_SES | CTX_F_MAP | CTX_F_CONCAT }, + { "^", json_parse_binop_expr, CTX_F_RHS | CTX_F_STMT | CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_SES | CTX_F_MAP | CTX_F_CONCAT }, + { "&", json_parse_binop_expr, CTX_F_RHS | CTX_F_STMT | CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_SES | CTX_F_MAP | CTX_F_CONCAT }, + { ">>", json_parse_binop_expr, CTX_F_RHS | CTX_F_STMT | CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_SES | CTX_F_MAP | CTX_F_CONCAT }, + { "<<", json_parse_binop_expr, CTX_F_RHS | CTX_F_STMT | CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_SES | CTX_F_MAP | CTX_F_CONCAT }, { "accept", json_parse_verdict_expr, CTX_F_RHS | CTX_F_SET_RHS }, { "drop", json_parse_verdict_expr, CTX_F_RHS | CTX_F_SET_RHS }, { "continue", json_parse_verdict_expr, CTX_F_RHS | CTX_F_SET_RHS }, @@ -1500,6 +1758,11 @@ static struct expr *json_parse_map_lhs_expr(struct json_ctx *ctx, json_t *root) return json_parse_flagged_expr(ctx, CTX_F_MAP, root); } +static struct expr *json_parse_concat_elem_expr(struct json_ctx *ctx, json_t *root) +{ + return json_parse_flagged_expr(ctx, CTX_F_CONCAT, root); +} + static struct expr *json_parse_dtype_expr(struct json_ctx *ctx, json_t *root) { if (json_is_string(root)) { @@ -1528,8 +1791,18 @@ static struct expr *json_parse_dtype_expr(struct json_ctx *ctx, json_t *root) } compound_expr_add(expr, i); } - return expr; + + return json_check_concat_expr(ctx, expr); + } else if (json_is_object(root)) { + const char *key; + json_t *val; + + if (!json_unpack_stmt(ctx, root, &key, &val) && + !strcmp(key, "typeof")) { + return json_parse_expr(ctx, val); + } } + json_error(ctx, "Invalid set datatype."); return NULL; } @@ -1553,13 +1826,18 @@ static struct stmt *json_parse_match_stmt(struct json_ctx *ctx, !strcmp(opstr, expr_op_symbols[op])) break; } - if (op == __OP_MAX) { + switch (op) { + case OP_EQ ... OP_NEG: + break; + case __OP_MAX: if (!strcmp(opstr, "in")) { op = OP_IMPLICIT; - } else { - json_error(ctx, "Unknown relational op '%s'.", opstr); - return NULL; + break; } + /* fall through */ + default: + json_error(ctx, "Invalid relational op '%s'.", opstr); + return NULL; } left = json_parse_expr(ctx, jleft); @@ -1579,7 +1857,7 @@ static struct stmt *json_parse_match_stmt(struct json_ctx *ctx, } static struct stmt *json_parse_counter_stmt(struct json_ctx *ctx, - const char *key, json_t *value) + const char *key, json_t *value) { uint64_t packets, bytes; struct stmt *stmt; @@ -1588,8 +1866,8 @@ static struct stmt *json_parse_counter_stmt(struct json_ctx *ctx, return counter_stmt_alloc(int_loc); if (!json_unpack(value, "{s:I, s:I}", - "packets", &packets, - "bytes", &bytes)) { + "packets", &packets, + "bytes", &bytes)) { stmt = counter_stmt_alloc(int_loc); stmt->counter.packets = packets; stmt->counter.bytes = bytes; @@ -1607,6 +1885,27 @@ static struct stmt *json_parse_counter_stmt(struct json_ctx *ctx, return stmt; } +static struct stmt *json_parse_last_stmt(struct json_ctx *ctx, + const char *key, json_t *value) +{ + struct stmt *stmt; + int64_t used; + + if (json_is_null(value)) + return last_stmt_alloc(int_loc); + + if (!json_unpack(value, "{s:I}", "used", &used)) { + stmt = last_stmt_alloc(int_loc); + if (used != -1) { + stmt->last.used = used; + stmt->last.set = 1; + } + return stmt; + } + + return NULL; +} + static struct stmt *json_parse_verdict_stmt(struct json_ctx *ctx, const char *key, json_t *value) { @@ -1620,14 +1919,14 @@ static struct stmt *json_parse_verdict_stmt(struct json_ctx *ctx, } static struct stmt *json_parse_mangle_stmt(struct json_ctx *ctx, - const char *type, json_t *root) + const char *type, json_t *root) { json_t *jkey, *jvalue; struct expr *key, *value; struct stmt *stmt; if (json_unpack_err(ctx, root, "{s:o, s:o}", - "key", &jkey, "value", &jvalue)) + "key", &jkey, "value", &jvalue)) return NULL; key = json_parse_mangle_lhs_expr(ctx, jkey); @@ -1664,6 +1963,8 @@ static struct stmt *json_parse_mangle_stmt(struct json_ctx *ctx, return stmt; default: json_error(ctx, "Invalid mangle statement key expression type."); + expr_free(key); + expr_free(value); return NULL; } } @@ -1680,7 +1981,7 @@ static uint64_t rate_to_bytes(uint64_t val, const char *unit) } static struct stmt *json_parse_quota_stmt(struct json_ctx *ctx, - const char *key, json_t *value) + const char *key, json_t *value) { struct stmt *stmt; int inv = 0; @@ -1741,6 +2042,9 @@ static struct stmt *json_parse_limit_stmt(struct json_ctx *ctx, stmt = limit_stmt_alloc(int_loc); if (!strcmp(rate_unit, "packets")) { + if (burst == 0) + burst = 5; + stmt->limit.type = NFT_LIMIT_PKTS; stmt->limit.rate = rate; stmt->limit.burst = burst; @@ -1804,8 +2108,30 @@ out_err: return NULL; } +static struct stmt *json_parse_flow_offload_stmt(struct json_ctx *ctx, + const char *key, json_t *value) +{ + const char *opstr, *flowtable; + + if (json_unpack_err(ctx, value, "{s:s, s:s}", + "op", &opstr, "flowtable", &flowtable)) + return NULL; + + if (strcmp(opstr, "add")) { + json_error(ctx, "Unknown flow offload statement op '%s'.", opstr); + return NULL; + } + + if (flowtable[0] != '@') { + json_error(ctx, "Illegal flowtable reference in flow offload statement."); + return NULL; + } + + return flow_offload_stmt_alloc(int_loc, xstrdup(flowtable + 1)); +} + static struct stmt *json_parse_notrack_stmt(struct json_ctx *ctx, - const char *key, json_t *value) + const char *key, json_t *value) { return notrack_stmt_alloc(int_loc); } @@ -1842,8 +2168,24 @@ static struct stmt *json_parse_dup_stmt(struct json_ctx *ctx, return stmt; } -static int json_parse_nat_flag(struct json_ctx *ctx, - json_t *root, int *flags) +static struct stmt *json_parse_secmark_stmt(struct json_ctx *ctx, + const char *key, json_t *value) +{ + struct stmt *stmt; + + stmt = objref_stmt_alloc(int_loc); + stmt->objref.type = NFT_OBJECT_SECMARK; + stmt->objref.expr = json_parse_stmt_expr(ctx, value); + if (!stmt->objref.expr) { + json_error(ctx, "Invalid secmark reference."); + stmt_free(stmt); + return NULL; + } + + return stmt; +} + +static unsigned int json_parse_nat_flag(const char *flag) { const struct { const char *flag; @@ -1852,48 +2194,34 @@ static int json_parse_nat_flag(struct json_ctx *ctx, { "random", NF_NAT_RANGE_PROTO_RANDOM }, { "fully-random", NF_NAT_RANGE_PROTO_RANDOM_FULLY }, { "persistent", NF_NAT_RANGE_PERSISTENT }, + { "netmap", NF_NAT_RANGE_NETMAP }, }; - const char *flag; unsigned int i; - assert(flags); - - if (!json_is_string(root)) { - json_error(ctx, "Invalid nat flag type %s, expected string.", - json_typename(root)); - return 1; - } - flag = json_string_value(root); for (i = 0; i < array_size(flag_tbl); i++) { - if (!strcmp(flag, flag_tbl[i].flag)) { - *flags |= flag_tbl[i].val; - return 0; - } + if (!strcmp(flag, flag_tbl[i].flag)) + return flag_tbl[i].val; } - json_error(ctx, "Unknown nat flag '%s'.", flag); - return 1; + return 0; } -static int json_parse_nat_flags(struct json_ctx *ctx, json_t *root) +static unsigned int json_parse_nat_type_flag(const char *flag) { - int flags = 0; - json_t *value; - size_t index; + const struct { + const char *flag; + int val; + } flag_tbl[] = { + { "interval", STMT_NAT_F_INTERVAL }, + { "prefix", STMT_NAT_F_PREFIX }, + { "concat", STMT_NAT_F_CONCAT }, + }; + unsigned int i; - if (json_is_string(root)) { - json_parse_nat_flag(ctx, root, &flags); - return flags; - } else if (!json_is_array(root)) { - json_error(ctx, "Invalid nat flags type %s.", - json_typename(root)); - return -1; - } - json_array_foreach(root, index, value) { - if (json_parse_nat_flag(ctx, value, &flags)) - json_error(ctx, "Parsing nat flag at index %zu failed.", - index); + for (i = 0; i < array_size(flag_tbl); i++) { + if (!strcmp(flag, flag_tbl[i].flag)) + return flag_tbl[i].val; } - return flags; + return 0; } static int nat_type_parse(const char *type) @@ -1916,7 +2244,7 @@ static int nat_type_parse(const char *type) static struct stmt *json_parse_nat_stmt(struct json_ctx *ctx, const char *key, json_t *value) { - int type, familyval; + int type, familyval, flags; struct stmt *stmt; json_t *tmp; @@ -1949,20 +2277,26 @@ static struct stmt *json_parse_nat_stmt(struct json_ctx *ctx, return NULL; } } - if (!json_unpack(value, "{s:o}", "flags", &tmp)) { - int flags = json_parse_nat_flags(ctx, tmp); + flags = parse_flags_array(ctx, value, "flags", json_parse_nat_flag); + if (flags < 0) { + stmt_free(stmt); + return NULL; + } + stmt->nat.flags = flags; - if (flags < 0) { - stmt_free(stmt); - return NULL; - } - stmt->nat.flags = flags; + flags = parse_flags_array(ctx, value, "type_flags", + json_parse_nat_type_flag); + if (flags < 0) { + stmt_free(stmt); + return NULL; } + stmt->nat.type_flags = flags; + return stmt; } static struct stmt *json_parse_tproxy_stmt(struct json_ctx *ctx, - const char *key, json_t *value) + const char *key, json_t *value) { json_t *jaddr, *tmp; struct stmt *stmt; @@ -1998,7 +2332,7 @@ out_free: } static struct stmt *json_parse_reject_stmt(struct json_ctx *ctx, - const char *key, json_t *value) + const char *key, json_t *value) { struct stmt *stmt = reject_stmt_alloc(int_loc); const struct datatype *dtype = NULL; @@ -2014,17 +2348,17 @@ static struct stmt *json_parse_reject_stmt(struct json_ctx *ctx, stmt->reject.icmp_code = 0; } else if (!strcmp(type, "icmpx")) { stmt->reject.type = NFT_REJECT_ICMPX_UNREACH; - dtype = &icmpx_code_type; + dtype = &reject_icmpx_code_type; stmt->reject.icmp_code = 0; } else if (!strcmp(type, "icmp")) { stmt->reject.type = NFT_REJECT_ICMP_UNREACH; stmt->reject.family = NFPROTO_IPV4; - dtype = &icmp_code_type; + dtype = &reject_icmp_code_type; stmt->reject.icmp_code = 0; } else if (!strcmp(type, "icmpv6")) { stmt->reject.type = NFT_REJECT_ICMP_UNREACH; stmt->reject.family = NFPROTO_IPV6; - dtype = &icmpv6_code_type; + dtype = &reject_icmpv6_code_type; stmt->reject.icmp_code = 0; } } @@ -2040,13 +2374,51 @@ static struct stmt *json_parse_reject_stmt(struct json_ctx *ctx, return stmt; } +static int json_parse_set_stmt_list(struct json_ctx *ctx, + struct list_head *stmt_list, + json_t *stmt_json) +{ + struct list_head *head; + struct stmt *stmt; + json_t *value; + size_t index; + + if (!stmt_json) + return 0; + + if (!json_is_array(stmt_json)) { + json_error(ctx, "Unexpected object type in stmt"); + return -1; + } + + head = stmt_list; + json_array_foreach(stmt_json, index, value) { + stmt = json_parse_stmt(ctx, value); + if (!stmt) { + json_error(ctx, "Parsing set statements array at index %zd failed.", index); + stmt_list_free(stmt_list); + return -1; + } + if (!(stmt->flags & STMT_F_STATEFUL)) { + stmt_free(stmt); + json_error(ctx, "Unsupported set statements array at index %zd failed.", index); + stmt_list_free(stmt_list); + return -1; + } + list_add(&stmt->list, head); + head = &stmt->list; + } + + return 0; +} + static struct stmt *json_parse_set_stmt(struct json_ctx *ctx, - const char *key, json_t *value) + const char *key, json_t *value) { const char *opstr, *set; struct expr *expr, *expr2; + json_t *elem, *stmt_json; struct stmt *stmt; - json_t *elem; int op; if (json_unpack_err(ctx, value, "{s:s, s:o, s:s}", @@ -2081,11 +2453,77 @@ static struct stmt *json_parse_set_stmt(struct json_ctx *ctx, stmt->set.op = op; stmt->set.key = expr; stmt->set.set = expr2; + + if (!json_unpack(value, "{s:o}", "stmt", &stmt_json) && + json_parse_set_stmt_list(ctx, &stmt->set.stmt_list, stmt_json) < 0) { + stmt_free(stmt); + return NULL; + } + + return stmt; +} + +static struct stmt *json_parse_map_stmt(struct json_ctx *ctx, + const char *key, json_t *value) +{ + struct expr *expr, *expr2, *expr_data; + json_t *elem, *data, *stmt_json; + const char *opstr, *set; + struct stmt *stmt; + int op; + + if (json_unpack_err(ctx, value, "{s:s, s:o, s:o, s:s}", + "op", &opstr, "elem", &elem, "data", &data, "map", &set)) + return NULL; + + if (!strcmp(opstr, "add")) { + op = NFT_DYNSET_OP_ADD; + } else if (!strcmp(opstr, "update")) { + op = NFT_DYNSET_OP_UPDATE; + } else if (!strcmp(opstr, "delete")) { + op = NFT_DYNSET_OP_DELETE; + } else { + json_error(ctx, "Unknown map statement op '%s'.", opstr); + return NULL; + } + + expr = json_parse_set_elem_expr_stmt(ctx, elem); + if (!expr) { + json_error(ctx, "Illegal map statement element."); + return NULL; + } + + expr_data = json_parse_set_elem_expr_stmt(ctx, data); + if (!expr_data) { + json_error(ctx, "Illegal map expression data."); + expr_free(expr); + return NULL; + } + + if (set[0] != '@') { + json_error(ctx, "Illegal map reference in map statement."); + expr_free(expr); + expr_free(expr_data); + return NULL; + } + expr2 = symbol_expr_alloc(int_loc, SYMBOL_SET, NULL, set + 1); + + stmt = map_stmt_alloc(int_loc); + stmt->map.op = op; + stmt->map.key = expr; + stmt->map.data = expr_data; + stmt->map.set = expr2; + + if (!json_unpack(value, "{s:o}", "stmt", &stmt_json) && + json_parse_set_stmt_list(ctx, &stmt->set.stmt_list, stmt_json) < 0) { + stmt_free(stmt); + return NULL; + } + return stmt; } -static int json_parse_log_flag(struct json_ctx *ctx, - json_t *root, int *flags) +static unsigned int json_parse_log_flag(const char *flag) { const struct { const char *flag; @@ -2098,47 +2536,13 @@ static int json_parse_log_flag(struct json_ctx *ctx, { "ether", NF_LOG_MACDECODE }, { "all", NF_LOG_MASK }, }; - const char *flag; unsigned int i; - assert(flags); - - if (!json_is_string(root)) { - json_error(ctx, "Invalid log flag type %s, expected string.", - json_typename(root)); - return 1; - } - flag = json_string_value(root); for (i = 0; i < array_size(flag_tbl); i++) { - if (!strcmp(flag, flag_tbl[i].flag)) { - *flags |= flag_tbl[i].val; - return 0; - } - } - json_error(ctx, "Unknown log flag '%s'.", flag); - return 1; -} - -static int json_parse_log_flags(struct json_ctx *ctx, json_t *root) -{ - int flags = 0; - json_t *value; - size_t index; - - if (json_is_string(root)) { - json_parse_log_flag(ctx, root, &flags); - return flags; - } else if (!json_is_array(root)) { - json_error(ctx, "Invalid log flags type %s.", - json_typename(root)); - return -1; - } - json_array_foreach(root, index, value) { - if (json_parse_log_flag(ctx, value, &flags)) - json_error(ctx, "Parsing log flag at index %zu failed.", - index); + if (!strcmp(flag, flag_tbl[i].flag)) + return flag_tbl[i].val; } - return flags; + return 0; } static struct stmt *json_parse_log_stmt(struct json_ctx *ctx, @@ -2146,8 +2550,7 @@ static struct stmt *json_parse_log_stmt(struct json_ctx *ctx, { const char *tmpstr; struct stmt *stmt; - json_t *jflags; - int tmp; + int tmp, flags; stmt = log_stmt_alloc(int_loc); @@ -2178,20 +2581,17 @@ static struct stmt *json_parse_log_stmt(struct json_ctx *ctx, stmt->log.level = level; stmt->log.flags |= STMT_LOG_LEVEL; } - if (!json_unpack(value, "{s:o}", "flags", &jflags)) { - int flags = json_parse_log_flags(ctx, jflags); - - if (flags < 0) { - stmt_free(stmt); - return NULL; - } - stmt->log.logflags = flags; + flags = parse_flags_array(ctx, value, "flags", json_parse_log_flag); + if (flags < 0) { + stmt_free(stmt); + return NULL; } + stmt->log.logflags = flags; + return stmt; } -static int json_parse_synproxy_flag(struct json_ctx *ctx, - json_t *root, int *flags) +static unsigned int json_parse_synproxy_flag(const char *flag) { const struct { const char *flag; @@ -2200,54 +2600,19 @@ static int json_parse_synproxy_flag(struct json_ctx *ctx, { "timestamp", NF_SYNPROXY_OPT_TIMESTAMP }, { "sack-perm", NF_SYNPROXY_OPT_SACK_PERM }, }; - const char *flag; unsigned int i; - assert(flags); - - if (!json_is_string(root)) { - json_error(ctx, "Invalid synproxy flag type %s, expected string.", - json_typename(root)); - return 1; - } - flag = json_string_value(root); for (i = 0; i < array_size(flag_tbl); i++) { - if (!strcmp(flag, flag_tbl[i].flag)) { - *flags |= flag_tbl[i].val; - return 0; - } - } - json_error(ctx, "Unknown synproxy flag '%s'.", flag); - return 1; -} - -static int json_parse_synproxy_flags(struct json_ctx *ctx, json_t *root) -{ - int flags = 0; - json_t *value; - size_t index; - - if (json_is_string(root)) { - json_parse_synproxy_flag(ctx, root, &flags); - return flags; - } else if (!json_is_array(root)) { - json_error(ctx, "Invalid synproxy flags type %s.", - json_typename(root)); - return -1; - } - json_array_foreach(root, index, value) { - if (json_parse_synproxy_flag(ctx, value, &flags)) - json_error(ctx, "Parsing synproxy flag at index %zu failed.", - index); + if (!strcmp(flag, flag_tbl[i].flag)) + return flag_tbl[i].val; } - return flags; + return 0; } static struct stmt *json_parse_synproxy_stmt(struct json_ctx *ctx, const char *key, json_t *value) { struct stmt *stmt = NULL; - json_t *jflags; int tmp, flags; if (json_typeof(value) == JSON_NULL) { @@ -2277,15 +2642,16 @@ static struct stmt *json_parse_synproxy_stmt(struct json_ctx *ctx, stmt->synproxy.wscale = tmp; stmt->synproxy.flags |= NF_SYNPROXY_OPT_WSCALE; } - if (!json_unpack(value, "{s:o}", "flags", &jflags)) { + + flags = parse_flags_array(ctx, value, "flags", + json_parse_synproxy_flag); + if (flags < 0) { + stmt_free(stmt); + return NULL; + } + if (flags) { if (!stmt) stmt = synproxy_stmt_alloc(int_loc); - flags = json_parse_synproxy_flags(ctx, jflags); - - if (flags < 0) { - stmt_free(stmt); - return NULL; - } stmt->synproxy.flags |= flags; } @@ -2318,7 +2684,7 @@ static struct stmt *json_parse_cthelper_stmt(struct json_ctx *ctx, } static struct stmt *json_parse_cttimeout_stmt(struct json_ctx *ctx, - const char *key, json_t *value) + const char *key, json_t *value) { struct stmt *stmt = objref_stmt_alloc(int_loc); @@ -2353,7 +2719,7 @@ static struct stmt *json_parse_meter_stmt(struct json_ctx *ctx, json_t *jkey, *jstmt; struct stmt *stmt; const char *name; - uint32_t size = 0xffff; + uint32_t size = 0; if (json_unpack_err(ctx, value, "{s:s, s:o, s:o}", "name", &name, "key", &jkey, "stmt", &jstmt)) @@ -2380,69 +2746,37 @@ static struct stmt *json_parse_meter_stmt(struct json_ctx *ctx, return stmt; } -static int queue_flag_parse(const char *name, uint16_t *flags) +static unsigned int queue_flag_parse(const char *name) { if (!strcmp(name, "bypass")) - *flags |= NFT_QUEUE_FLAG_BYPASS; + return NFT_QUEUE_FLAG_BYPASS; else if (!strcmp(name, "fanout")) - *flags |= NFT_QUEUE_FLAG_CPU_FANOUT; - else - return 1; + return NFT_QUEUE_FLAG_CPU_FANOUT; return 0; } static struct stmt *json_parse_queue_stmt(struct json_ctx *ctx, const char *key, json_t *value) { - struct stmt *stmt = queue_stmt_alloc(int_loc); + struct expr *qexpr = NULL; json_t *tmp; + int flags; if (!json_unpack(value, "{s:o}", "num", &tmp)) { - stmt->queue.queue = json_parse_stmt_expr(ctx, tmp); - if (!stmt->queue.queue) { + qexpr = json_parse_stmt_expr(ctx, tmp); + if (!qexpr) { json_error(ctx, "Invalid queue num."); - stmt_free(stmt); return NULL; } } - if (!json_unpack(value, "{s:o}", "flags", &tmp)) { - const char *flag; - size_t index; - json_t *val; - - if (json_is_string(tmp)) { - flag = json_string_value(tmp); - if (queue_flag_parse(flag, &stmt->queue.flags)) { - json_error(ctx, "Invalid queue flag '%s'.", - flag); - stmt_free(stmt); - return NULL; - } - } else if (!json_is_array(tmp)) { - json_error(ctx, "Unexpected object type in queue flags."); - stmt_free(stmt); - return NULL; - } - - json_array_foreach(tmp, index, val) { - if (!json_is_string(val)) { - json_error(ctx, "Invalid object in queue flag array at index %zu.", - index); - stmt_free(stmt); - return NULL; - } - flag = json_string_value(val); - - if (queue_flag_parse(flag, &stmt->queue.flags)) { - json_error(ctx, "Invalid queue flag '%s'.", - flag); - stmt_free(stmt); - return NULL; - } - } + flags = parse_flags_array(ctx, value, "flags", queue_flag_parse); + if (flags < 0) { + expr_free(qexpr); + return NULL; } - return stmt; + + return queue_stmt_alloc(int_loc, qexpr, flags); } static struct stmt *json_parse_connlimit_stmt(struct json_ctx *ctx, @@ -2463,6 +2797,22 @@ static struct stmt *json_parse_connlimit_stmt(struct json_ctx *ctx, return stmt; } +static struct stmt *json_parse_optstrip_stmt(struct json_ctx *ctx, + const char *key, json_t *value) +{ + struct expr *expr = json_parse_expr(ctx, value); + + if (!expr || + expr->etype != EXPR_EXTHDR || + expr->exthdr.op != NFT_EXTHDR_OP_TCPOPT) { + json_error(ctx, "Illegal TCP optstrip argument"); + expr_free(expr); + return NULL; + } + + return optstrip_stmt_alloc(int_loc, expr); +} + static struct stmt *json_parse_stmt(struct json_ctx *ctx, json_t *root) { struct { @@ -2479,7 +2829,9 @@ static struct stmt *json_parse_stmt(struct json_ctx *ctx, json_t *root) { "counter", json_parse_counter_stmt }, { "mangle", json_parse_mangle_stmt }, { "quota", json_parse_quota_stmt }, + { "last", json_parse_last_stmt }, { "limit", json_parse_limit_stmt }, + { "flow", json_parse_flow_offload_stmt }, { "fwd", json_parse_fwd_stmt }, { "notrack", json_parse_notrack_stmt }, { "dup", json_parse_dup_stmt }, @@ -2489,6 +2841,7 @@ static struct stmt *json_parse_stmt(struct json_ctx *ctx, json_t *root) { "redirect", json_parse_nat_stmt }, { "reject", json_parse_reject_stmt }, { "set", json_parse_set_stmt }, + { "map", json_parse_map_stmt }, { "log", json_parse_log_stmt }, { "ct helper", json_parse_cthelper_stmt }, { "ct timeout", json_parse_cttimeout_stmt }, @@ -2498,6 +2851,8 @@ static struct stmt *json_parse_stmt(struct json_ctx *ctx, json_t *root) { "ct count", json_parse_connlimit_stmt }, { "tproxy", json_parse_tproxy_stmt }, { "synproxy", json_parse_synproxy_stmt }, + { "reset", json_parse_optstrip_stmt }, + { "secmark", json_parse_secmark_stmt }, }; const char *type; unsigned int i; @@ -2517,6 +2872,11 @@ static struct stmt *json_parse_stmt(struct json_ctx *ctx, json_t *root) return verdict_stmt_alloc(int_loc, expr); } + if (!strcmp(type, "xt")) { + json_error(ctx, "unsupported xtables compat expression, use iptables-nft with this ruleset"); + return NULL; + } + for (i = 0; i < array_size(stmt_parser_tbl); i++) { if (!strcmp(type, stmt_parser_tbl[i].key)) return stmt_parser_tbl[i].cb(ctx, stmt_parser_tbl[i].key, tmp); @@ -2529,17 +2889,26 @@ static struct stmt *json_parse_stmt(struct json_ctx *ctx, json_t *root) static struct cmd *json_parse_cmd_add_table(struct json_ctx *ctx, json_t *root, enum cmd_ops op, enum cmd_obj obj) { + const char *family = "", *comment = NULL; struct handle h = { .table.location = *int_loc, }; - const char *family = ""; + struct table *table = NULL; + int flags = 0; if (json_unpack_err(ctx, root, "{s:s}", "family", &family)) return NULL; - if (op != CMD_DELETE && - json_unpack_err(ctx, root, "{s:s}", "name", &h.table.name)) { - return NULL; + + if (op != CMD_DELETE) { + if (json_unpack_err(ctx, root, "{s:s}", "name", &h.table.name)) + return NULL; + + json_unpack(root, "{s:s}", "comment", &comment); + + flags = parse_flags_array(ctx, root, "flags", parse_table_flag); + if (flags < 0) + return NULL; } else if (op == CMD_DELETE && json_unpack(root, "{s:s}", "name", &h.table.name) && json_unpack(root, "{s:I}", "handle", &h.handle.id)) { @@ -2553,10 +2922,18 @@ static struct cmd *json_parse_cmd_add_table(struct json_ctx *ctx, json_t *root, if (h.table.name) h.table.name = xstrdup(h.table.name); + if (comment || flags) { + table = table_alloc(); + handle_merge(&table->handle, &h); + if (comment) + table->comment = xstrdup(comment); + table->flags = flags; + } + if (op == CMD_ADD) json_object_del(root, "handle"); - return cmd_alloc(op, obj, &h, int_loc, NULL); + return cmd_alloc(op, obj, &h, int_loc, table); } static struct expr *parse_policy(const char *policy) @@ -2575,24 +2952,73 @@ static struct expr *parse_policy(const char *policy) sizeof(int) * BITS_PER_BYTE, &policy_num); } +static struct expr *json_parse_devs(struct json_ctx *ctx, json_t *root) +{ + struct expr *tmp, *expr = compound_expr_alloc(int_loc, EXPR_LIST); + const char *dev; + json_t *value; + size_t index; + + if (!json_unpack(root, "s", &dev)) { + if (strlen(dev) >= IFNAMSIZ) { + json_error(ctx, "Device name %s too long", dev); + expr_free(expr); + return NULL; + } + + tmp = constant_expr_alloc(int_loc, &ifname_type, + BYTEORDER_HOST_ENDIAN, + strlen(dev) * BITS_PER_BYTE, dev); + compound_expr_add(expr, tmp); + return expr; + } + if (!json_is_array(root)) { + expr_free(expr); + return NULL; + } + + json_array_foreach(root, index, value) { + if (json_unpack(value, "s", &dev)) { + json_error(ctx, "Invalid device at index %zu.", + index); + expr_free(expr); + return NULL; + } + + if (strlen(dev) >= IFNAMSIZ) { + json_error(ctx, "Device name %s too long at index %zu", dev, index); + expr_free(expr); + return NULL; + } + + tmp = constant_expr_alloc(int_loc, &ifname_type, + BYTEORDER_HOST_ENDIAN, + strlen(dev) * BITS_PER_BYTE, dev); + compound_expr_add(expr, tmp); + } + return expr; +} + static struct cmd *json_parse_cmd_add_chain(struct json_ctx *ctx, json_t *root, enum cmd_ops op, enum cmd_obj obj) { struct handle h = { .table.location = *int_loc, }; - const char *family = "", *policy = "", *type, *hookstr; - const char name[IFNAMSIZ]; - struct chain *chain; + const char *family = "", *policy = "", *type, *hookstr, *comment = NULL; + struct chain *chain = NULL; + json_t *devs = NULL; int prio; if (json_unpack_err(ctx, root, "{s:s, s:s}", "family", &family, "table", &h.table.name)) return NULL; - if (op != CMD_DELETE && - json_unpack_err(ctx, root, "{s:s}", "name", &h.chain.name)) { - return NULL; + if (op != CMD_DELETE) { + if (json_unpack_err(ctx, root, "{s:s}", "name", &h.chain.name)) + return NULL; + + json_unpack(root, "{s:s}", "comment", &comment); } else if (op == CMD_DELETE && json_unpack(root, "{s:s}", "name", &h.chain.name) && json_unpack(root, "{s:I}", "handle", &h.handle.id)) { @@ -2607,45 +3033,49 @@ static struct cmd *json_parse_cmd_add_chain(struct json_ctx *ctx, json_t *root, if (h.chain.name) h.chain.name = xstrdup(h.chain.name); + if (comment) { + chain = chain_alloc(); + handle_merge(&chain->handle, &h); + chain->comment = xstrdup(comment); + } + if (op == CMD_DELETE || op == CMD_LIST || op == CMD_FLUSH || json_unpack(root, "{s:s, s:s, s:i}", "type", &type, "hook", &hookstr, "prio", &prio)) - return cmd_alloc(op, obj, &h, int_loc, NULL); + return cmd_alloc(op, obj, &h, int_loc, chain); + + if (!chain) + chain = chain_alloc(); - chain = chain_alloc(NULL); chain->flags |= CHAIN_F_BASECHAIN; - chain->type = xstrdup(type); + chain->type.str = xstrdup(type); chain->priority.expr = constant_expr_alloc(int_loc, &integer_type, BYTEORDER_HOST_ENDIAN, sizeof(int) * BITS_PER_BYTE, &prio); - chain->hookstr = chain_hookname_lookup(hookstr); - if (!chain->hookstr) { + chain->hook.name = chain_hookname_lookup(hookstr); + if (!chain->hook.name) { json_error(ctx, "Invalid chain hook '%s'.", hookstr); - chain_free(chain); - return NULL; + goto err_free_chain; } - if (!json_unpack(root, "{s:s}", "dev", &name)) { - struct expr *dev_expr, *expr; + json_unpack(root, "{s:o}", "dev", &devs); - dev_expr = compound_expr_alloc(int_loc, EXPR_LIST); - expr = constant_expr_alloc(int_loc, &integer_type, - BYTEORDER_HOST_ENDIAN, - strlen(name) * BITS_PER_BYTE, - name); - compound_expr_add(dev_expr, expr); - chain->dev_expr = dev_expr; + if (devs) { + chain->dev_expr = json_parse_devs(ctx, devs); + if (!chain->dev_expr) { + json_error(ctx, "Invalid chain dev."); + goto err_free_chain; + } } if (!json_unpack(root, "{s:s}", "policy", &policy)) { chain->policy = parse_policy(policy); if (!chain->policy) { json_error(ctx, "Unknown policy '%s'.", policy); - chain_free(chain); - return NULL; + goto err_free_chain; } } @@ -2654,6 +3084,11 @@ static struct cmd *json_parse_cmd_add_chain(struct json_ctx *ctx, json_t *root, handle_merge(&chain->handle, &h); return cmd_alloc(op, obj, &h, int_loc, chain); + +err_free_chain: + chain_free(chain); + handle_free(&h); + return NULL; } static struct cmd *json_parse_cmd_add_rule(struct json_ctx *ctx, json_t *root, @@ -2677,7 +3112,7 @@ static struct cmd *json_parse_cmd_add_rule(struct json_ctx *ctx, json_t *root, if (op != CMD_DELETE && json_unpack_err(ctx, root, "{s:o}", "expr", &tmp)) return NULL; - else if (op == CMD_DELETE && + else if ((op == CMD_DELETE || op == CMD_DESTROY) && json_unpack_err(ctx, root, "{s:I}", "handle", &h.handle.id)) return NULL; @@ -2688,11 +3123,12 @@ static struct cmd *json_parse_cmd_add_rule(struct json_ctx *ctx, json_t *root, h.table.name = xstrdup(h.table.name); h.chain.name = xstrdup(h.chain.name); - if (op == CMD_DELETE) + if (op == CMD_DELETE || op == CMD_DESTROY) return cmd_alloc(op, obj, &h, int_loc, NULL); if (!json_is_array(tmp)) { json_error(ctx, "Value of property \"expr\" must be an array."); + handle_free(&h); return NULL; } @@ -2712,46 +3148,52 @@ static struct cmd *json_parse_cmd_add_rule(struct json_ctx *ctx, json_t *root, if (!json_is_object(value)) { json_error(ctx, "Unexpected expr array element of type %s, expected object.", json_typename(value)); - rule_free(rule); - return NULL; + goto err_free_rule; } stmt = json_parse_stmt(ctx, value); if (!stmt) { json_error(ctx, "Parsing expr array at index %zd failed.", index); - rule_free(rule); - return NULL; + goto err_free_rule; } - rule->num_stmts++; - list_add_tail(&stmt->list, &rule->stmts); + rule_stmt_append(rule, stmt); } if (op == CMD_ADD) json_object_del(root, "handle"); return cmd_alloc(op, obj, &h, int_loc, rule); + +err_free_rule: + rule_free(rule); + handle_free(&h); + return NULL; } static int string_to_nft_object(const char *str) { const char *obj_tbl[__NFT_OBJECT_MAX] = { - [NFT_OBJECT_COUNTER] = "counter", - [NFT_OBJECT_QUOTA] = "quota", - [NFT_OBJECT_LIMIT] = "limit", - [NFT_OBJECT_SECMARK] = "secmark", + [NFT_OBJECT_COUNTER] = "counter", + [NFT_OBJECT_QUOTA] = "quota", + [NFT_OBJECT_CT_HELPER] = "ct helper", + [NFT_OBJECT_LIMIT] = "limit", + [NFT_OBJECT_CT_TIMEOUT] = "ct timeout", + [NFT_OBJECT_SECMARK] = "secmark", + [NFT_OBJECT_CT_EXPECT] = "ct expectation", + [NFT_OBJECT_SYNPROXY] = "synproxy", }; unsigned int i; - for (i = 0; i < NFT_OBJECT_MAX; i++) { + for (i = 0; i <= NFT_OBJECT_MAX; i++) { if (obj_tbl[i] && !strcmp(str, obj_tbl[i])) return i; } return 0; } -static int string_to_set_flag(const char *str) +static unsigned int string_to_set_flag(const char *str) { const struct { enum nft_set_flags val; @@ -2760,6 +3202,7 @@ static int string_to_set_flag(const char *str) { NFT_SET_CONSTANT, "constant" }, { NFT_SET_INTERVAL, "interval" }, { NFT_SET_TIMEOUT, "timeout" }, + { NFT_SET_EVAL, "dynamic" }, }; unsigned int i; @@ -2774,9 +3217,10 @@ static struct cmd *json_parse_cmd_add_set(struct json_ctx *ctx, json_t *root, enum cmd_ops op, enum cmd_obj obj) { struct handle h = { 0 }; - const char *family = "", *policy, *dtype_ext = NULL; + const char *family = "", *policy; + json_t *tmp, *stmt_json; struct set *set; - json_t *tmp; + int flags; if (json_unpack_err(ctx, root, "{s:s, s:s}", "family", &family, @@ -2785,7 +3229,7 @@ static struct cmd *json_parse_cmd_add_set(struct json_ctx *ctx, json_t *root, if (op != CMD_DELETE && json_unpack_err(ctx, root, "{s:s}", "name", &h.set.name)) { return NULL; - } else if (op == CMD_DELETE && + } else if ((op == CMD_DELETE || op == CMD_DESTROY) && json_unpack(root, "{s:s}", "name", &h.set.name) && json_unpack(root, "{s:I}", "handle", &h.handle.id)) { json_error(ctx, "Either name or handle required to delete a set."); @@ -2802,14 +3246,16 @@ static struct cmd *json_parse_cmd_add_set(struct json_ctx *ctx, json_t *root, switch (op) { case CMD_DELETE: + case CMD_DESTROY: case CMD_LIST: case CMD_FLUSH: + case CMD_RESET: return cmd_alloc(op, obj, &h, int_loc, NULL); default: break; } - set = set_alloc(NULL); + set = set_alloc(&internal_location); if (json_unpack(root, "{s:o}", "type", &tmp)) { json_error(ctx, "Invalid set type."); @@ -2825,19 +3271,21 @@ static struct cmd *json_parse_cmd_add_set(struct json_ctx *ctx, json_t *root, return NULL; } - if (!json_unpack(root, "{s:s}", "map", &dtype_ext)) { - const struct datatype *dtype; + if (!json_unpack(root, "{s:o}", "map", &tmp)) { + if (json_is_string(tmp)) { + const char *s = json_string_value(tmp); - set->objtype = string_to_nft_object(dtype_ext); + set->objtype = string_to_nft_object(s); + } if (set->objtype) { set->flags |= NFT_SET_OBJECT; - } else if ((dtype = datatype_lookup_byname(dtype_ext))) { - set->data = constant_expr_alloc(&netlink_location, - dtype, dtype->byteorder, - dtype->size, NULL); + } else if ((set->data = json_parse_dtype_expr(ctx, tmp))) { set->flags |= NFT_SET_MAP; } else { - json_error(ctx, "Invalid map type '%s'.", dtype_ext); + const char *dump = json_dumps(tmp, 0); + + json_error(ctx, "Invalid map type '%s'.", dump); + free_const(dump); set_free(set); handle_free(&h); return NULL; @@ -2855,23 +3303,16 @@ static struct cmd *json_parse_cmd_add_set(struct json_ctx *ctx, json_t *root, return NULL; } } - if (!json_unpack(root, "{s:o}", "flags", &tmp)) { - json_t *value; - size_t index; - - json_array_foreach(tmp, index, value) { - int flag; - if (!json_is_string(value) || - !(flag = string_to_set_flag(json_string_value(value)))) { - json_error(ctx, "Invalid set flag at index %zu.", index); - set_free(set); - handle_free(&h); - return NULL; - } - set->flags |= flag; - } + flags = parse_flags_array(ctx, root, "flags", string_to_set_flag); + if (flags < 0) { + json_error(ctx, "Invalid set flags in set '%s'.", h.set.name); + set_free(set); + handle_free(&h); + return NULL; } + set->flags |= flags; + if (!json_unpack(root, "{s:o}", "elem", &tmp)) { set->init = json_parse_set_expr(ctx, "elem", tmp); if (!set->init) { @@ -2886,6 +3327,14 @@ static struct cmd *json_parse_cmd_add_set(struct json_ctx *ctx, json_t *root, if (!json_unpack(root, "{s:i}", "gc-interval", &set->gc_int)) set->gc_int *= 1000; json_unpack(root, "{s:i}", "size", &set->desc.size); + json_unpack(root, "{s:b}", "auto-merge", &set->automerge); + + if (!json_unpack(root, "{s:o}", "stmt", &stmt_json) && + json_parse_set_stmt_list(ctx, &set->stmt_list, stmt_json) < 0) { + set_free(set); + handle_free(&h); + return NULL; + } handle_merge(&set->handle, &h); @@ -2924,38 +3373,16 @@ static struct cmd *json_parse_cmd_add_element(struct json_ctx *ctx, handle_free(&h); return NULL; } - return cmd_alloc(op, cmd_obj, &h, int_loc, expr); -} -static struct expr *json_parse_flowtable_devs(struct json_ctx *ctx, - json_t *root) -{ - struct expr *tmp, *expr = compound_expr_alloc(int_loc, EXPR_LIST); - const char *dev; - json_t *value; - size_t index; - - if (!json_unpack(root, "s", &dev)) { - tmp = symbol_expr_alloc(int_loc, SYMBOL_VALUE, NULL, dev); - compound_expr_add(expr, tmp); - return expr; - } - if (!json_is_array(root)) { + if ((op == CMD_CREATE || op == CMD_ADD) && + nft_cmd_collapse_elems(op, ctx->cmds, &h, expr)) { + handle_free(&h); expr_free(expr); + ctx->flags |= CTX_F_COLLAPSED; return NULL; } - json_array_foreach(root, index, value) { - if (json_unpack(value, "s", &dev)) { - json_error(ctx, "Invalid flowtable dev at index %zu.", - index); - expr_free(expr); - return NULL; - } - tmp = symbol_expr_alloc(int_loc, SYMBOL_VALUE, NULL, dev); - compound_expr_add(expr, tmp); - } - return expr; + return cmd_alloc(op, cmd_obj, &h, int_loc, expr); } static struct cmd *json_parse_cmd_add_flowtable(struct json_ctx *ctx, @@ -2965,7 +3392,7 @@ static struct cmd *json_parse_cmd_add_flowtable(struct json_ctx *ctx, const char *family, *hook, *hookstr; struct flowtable *flowtable; struct handle h = { 0 }; - json_t *devs; + json_t *devs = NULL; int prio; if (json_unpack_err(ctx, root, "{s:s, s:s}", @@ -2976,7 +3403,7 @@ static struct cmd *json_parse_cmd_add_flowtable(struct json_ctx *ctx, if (op != CMD_DELETE && json_unpack_err(ctx, root, "{s:s}", "name", &h.flowtable.name)) { return NULL; - } else if (op == CMD_DELETE && + } else if ((op == CMD_DELETE || op == CMD_DESTROY) && json_unpack(root, "{s:s}", "name", &h.flowtable.name) && json_unpack(root, "{s:I}", "handle", &h.handle.id)) { json_error(ctx, "Either name or handle required to delete a flowtable."); @@ -2991,17 +3418,18 @@ static struct cmd *json_parse_cmd_add_flowtable(struct json_ctx *ctx, if (h.flowtable.name) h.flowtable.name = xstrdup(h.flowtable.name); - if (op == CMD_DELETE || op == CMD_LIST) + if (op == CMD_DELETE || op == CMD_LIST || op == CMD_DESTROY) return cmd_alloc(op, cmd_obj, &h, int_loc, NULL); - if (json_unpack_err(ctx, root, "{s:s, s:I, s:o}", + if (json_unpack_err(ctx, root, "{s:s, s:i}", "hook", &hook, - "prio", &prio, - "dev", &devs)) { + "prio", &prio)) { handle_free(&h); return NULL; } + json_unpack(root, "{s:o}", "dev", &devs); + hookstr = chain_hookname_lookup(hook); if (!hookstr) { json_error(ctx, "Invalid flowtable hook '%s'.", hook); @@ -3010,18 +3438,20 @@ static struct cmd *json_parse_cmd_add_flowtable(struct json_ctx *ctx, } flowtable = flowtable_alloc(int_loc); - flowtable->hookstr = hookstr; + flowtable->hook.name = hookstr; flowtable->priority.expr = constant_expr_alloc(int_loc, &integer_type, BYTEORDER_HOST_ENDIAN, sizeof(int) * BITS_PER_BYTE, &prio); - flowtable->dev_expr = json_parse_flowtable_devs(ctx, devs); - if (!flowtable->dev_expr) { - json_error(ctx, "Invalid flowtable dev."); - flowtable_free(flowtable); - handle_free(&h); - return NULL; + if (devs) { + flowtable->dev_expr = json_parse_devs(ctx, devs); + if (!flowtable->dev_expr) { + json_error(ctx, "Invalid flowtable dev."); + flowtable_free(flowtable); + handle_free(&h); + return NULL; + } } return cmd_alloc(op, cmd_obj, &h, int_loc, flowtable); } @@ -3032,7 +3462,7 @@ static int json_parse_ct_timeout_policy(struct json_ctx *ctx, json_t *tmp, *val; const char *key; - if (!json_unpack(root, "{s:o}", "policy", &tmp)) + if (json_unpack(root, "{s:o}", "policy", &tmp)) return 0; if (!json_is_object(tmp)) { @@ -3040,7 +3470,6 @@ static int json_parse_ct_timeout_policy(struct json_ctx *ctx, return 1; } - init_list_head(&obj->ct_timeout.timeout_list); json_object_foreach(tmp, key, val) { struct timeout_state *ts; @@ -3065,10 +3494,9 @@ static struct cmd *json_parse_cmd_add_object(struct json_ctx *ctx, { const char *family, *tmp, *rate_unit = "packets", *burst_unit = "bytes"; uint32_t l3proto = NFPROTO_UNSPEC; + int inv = 0, flags = 0, i, j; struct handle h = { 0 }; - int inv = 0, flags = 0; struct obj *obj; - json_t *jflags; if (json_unpack_err(ctx, root, "{s:s, s:s}", "family", &family, @@ -3078,7 +3506,7 @@ static struct cmd *json_parse_cmd_add_object(struct json_ctx *ctx, cmd_obj == NFT_OBJECT_CT_HELPER) && json_unpack_err(ctx, root, "{s:s}", "name", &h.obj.name)) { return NULL; - } else if (op == CMD_DELETE && + } else if ((op == CMD_DELETE || op == CMD_DESTROY) && cmd_obj != NFT_OBJECT_CT_HELPER && json_unpack(root, "{s:s}", "name", &h.obj.name) && json_unpack(root, "{s:I}", "handle", &h.handle.id)) { @@ -3094,7 +3522,7 @@ static struct cmd *json_parse_cmd_add_object(struct json_ctx *ctx, if (h.obj.name) h.obj.name = xstrdup(h.obj.name); - if (op == CMD_DELETE || op == CMD_LIST) { + if (op == CMD_DELETE || op == CMD_LIST || op == CMD_DESTROY) { if (cmd_obj == NFT_OBJECT_CT_HELPER) return cmd_alloc_obj_ct(op, NFT_OBJECT_CT_HELPER, &h, int_loc, obj_alloc(int_loc)); @@ -3103,6 +3531,9 @@ static struct cmd *json_parse_cmd_add_object(struct json_ctx *ctx, obj = obj_alloc(int_loc); + if (!json_unpack(root, "{s:s}", "comment", &obj->comment)) + obj->comment = xstrdup(obj->comment); + switch (cmd_obj) { case CMD_OBJ_COUNTER: obj->type = NFT_OBJECT_COUNTER; @@ -3125,8 +3556,7 @@ static struct cmd *json_parse_cmd_add_object(struct json_ctx *ctx, if (ret < 0 || ret >= (int)sizeof(obj->secmark.ctx)) { json_error(ctx, "Invalid secmark context '%s', max length is %zu.", tmp, sizeof(obj->secmark.ctx)); - obj_free(obj); - return NULL; + goto err_free_obj; } } break; @@ -3142,8 +3572,7 @@ static struct cmd *json_parse_cmd_add_object(struct json_ctx *ctx, ret >= (int)sizeof(obj->ct_helper.name)) { json_error(ctx, "Invalid CT helper type '%s', max length is %zu.", tmp, sizeof(obj->ct_helper.name)); - obj_free(obj); - return NULL; + goto err_free_obj; } } if (!json_unpack(root, "{s:s}", "protocol", &tmp)) { @@ -3153,20 +3582,19 @@ static struct cmd *json_parse_cmd_add_object(struct json_ctx *ctx, obj->ct_helper.l4proto = IPPROTO_UDP; } else { json_error(ctx, "Invalid ct helper protocol '%s'.", tmp); - obj_free(obj); - return NULL; + goto err_free_obj; } } if (!json_unpack(root, "{s:s}", "l3proto", &tmp) && parse_family(tmp, &l3proto)) { json_error(ctx, "Invalid ct helper l3proto '%s'.", tmp); - obj_free(obj); - return NULL; + goto err_free_obj; } obj->ct_helper.l3proto = l3proto; break; case NFT_OBJECT_CT_TIMEOUT: cmd_obj = CMD_OBJ_CT_TIMEOUT; + init_list_head(&obj->ct_timeout.timeout_list); obj->type = NFT_OBJECT_CT_TIMEOUT; if (!json_unpack(root, "{s:s}", "protocol", &tmp)) { if (!strcmp(tmp, "tcp")) { @@ -3175,22 +3603,18 @@ static struct cmd *json_parse_cmd_add_object(struct json_ctx *ctx, obj->ct_timeout.l4proto = IPPROTO_UDP; } else { json_error(ctx, "Invalid ct timeout protocol '%s'.", tmp); - obj_free(obj); - return NULL; + goto err_free_obj; } } if (!json_unpack(root, "{s:s}", "l3proto", &tmp) && parse_family(tmp, &l3proto)) { json_error(ctx, "Invalid ct timeout l3proto '%s'.", tmp); - obj_free(obj); - return NULL; + goto err_free_obj; } - obj->ct_helper.l3proto = l3proto; + obj->ct_timeout.l3proto = l3proto; - if (json_parse_ct_timeout_policy(ctx, root, obj)) { - obj_free(obj); - return NULL; - } + if (json_parse_ct_timeout_policy(ctx, root, obj)) + goto err_free_obj; break; case NFT_OBJECT_CT_EXPECT: cmd_obj = CMD_OBJ_CT_EXPECT; @@ -3198,8 +3622,7 @@ static struct cmd *json_parse_cmd_add_object(struct json_ctx *ctx, if (!json_unpack(root, "{s:s}", "l3proto", &tmp) && parse_family(tmp, &l3proto)) { json_error(ctx, "Invalid ct expectation l3proto '%s'.", tmp); - obj_free(obj); - return NULL; + goto err_free_obj; } obj->ct_expect.l3proto = l3proto; if (!json_unpack(root, "{s:s}", "protocol", &tmp)) { @@ -3209,27 +3632,26 @@ static struct cmd *json_parse_cmd_add_object(struct json_ctx *ctx, obj->ct_expect.l4proto = IPPROTO_UDP; } else { json_error(ctx, "Invalid ct expectation protocol '%s'.", tmp); - obj_free(obj); - return NULL; + goto err_free_obj; } } - if (!json_unpack(root, "{s:o}", "dport", &tmp)) - obj->ct_expect.dport = atoi(tmp); - json_unpack(root, "{s:I}", "timeout", &obj->ct_expect.timeout); - if (!json_unpack(root, "{s:o}", "size", &tmp)) - obj->ct_expect.size = atoi(tmp); + if (!json_unpack(root, "{s:i}", "dport", &i)) + obj->ct_expect.dport = i; + if (!json_unpack(root, "{s:i}", "timeout", &i)) + obj->ct_expect.timeout = i; + if (!json_unpack(root, "{s:i}", "size", &i)) + obj->ct_expect.size = i; break; case CMD_OBJ_LIMIT: obj->type = NFT_OBJECT_LIMIT; if (json_unpack_err(ctx, root, "{s:I, s:s}", "rate", &obj->limit.rate, - "per", &tmp)) { - obj_free(obj); - return NULL; - } + "per", &tmp)) + goto err_free_obj; + json_unpack(root, "{s:s}", "rate_unit", &rate_unit); json_unpack(root, "{s:b}", "inv", &inv); - json_unpack(root, "{s:I}", "burst", &obj->limit.burst); + json_unpack(root, "{s:i}", "burst", &obj->limit.burst); json_unpack(root, "{s:s}", "burst_unit", &burst_unit); if (!strcmp(rate_unit, "packets")) { @@ -3247,21 +3669,19 @@ static struct cmd *json_parse_cmd_add_object(struct json_ctx *ctx, case CMD_OBJ_SYNPROXY: obj->type = NFT_OBJECT_SYNPROXY; if (json_unpack_err(ctx, root, "{s:i, s:i}", - "mss", &obj->synproxy.mss, - "wscale", &obj->synproxy.wscale)) { - obj_free(obj); - return NULL; - } + "mss", &i, "wscale", &j)) + goto err_free_obj; + + obj->synproxy.mss = i; + obj->synproxy.wscale = j; obj->synproxy.flags |= NF_SYNPROXY_OPT_MSS; obj->synproxy.flags |= NF_SYNPROXY_OPT_WSCALE; - if (!json_unpack(root, "{s:o}", "flags", &jflags)) { - flags = json_parse_synproxy_flags(ctx, jflags); - if (flags < 0) { - obj_free(obj); - return NULL; - } - obj->synproxy.flags |= flags; - } + flags = parse_flags_array(ctx, root, "flags", + json_parse_synproxy_flag); + if (flags < 0) + goto err_free_obj; + + obj->synproxy.flags |= flags; break; default: BUG("Invalid CMD '%d'", cmd_obj); @@ -3271,6 +3691,11 @@ static struct cmd *json_parse_cmd_add_object(struct json_ctx *ctx, json_object_del(root, "handle"); return cmd_alloc(op, cmd_obj, &h, int_loc, obj); + +err_free_obj: + obj_free(obj); + handle_free(&h); + return NULL; } static struct cmd *json_parse_cmd_add(struct json_ctx *ctx, @@ -3287,7 +3712,7 @@ static struct cmd *json_parse_cmd_add(struct json_ctx *ctx, { "rule", CMD_OBJ_RULE, json_parse_cmd_add_rule }, { "set", CMD_OBJ_SET, json_parse_cmd_add_set }, { "map", CMD_OBJ_SET, json_parse_cmd_add_set }, - { "element", CMD_OBJ_SETELEM, json_parse_cmd_add_element }, + { "element", CMD_OBJ_ELEMENTS, json_parse_cmd_add_element }, { "flowtable", CMD_OBJ_FLOWTABLE, json_parse_cmd_add_flowtable }, { "counter", CMD_OBJ_COUNTER, json_parse_cmd_add_object }, { "quota", CMD_OBJ_QUOTA, json_parse_cmd_add_object }, @@ -3295,7 +3720,8 @@ static struct cmd *json_parse_cmd_add(struct json_ctx *ctx, { "ct timeout", NFT_OBJECT_CT_TIMEOUT, json_parse_cmd_add_object }, { "ct expectation", NFT_OBJECT_CT_EXPECT, json_parse_cmd_add_object }, { "limit", CMD_OBJ_LIMIT, json_parse_cmd_add_object }, - { "secmark", CMD_OBJ_SECMARK, json_parse_cmd_add_object } + { "secmark", CMD_OBJ_SECMARK, json_parse_cmd_add_object }, + { "synproxy", CMD_OBJ_SYNPROXY, json_parse_cmd_add_object } }; unsigned int i; json_t *tmp; @@ -3384,8 +3810,7 @@ static struct cmd *json_parse_cmd_replace(struct json_ctx *ctx, if (!json_is_object(value)) { json_error(ctx, "Unexpected expr array element of type %s, expected object.", json_typename(value)); - rule_free(rule); - return NULL; + goto err_free_replace; } stmt = json_parse_stmt(ctx, value); @@ -3393,18 +3818,21 @@ static struct cmd *json_parse_cmd_replace(struct json_ctx *ctx, if (!stmt) { json_error(ctx, "Parsing expr array at index %zd failed.", index); - rule_free(rule); - return NULL; + goto err_free_replace; } - rule->num_stmts++; - list_add_tail(&stmt->list, &rule->stmts); + rule_stmt_append(rule, stmt); } if (op == CMD_REPLACE) json_object_del(root, "handle"); return cmd_alloc(op, CMD_OBJ_RULE, &h, int_loc, rule); + +err_free_replace: + rule_free(rule); + handle_free(&h); + return NULL; } static struct cmd *json_parse_cmd_list_multiple(struct json_ctx *ctx, @@ -3493,6 +3921,39 @@ static struct cmd *json_parse_cmd_list(struct json_ctx *ctx, return NULL; } +static struct cmd *json_parse_cmd_reset_rule(struct json_ctx *ctx, + json_t *root, enum cmd_ops op, + enum cmd_obj obj) +{ + struct handle h = { + .family = NFPROTO_UNSPEC, + }; + const char *family = NULL, *table = NULL, *chain = NULL; + + + if (obj == CMD_OBJ_RULE && + json_unpack_err(ctx, root, "{s:s, s:s, s:s, s:I}", + "family", &family, "table", &table, + "chain", &chain, "handle", &h.handle.id)) + return NULL; + else if (obj == CMD_OBJ_RULES) { + json_unpack(root, "{s:s}", "family", &family); + json_unpack(root, "{s:s}", "table", &table); + json_unpack(root, "{s:s}", "chain", &chain); + } + + if (family && parse_family(family, &h.family)) { + json_error(ctx, "Unknown family '%s'.", family); + return NULL; + } + if (table) { + h.table.name = xstrdup(table); + if (chain) + h.chain.name = xstrdup(chain); + } + return cmd_alloc(op, obj, &h, int_loc, NULL); +} + static struct cmd *json_parse_cmd_reset(struct json_ctx *ctx, json_t *root, enum cmd_ops op) { @@ -3506,6 +3967,11 @@ static struct cmd *json_parse_cmd_reset(struct json_ctx *ctx, { "counters", CMD_OBJ_COUNTERS, json_parse_cmd_list_multiple }, { "quota", CMD_OBJ_QUOTA, json_parse_cmd_add_object }, { "quotas", CMD_OBJ_QUOTAS, json_parse_cmd_list_multiple }, + { "rule", CMD_OBJ_RULE, json_parse_cmd_reset_rule }, + { "rules", CMD_OBJ_RULES, json_parse_cmd_reset_rule }, + { "element", CMD_OBJ_ELEMENTS, json_parse_cmd_add_element }, + { "set", CMD_OBJ_SET, json_parse_cmd_add_set }, + { "map", CMD_OBJ_MAP, json_parse_cmd_add_set }, }; unsigned int i; json_t *tmp; @@ -3604,6 +4070,7 @@ static struct cmd *json_parse_cmd(struct json_ctx *ctx, json_t *root) { "reset", CMD_RESET, json_parse_cmd_reset }, { "flush", CMD_FLUSH, json_parse_cmd_flush }, { "rename", CMD_RENAME, json_parse_cmd_rename }, + { "destroy", CMD_DESTROY, json_parse_cmd_add }, //{ "export", CMD_EXPORT, json_parse_cmd_export }, //{ "monitor", CMD_MONITOR, json_parse_cmd_monitor }, //{ "describe", CMD_DESCRIBE, json_parse_cmd_describe } @@ -3626,13 +4093,14 @@ static int json_verify_metainfo(struct json_ctx *ctx, json_t *root) { int schema_version; - if (!json_unpack(root, "{s:i}", "json_schema_version", &schema_version)) - return 0; - - if (schema_version > JSON_SCHEMA_VERSION) { - json_error(ctx, "Schema version %d not supported, maximum supported version is %d\n", - schema_version, JSON_SCHEMA_VERSION); - return 1; + if (!json_unpack(root, "{s:i}", "json_schema_version", &schema_version)) { + if (schema_version > JSON_SCHEMA_VERSION) { + json_error(ctx, + "Schema version %d not supported, maximum" + " supported version is %d\n", + schema_version, JSON_SCHEMA_VERSION); + return 1; + } } return 0; @@ -3640,20 +4108,33 @@ static int json_verify_metainfo(struct json_ctx *ctx, json_t *root) struct json_cmd_assoc { struct json_cmd_assoc *next; + struct hlist_node hnode; const struct cmd *cmd; json_t *json; }; -static struct json_cmd_assoc *json_cmd_list = NULL; +#define CMD_ASSOC_HSIZE 512 +static struct hlist_head json_cmd_assoc_hash[CMD_ASSOC_HSIZE]; +static struct json_cmd_assoc *json_cmd_assoc_list; static void json_cmd_assoc_free(void) { struct json_cmd_assoc *cur; + struct hlist_node *pos, *n; + int i; - while (json_cmd_list) { - cur = json_cmd_list; - json_cmd_list = cur->next; - free(cur); + while (json_cmd_assoc_list) { + cur = json_cmd_assoc_list->next; + free(json_cmd_assoc_list); + json_cmd_assoc_list = cur; + } + + for (i = 0; i < CMD_ASSOC_HSIZE; i++) { + hlist_for_each_entry_safe(cur, pos, n, + &json_cmd_assoc_hash[i], hnode) { + hlist_del(&cur->hnode); + free(cur); + } } } @@ -3661,20 +4142,33 @@ static void json_cmd_assoc_add(json_t *json, const struct cmd *cmd) { struct json_cmd_assoc *new = xzalloc(sizeof *new); - new->next = json_cmd_list; new->json = json; new->cmd = cmd; - json_cmd_list = new; + new->next = json_cmd_assoc_list; + + json_cmd_assoc_list = new; } static json_t *seqnum_to_json(const uint32_t seqnum) { - const struct json_cmd_assoc *cur; + struct json_cmd_assoc *cur; + struct hlist_node *n; + int key; + + while (json_cmd_assoc_list) { + cur = json_cmd_assoc_list; + json_cmd_assoc_list = cur->next; + + key = cur->cmd->seqnum_from % CMD_ASSOC_HSIZE; + hlist_add_head(&cur->hnode, &json_cmd_assoc_hash[key]); + } - for (cur = json_cmd_list; cur; cur = cur->next) { - if (cur->cmd->seqnum == seqnum) + key = seqnum % CMD_ASSOC_HSIZE; + hlist_for_each_entry(cur, n, &json_cmd_assoc_hash[key], hnode) { + if (cur->cmd->seqnum_from == seqnum) return cur->json; } + return NULL; } @@ -3715,6 +4209,11 @@ static int __json_parse(struct json_ctx *ctx) cmd = json_parse_cmd(ctx, value); if (!cmd) { + if (ctx->flags & CTX_F_COLLAPSED) { + ctx->flags &= ~CTX_F_COLLAPSED; + continue; + } + json_error(ctx, "Parsing command array at index %zd failed.", index); return -1; } @@ -3734,16 +4233,16 @@ int nft_parse_json_buffer(struct nft_ctx *nft, const char *buf, struct list_head *msgs, struct list_head *cmds) { struct json_ctx ctx = { - .indesc = { - .type = INDESC_BUFFER, - .data = buf, - }, .nft = nft, .msgs = msgs, .cmds = cmds, }; int ret; + json_indesc.type = INDESC_BUFFER; + json_indesc.data = buf; + + parser_init(nft, nft->state, msgs, cmds, nft->top_scope); nft->json_root = json_loads(buf, 0, NULL); if (!nft->json_root) return -EINVAL; @@ -3761,10 +4260,6 @@ int nft_parse_json_filename(struct nft_ctx *nft, const char *filename, struct list_head *msgs, struct list_head *cmds) { struct json_ctx ctx = { - .indesc = { - .type = INDESC_FILE, - .name = filename, - }, .nft = nft, .msgs = msgs, .cmds = cmds, @@ -3772,6 +4267,17 @@ int nft_parse_json_filename(struct nft_ctx *nft, const char *filename, json_error_t err; int ret; + if (nft->stdin_buf) { + json_indesc.type = INDESC_STDIN; + json_indesc.name = "/dev/stdin"; + + return nft_parse_json_buffer(nft, nft->stdin_buf, msgs, cmds); + } + + json_indesc.type = INDESC_FILE; + json_indesc.name = filename; + + parser_init(nft, nft->state, msgs, cmds, nft->top_scope); nft->json_root = json_load_file(filename, 0, &err); if (!nft->json_root) return -EINVAL; @@ -3801,6 +4307,7 @@ static int json_echo_error(struct netlink_mon_handler *monh, static uint64_t handle_from_nlmsg(const struct nlmsghdr *nlh) { + struct nftnl_flowtable *nlf; struct nftnl_table *nlt; struct nftnl_chain *nlc; struct nftnl_rule *nlr; @@ -3837,18 +4344,29 @@ static uint64_t handle_from_nlmsg(const struct nlmsghdr *nlh) handle = nftnl_obj_get_u64(nlo, NFTNL_OBJ_HANDLE); nftnl_obj_free(nlo); break; + case NFT_MSG_NEWFLOWTABLE: + nlf = netlink_flowtable_alloc(nlh); + handle = nftnl_flowtable_get_u64(nlf, NFTNL_FLOWTABLE_HANDLE); + nftnl_flowtable_free(nlf); + break; } return handle; } int json_events_cb(const struct nlmsghdr *nlh, struct netlink_mon_handler *monh) { - json_t *tmp, *json = seqnum_to_json(nlh->nlmsg_seq); uint64_t handle = handle_from_nlmsg(nlh); + json_t *tmp, *json; void *iter; - /* might be anonymous set, ignore message */ - if (!json || !handle) + if (!handle) + return MNL_CB_OK; + + json = seqnum_to_json(nlh->nlmsg_seq); + if (!json) { + json_echo_error(monh, "No JSON command found with seqnum %lu\n", + nlh->nlmsg_seq); return MNL_CB_OK; + } tmp = json_object_get(json, "add"); if (!tmp) @@ -3874,11 +4392,20 @@ int json_events_cb(const struct nlmsghdr *nlh, struct netlink_mon_handler *monh) void json_print_echo(struct nft_ctx *ctx) { - if (!ctx->json_root) - return; - - json_dumpf(ctx->json_root, ctx->output.output_fp, JSON_PRESERVE_ORDER); - json_cmd_assoc_free(); - json_decref(ctx->json_root); - ctx->json_root = NULL; + if (!ctx->json_root) { + if (!ctx->json_echo) + return; + + ctx->json_echo = json_pack("{s:o}", "nftables", ctx->json_echo); + json_dumpf(ctx->json_echo, ctx->output.output_fp, JSON_PRESERVE_ORDER); + json_decref(ctx->json_echo); + ctx->json_echo = NULL; + fprintf(ctx->output.output_fp, "\n"); + fflush(ctx->output.output_fp); + } else { + json_dumpf(ctx->json_root, ctx->output.output_fp, JSON_PRESERVE_ORDER); + json_cmd_assoc_free(); + json_decref(ctx->json_root); + ctx->json_root = NULL; + } } diff --git a/src/payload.c b/src/payload.c index 29242537..a38f5bf7 100644 --- a/src/payload.c +++ b/src/payload.c @@ -10,15 +10,16 @@ * Development of this code funded by Astaro AG (http://www.astaro.com/) */ +#include <nft.h> + #include <stddef.h> -#include <stdlib.h> #include <stdio.h> -#include <stdint.h> -#include <string.h> #include <net/if_arp.h> #include <arpa/inet.h> #include <linux/netfilter.h> #include <linux/if_ether.h> +#include <netinet/ip_icmp.h> +#include <netinet/icmp6.h> #include <rule.h> #include <expression.h> @@ -45,6 +46,10 @@ static void payload_expr_print(const struct expr *expr, struct output_ctx *octx) const struct proto_desc *desc; const struct proto_hdr_template *tmpl; + if (expr->payload.inner_desc && + expr->payload.inner_desc != expr->payload.desc) + nft_print(octx, "%s ", expr->payload.inner_desc->name); + desc = expr->payload.desc; tmpl = expr->payload.tmpl; if (payload_is_known(expr)) @@ -57,7 +62,8 @@ static void payload_expr_print(const struct expr *expr, struct output_ctx *octx) bool payload_expr_cmp(const struct expr *e1, const struct expr *e2) { - return e1->payload.desc == e2->payload.desc && + return e1->payload.inner_desc == e2->payload.inner_desc && + e1->payload.desc == e2->payload.desc && e1->payload.tmpl == e2->payload.tmpl && e1->payload.base == e2->payload.base && e1->payload.offset == e2->payload.offset; @@ -65,6 +71,7 @@ bool payload_expr_cmp(const struct expr *e1, const struct expr *e2) static void payload_expr_clone(struct expr *new, const struct expr *expr) { + new->payload.inner_desc = expr->payload.inner_desc; new->payload.desc = expr->payload.desc; new->payload.tmpl = expr->payload.tmpl; new->payload.base = expr->payload.base; @@ -80,9 +87,10 @@ static void payload_expr_clone(struct expr *new, const struct expr *expr) * Update protocol context for relational payload expressions. */ static void payload_expr_pctx_update(struct proto_ctx *ctx, - const struct expr *expr) + const struct location *loc, + const struct expr *left, + const struct expr *right) { - const struct expr *left = expr->left, *right = expr->right; const struct proto_desc *base, *desc; unsigned int proto = 0; @@ -94,24 +102,49 @@ static void payload_expr_pctx_update(struct proto_ctx *ctx, base = ctx->protocol[left->payload.base].desc; desc = proto_find_upper(base, proto); - if (!desc) + if (!desc) { + if (base == &proto_icmp) { + /* proto 0 is ECHOREPLY, just pretend its ECHO. + * Not doing this would need an additional marker + * bit to tell when icmp.type was set. + */ + ctx->th_dep.icmp.type = proto ? proto : ICMP_ECHO; + } else if (base == &proto_icmp6) { + if (proto == ICMP6_ECHO_REPLY) + proto = ICMP6_ECHO_REQUEST; + ctx->th_dep.icmp.type = proto; + } return; + } assert(desc->base <= PROTO_BASE_MAX); if (desc->base == base->base) { - assert(base->length > 0); - ctx->protocol[base->base].offset += base->length; + if (!left->payload.is_raw) { + if (desc->base == PROTO_BASE_LL_HDR && + ctx->stacked_ll_count < PROTO_CTX_NUM_PROTOS) { + assert(base->length > 0); + ctx->stacked_ll[ctx->stacked_ll_count] = base; + ctx->stacked_ll_count++; + } + } } - proto_ctx_update(ctx, desc->base, &expr->location, desc); + proto_ctx_update(ctx, desc->base, loc, desc); } #define NFTNL_UDATA_SET_KEY_PAYLOAD_DESC 0 #define NFTNL_UDATA_SET_KEY_PAYLOAD_TYPE 1 -#define NFTNL_UDATA_SET_KEY_PAYLOAD_MAX 2 +#define NFTNL_UDATA_SET_KEY_PAYLOAD_BASE 2 +#define NFTNL_UDATA_SET_KEY_PAYLOAD_OFFSET 3 +#define NFTNL_UDATA_SET_KEY_PAYLOAD_LEN 4 +#define NFTNL_UDATA_SET_KEY_PAYLOAD_INNER_DESC 5 +#define NFTNL_UDATA_SET_KEY_PAYLOAD_MAX 6 static unsigned int expr_payload_type(const struct proto_desc *desc, const struct proto_hdr_template *tmpl) { + if (desc->id == PROTO_DESC_UNKNOWN) + return 0; + return (unsigned int)(tmpl - &desc->templates[0]); } @@ -125,10 +158,24 @@ static int payload_expr_build_udata(struct nftnl_udata_buf *udbuf, nftnl_udata_put_u32(udbuf, NFTNL_UDATA_SET_KEY_PAYLOAD_DESC, desc->id); nftnl_udata_put_u32(udbuf, NFTNL_UDATA_SET_KEY_PAYLOAD_TYPE, type); + if (desc->id == 0) { + nftnl_udata_put_u32(udbuf, NFTNL_UDATA_SET_KEY_PAYLOAD_BASE, + expr->payload.base); + nftnl_udata_put_u32(udbuf, NFTNL_UDATA_SET_KEY_PAYLOAD_OFFSET, + expr->payload.offset); + } + if (expr->dtype->type == TYPE_INTEGER) + nftnl_udata_put_u32(udbuf, NFTNL_UDATA_SET_KEY_PAYLOAD_LEN, expr->len); + + if (expr->payload.inner_desc) { + nftnl_udata_put_u32(udbuf, NFTNL_UDATA_SET_KEY_PAYLOAD_INNER_DESC, + expr->payload.inner_desc->id); + } + return 0; } -static const struct proto_desc *find_proto_desc(const struct nftnl_udata *ud) +const struct proto_desc *find_proto_desc(const struct nftnl_udata *ud) { return proto_find_desc(nftnl_udata_get_u32(ud)); } @@ -142,6 +189,10 @@ static int payload_parse_udata(const struct nftnl_udata *attr, void *data) switch (type) { case NFTNL_UDATA_SET_KEY_PAYLOAD_DESC: case NFTNL_UDATA_SET_KEY_PAYLOAD_TYPE: + case NFTNL_UDATA_SET_KEY_PAYLOAD_BASE: + case NFTNL_UDATA_SET_KEY_PAYLOAD_OFFSET: + case NFTNL_UDATA_SET_KEY_PAYLOAD_LEN: + case NFTNL_UDATA_SET_KEY_PAYLOAD_INNER_DESC: if (len != sizeof(uint32_t)) return -1; break; @@ -156,8 +207,10 @@ static int payload_parse_udata(const struct nftnl_udata *attr, void *data) static struct expr *payload_expr_parse_udata(const struct nftnl_udata *attr) { const struct nftnl_udata *ud[NFTNL_UDATA_SET_KEY_PAYLOAD_MAX + 1] = {}; + unsigned int type, base, offset, len = 0; const struct proto_desc *desc; - unsigned int type; + bool is_raw = false; + struct expr *expr; int err; err = nftnl_udata_parse(nftnl_udata_get(attr), nftnl_udata_len(attr), @@ -170,12 +223,44 @@ static struct expr *payload_expr_parse_udata(const struct nftnl_udata *attr) return NULL; desc = find_proto_desc(ud[NFTNL_UDATA_SET_KEY_PAYLOAD_DESC]); - if (!desc) - return NULL; + if (!desc) { + if (!ud[NFTNL_UDATA_SET_KEY_PAYLOAD_BASE] || + !ud[NFTNL_UDATA_SET_KEY_PAYLOAD_OFFSET]) + return NULL; + + base = nftnl_udata_get_u32(ud[NFTNL_UDATA_SET_KEY_PAYLOAD_BASE]); + offset = nftnl_udata_get_u32(ud[NFTNL_UDATA_SET_KEY_PAYLOAD_OFFSET]); + is_raw = true; + } type = nftnl_udata_get_u32(ud[NFTNL_UDATA_SET_KEY_PAYLOAD_TYPE]); + if (ud[NFTNL_UDATA_SET_KEY_PAYLOAD_LEN]) + len = nftnl_udata_get_u32(ud[NFTNL_UDATA_SET_KEY_PAYLOAD_LEN]); - return payload_expr_alloc(&internal_location, desc, type); + expr = payload_expr_alloc(&internal_location, desc, type); + + if (len) + expr->len = len; + + if (is_raw) { + struct datatype *dtype; + + expr->payload.base = base; + expr->payload.offset = offset; + expr->payload.is_raw = true; + expr->len = len; + dtype = datatype_clone(&xinteger_type); + dtype->size = len; + dtype->byteorder = BYTEORDER_BIG_ENDIAN; + __datatype_set(expr, dtype); + } + + if (ud[NFTNL_UDATA_SET_KEY_PAYLOAD_INNER_DESC]) { + desc = find_proto_desc(ud[NFTNL_UDATA_SET_KEY_PAYLOAD_INNER_DESC]); + expr->payload.inner_desc = desc; + } + + return expr; } const struct expr_ops payload_expr_ops = { @@ -252,7 +337,7 @@ void payload_init_raw(struct expr *expr, enum proto_bases base, expr->payload.base = base; expr->payload.offset = offset; expr->len = len; - expr->dtype = &integer_type; + expr->dtype = &xinteger_type; if (base != PROTO_BASE_TRANSPORT_HDR) return; @@ -269,7 +354,6 @@ void payload_init_raw(struct expr *expr, enum proto_bases base, expr->payload.tmpl = &proto_th.templates[thf]; expr->payload.desc = &proto_th; expr->dtype = &inet_service_type; - expr->payload.desc = &proto_th; break; default: break; @@ -294,7 +378,7 @@ static void payload_stmt_destroy(struct stmt *stmt) expr_free(stmt->payload.val); } -static const struct stmt_ops payload_stmt_ops = { +const struct stmt_ops payload_stmt_ops = { .type = STMT_PAYLOAD, .name = "payload", .print = payload_stmt_print, @@ -321,9 +405,11 @@ static int payload_add_dependency(struct eval_ctx *ctx, { const struct proto_hdr_template *tmpl; struct expr *dep, *left, *right; + struct proto_ctx *pctx; struct stmt *stmt; - int protocol = proto_find_num(desc, upper); + int protocol; + protocol = proto_find_num(desc, upper); if (protocol < 0) return expr_error(ctx->msgs, expr, "conflicting protocols specified: %s vs. %s", @@ -340,20 +426,28 @@ static int payload_add_dependency(struct eval_ctx *ctx, constant_data_ptr(protocol, tmpl->len)); dep = relational_expr_alloc(&expr->location, OP_EQ, left, right); + stmt = expr_stmt_alloc(&dep->location, dep); - if (stmt_evaluate(ctx, stmt) < 0) { - return expr_error(ctx->msgs, expr, - "dependency statement is invalid"); + if (stmt_dependency_evaluate(ctx, stmt) < 0) + return -1; + + if (ctx->inner_desc) { + if (tmpl->meta_key) + left->meta.inner_desc = ctx->inner_desc; + else + left->payload.inner_desc = ctx->inner_desc; } - relational_expr_pctx_update(&ctx->pctx, dep); + + pctx = eval_proto_ctx(ctx); + relational_expr_pctx_update(pctx, dep); *res = stmt; return 0; } static const struct proto_desc * -payload_get_get_ll_hdr(const struct eval_ctx *ctx) +payload_get_get_ll_hdr(const struct proto_ctx *pctx) { - switch (ctx->pctx.family) { + switch (pctx->family) { case NFPROTO_INET: return &proto_inet; case NFPROTO_BRIDGE: @@ -370,9 +464,11 @@ payload_get_get_ll_hdr(const struct eval_ctx *ctx) static const struct proto_desc * payload_gen_special_dependency(struct eval_ctx *ctx, const struct expr *expr) { + struct proto_ctx *pctx = eval_proto_ctx(ctx); + switch (expr->payload.base) { case PROTO_BASE_LL_HDR: - return payload_get_get_ll_hdr(ctx); + return payload_get_get_ll_hdr(pctx); case PROTO_BASE_TRANSPORT_HDR: if (expr->payload.desc == &proto_icmp || expr->payload.desc == &proto_icmp6 || @@ -380,13 +476,21 @@ payload_gen_special_dependency(struct eval_ctx *ctx, const struct expr *expr) const struct proto_desc *desc, *desc_upper; struct stmt *nstmt; - desc = ctx->pctx.protocol[PROTO_BASE_LL_HDR].desc; + desc = pctx->protocol[PROTO_BASE_LL_HDR].desc; if (!desc) { - desc = payload_get_get_ll_hdr(ctx); + desc = payload_get_get_ll_hdr(pctx); if (!desc) break; } + /* this tunnel protocol does not encapsulate an inner + * link layer, use proto_netdev which relies on + * NFT_META_PROTOCOL for dependencies. + */ + if (expr->payload.inner_desc && + !(expr->payload.inner_desc->inner.flags & NFT_INNER_LL)) + desc = &proto_netdev; + desc_upper = &proto_ip6; if (expr->payload.desc == &proto_icmp || expr->payload.desc == &proto_igmp) @@ -432,11 +536,14 @@ payload_gen_special_dependency(struct eval_ctx *ctx, const struct expr *expr) int payload_gen_dependency(struct eval_ctx *ctx, const struct expr *expr, struct stmt **res) { - const struct hook_proto_desc *h = &hook_proto_desc[ctx->pctx.family]; + const struct hook_proto_desc *h; const struct proto_desc *desc; + struct proto_ctx *pctx; struct stmt *stmt; uint16_t type; + pctx = eval_proto_ctx(ctx); + h = &hook_proto_desc[pctx->family]; if (expr->payload.base < h->base) { if (expr->payload.base < h->base - 1) return expr_error(ctx->msgs, expr, @@ -449,15 +556,15 @@ int payload_gen_dependency(struct eval_ctx *ctx, const struct expr *expr, "for this family"); stmt = meta_stmt_meta_iiftype(&expr->location, type); - if (stmt_evaluate(ctx, stmt) < 0) { - return expr_error(ctx->msgs, expr, - "dependency statement is invalid"); - } + if (stmt_dependency_evaluate(ctx, stmt) < 0) + return -1; + *res = stmt; + return 0; } - desc = ctx->pctx.protocol[expr->payload.base - 1].desc; + desc = pctx->protocol[expr->payload.base - 1].desc; /* Special case for mixed IPv4/IPv6 and bridge tables */ if (desc == NULL) desc = payload_gen_special_dependency(ctx, expr); @@ -468,7 +575,7 @@ int payload_gen_dependency(struct eval_ctx *ctx, const struct expr *expr, "no %s protocol specified", proto_base_names[expr->payload.base - 1]); - if (ctx->pctx.family == NFPROTO_BRIDGE && desc == &proto_eth) { + if (pctx->family == NFPROTO_BRIDGE && desc == &proto_eth) { /* prefer netdev proto, which adds dependencies based * on skb->protocol. * @@ -493,11 +600,13 @@ int exthdr_gen_dependency(struct eval_ctx *ctx, const struct expr *expr, enum proto_bases pb, struct stmt **res) { const struct proto_desc *desc; + struct proto_ctx *pctx; - desc = ctx->pctx.protocol[pb].desc; + pctx = eval_proto_ctx(ctx); + desc = pctx->protocol[pb].desc; if (desc == NULL) { if (expr->exthdr.op == NFT_EXTHDR_OP_TCPOPT) { - switch (ctx->pctx.family) { + switch (pctx->family) { case NFPROTO_NETDEV: case NFPROTO_BRIDGE: case NFPROTO_INET: @@ -543,6 +652,41 @@ void payload_dependency_reset(struct payload_dep_ctx *ctx) memset(ctx, 0, sizeof(*ctx)); } +static bool payload_dependency_store_icmp_type(struct payload_dep_ctx *ctx, + const struct stmt *stmt) +{ + struct expr *dep = stmt->expr; + const struct proto_desc *desc; + const struct expr *right; + uint8_t type; + + if (dep->left->etype != EXPR_PAYLOAD) + return false; + + right = dep->right; + if (right->etype != EXPR_VALUE || right->len != BITS_PER_BYTE) + return false; + + desc = dep->left->payload.desc; + if (desc == &proto_icmp) { + type = mpz_get_uint8(right->value); + + if (type == ICMP_ECHOREPLY) + type = ICMP_ECHO; + + ctx->icmp_type = type; + + return type == ICMP_ECHO; + } else if (desc == &proto_icmp6) { + type = mpz_get_uint8(right->value); + + ctx->icmp_type = type; + return type == ICMP6_ECHO_REQUEST || type == ICMP6_ECHO_REPLY; + } + + return false; +} + /** * payload_dependency_store - store a possibly redundant protocol match * @@ -553,8 +697,12 @@ void payload_dependency_reset(struct payload_dep_ctx *ctx) void payload_dependency_store(struct payload_dep_ctx *ctx, struct stmt *stmt, enum proto_bases base) { - ctx->pbase = base + 1; - ctx->pdep = stmt; + bool ignore_dep = payload_dependency_store_icmp_type(ctx, stmt); + + if (ignore_dep) + return; + + ctx->pdeps[base + 1] = stmt; } /** @@ -569,26 +717,145 @@ void payload_dependency_store(struct payload_dep_ctx *ctx, bool payload_dependency_exists(const struct payload_dep_ctx *ctx, enum proto_bases base) { - return ctx->pbase != PROTO_BASE_INVALID && - ctx->pbase == base && - ctx->pdep != NULL; + if (ctx->pdeps[base]) + return true; + + return base == PROTO_BASE_TRANSPORT_HDR && + ctx->pdeps[PROTO_BASE_INNER_HDR]; +} + +/** + * payload_dependency_get - return a payload dependency if available + * @ctx: payload dependency context + * @base: payload protocol base + * + * If we have seen a protocol key payload expression for this base, we return + * it. + */ +struct expr *payload_dependency_get(struct payload_dep_ctx *ctx, + enum proto_bases base) +{ + if (ctx->pdeps[base]) + return ctx->pdeps[base]->expr; + + if (base == PROTO_BASE_TRANSPORT_HDR && + ctx->pdeps[PROTO_BASE_INNER_HDR]) + return ctx->pdeps[PROTO_BASE_INNER_HDR]->expr; + + return NULL; } -void payload_dependency_release(struct payload_dep_ctx *ctx) +static void __payload_dependency_release(struct payload_dep_ctx *ctx, + enum proto_bases base) { - list_del(&ctx->pdep->list); - stmt_free(ctx->pdep); + list_del(&ctx->pdeps[base]->list); + stmt_free(ctx->pdeps[base]); - ctx->pbase = PROTO_BASE_INVALID; - if (ctx->pdep == ctx->prev) + if (ctx->pdeps[base] == ctx->prev) ctx->prev = NULL; - ctx->pdep = NULL; + ctx->pdeps[base] = NULL; +} + +void payload_dependency_release(struct payload_dep_ctx *ctx, + enum proto_bases base) +{ + if (ctx->pdeps[base]) + __payload_dependency_release(ctx, base); + else if (base == PROTO_BASE_TRANSPORT_HDR && + ctx->pdeps[PROTO_BASE_INNER_HDR]) + __payload_dependency_release(ctx, PROTO_BASE_INNER_HDR); +} + +static uint8_t icmp_dep_to_type(enum icmp_hdr_field_type t) +{ + switch (t) { + case PROTO_ICMP_ANY: + BUG("Invalid map for simple dependency"); + case PROTO_ICMP_ECHO: return ICMP_ECHO; + case PROTO_ICMP6_ECHO: return ICMP6_ECHO_REQUEST; + case PROTO_ICMP_MTU: return ICMP_DEST_UNREACH; + case PROTO_ICMP_ADDRESS: return ICMP_REDIRECT; + case PROTO_ICMP6_MTU: return ICMP6_PACKET_TOO_BIG; + case PROTO_ICMP6_MGMQ: return MLD_LISTENER_QUERY; + case PROTO_ICMP6_PPTR: return ICMP6_PARAM_PROB; + case PROTO_ICMP6_REDIRECT: return ND_REDIRECT; + case PROTO_ICMP6_ADDRESS: return ND_NEIGHBOR_SOLICIT; + } + + BUG("Missing icmp type mapping"); +} + +static bool icmp_dep_type_match(enum icmp_hdr_field_type t, uint8_t type) +{ + switch (t) { + case PROTO_ICMP_ECHO: + return type == ICMP_ECHO || type == ICMP_ECHOREPLY; + case PROTO_ICMP6_ECHO: + return type == ICMP6_ECHO_REQUEST || type == ICMP6_ECHO_REPLY; + case PROTO_ICMP6_ADDRESS: + return type == ND_NEIGHBOR_SOLICIT || + type == ND_NEIGHBOR_ADVERT || + type == ND_REDIRECT || + type == MLD_LISTENER_QUERY || + type == MLD_LISTENER_REPORT || + type == MLD_LISTENER_REDUCTION; + case PROTO_ICMP_ADDRESS: + case PROTO_ICMP_MTU: + case PROTO_ICMP6_MTU: + case PROTO_ICMP6_MGMQ: + case PROTO_ICMP6_PPTR: + case PROTO_ICMP6_REDIRECT: + return icmp_dep_to_type(t) == type; + case PROTO_ICMP_ANY: + return true; + } + BUG("Missing icmp type mapping"); +} + +static bool payload_may_dependency_kill_icmp(struct payload_dep_ctx *ctx, const struct expr *expr) +{ + const struct expr *dep = payload_dependency_get(ctx, expr->payload.base); + enum icmp_hdr_field_type icmp_dep; + + icmp_dep = expr->payload.tmpl->icmp_dep; + if (icmp_dep == PROTO_ICMP_ANY) + return false; + + if (dep->left->payload.desc != expr->payload.desc) + return false; + + if (expr->payload.tmpl->icmp_dep == PROTO_ICMP_ECHO || + expr->payload.tmpl->icmp_dep == PROTO_ICMP6_ECHO || + expr->payload.tmpl->icmp_dep == PROTO_ICMP6_ADDRESS) + return false; + + return ctx->icmp_type == icmp_dep_to_type(icmp_dep); +} + +static bool payload_may_dependency_kill_ll(struct payload_dep_ctx *ctx, const struct expr *expr) +{ + const struct expr *dep = payload_dependency_get(ctx, expr->payload.base); + + /* Never remove a 'vlan type 0x...' expression, they are never added + * implicitly + */ + if (dep->left->payload.desc == &proto_vlan) + return false; + + /* 'vlan id 2' implies 'ether type 8021Q'. If a different protocol is + * tested, this is not a redundant expression. + */ + if (dep->left->payload.desc == &proto_eth && + dep->right->etype == EXPR_VALUE && dep->right->len == 16) + return mpz_get_uint16(dep->right->value) == ETH_P_8021Q; + + return true; } static bool payload_may_dependency_kill(struct payload_dep_ctx *ctx, unsigned int family, struct expr *expr) { - struct expr *dep = ctx->pdep->expr; + struct expr *dep = payload_dependency_get(ctx, expr->payload.base); /* Protocol key payload expression at network base such as 'ip6 nexthdr' * need to be left in place since it implicitly restricts matching to @@ -613,12 +880,42 @@ static bool payload_may_dependency_kill(struct payload_dep_ctx *ctx, * for stacked protocols if we only have protcol type matches. */ if (dep->left->etype == EXPR_PAYLOAD && dep->op == OP_EQ && - expr->flags & EXPR_F_PROTOCOL && - expr->payload.base == dep->left->payload.base) - return false; + expr->payload.base == dep->left->payload.base) { + if (expr->flags & EXPR_F_PROTOCOL) + return false; + + if (expr->payload.base == PROTO_BASE_LL_HDR) + return payload_may_dependency_kill_ll(ctx, expr); + } + break; } + if (expr->payload.base != PROTO_BASE_TRANSPORT_HDR) + return true; + + if (expr->payload.desc == &proto_th) { + /* &proto_th could mean any of udp, tcp, dccp, ... so we + * cannot remove the dependency. + * + * Also prefer raw payload @th syntax, there is no + * 'source/destination port' protocol here. + */ + expr->payload.desc = &proto_unknown; + expr->dtype = &xinteger_type; + return false; + } + + if (dep->left->etype != EXPR_PAYLOAD || + dep->left->payload.base != PROTO_BASE_TRANSPORT_HDR) + return true; + + if (dep->left->payload.desc == &proto_icmp) + return payload_may_dependency_kill_icmp(ctx, expr); + + if (dep->left->payload.desc == &proto_icmp6) + return payload_may_dependency_kill_icmp(ctx, expr); + return true; } @@ -635,9 +932,10 @@ static bool payload_may_dependency_kill(struct payload_dep_ctx *ctx, void payload_dependency_kill(struct payload_dep_ctx *ctx, struct expr *expr, unsigned int family) { - if (payload_dependency_exists(ctx, expr->payload.base) && + if (expr->payload.desc != &proto_unknown && + payload_dependency_exists(ctx, expr->payload.base) && payload_may_dependency_kill(ctx, family, expr)) - payload_dependency_release(ctx); + payload_dependency_release(ctx, expr->payload.base); } void exthdr_dependency_kill(struct payload_dep_ctx *ctx, struct expr *expr, @@ -646,21 +944,53 @@ void exthdr_dependency_kill(struct payload_dep_ctx *ctx, struct expr *expr, switch (expr->exthdr.op) { case NFT_EXTHDR_OP_TCPOPT: if (payload_dependency_exists(ctx, PROTO_BASE_TRANSPORT_HDR)) - payload_dependency_release(ctx); + payload_dependency_release(ctx, PROTO_BASE_TRANSPORT_HDR); break; case NFT_EXTHDR_OP_IPV6: if (payload_dependency_exists(ctx, PROTO_BASE_NETWORK_HDR)) - payload_dependency_release(ctx); + payload_dependency_release(ctx, PROTO_BASE_NETWORK_HDR); break; case NFT_EXTHDR_OP_IPV4: if (payload_dependency_exists(ctx, PROTO_BASE_NETWORK_HDR)) - payload_dependency_release(ctx); + payload_dependency_release(ctx, PROTO_BASE_NETWORK_HDR); break; default: break; } } +static const struct proto_desc *get_stacked_desc(const struct proto_ctx *ctx, + const struct proto_desc *top, + const struct expr *e, + unsigned int *skip) +{ + unsigned int i, total, payload_offset = e->payload.offset; + + assert(e->etype == EXPR_PAYLOAD); + + if (e->payload.base != PROTO_BASE_LL_HDR || + payload_offset < top->length) { + *skip = 0; + return top; + } + + for (i = 0, total = 0; i < ctx->stacked_ll_count; i++) { + const struct proto_desc *stacked; + + stacked = ctx->stacked_ll[i]; + if (payload_offset < stacked->length) { + *skip = total; + return stacked; + } + + payload_offset -= stacked->length; + total += stacked->length; + } + + *skip = total; + return top; +} + /** * payload_expr_complete - fill in type information of a raw payload expr * @@ -672,9 +1002,10 @@ void exthdr_dependency_kill(struct payload_dep_ctx *ctx, struct expr *expr, */ void payload_expr_complete(struct expr *expr, const struct proto_ctx *ctx) { + unsigned int payload_offset = expr->payload.offset; const struct proto_desc *desc; const struct proto_hdr_template *tmpl; - unsigned int i; + unsigned int i, total; assert(expr->etype == EXPR_PAYLOAD); @@ -683,13 +1014,26 @@ void payload_expr_complete(struct expr *expr, const struct proto_ctx *ctx) return; assert(desc->base == expr->payload.base); + desc = get_stacked_desc(ctx, desc, expr, &total); + payload_offset -= total; + for (i = 0; i < array_size(desc->templates); i++) { tmpl = &desc->templates[i]; - if (tmpl->offset != expr->payload.offset || + if (tmpl->offset != payload_offset || tmpl->len != expr->len) continue; + + if (tmpl->meta_key && i == 0) + continue; + + if (tmpl->icmp_dep && ctx->th_dep.icmp.type && + !icmp_dep_type_match(tmpl->icmp_dep, + ctx->th_dep.icmp.type)) + continue; + expr->dtype = tmpl->dtype; expr->payload.desc = desc; + expr->byteorder = tmpl->byteorder; expr->payload.tmpl = tmpl; return; } @@ -715,6 +1059,7 @@ static unsigned int mask_length(const struct expr *mask) * @expr: the payload expression * @mask: mask to use when searching templates * @ctx: protocol context + * @shift: shift adjustment to fix up RHS value * * Walk the template list and determine if a match can be found without * using the provided mask. @@ -734,6 +1079,7 @@ bool payload_expr_trim(struct expr *expr, struct expr *mask, unsigned int payload_len = expr->len; const struct proto_desc *desc; unsigned int off, i, len = 0; + unsigned int total; assert(expr->etype == EXPR_PAYLOAD); @@ -743,10 +1089,8 @@ bool payload_expr_trim(struct expr *expr, struct expr *mask, assert(desc->base == expr->payload.base); - if (ctx->protocol[expr->payload.base].offset) { - assert(payload_offset >= ctx->protocol[expr->payload.base].offset); - payload_offset -= ctx->protocol[expr->payload.base].offset; - } + desc = get_stacked_desc(ctx, desc, expr, &total); + payload_offset -= total; off = round_up(mask->len, BITS_PER_BYTE) - mask_len; payload_offset += off; @@ -777,6 +1121,230 @@ bool payload_expr_trim(struct expr *expr, struct expr *mask, } /** + * payload_expr_trim_force - adjust payload len/offset according to mask + * + * @expr: the payload expression + * @mask: mask to use when searching templates + * @shift: shift adjustment to fix up RHS value + * + * Force-trim an unknown payload expression according to mask. + * + * This is only useful for unkown payload expressions that need + * to be printed in raw syntax (@base,offset,length). The kernel + * can only deal with byte-divisible offsets/length, e.g. @th,16,8. + * In such case we might be able to get rid of the mask. + * @base,offset,length & MASK OPERATOR VALUE then becomes + * @base,offset,length VALUE, where at least one of offset increases + * and length decreases. + * + * This function also returns the shift for the right hand + * constant side of the expression. + * + * @return: true if @expr was adjusted and mask can be discarded. + */ +bool payload_expr_trim_force(struct expr *expr, struct expr *mask, unsigned int *shift) +{ + unsigned int payload_offset = expr->payload.offset; + unsigned int mask_len = mask_length(mask); + unsigned int off, real_len; + + if (payload_is_known(expr) || expr->len <= mask_len) + return false; + + /* This provides the payload offset to use. + * mask->len is the total length of the mask, e.g. 16. + * mask_len holds the last bit number that will be zeroed, + */ + off = round_up(mask->len, BITS_PER_BYTE) - mask_len; + payload_offset += off; + + /* kernel only allows offsets <= 255 */ + if (round_up(payload_offset, BITS_PER_BYTE) > 255) + return false; + + real_len = mpz_popcount(mask->value); + if (real_len > expr->len) + return false; + + expr->payload.offset = payload_offset; + expr->len = real_len; + + *shift = mask_to_offset(mask); + return true; +} + +/** + * stmt_payload_expr_trim - adjust payload len/offset according to mask + * + * @stmt: the payload statement + * @pctx: protocol context + * + * Infer offset to header field from mask, walk the template list to determine + * if offset falls within a matching header field. + * + * Trim the payload expression length accordingly, adjust the payload offset + * and return true if payload statement expressions has been updated. + * + * @return: true if @stmt was adjusted. + */ +bool stmt_payload_expr_trim(struct stmt *stmt, const struct proto_ctx *pctx) +{ + struct expr *expr = stmt->payload.val; + const struct proto_hdr_template *tmpl; + const struct proto_desc *desc; + struct expr *payload, *mask; + uint32_t offset, i, shift; + unsigned int mask_offset; + mpz_t bitmask, tmp, tmp2; + unsigned long n; + + assert(stmt->type == STMT_PAYLOAD); + assert(expr->etype == EXPR_BINOP); + + payload = expr->left; + mask = expr->right; + + if (payload->etype != EXPR_PAYLOAD || + mask->etype != EXPR_VALUE) + return false; + + if (payload_is_known(payload) || + !pctx->protocol[payload->payload.base].desc || + payload->len % (2 * BITS_PER_BYTE) != 0) + return false; + + switch (expr->op) { + case OP_AND: + /* infer offset from first 0 in mask */ + n = mpz_scan0(mask->value, 0); + if (n == ULONG_MAX) + return false; + + mask_offset = payload->len - n; + break; + case OP_OR: + case OP_XOR: + /* infer offset from first 1 in mask */ + n = mpz_scan1(mask->value, 0); + if (n == ULONG_MAX) + return false; + + mask_offset = payload->len - n; + break; + default: + return false; + } + + offset = payload->payload.offset + mask_offset; + + desc = pctx->protocol[payload->payload.base].desc; + for (i = 1; i < array_size(desc->templates); i++) { + tmpl = &desc->templates[i]; + + if (tmpl->len == 0) + return false; + + /* Is this inferred offset within this header field? */ + if (tmpl->offset + tmpl->len >= offset) { + /* Infer shift to reach this header field. */ + if ((tmpl->offset % (2 * BITS_PER_BYTE)) < 8) { + shift = BITS_PER_BYTE - (tmpl->offset % BITS_PER_BYTE + tmpl->len); + shift += BITS_PER_BYTE; + } else { + shift = (2 * BITS_PER_BYTE) - (tmpl->offset % (2 * BITS_PER_BYTE) + tmpl->len); + } + + /* Build bitmask to fetch this header field. */ + mpz_init2(bitmask, payload->len); + mpz_bitmask(bitmask, tmpl->len); + if (shift) + mpz_lshift_ui(bitmask, shift); + + /* Check if mask expression falls within this header + * bitfield, if the mask expression is over this header + * field, then skip this delinearization, this could be + * a raw expression. + */ + switch (expr->op) { + case OP_AND: + /* Inverted bitmask to fetch untouched bits. */ + mpz_init_bitmask(tmp, payload->len); + mpz_xor(tmp, bitmask, tmp); + + /* Get untouched bits out of the header field. */ + mpz_init2(tmp2, payload->len); + mpz_and(tmp2, mask->value, tmp); + + /* Modified any bits out of the header field? */ + if (mpz_cmp(tmp, tmp2) != 0) { + mpz_clear(tmp); + mpz_clear(tmp2); + mpz_clear(bitmask); + return false; + } + mpz_clear(tmp2); + break; + case OP_OR: + case OP_XOR: + mpz_init2(tmp, payload->len); + + /* Get modified bits in header field. */ + mpz_and(tmp, mask->value, bitmask); + + /* Modified any bits out of the header field? */ + if (mpz_cmp(tmp, mask->value) != 0) { + mpz_clear(tmp); + mpz_clear(bitmask); + return false; + } + break; + default: + assert(0); + break; + } + mpz_clear(tmp); + + /* Clear unrelated bits for this header field. Shrink + * to "real size". Shift bits when needed. + */ + mpz_and(mask->value, bitmask, mask->value); + mpz_clear(bitmask); + + mask->len -= (tmpl->offset - payload->payload.offset); + if (shift) { + mask->len -= shift; + mpz_rshift_ui(mask->value, shift); + } + payload->payload.offset = tmpl->offset; + payload->len = tmpl->len; + + expr_free(stmt->payload.expr); + stmt->payload.expr = expr_get(payload); + + if (expr->op == OP_AND) { + /* Reduce 'expr AND 0x0', otherwise listing + * shows: + * + * ip dscp set ip dscp & 0x0 + * + * instead of the more compact: + * + * ip dscp set 0x0 + */ + if (mpz_cmp_ui(mask->value, 0) == 0) { + expr = stmt->payload.val; + stmt->payload.val = expr_get(mask); + expr_free(expr); + } + } + return true; + } + } + + return false; +} + +/** * payload_expr_expand - expand raw merged adjacent payload expressions into its * original components * @@ -793,25 +1361,35 @@ bool payload_expr_trim(struct expr *expr, struct expr *mask, void payload_expr_expand(struct list_head *list, struct expr *expr, const struct proto_ctx *ctx) { + unsigned int payload_offset = expr->payload.offset; const struct proto_hdr_template *tmpl; const struct proto_desc *desc; + unsigned int i, total; struct expr *new; - unsigned int i; assert(expr->etype == EXPR_PAYLOAD); desc = ctx->protocol[expr->payload.base].desc; - if (desc == NULL) + if (desc == NULL || desc == &proto_unknown) goto raw; + assert(desc->base == expr->payload.base); + desc = get_stacked_desc(ctx, desc, expr, &total); + payload_offset -= total; + for (i = 1; i < array_size(desc->templates); i++) { tmpl = &desc->templates[i]; if (tmpl->len == 0) break; - if (tmpl->offset != expr->payload.offset) + if (tmpl->offset != payload_offset) + continue; + + if (tmpl->icmp_dep && ctx->th_dep.icmp.type && + !icmp_dep_type_match(tmpl->icmp_dep, + ctx->th_dep.icmp.type)) continue; if (tmpl->len <= expr->len) { @@ -819,15 +1397,25 @@ void payload_expr_expand(struct list_head *list, struct expr *expr, list_add_tail(&new->list, list); expr->len -= tmpl->len; expr->payload.offset += tmpl->len; + payload_offset += tmpl->len; if (expr->len == 0) return; + } else if (expr->len > 0) { + new = payload_expr_alloc(&expr->location, desc, i); + new->len = expr->len; + list_add_tail(&new->list, list); + return; } else break; } raw: new = payload_expr_alloc(&expr->location, NULL, 0); - payload_init_raw(new, expr->payload.base, expr->payload.offset, + payload_init_raw(new, expr->payload.base, payload_offset, expr->len); + + if (expr->payload.inner_desc) + new->dtype = &integer_type; + list_add_tail(&new->list, list); } @@ -849,6 +1437,9 @@ bool payload_can_merge(const struct expr *e1, const struct expr *e2) { unsigned int total; + if (e1->payload.inner_desc != e2->payload.inner_desc) + return false; + if (!payload_is_adjacent(e1, e2)) return false; @@ -905,5 +1496,221 @@ struct expr *payload_expr_join(const struct expr *e1, const struct expr *e2) expr->payload.base = e1->payload.base; expr->payload.offset = e1->payload.offset; expr->len = e1->len + e2->len; + expr->payload.inner_desc = e1->payload.inner_desc; + return expr; } + +static struct stmt * +__payload_gen_icmp_simple_dependency(struct eval_ctx *ctx, const struct expr *expr, + const struct datatype *icmp_type, + const struct proto_desc *desc, + uint8_t type) +{ + struct expr *left, *right, *dep; + + left = payload_expr_alloc(&expr->location, desc, desc->protocol_key); + right = constant_expr_alloc(&expr->location, icmp_type, + BYTEORDER_BIG_ENDIAN, BITS_PER_BYTE, + constant_data_ptr(type, BITS_PER_BYTE)); + + dep = relational_expr_alloc(&expr->location, OP_EQ, left, right); + return expr_stmt_alloc(&dep->location, dep); +} + +static struct stmt * +__payload_gen_icmp_echo_dependency(struct eval_ctx *ctx, const struct expr *expr, + uint8_t echo, uint8_t reply, + const struct datatype *icmp_type, + const struct proto_desc *desc) +{ + struct expr *left, *right, *dep, *set; + + left = payload_expr_alloc(&expr->location, desc, desc->protocol_key); + + set = set_expr_alloc(&expr->location, NULL); + + right = constant_expr_alloc(&expr->location, icmp_type, + BYTEORDER_BIG_ENDIAN, BITS_PER_BYTE, + constant_data_ptr(echo, BITS_PER_BYTE)); + right = set_elem_expr_alloc(&expr->location, right); + compound_expr_add(set, right); + + right = constant_expr_alloc(&expr->location, icmp_type, + BYTEORDER_BIG_ENDIAN, BITS_PER_BYTE, + constant_data_ptr(reply, BITS_PER_BYTE)); + right = set_elem_expr_alloc(&expr->location, right); + compound_expr_add(set, right); + + dep = relational_expr_alloc(&expr->location, OP_IMPLICIT, left, set); + return expr_stmt_alloc(&dep->location, dep); +} + +static struct stmt * +__payload_gen_icmp6_addr_dependency(struct eval_ctx *ctx, const struct expr *expr, + const struct proto_desc *desc) +{ + static const uint8_t icmp_addr_types[] = { + MLD_LISTENER_QUERY, + MLD_LISTENER_REPORT, + MLD_LISTENER_REDUCTION, + ND_NEIGHBOR_SOLICIT, + ND_NEIGHBOR_ADVERT, + ND_REDIRECT + }; + struct expr *left, *right, *dep, *set; + size_t i; + + left = payload_expr_alloc(&expr->location, desc, desc->protocol_key); + + set = set_expr_alloc(&expr->location, NULL); + + for (i = 0; i < array_size(icmp_addr_types); ++i) { + right = constant_expr_alloc(&expr->location, &icmp6_type_type, + BYTEORDER_BIG_ENDIAN, BITS_PER_BYTE, + constant_data_ptr(icmp_addr_types[i], + BITS_PER_BYTE)); + right = set_elem_expr_alloc(&expr->location, right); + compound_expr_add(set, right); + } + + dep = relational_expr_alloc(&expr->location, OP_IMPLICIT, left, set); + return expr_stmt_alloc(&dep->location, dep); +} + +int payload_gen_icmp_dependency(struct eval_ctx *ctx, const struct expr *expr, + struct stmt **res) +{ + struct proto_ctx *pctx = eval_proto_ctx(ctx); + const struct proto_hdr_template *tmpl; + const struct proto_desc *desc; + struct stmt *stmt = NULL; + uint8_t type; + + assert(expr->etype == EXPR_PAYLOAD); + + tmpl = expr->payload.tmpl; + desc = expr->payload.desc; + + switch (tmpl->icmp_dep) { + case PROTO_ICMP_ANY: + BUG("No dependency needed"); + break; + case PROTO_ICMP_ECHO: + /* do not test ICMP_ECHOREPLY here: its 0 */ + if (pctx->th_dep.icmp.type == ICMP_ECHO) + goto done; + + type = ICMP_ECHO; + if (pctx->th_dep.icmp.type) + goto bad_proto; + + stmt = __payload_gen_icmp_echo_dependency(ctx, expr, + ICMP_ECHO, ICMP_ECHOREPLY, + &icmp_type_type, + desc); + break; + case PROTO_ICMP_MTU: + case PROTO_ICMP_ADDRESS: + type = icmp_dep_to_type(tmpl->icmp_dep); + if (pctx->th_dep.icmp.type == type) + goto done; + if (pctx->th_dep.icmp.type) + goto bad_proto; + stmt = __payload_gen_icmp_simple_dependency(ctx, expr, + &icmp_type_type, + desc, type); + break; + case PROTO_ICMP6_ECHO: + if (pctx->th_dep.icmp.type == ICMP6_ECHO_REQUEST || + pctx->th_dep.icmp.type == ICMP6_ECHO_REPLY) + goto done; + + type = ICMP6_ECHO_REQUEST; + if (pctx->th_dep.icmp.type) + goto bad_proto; + + stmt = __payload_gen_icmp_echo_dependency(ctx, expr, + ICMP6_ECHO_REQUEST, + ICMP6_ECHO_REPLY, + &icmp6_type_type, + desc); + break; + case PROTO_ICMP6_ADDRESS: + if (icmp_dep_type_match(PROTO_ICMP6_ADDRESS, + pctx->th_dep.icmp.type)) + goto done; + type = ND_NEIGHBOR_SOLICIT; + if (pctx->th_dep.icmp.type) + goto bad_proto; + stmt = __payload_gen_icmp6_addr_dependency(ctx, expr, desc); + break; + case PROTO_ICMP6_REDIRECT: + case PROTO_ICMP6_MTU: + case PROTO_ICMP6_MGMQ: + case PROTO_ICMP6_PPTR: + type = icmp_dep_to_type(tmpl->icmp_dep); + if (pctx->th_dep.icmp.type == type) + goto done; + if (pctx->th_dep.icmp.type) + goto bad_proto; + stmt = __payload_gen_icmp_simple_dependency(ctx, expr, + &icmp6_type_type, + desc, type); + break; + break; + default: + BUG("Unhandled icmp dependency code"); + } + + pctx->th_dep.icmp.type = type; + + if (stmt_dependency_evaluate(ctx, stmt) < 0) + return -1; +done: + *res = stmt; + return 0; + +bad_proto: + return expr_error(ctx->msgs, expr, "incompatible icmp match: rule has %d, need %u", + pctx->th_dep.icmp.type, type); +} + +int payload_gen_inner_dependency(struct eval_ctx *ctx, const struct expr *expr, + struct stmt **res) +{ + struct proto_ctx *pctx = eval_proto_ctx(ctx); + const struct proto_hdr_template *tmpl; + const struct proto_desc *desc, *inner_desc; + struct expr *left, *right, *dep; + struct stmt *stmt = NULL; + int protocol; + + assert(expr->etype == EXPR_PAYLOAD); + + inner_desc = expr->payload.inner_desc; + desc = pctx->protocol[inner_desc->base - 1].desc; + if (desc == NULL) + desc = &proto_ip; + + tmpl = &inner_desc->templates[0]; + assert(tmpl); + + protocol = proto_find_num(desc, inner_desc); + if (protocol < 0) + return expr_error(ctx->msgs, expr, + "conflicting protocols specified: %s vs. %s", + desc->name, inner_desc->name); + + left = meta_expr_alloc(&expr->location, tmpl->meta_key); + + right = constant_expr_alloc(&expr->location, tmpl->dtype, + tmpl->dtype->byteorder, tmpl->len, + constant_data_ptr(protocol, tmpl->len)); + + dep = relational_expr_alloc(&expr->location, OP_EQ, left, right); + stmt = expr_stmt_alloc(&dep->location, dep); + + *res = stmt; + return 0; +} diff --git a/src/preprocess.c b/src/preprocess.c new file mode 100644 index 00000000..619f67a1 --- /dev/null +++ b/src/preprocess.c @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2013-2024 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. + */ + +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <stdbool.h> +#include <string.h> +#include <utils.h> + +#include "list.h" +#include "parser.h" +#include "erec.h" + +struct str_buf { + uint8_t *str; + uint32_t len; + uint32_t size; +}; + +#define STR_BUF_LEN 128 + +static struct str_buf *str_buf_alloc(void) +{ + struct str_buf *buf; + + buf = xzalloc(sizeof(*buf)); + buf->str = xzalloc_array(1, STR_BUF_LEN); + buf->size = STR_BUF_LEN; + + return buf; +} + +static int str_buf_add(struct str_buf *buf, const char *str, uint32_t len) +{ + uint8_t *tmp; + + if (len + buf->len > buf->size) { + buf->size = (len + buf->len) * 2; + tmp = xrealloc(buf->str, buf->size); + buf->str = tmp; + } + + memcpy(&buf->str[buf->len], str, len); + buf->len += len; + + return 0; +} + +struct str_chunk { + struct list_head list; + char *str; + uint32_t len; + bool is_sym; +}; + +static void add_str_chunk(const char *x, int from, int to, struct list_head *list, bool is_sym) +{ + struct str_chunk *chunk; + int len = to - from; + + chunk = xzalloc_array(1, sizeof(*chunk)); + chunk->str = xzalloc_array(1, len + 1); + chunk->is_sym = is_sym; + chunk->len = len; + memcpy(chunk->str, &x[from], len); + + list_add_tail(&chunk->list, list); +} + +static void free_str_chunk(struct str_chunk *chunk) +{ + free(chunk->str); + free(chunk); +} + +const char *str_preprocess(struct parser_state *state, struct location *loc, + struct scope *scope, const char *x, + struct error_record **erec) +{ + struct str_chunk *chunk, *next; + struct str_buf *buf; + const char *str; + int i, j, start; + LIST_HEAD(list); + + start = 0; + i = 0; + while (1) { + if (x[i] == '\0') { + i++; + break; + } + + if (x[i] != '$') { + i++; + continue; + } + + if (isdigit(x[++i])) + continue; + + j = i; + while (1) { + if (isalpha(x[i]) || + isdigit(x[i]) || + x[i] == '_') { + i++; + continue; + } + break; + } + add_str_chunk(x, start, j-1, &list, false); + add_str_chunk(x, j, i, &list, true); + start = i; + } + if (start != i) + add_str_chunk(x, start, i, &list, false); + + buf = str_buf_alloc(); + + list_for_each_entry_safe(chunk, next, &list, list) { + if (chunk->is_sym) { + struct symbol *sym; + + sym = symbol_lookup(scope, chunk->str); + if (!sym) { + sym = symbol_lookup_fuzzy(scope, chunk->str); + if (sym) { + *erec = error(loc, "unknown identifier '%s'; " + "did you mean identifier '%s'?", + chunk->str, sym->identifier); + } else { + *erec = error(loc, "unknown identifier '%s'", + chunk->str); + } + goto err; + } + str_buf_add(buf, sym->expr->identifier, + strlen(sym->expr->identifier)); + } else { + str_buf_add(buf, chunk->str, chunk->len); + } + list_del(&chunk->list); + free_str_chunk(chunk); + } + + str = (char *)buf->str; + + free(buf); + + return (char *)str; +err: + list_for_each_entry_safe(chunk, next, &list, list) { + list_del(&chunk->list); + free_str_chunk(chunk); + } + free(buf->str); + free(buf); + + return NULL; +} diff --git a/src/print.c b/src/print.c index d1b25e8b..8aefa961 100644 --- a/src/print.c +++ b/src/print.c @@ -2,11 +2,12 @@ * Copyright (c) 2017 Phil Sutter <phil@nwl.cc> * * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * + * it under the terms of the GNU General Public License version 2 (or any + * later) as published by the Free Software Foundation. */ +#include <nft.h> + #include <stdarg.h> #include <nftables.h> #include <utils.h> diff --git a/src/proto.c b/src/proto.c index 7d001501..05ddb070 100644 --- a/src/proto.c +++ b/src/proto.c @@ -9,10 +9,9 @@ * */ +#include <nft.h> + #include <stddef.h> -#include <stdlib.h> -#include <stdint.h> -#include <string.h> #include <net/if_arp.h> #include <arpa/inet.h> #include <linux/netfilter.h> @@ -28,6 +27,7 @@ const char *proto_base_names[] = { [PROTO_BASE_LL_HDR] = "link layer", [PROTO_BASE_NETWORK_HDR] = "network layer", [PROTO_BASE_TRANSPORT_HDR] = "transport layer", + [PROTO_BASE_INNER_HDR] = "payload data", }; const char *proto_base_tokens[] = { @@ -35,6 +35,7 @@ const char *proto_base_tokens[] = { [PROTO_BASE_LL_HDR] = "ll", [PROTO_BASE_NETWORK_HDR] = "nh", [PROTO_BASE_TRANSPORT_HDR] = "th", + [PROTO_BASE_INNER_HDR] = "ih", }; const struct proto_hdr_template proto_unknown_template = @@ -58,6 +59,8 @@ proto_find_upper(const struct proto_desc *base, unsigned int num) unsigned int i; for (i = 0; i < array_size(base->protocols); i++) { + if (!base->protocols[i].desc) + break; if (base->protocols[i].num == num) return base->protocols[i].desc; } @@ -76,12 +79,38 @@ int proto_find_num(const struct proto_desc *base, unsigned int i; for (i = 0; i < array_size(base->protocols); i++) { + if (!base->protocols[i].desc) + break; if (base->protocols[i].desc == desc) return base->protocols[i].num; } return -1; } +static const struct proto_desc *inner_protocols[] = { + &proto_vxlan, + &proto_geneve, + &proto_gre, + &proto_gretap, +}; + +const struct proto_desc *proto_find_inner(uint32_t type, uint32_t hdrsize, + uint32_t flags) +{ + const struct proto_desc *desc; + unsigned int i; + + for (i = 0; i < array_size(inner_protocols); i++) { + desc = inner_protocols[i]; + if (desc->inner.type == type && + desc->inner.hdrsize == hdrsize && + desc->inner.flags == flags) + return inner_protocols[i]; + } + + return &proto_unknown; +} + static const struct dev_proto_desc dev_proto_desc[] = { DEV_PROTO_DESC(ARPHRD_ETHER, &proto_eth), }; @@ -104,6 +133,8 @@ int proto_dev_type(const struct proto_desc *desc, uint16_t *res) return 0; } for (j = 0; j < array_size(base->protocols); j++) { + if (!base->protocols[j].desc) + break; if (base->protocols[j].desc == desc) { *res = dev_proto_desc[i].type; return 0; @@ -146,14 +177,20 @@ static void proto_ctx_debug(const struct proto_ctx *ctx, enum proto_bases base, if (!(debug_mask & NFT_DEBUG_PROTO_CTX)) return; - pr_debug("update %s protocol context:\n", proto_base_names[base]); + if (base == PROTO_BASE_LL_HDR && ctx->stacked_ll_count) { + pr_debug(" saved ll headers:"); + for (i = 0; i < ctx->stacked_ll_count; i++) + pr_debug(" %s", ctx->stacked_ll[i]->name); + } + + pr_debug("update %s protocol context%s:\n", + proto_base_names[base], ctx->inner ? " (inner)" : ""); + for (i = PROTO_BASE_LL_HDR; i <= PROTO_BASE_MAX; i++) { pr_debug(" %-20s: %s", proto_base_names[i], ctx->protocol[i].desc ? ctx->protocol[i].desc->name : "none"); - if (ctx->protocol[i].offset) - pr_debug(" (offset: %u)", ctx->protocol[i].offset); if (i == base) pr_debug(" <-"); pr_debug("\n"); @@ -169,7 +206,7 @@ static void proto_ctx_debug(const struct proto_ctx *ctx, enum proto_bases base, * @debug_mask: display debugging information */ void proto_ctx_init(struct proto_ctx *ctx, unsigned int family, - unsigned int debug_mask) + unsigned int debug_mask, bool inner) { const struct hook_proto_desc *h = &hook_proto_desc[family]; @@ -177,6 +214,7 @@ void proto_ctx_init(struct proto_ctx *ctx, unsigned int family, ctx->family = family; ctx->protocol[h->base].desc = h->desc; ctx->debug_mask = debug_mask; + ctx->inner = inner; proto_ctx_debug(ctx, h->base, debug_mask); } @@ -193,12 +231,71 @@ void proto_ctx_update(struct proto_ctx *ctx, enum proto_bases base, const struct location *loc, const struct proto_desc *desc) { + bool found = false; + unsigned int i; + + switch (base) { + case PROTO_BASE_LL_HDR: + case PROTO_BASE_NETWORK_HDR: + break; + case PROTO_BASE_TRANSPORT_HDR: + if (ctx->protocol[base].num_protos >= PROTO_CTX_NUM_PROTOS) + break; + + for (i = 0; i < ctx->protocol[base].num_protos; i++) { + if (ctx->protocol[base].protos[i].desc == desc) { + found = true; + break; + } + } + if (!found) { + i = ctx->protocol[base].num_protos++; + ctx->protocol[base].protos[i].desc = desc; + ctx->protocol[base].protos[i].location = *loc; + } + break; + case PROTO_BASE_INNER_HDR: + break; + default: + BUG("unknown protocol base %d", base); + } + ctx->protocol[base].location = *loc; ctx->protocol[base].desc = desc; proto_ctx_debug(ctx, base, ctx->debug_mask); } +bool proto_ctx_is_ambiguous(struct proto_ctx *ctx, enum proto_bases base) +{ + return ctx->protocol[base].num_protos > 1; +} + +const struct proto_desc *proto_ctx_find_conflict(struct proto_ctx *ctx, + enum proto_bases base, + const struct proto_desc *desc) +{ + unsigned int i; + + switch (base) { + case PROTO_BASE_LL_HDR: + case PROTO_BASE_NETWORK_HDR: + if (desc != ctx->protocol[base].desc) + return ctx->protocol[base].desc; + break; + case PROTO_BASE_TRANSPORT_HDR: + for (i = 0; i < ctx->protocol[base].num_protos; i++) { + if (desc != ctx->protocol[base].protos[i].desc) + return ctx->protocol[base].protos[i].desc; + } + break; + default: + BUG("unknown protocol base %d", base); + } + + return NULL; +} + #define HDR_TEMPLATE(__name, __dtype, __type, __member) \ PROTO_HDR_TEMPLATE(__name, __dtype, \ BYTEORDER_BIG_ENDIAN, \ @@ -207,6 +304,8 @@ void proto_ctx_update(struct proto_ctx *ctx, enum proto_bases base, #define HDR_FIELD(__name, __struct, __member) \ HDR_TEMPLATE(__name, &integer_type, __struct, __member) +#define HDR_HEX_FIELD(__name, __struct, __member) \ + HDR_TEMPLATE(__name, &xinteger_type, __struct, __member) #define HDR_BITFIELD(__name, __dtype, __offset, __len) \ PROTO_HDR_TEMPLATE(__name, __dtype, BYTEORDER_BIG_ENDIAN, \ __offset, __len) @@ -339,24 +438,37 @@ const struct datatype icmp_type_type = { .sym_tbl = &icmp_type_tbl, }; -#define ICMPHDR_FIELD(__name, __member) \ - HDR_FIELD(__name, struct icmphdr, __member) +#define ICMP46HDR_FIELD(__token, __dtype, __struct, __member, __dep) \ + { \ + .token = (__token), \ + .dtype = &__dtype, \ + .byteorder = BYTEORDER_BIG_ENDIAN, \ + .offset = offsetof(__struct, __member) * 8, \ + .len = field_sizeof(__struct, __member) * 8, \ + .icmp_dep = (__dep), \ + } + +#define ICMPHDR_FIELD(__token, __member, __dep) \ + ICMP46HDR_FIELD(__token, integer_type, struct icmphdr, __member, __dep) + #define ICMPHDR_TYPE(__name, __type, __member) \ - HDR_TYPE(__name, __type, struct icmphdr, __member) + HDR_TYPE(__name, __type, struct icmphdr, __member) const struct proto_desc proto_icmp = { .name = "icmp", .id = PROTO_DESC_ICMP, .base = PROTO_BASE_TRANSPORT_HDR, + .protocol_key = ICMPHDR_TYPE, .checksum_key = ICMPHDR_CHECKSUM, + .checksum_type = NFT_PAYLOAD_CSUM_INET, .templates = { [ICMPHDR_TYPE] = ICMPHDR_TYPE("type", &icmp_type_type, type), [ICMPHDR_CODE] = ICMPHDR_TYPE("code", &icmp_code_type, code), - [ICMPHDR_CHECKSUM] = ICMPHDR_FIELD("checksum", checksum), - [ICMPHDR_ID] = ICMPHDR_FIELD("id", un.echo.id), - [ICMPHDR_SEQ] = ICMPHDR_FIELD("sequence", un.echo.sequence), - [ICMPHDR_GATEWAY] = ICMPHDR_FIELD("gateway", un.gateway), - [ICMPHDR_MTU] = ICMPHDR_FIELD("mtu", un.frag.mtu), + [ICMPHDR_CHECKSUM] = ICMPHDR_FIELD("checksum", checksum, PROTO_ICMP_ANY), + [ICMPHDR_ID] = ICMPHDR_FIELD("id", un.echo.id, PROTO_ICMP_ECHO), + [ICMPHDR_SEQ] = ICMPHDR_FIELD("sequence", un.echo.sequence, PROTO_ICMP_ECHO), + [ICMPHDR_GATEWAY] = ICMPHDR_FIELD("gateway", un.gateway, PROTO_ICMP_ADDRESS), + [ICMPHDR_MTU] = ICMPHDR_FIELD("mtu", un.frag.mtu, PROTO_ICMP_MTU), }, }; @@ -402,6 +514,7 @@ const struct proto_desc proto_igmp = { .id = PROTO_DESC_IGMP, .base = PROTO_BASE_TRANSPORT_HDR, .checksum_key = IGMPHDR_CHECKSUM, + .checksum_type = NFT_PAYLOAD_CSUM_INET, .templates = { [IGMPHDR_TYPE] = IGMPHDR_TYPE("type", &igmp_type_type, igmp_type), [IGMPHDR_MRT] = IGMPHDR_FIELD("mrt", igmp_code), @@ -422,13 +535,16 @@ const struct proto_desc proto_udp = { .name = "udp", .id = PROTO_DESC_UDP, .base = PROTO_BASE_TRANSPORT_HDR, - .checksum_key = UDPHDR_CHECKSUM, .templates = { [UDPHDR_SPORT] = INET_SERVICE("sport", struct udphdr, source), [UDPHDR_DPORT] = INET_SERVICE("dport", struct udphdr, dest), [UDPHDR_LENGTH] = UDPHDR_FIELD("length", len), [UDPHDR_CHECKSUM] = UDPHDR_FIELD("checksum", check), }, + .protocols = { + PROTO_LINK(0, &proto_vxlan), + PROTO_LINK(0, &proto_geneve), + }, }; const struct proto_desc proto_udplite = { @@ -482,6 +598,7 @@ const struct proto_desc proto_tcp = { .id = PROTO_DESC_TCP, .base = PROTO_BASE_TRANSPORT_HDR, .checksum_key = TCPHDR_CHECKSUM, + .checksum_type = NFT_PAYLOAD_CSUM_INET, .templates = { [TCPHDR_SPORT] = INET_SERVICE("sport", struct tcphdr, source), [TCPHDR_DPORT] = INET_SERVICE("dport", struct tcphdr, dest), @@ -563,6 +680,8 @@ const struct proto_desc proto_sctp = { .name = "sctp", .id = PROTO_DESC_SCTP, .base = PROTO_BASE_TRANSPORT_HDR, + .checksum_key = SCTPHDR_CHECKSUM, + .checksum_type = NFT_PAYLOAD_CSUM_SCTP, .templates = { [SCTPHDR_SPORT] = INET_SERVICE("sport", struct sctphdr, source), [SCTPHDR_DPORT] = INET_SERVICE("dport", struct sctphdr, dest), @@ -601,7 +720,9 @@ static const struct symbol_table dscp_type_tbl = { SYMBOL("cs5", 0x28), SYMBOL("cs6", 0x30), SYMBOL("cs7", 0x38), + SYMBOL("df", 0x00), SYMBOL("be", 0x00), + SYMBOL("lephb", 0x01), SYMBOL("af11", 0x0a), SYMBOL("af12", 0x0c), SYMBOL("af13", 0x0e), @@ -614,6 +735,7 @@ static const struct symbol_table dscp_type_tbl = { SYMBOL("af41", 0x22), SYMBOL("af42", 0x24), SYMBOL("af43", 0x26), + SYMBOL("va", 0x2c), SYMBOL("ef", 0x2e), SYMBOL_LIST_END }, @@ -652,6 +774,42 @@ const struct datatype ecn_type = { .sym_tbl = &ecn_type_tbl, }; +#define GREHDR_TEMPLATE(__name, __dtype, __member) \ + HDR_TEMPLATE(__name, __dtype, struct grehdr, __member) +#define GREHDR_TYPE(__name, __member) \ + GREHDR_TEMPLATE(__name, ðertype_type, __member) + +const struct proto_desc proto_gre = { + .name = "gre", + .id = PROTO_DESC_GRE, + .base = PROTO_BASE_TRANSPORT_HDR, + .templates = { + [0] = PROTO_META_TEMPLATE("l4proto", &inet_protocol_type, NFT_META_L4PROTO, 8), + [GREHDR_FLAGS] = HDR_BITFIELD("flags", &integer_type, 0, 5), + [GREHDR_VERSION] = HDR_BITFIELD("version", &integer_type, 13, 3), + [GREHDR_PROTOCOL] = HDR_BITFIELD("protocol", ðertype_type, 16, 16), + }, + .inner = { + .hdrsize = sizeof(struct grehdr), + .flags = NFT_INNER_NH | NFT_INNER_TH, + .type = NFT_INNER_GENEVE + 1, + }, +}; + +const struct proto_desc proto_gretap = { + .name = "gretap", + .id = PROTO_DESC_GRETAP, + .base = PROTO_BASE_TRANSPORT_HDR, + .templates = { + [0] = PROTO_META_TEMPLATE("l4proto", &inet_protocol_type, NFT_META_L4PROTO, 8), + }, + .inner = { + .hdrsize = sizeof(struct grehdr), + .flags = NFT_INNER_LL | NFT_INNER_NH | NFT_INNER_TH, + .type = NFT_INNER_GENEVE + 2, + }, +}; + #define IPHDR_FIELD(__name, __member) \ HDR_FIELD(__name, struct iphdr, __member) #define IPHDR_ADDR(__name, __member) \ @@ -662,6 +820,7 @@ const struct proto_desc proto_ip = { .id = PROTO_DESC_IP, .base = PROTO_BASE_NETWORK_HDR, .checksum_key = IPHDR_CHECKSUM, + .checksum_type = NFT_PAYLOAD_CSUM_INET, .protocols = { PROTO_LINK(IPPROTO_ICMP, &proto_icmp), PROTO_LINK(IPPROTO_IGMP, &proto_igmp), @@ -674,6 +833,8 @@ const struct proto_desc proto_ip = { PROTO_LINK(IPPROTO_TCP, &proto_tcp), PROTO_LINK(IPPROTO_DCCP, &proto_dccp), PROTO_LINK(IPPROTO_SCTP, &proto_sctp), + PROTO_LINK(IPPROTO_GRE, &proto_gre), + PROTO_LINK(IPPROTO_GRE, &proto_gretap), }, .templates = { [0] = PROTO_META_TEMPLATE("l4proto", &inet_protocol_type, NFT_META_L4PROTO, 8), @@ -683,7 +844,7 @@ const struct proto_desc proto_ip = { [IPHDR_ECN] = HDR_BITFIELD("ecn", &ecn_type, 14, 2), [IPHDR_LENGTH] = IPHDR_FIELD("length", tot_len), [IPHDR_ID] = IPHDR_FIELD("id", id), - [IPHDR_FRAG_OFF] = IPHDR_FIELD("frag-off", frag_off), + [IPHDR_FRAG_OFF] = HDR_HEX_FIELD("frag-off", struct iphdr, frag_off), [IPHDR_TTL] = IPHDR_FIELD("ttl", ttl), [IPHDR_PROTOCOL] = INET_PROTOCOL("protocol", struct iphdr, protocol), [IPHDR_CHECKSUM] = IPHDR_FIELD("checksum", check), @@ -749,8 +910,8 @@ const struct datatype icmp6_type_type = { .sym_tbl = &icmp6_type_tbl, }; -#define ICMP6HDR_FIELD(__name, __member) \ - HDR_FIELD(__name, struct icmp6_hdr, __member) +#define ICMP6HDR_FIELD(__token, __member, __dep) \ + ICMP46HDR_FIELD(__token, integer_type, struct icmp6_hdr, __member, __dep) #define ICMP6HDR_TYPE(__name, __type, __member) \ HDR_TYPE(__name, __type, struct icmp6_hdr, __member) @@ -758,16 +919,24 @@ const struct proto_desc proto_icmp6 = { .name = "icmpv6", .id = PROTO_DESC_ICMPV6, .base = PROTO_BASE_TRANSPORT_HDR, + .protocol_key = ICMP6HDR_TYPE, .checksum_key = ICMP6HDR_CHECKSUM, + .checksum_type = NFT_PAYLOAD_CSUM_INET, .templates = { [ICMP6HDR_TYPE] = ICMP6HDR_TYPE("type", &icmp6_type_type, icmp6_type), [ICMP6HDR_CODE] = ICMP6HDR_TYPE("code", &icmpv6_code_type, icmp6_code), - [ICMP6HDR_CHECKSUM] = ICMP6HDR_FIELD("checksum", icmp6_cksum), - [ICMP6HDR_PPTR] = ICMP6HDR_FIELD("parameter-problem", icmp6_pptr), - [ICMP6HDR_MTU] = ICMP6HDR_FIELD("mtu", icmp6_mtu), - [ICMP6HDR_ID] = ICMP6HDR_FIELD("id", icmp6_id), - [ICMP6HDR_SEQ] = ICMP6HDR_FIELD("sequence", icmp6_seq), - [ICMP6HDR_MAXDELAY] = ICMP6HDR_FIELD("max-delay", icmp6_maxdelay), + [ICMP6HDR_CHECKSUM] = ICMP6HDR_FIELD("checksum", icmp6_cksum, PROTO_ICMP_ANY), + [ICMP6HDR_PPTR] = ICMP6HDR_FIELD("parameter-problem", icmp6_pptr, PROTO_ICMP6_PPTR), + [ICMP6HDR_MTU] = ICMP6HDR_FIELD("mtu", icmp6_mtu, PROTO_ICMP6_MTU), + [ICMP6HDR_ID] = ICMP6HDR_FIELD("id", icmp6_id, PROTO_ICMP6_ECHO), + [ICMP6HDR_SEQ] = ICMP6HDR_FIELD("sequence", icmp6_seq, PROTO_ICMP6_ECHO), + [ICMP6HDR_MAXDELAY] = ICMP6HDR_FIELD("max-delay", icmp6_maxdelay, PROTO_ICMP6_MGMQ), + [ICMP6HDR_TADDR] = ICMP46HDR_FIELD("taddr", ip6addr_type, + struct nd_neighbor_solicit, nd_ns_target, + PROTO_ICMP6_ADDRESS), + [ICMP6HDR_DADDR] = ICMP46HDR_FIELD("daddr", ip6addr_type, + struct nd_redirect, nd_rd_dst, + PROTO_ICMP6_REDIRECT), }, }; @@ -798,6 +967,8 @@ const struct proto_desc proto_ip6 = { PROTO_LINK(IPPROTO_ICMP, &proto_icmp), PROTO_LINK(IPPROTO_IGMP, &proto_igmp), PROTO_LINK(IPPROTO_ICMPV6, &proto_icmp6), + PROTO_LINK(IPPROTO_GRE, &proto_gre), + PROTO_LINK(IPPROTO_GRE, &proto_gretap), }, .templates = { [0] = PROTO_META_TEMPLATE("l4proto", &inet_protocol_type, NFT_META_L4PROTO, 8), @@ -863,6 +1034,8 @@ const struct proto_desc proto_inet_service = { PROTO_LINK(IPPROTO_ICMP, &proto_icmp), PROTO_LINK(IPPROTO_IGMP, &proto_igmp), PROTO_LINK(IPPROTO_ICMPV6, &proto_icmp6), + PROTO_LINK(IPPROTO_GRE, &proto_gre), + PROTO_LINK(IPPROTO_GRE, &proto_gretap), }, .templates = { [0] = PROTO_META_TEMPLATE("l4proto", &inet_protocol_type, NFT_META_L4PROTO, 8), @@ -915,8 +1088,8 @@ const struct proto_desc proto_arp = { [ARPHDR_PLN] = ARPHDR_FIELD("plen", plen), [ARPHDR_OP] = ARPHDR_TYPE("operation", &arpop_type, oper), [ARPHDR_SADDR_ETHER] = ARPHDR_TYPE("saddr ether", ðeraddr_type, sha), - [ARPHDR_DADDR_ETHER] = ARPHDR_TYPE("daddr ether", ðeraddr_type, tha), [ARPHDR_SADDR_IP] = ARPHDR_TYPE("saddr ip", &ipaddr_type, spa), + [ARPHDR_DADDR_ETHER] = ARPHDR_TYPE("daddr ether", ðeraddr_type, tha), [ARPHDR_DADDR_IP] = ARPHDR_TYPE("daddr ip", &ipaddr_type, tpa), }, .format = { @@ -949,10 +1122,12 @@ const struct proto_desc proto_vlan = { PROTO_LINK(__constant_htons(ETH_P_ARP), &proto_arp), PROTO_LINK(__constant_htons(ETH_P_IPV6), &proto_ip6), PROTO_LINK(__constant_htons(ETH_P_8021Q), &proto_vlan), + PROTO_LINK(__constant_htons(ETH_P_8021AD), &proto_vlan), }, .templates = { [VLANHDR_PCP] = VLANHDR_BITFIELD("pcp", 0, 3), + [VLANHDR_DEI] = VLANHDR_BITFIELD("dei", 3, 1), [VLANHDR_CFI] = VLANHDR_BITFIELD("cfi", 3, 1), [VLANHDR_VID] = VLANHDR_BITFIELD("id", 4, 12), [VLANHDR_TYPE] = VLANHDR_TYPE("type", ðertype_type, vlan_type), @@ -978,6 +1153,10 @@ static const struct symbol_table ethertype_tbl = { SYMBOL("ip", __constant_htons(ETH_P_IP)), SYMBOL("arp", __constant_htons(ETH_P_ARP)), SYMBOL("ip6", __constant_htons(ETH_P_IPV6)), + SYMBOL("8021q", __constant_htons(ETH_P_8021Q)), + SYMBOL("8021ad", __constant_htons(ETH_P_8021AD)), + + /* for compatibility with older versions */ SYMBOL("vlan", __constant_htons(ETH_P_8021Q)), SYMBOL_LIST_END }, @@ -1021,6 +1200,7 @@ const struct proto_desc proto_eth = { PROTO_LINK(__constant_htons(ETH_P_ARP), &proto_arp), PROTO_LINK(__constant_htons(ETH_P_IPV6), &proto_ip6), PROTO_LINK(__constant_htons(ETH_P_8021Q), &proto_vlan), + PROTO_LINK(__constant_htons(ETH_P_8021AD), &proto_vlan), }, .templates = { [ETHHDR_DADDR] = ETHHDR_ADDR("daddr", ether_dhost), @@ -1036,6 +1216,57 @@ const struct proto_desc proto_eth = { }; /* + * VXLAN + */ + +const struct proto_desc proto_vxlan = { + .name = "vxlan", + .id = PROTO_DESC_VXLAN, + .base = PROTO_BASE_INNER_HDR, + .templates = { + [VXLANHDR_FLAGS] = HDR_BITFIELD("flags", &bitmask_type, 0, 8), + [VXLANHDR_VNI] = HDR_BITFIELD("vni", &integer_type, (4 * BITS_PER_BYTE), 24), + }, + .protocols = { + PROTO_LINK(__constant_htons(ETH_P_IP), &proto_ip), + PROTO_LINK(__constant_htons(ETH_P_ARP), &proto_arp), + PROTO_LINK(__constant_htons(ETH_P_IPV6), &proto_ip6), + PROTO_LINK(__constant_htons(ETH_P_8021Q), &proto_vlan), + }, + .inner = { + .hdrsize = sizeof(struct vxlanhdr), + .flags = NFT_INNER_HDRSIZE | NFT_INNER_LL | NFT_INNER_NH | NFT_INNER_TH, + .type = NFT_INNER_VXLAN, + }, +}; + +/* + * GENEVE + */ + +const struct proto_desc proto_geneve = { + .name = "geneve", + .id = PROTO_DESC_GENEVE, + .base = PROTO_BASE_INNER_HDR, + .templates = { + [GNVHDR_TYPE] = HDR_TYPE("type", ðertype_type, struct gnvhdr, type), + [GNVHDR_VNI] = HDR_BITFIELD("vni", &integer_type, (4 * BITS_PER_BYTE), 24), + }, + .protocols = { + PROTO_LINK(__constant_htons(ETH_P_IP), &proto_ip), + PROTO_LINK(__constant_htons(ETH_P_ARP), &proto_arp), + PROTO_LINK(__constant_htons(ETH_P_IPV6), &proto_ip6), + PROTO_LINK(__constant_htons(ETH_P_8021Q), &proto_vlan), + }, + .inner = { + .hdrsize = sizeof(struct gnvhdr), + .flags = NFT_INNER_HDRSIZE | NFT_INNER_LL | NFT_INNER_NH | NFT_INNER_TH, + .type = NFT_INNER_GENEVE, + }, +}; + + +/* * Dummy protocol for netdev tables. */ const struct proto_desc proto_netdev = { @@ -1046,13 +1277,14 @@ const struct proto_desc proto_netdev = { PROTO_LINK(__constant_htons(ETH_P_ARP), &proto_arp), PROTO_LINK(__constant_htons(ETH_P_IPV6), &proto_ip6), PROTO_LINK(__constant_htons(ETH_P_8021Q), &proto_vlan), + PROTO_LINK(__constant_htons(ETH_P_8021AD), &proto_vlan), }, .templates = { [0] = PROTO_META_TEMPLATE("protocol", ðertype_type, NFT_META_PROTOCOL, 16), }, }; -static const struct proto_desc *proto_definitions[PROTO_DESC_MAX + 1] = { +static const struct proto_desc *const proto_definitions[PROTO_DESC_MAX + 1] = { [PROTO_DESC_AH] = &proto_ah, [PROTO_DESC_ESP] = &proto_esp, [PROTO_DESC_COMP] = &proto_comp, @@ -1070,6 +1302,10 @@ static const struct proto_desc *proto_definitions[PROTO_DESC_MAX + 1] = { [PROTO_DESC_ARP] = &proto_arp, [PROTO_DESC_VLAN] = &proto_vlan, [PROTO_DESC_ETHER] = &proto_eth, + [PROTO_DESC_VXLAN] = &proto_vxlan, + [PROTO_DESC_GENEVE] = &proto_geneve, + [PROTO_DESC_GRE] = &proto_gre, + [PROTO_DESC_GRETAP] = &proto_gretap, }; const struct proto_desc *proto_find_desc(enum proto_desc_id desc_id) diff --git a/src/rbtree.c b/src/rbtree.c deleted file mode 100644 index 325c0123..00000000 --- a/src/rbtree.c +++ /dev/null @@ -1,388 +0,0 @@ -/* - * Red Black Trees - * (C) 1999 Andrea Arcangeli <andrea@suse.de> - * (C) 2002 David Woodhouse <dwmw2@infradead.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. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - */ - -#include <rbtree.h> - -static void __rb_rotate_left(struct rb_node *node, struct rb_root *root) -{ - struct rb_node *right = node->rb_right; - struct rb_node *parent = rb_parent(node); - - if ((node->rb_right = right->rb_left)) - rb_set_parent(right->rb_left, node); - right->rb_left = node; - - rb_set_parent(right, parent); - - if (parent) - { - if (node == parent->rb_left) - parent->rb_left = right; - else - parent->rb_right = right; - } - else - root->rb_node = right; - rb_set_parent(node, right); -} - -static void __rb_rotate_right(struct rb_node *node, struct rb_root *root) -{ - struct rb_node *left = node->rb_left; - struct rb_node *parent = rb_parent(node); - - if ((node->rb_left = left->rb_right)) - rb_set_parent(left->rb_right, node); - left->rb_right = node; - - rb_set_parent(left, parent); - - if (parent) - { - if (node == parent->rb_right) - parent->rb_right = left; - else - parent->rb_left = left; - } - else - root->rb_node = left; - rb_set_parent(node, left); -} - -void rb_insert_color(struct rb_node *node, struct rb_root *root) -{ - struct rb_node *parent, *gparent; - - while ((parent = rb_parent(node)) && rb_is_red(parent)) - { - gparent = rb_parent(parent); - - if (parent == gparent->rb_left) - { - { - register struct rb_node *uncle = gparent->rb_right; - if (uncle && rb_is_red(uncle)) - { - rb_set_black(uncle); - rb_set_black(parent); - rb_set_red(gparent); - node = gparent; - continue; - } - } - - if (parent->rb_right == node) - { - register struct rb_node *tmp; - __rb_rotate_left(parent, root); - tmp = parent; - parent = node; - node = tmp; - } - - rb_set_black(parent); - rb_set_red(gparent); - __rb_rotate_right(gparent, root); - } else { - { - register struct rb_node *uncle = gparent->rb_left; - if (uncle && rb_is_red(uncle)) - { - rb_set_black(uncle); - rb_set_black(parent); - rb_set_red(gparent); - node = gparent; - continue; - } - } - - if (parent->rb_left == node) - { - register struct rb_node *tmp; - __rb_rotate_right(parent, root); - tmp = parent; - parent = node; - node = tmp; - } - - rb_set_black(parent); - rb_set_red(gparent); - __rb_rotate_left(gparent, root); - } - } - - rb_set_black(root->rb_node); -} - -static void __rb_erase_color(struct rb_node *node, struct rb_node *parent, - struct rb_root *root) -{ - struct rb_node *other; - - while ((!node || rb_is_black(node)) && node != root->rb_node) - { - if (parent->rb_left == node) - { - other = parent->rb_right; - if (rb_is_red(other)) - { - rb_set_black(other); - rb_set_red(parent); - __rb_rotate_left(parent, root); - other = parent->rb_right; - } - if ((!other->rb_left || rb_is_black(other->rb_left)) && - (!other->rb_right || rb_is_black(other->rb_right))) - { - rb_set_red(other); - node = parent; - parent = rb_parent(node); - } - else - { - if (!other->rb_right || rb_is_black(other->rb_right)) - { - struct rb_node *o_left; - if ((o_left = other->rb_left)) - rb_set_black(o_left); - rb_set_red(other); - __rb_rotate_right(other, root); - other = parent->rb_right; - } - rb_set_color(other, rb_color(parent)); - rb_set_black(parent); - if (other->rb_right) - rb_set_black(other->rb_right); - __rb_rotate_left(parent, root); - node = root->rb_node; - break; - } - } - else - { - other = parent->rb_left; - if (rb_is_red(other)) - { - rb_set_black(other); - rb_set_red(parent); - __rb_rotate_right(parent, root); - other = parent->rb_left; - } - if ((!other->rb_left || rb_is_black(other->rb_left)) && - (!other->rb_right || rb_is_black(other->rb_right))) - { - rb_set_red(other); - node = parent; - parent = rb_parent(node); - } - else - { - if (!other->rb_left || rb_is_black(other->rb_left)) - { - register struct rb_node *o_right; - if ((o_right = other->rb_right)) - rb_set_black(o_right); - rb_set_red(other); - __rb_rotate_left(other, root); - other = parent->rb_left; - } - rb_set_color(other, rb_color(parent)); - rb_set_black(parent); - if (other->rb_left) - rb_set_black(other->rb_left); - __rb_rotate_right(parent, root); - node = root->rb_node; - break; - } - } - } - if (node) - rb_set_black(node); -} - -void rb_erase(struct rb_node *node, struct rb_root *root) -{ - struct rb_node *child, *parent; - int color; - - if (!node->rb_left) - child = node->rb_right; - else if (!node->rb_right) - child = node->rb_left; - else - { - struct rb_node *old = node, *left; - - node = node->rb_right; - while ((left = node->rb_left) != NULL) - node = left; - child = node->rb_right; - parent = rb_parent(node); - color = rb_color(node); - - if (child) - rb_set_parent(child, parent); - if (parent == old) { - parent->rb_right = child; - parent = node; - } else - parent->rb_left = child; - - node->rb_parent_color = old->rb_parent_color; - node->rb_right = old->rb_right; - node->rb_left = old->rb_left; - - if (rb_parent(old)) - { - if (rb_parent(old)->rb_left == old) - rb_parent(old)->rb_left = node; - else - rb_parent(old)->rb_right = node; - } else - root->rb_node = node; - - rb_set_parent(old->rb_left, node); - if (old->rb_right) - rb_set_parent(old->rb_right, node); - goto color; - } - - parent = rb_parent(node); - color = rb_color(node); - - if (child) - rb_set_parent(child, parent); - if (parent) - { - if (parent->rb_left == node) - parent->rb_left = child; - else - parent->rb_right = child; - } - else - root->rb_node = child; - - color: - if (color == RB_BLACK) - __rb_erase_color(child, parent, root); -} - -/* - * This function returns the first node (in sort order) of the tree. - */ -struct rb_node *rb_first(struct rb_root *root) -{ - struct rb_node *n; - - n = root->rb_node; - if (!n) - return NULL; - while (n->rb_left) - n = n->rb_left; - return n; -} - -struct rb_node *rb_last(struct rb_root *root) -{ - struct rb_node *n; - - n = root->rb_node; - if (!n) - return NULL; - while (n->rb_right) - n = n->rb_right; - return n; -} - -struct rb_node *rb_next(struct rb_node *node) -{ - struct rb_node *parent; - - if (rb_parent(node) == node) - return NULL; - - /* If we have a right-hand child, go down and then left as far - as we can. */ - if (node->rb_right) { - node = node->rb_right; - while (node->rb_left) - node=node->rb_left; - return node; - } - - /* No right-hand children. Everything down and left is - smaller than us, so any 'next' node must be in the general - direction of our parent. Go up the tree; any time the - ancestor is a right-hand child of its parent, keep going - up. First time it's a left-hand child of its parent, said - parent is our 'next' node. */ - while ((parent = rb_parent(node)) && node == parent->rb_right) - node = parent; - - return parent; -} - -struct rb_node *rb_prev(struct rb_node *node) -{ - struct rb_node *parent; - - if (rb_parent(node) == node) - return NULL; - - /* If we have a left-hand child, go down and then right as far - as we can. */ - if (node->rb_left) { - node = node->rb_left; - while (node->rb_right) - node=node->rb_right; - return node; - } - - /* No left-hand children. Go up till we find an ancestor which - is a right-hand child of its parent */ - while ((parent = rb_parent(node)) && node == parent->rb_left) - node = parent; - - return parent; -} - -void rb_replace_node(struct rb_node *victim, struct rb_node *new, - struct rb_root *root) -{ - struct rb_node *parent = rb_parent(victim); - - /* Set the surrounding nodes to point to the replacement */ - if (parent) { - if (victim == parent->rb_left) - parent->rb_left = new; - else - parent->rb_right = new; - } else { - root->rb_node = new; - } - if (victim->rb_left) - rb_set_parent(victim->rb_left, new); - if (victim->rb_right) - rb_set_parent(victim->rb_right, new); - - /* Copy the pointers/colour from the victim to the replacement */ - *new = *victim; -} @@ -8,12 +8,11 @@ * published by the Free Software Foundation. */ +#include <nft.h> + #include <errno.h> #include <stddef.h> -#include <stdlib.h> #include <stdio.h> -#include <stdint.h> -#include <string.h> #include <arpa/inet.h> #include <linux/netfilter.h> @@ -26,7 +25,7 @@ void realm_table_rt_init(struct nft_ctx *ctx) { - ctx->output.tbl.realm = rt_symbol_table_init("/etc/iproute2/rt_realms"); + ctx->output.tbl.realm = rt_symbol_table_init("rt_realms"); } void realm_table_rt_exit(struct nft_ctx *ctx) @@ -46,16 +45,22 @@ static struct error_record *realm_type_parse(struct parse_ctx *ctx, return symbolic_constant_parse(ctx, sym, ctx->tbl->realm, res); } +static void realm_type_describe(struct output_ctx *octx) +{ + rt_symbol_table_describe(octx, "rt_realms", + octx->tbl.realm, &realm_type); +} + const struct datatype realm_type = { .type = TYPE_REALM, .name = "realm", .desc = "routing realm", + .describe = realm_type_describe, .byteorder = BYTEORDER_HOST_ENDIAN, .size = 4 * BITS_PER_BYTE, .basetype = &integer_type, .print = realm_type_print, .parse = realm_type_parse, - .flags = DTYPE_F_PREFIX, }; const struct rt_template rt_templates[] = { @@ -8,11 +8,10 @@ * Development of this code funded by Astaro AG (http://www.astaro.com/) */ +#include <nft.h> + #include <stddef.h> -#include <stdlib.h> #include <stdio.h> -#include <stdint.h> -#include <string.h> #include <inttypes.h> #include <errno.h> @@ -25,6 +24,9 @@ #include <misspell.h> #include <json.h> #include <cache.h> +#include <owner.h> +#include <intervals.h> +#include "nftutils.h" #include <libnftnl/common.h> #include <libnftnl/ruleset.h> @@ -71,10 +73,10 @@ static uint32_t tcp_dflt_timeout[] = { static uint32_t udp_dflt_timeout[] = { [NFTNL_CTTIMEOUT_UDP_UNREPLIED] = 30, - [NFTNL_CTTIMEOUT_UDP_REPLIED] = 180, + [NFTNL_CTTIMEOUT_UDP_REPLIED] = 120, }; -struct timeout_protocol timeout_protocol[IPPROTO_MAX] = { +struct timeout_protocol timeout_protocol[UINT8_MAX + 1] = { [IPPROTO_TCP] = { .array_size = NFTNL_CTTIMEOUT_TCP_MAX, .state_to_name = tcp_state_to_name, @@ -102,11 +104,11 @@ int timeout_str2num(uint16_t l4proto, struct timeout_state *ts) void handle_free(struct handle *h) { - xfree(h->table.name); - xfree(h->chain.name); - xfree(h->set.name); - xfree(h->flowtable.name); - xfree(h->obj.name); + free_const(h->table.name); + free_const(h->chain.name); + free_const(h->set.name); + free_const(h->flowtable.name); + free_const(h->obj.name); } void handle_merge(struct handle *dst, const struct handle *src) @@ -137,176 +139,6 @@ void handle_merge(struct handle *dst, const struct handle *src) dst->index = src->index; } -static int cache_init_tables(struct netlink_ctx *ctx, struct handle *h, - struct nft_cache *cache) -{ - int ret; - - ret = netlink_list_tables(ctx, h); - if (ret < 0) - return -1; - - list_splice_tail_init(&ctx->list, &cache->list); - return 0; -} - -static int cache_init_objects(struct netlink_ctx *ctx, unsigned int flags) -{ - struct rule *rule, *nrule; - struct table *table; - struct chain *chain; - struct set *set; - int ret; - - list_for_each_entry(table, &ctx->nft->cache.list, list) { - if (flags & NFT_CACHE_SET_BIT) { - ret = netlink_list_sets(ctx, &table->handle); - list_splice_tail_init(&ctx->list, &table->sets); - if (ret < 0) - return -1; - } - if (flags & NFT_CACHE_SETELEM_BIT) { - list_for_each_entry(set, &table->sets, list) { - ret = netlink_list_setelems(ctx, &set->handle, - set); - if (ret < 0) - return -1; - } - } - if (flags & NFT_CACHE_CHAIN_BIT) { - ret = netlink_list_chains(ctx, &table->handle); - if (ret < 0) - return -1; - list_splice_tail_init(&ctx->list, &table->chains); - } - if (flags & NFT_CACHE_FLOWTABLE_BIT) { - ret = netlink_list_flowtables(ctx, &table->handle); - if (ret < 0) - return -1; - list_splice_tail_init(&ctx->list, &table->flowtables); - } - if (flags & NFT_CACHE_OBJECT_BIT) { - ret = netlink_list_objs(ctx, &table->handle); - if (ret < 0) - return -1; - list_splice_tail_init(&ctx->list, &table->objs); - } - - if (flags & NFT_CACHE_RULE_BIT) { - ret = netlink_list_rules(ctx, &table->handle); - list_for_each_entry_safe(rule, nrule, &ctx->list, list) { - chain = chain_lookup(table, &rule->handle); - list_move_tail(&rule->list, &chain->rules); - } - if (ret < 0) - return -1; - } - } - return 0; -} - -static int cache_init(struct netlink_ctx *ctx, unsigned int flags) -{ - struct handle handle = { - .family = NFPROTO_UNSPEC, - }; - int ret; - - if (flags == NFT_CACHE_EMPTY) - return 0; - - /* assume NFT_CACHE_TABLE is always set. */ - ret = cache_init_tables(ctx, &handle, &ctx->nft->cache); - if (ret < 0) - return ret; - ret = cache_init_objects(ctx, flags); - if (ret < 0) - return ret; - - return 0; -} - -static bool cache_is_complete(struct nft_cache *cache, unsigned int flags) -{ - return (cache->flags & flags) == flags; -} - -static bool cache_is_updated(struct nft_cache *cache, uint16_t genid) -{ - return genid && genid == cache->genid; -} - -bool cache_needs_update(struct nft_cache *cache) -{ - return cache->flags & NFT_CACHE_UPDATE; -} - -int cache_update(struct nft_ctx *nft, unsigned int flags, struct list_head *msgs) -{ - struct netlink_ctx ctx = { - .list = LIST_HEAD_INIT(ctx.list), - .nft = nft, - .msgs = msgs, - }; - struct nft_cache *cache = &nft->cache; - uint32_t genid, genid_stop, oldflags; - int ret; -replay: - ctx.seqnum = cache->seqnum++; - genid = mnl_genid_get(&ctx); - if (cache_is_complete(cache, flags) && - cache_is_updated(cache, genid)) - return 0; - - if (cache->genid) - cache_release(cache); - - if (flags & NFT_CACHE_FLUSHED) { - oldflags = flags; - flags = NFT_CACHE_EMPTY; - if (oldflags & NFT_CACHE_UPDATE) - flags |= NFT_CACHE_UPDATE; - goto skip; - } - - ret = cache_init(&ctx, flags); - if (ret < 0) { - cache_release(cache); - if (errno == EINTR) { - nft->nf_sock = nft_mnl_socket_reopen(nft->nf_sock); - goto replay; - } - return -1; - } - - genid_stop = mnl_genid_get(&ctx); - if (genid != genid_stop) { - cache_release(cache); - goto replay; - } -skip: - cache->genid = genid; - cache->flags = flags; - return 0; -} - -static void __cache_flush(struct list_head *table_list) -{ - struct table *table, *next; - - list_for_each_entry_safe(table, next, table_list, list) { - list_del(&table->list); - table_free(table); - } -} - -void cache_release(struct nft_cache *cache) -{ - __cache_flush(&cache->list); - cache->genid = 0; - cache->flags = NFT_CACHE_EMPTY; -} - /* internal ID to uniquely identify a set in the batch */ static uint32_t set_id; @@ -314,11 +146,15 @@ struct set *set_alloc(const struct location *loc) { struct set *set; + assert(loc); + set = xzalloc(sizeof(*set)); set->refcnt = 1; set->handle.set_id = ++set_id; - if (loc != NULL) - set->location = *loc; + set->location = *loc; + + init_list_head(&set->stmt_list); + return set; } @@ -326,7 +162,7 @@ struct set *set_clone(const struct set *set) { struct set *new_set; - new_set = set_alloc(NULL); + new_set = set_alloc(&internal_location); handle_merge(&new_set->handle, &set->handle); new_set->flags = set->flags; new_set->gc_int = set->gc_int; @@ -337,7 +173,8 @@ struct set *set_clone(const struct set *set) new_set->objtype = set->objtype; new_set->policy = set->policy; new_set->automerge = set->automerge; - new_set->desc.size = set->desc.size; + new_set->desc = set->desc; + init_list_head(&new_set->stmt_list); return new_set; } @@ -350,30 +187,20 @@ struct set *set_get(struct set *set) void set_free(struct set *set) { + struct stmt *stmt, *next; + if (--set->refcnt > 0) return; - if (set->init != NULL) - expr_free(set->init); + + expr_free(set->init); + if (set->comment) + free_const(set->comment); handle_free(&set->handle); + list_for_each_entry_safe(stmt, next, &set->stmt_list, list) + stmt_free(stmt); expr_free(set->key); expr_free(set->data); - xfree(set); -} - -void set_add_hash(struct set *set, struct table *table) -{ - list_add_tail(&set->list, &table->sets); -} - -struct set *set_lookup(const struct table *table, const char *name) -{ - struct set *set; - - list_for_each_entry(set, &table->sets, list) { - if (!strcmp(set->handle.set.name, name)) - return set; - } - return NULL; + free(set); } struct set *set_lookup_fuzzy(const char *set_name, @@ -384,16 +211,15 @@ struct set *set_lookup_fuzzy(const char *set_name, struct table *table; struct set *set; + if (!set_name) + return NULL; + string_misspell_init(&st); - list_for_each_entry(table, &cache->list, list) { - list_for_each_entry(set, &table->sets, list) { + list_for_each_entry(table, &cache->table_cache.list, cache.list) { + list_for_each_entry(set, &table->set_cache.list, cache.list) { if (set_is_anonymous(set->flags)) continue; - if (!strcmp(set->handle.set.name, set_name)) { - *t = table; - return set; - } if (string_misspell_update(set->handle.set.name, set_name, set, &st)) *t = table; @@ -405,17 +231,13 @@ struct set *set_lookup_fuzzy(const char *set_name, struct set *set_lookup_global(uint32_t family, const char *table, const char *name, struct nft_cache *cache) { - struct handle h; struct table *t; - h.family = family; - h.table.name = table; - - t = table_lookup(&h, cache); + t = table_cache_find(&cache->table_cache, table, family); if (t == NULL) return NULL; - return set_lookup(t, name); + return set_cache_find(t, name); } struct print_fmt_options { @@ -461,6 +283,9 @@ static void set_print_key_and_data(const struct set *set, struct output_ctx *oct if (set_is_datamap(set->flags)) { nft_print(octx, " : "); + if (set->data->flags & EXPR_F_INTERVAL) + nft_print(octx, "interval "); + if (use_typeof) expr_print(set->data, octx); else @@ -475,6 +300,7 @@ static void set_print_declaration(const struct set *set, struct output_ctx *octx) { const char *delim = ""; + struct stmt *stmt; const char *type; uint32_t flags; @@ -512,10 +338,13 @@ static void set_print_declaration(const struct set *set, } if (set->desc.size > 0) { - nft_print(octx, "%s%ssize %u%s", + nft_print(octx, "%s%ssize %u", opts->tab, opts->tab, - set->desc.size, - opts->stmt_separator); + set->desc.size); + if (set->count > 0) + nft_print(octx, "%s# count %u", opts->tab, + set->count); + nft_print(octx, "%s", opts->stmt_separator); } } @@ -544,6 +373,23 @@ static void set_print_declaration(const struct set *set, } nft_print(octx, "%s", opts->stmt_separator); } + + if (!list_empty(&set->stmt_list)) { + unsigned int flags = octx->flags; + + nft_print(octx, "%s%s", opts->tab, opts->tab); + + octx->flags |= NFT_CTX_OUTPUT_STATELESS; + list_for_each_entry(stmt, &set->stmt_list, list) { + stmt_print(stmt, octx); + if (!list_is_last(&stmt->list, &set->stmt_list)) + nft_print(octx, " "); + } + octx->flags = flags; + + nft_print(octx, "%s", opts->stmt_separator); + } + if (set->automerge) nft_print(octx, "%s%sauto-merge%s", opts->tab, opts->tab, opts->stmt_separator); @@ -558,6 +404,13 @@ static void set_print_declaration(const struct set *set, time_print(set->gc_int, octx); nft_print(octx, "%s", opts->stmt_separator); } + + if (set->comment) { + nft_print(octx, "%s%scomment \"%s\"%s", + opts->tab, opts->tab, + set->comment, + opts->stmt_separator); + } } static void do_set_print(const struct set *set, struct print_fmt_options *opts, @@ -571,9 +424,18 @@ static void do_set_print(const struct set *set, struct print_fmt_options *opts, return; } - if (set->init != NULL && set->init->size > 0) { + if (set->init != NULL && expr_set(set->init)->size > 0) { nft_print(octx, "%s%selements = ", opts->tab, opts->tab); + + if (set->timeout || set->elem_has_comment || + (set->flags & (NFT_SET_MAP | NFT_SET_OBJECT | + NFT_SET_TIMEOUT | NFT_SET_CONCAT)) || + !list_empty(&set->stmt_list)) + octx->force_newline = true; + expr_print(set->init, octx); + octx->force_newline = false; + nft_print(octx, "%s", opts->nl); } nft_print(octx, "%s}%s", opts->tab, opts->nl); @@ -607,6 +469,8 @@ struct rule *rule_alloc(const struct location *loc, const struct handle *h) { struct rule *rule; + assert(loc); + rule = xzalloc(sizeof(*rule)); rule->location = *loc; init_list_head(&rule->list); @@ -630,16 +494,18 @@ void rule_free(struct rule *rule) return; stmt_list_free(&rule->stmts); handle_free(&rule->handle); - xfree(rule->comment); - xfree(rule); + free_const(rule->comment); + free(rule); } void rule_print(const struct rule *rule, struct output_ctx *octx) { + const struct stmt_ops *ops; const struct stmt *stmt; list_for_each_entry(stmt, &rule->stmts, list) { - stmt->ops->print(stmt, octx); + ops = stmt_ops(stmt); + ops->print(stmt, octx); if (!list_is_last(&stmt->list, &rule->stmts)) nft_print(octx, " "); } @@ -673,6 +539,19 @@ struct rule *rule_lookup_by_index(const struct chain *chain, uint64_t index) return NULL; } +void rule_stmt_append(struct rule *rule, struct stmt *stmt) +{ + list_add_tail(&stmt->list, &rule->stmts); + rule->num_stmts++; +} + +void rule_stmt_insert_at(struct rule *rule, struct stmt *nstmt, + struct stmt *stmt) +{ + list_add_tail(&nstmt->list, &stmt->list); + rule->num_stmts++; +} + struct scope *scope_alloc(void) { struct scope *scope = xzalloc(sizeof(struct scope)); @@ -695,16 +574,16 @@ void scope_release(const struct scope *scope) list_for_each_entry_safe(sym, next, &scope->symbols, list) { assert(sym->refcnt == 1); list_del(&sym->list); - xfree(sym->identifier); + free_const(sym->identifier); expr_free(sym->expr); - xfree(sym); + free(sym); } } void scope_free(struct scope *scope) { scope_release(scope); - xfree(scope); + free(scope); } void symbol_bind(struct scope *scope, const char *identifier, struct expr *expr) @@ -735,9 +614,9 @@ struct symbol *symbol_get(const struct scope *scope, const char *identifier) static void symbol_put(struct symbol *sym) { if (--sym->refcnt == 0) { - xfree(sym->identifier); + free_const(sym->identifier); expr_free(sym->expr); - xfree(sym); + free(sym); } } @@ -817,6 +696,7 @@ static const char * const chain_hookname_str_array[] = { "postrouting", "output", "ingress", + "egress", NULL, }; @@ -832,16 +712,19 @@ const char *chain_hookname_lookup(const char *name) return NULL; } -struct chain *chain_alloc(const char *name) +/* internal ID to uniquely identify a set in the batch */ +static uint32_t chain_id; + +struct chain *chain_alloc(void) { struct chain *chain; chain = xzalloc(sizeof(*chain)); + chain->location = internal_location; chain->refcnt = 1; + chain->handle.chain_id = ++chain_id; init_list_head(&chain->rules); init_list_head(&chain->scope.symbols); - if (name != NULL) - chain->handle.chain.name = xstrdup(name); chain->policy = NULL; return chain; @@ -863,28 +746,29 @@ void chain_free(struct chain *chain) list_for_each_entry_safe(rule, next, &chain->rules, list) rule_free(rule); handle_free(&chain->handle); - scope_release(&chain->scope); - xfree(chain->type); + free_const(chain->type.str); expr_free(chain->dev_expr); for (i = 0; i < chain->dev_array_len; i++) - xfree(chain->dev_array[i]); - xfree(chain->dev_array); + free_const(chain->dev_array[i]); + free(chain->dev_array); expr_free(chain->priority.expr); expr_free(chain->policy); - xfree(chain); -} + free_const(chain->comment); -void chain_add_hash(struct chain *chain, struct table *table) -{ - list_add_tail(&chain->list, &table->chains); + /* MUST be released after all expressions, they could + * hold refcounts. + */ + scope_release(&chain->scope); + free(chain); } -struct chain *chain_lookup(const struct table *table, const struct handle *h) +struct chain *chain_binding_lookup(const struct table *table, + const char *chain_name) { struct chain *chain; - list_for_each_entry(chain, &table->chains, list) { - if (!strcmp(chain->handle.chain.name, h->chain.name)) + list_for_each_entry(chain, &table->chain_bindings, cache.list) { + if (!strcmp(chain->handle.chain.name, chain_name)) return chain; } return NULL; @@ -898,14 +782,13 @@ struct chain *chain_lookup_fuzzy(const struct handle *h, struct table *table; struct chain *chain; + if (!h->chain.name) + return NULL; + string_misspell_init(&st); - list_for_each_entry(table, &cache->list, list) { - list_for_each_entry(chain, &table->chains, list) { - if (!strcmp(chain->handle.chain.name, h->chain.name)) { - *t = table; - return chain; - } + list_for_each_entry(table, &cache->table_cache.list, cache.list) { + list_for_each_entry(chain, &table->chain_cache.list, cache.list) { if (string_misspell_update(chain->handle.chain.name, h->chain.name, chain, &st)) *t = table; @@ -953,6 +836,8 @@ const char *hooknum2str(unsigned int family, unsigned int hooknum) return "postrouting"; case NF_INET_LOCAL_OUT: return "output"; + case NF_INET_INGRESS: + return "ingress"; default: break; }; @@ -965,6 +850,8 @@ const char *hooknum2str(unsigned int family, unsigned int hooknum) return "forward"; case NF_ARP_OUT: return "output"; + case __NF_ARP_INGRESS: + return "ingress"; default: break; } @@ -973,6 +860,8 @@ const char *hooknum2str(unsigned int family, unsigned int hooknum) switch (hooknum) { case NF_NETDEV_INGRESS: return "ingress"; + case NF_NETDEV_EGRESS: + return "egress"; } break; default: @@ -998,7 +887,7 @@ struct prio_tag { const char *str; }; -const static struct prio_tag std_prios[] = { +static const struct prio_tag std_prios[] = { { NF_IP_PRI_RAW, "raw" }, { NF_IP_PRI_MANGLE, "mangle" }, { NF_IP_PRI_NAT_DST, "dstnat" }, @@ -1007,7 +896,7 @@ const static struct prio_tag std_prios[] = { { NF_IP_PRI_NAT_SRC, "srcnat" }, }; -const static struct prio_tag bridge_std_prios[] = { +static const struct prio_tag bridge_std_prios[] = { { NF_BR_PRI_NAT_DST_BRIDGED, "dstnat" }, { NF_BR_PRI_FILTER_BRIDGED, "filter" }, { NF_BR_PRI_NAT_DST_OTHER, "out" }, @@ -1061,7 +950,8 @@ static bool std_prio_family_hook_compat(int prio, int family, int hook) case NFPROTO_INET: case NFPROTO_IPV4: case NFPROTO_IPV6: - if (hook == NF_INET_PRE_ROUTING) + if (hook == NF_INET_PRE_ROUTING || + hook == NF_INET_LOCAL_OUT) return true; } break; @@ -1070,7 +960,8 @@ static bool std_prio_family_hook_compat(int prio, int family, int hook) case NFPROTO_INET: case NFPROTO_IPV4: case NFPROTO_IPV6: - if (hook == NF_INET_POST_ROUTING) + if (hook == NF_INET_LOCAL_IN || + hook == NF_INET_POST_ROUTING) return true; } } @@ -1103,10 +994,11 @@ static const char *prio2str(const struct output_ctx *octx, const struct expr *expr) { const struct prio_tag *prio_arr; - int std_prio, offset, prio; + const uint32_t reach = 10; const char *std_prio_str; - const int reach = 10; + int std_prio, prio; size_t i, arr_size; + int64_t offset; mpz_export_data(&prio, expr->value, BYTEORDER_HOST_ENDIAN, sizeof(int)); if (family == NFPROTO_BRIDGE) { @@ -1121,19 +1013,21 @@ static const char *prio2str(const struct output_ctx *octx, for (i = 0; i < arr_size; ++i) { std_prio = prio_arr[i].val; std_prio_str = prio_arr[i].str; - if (abs(prio - std_prio) <= reach) { + + offset = (int64_t)prio - std_prio; + if (llabs(offset) <= reach) { if (!std_prio_family_hook_compat(std_prio, family, hook)) break; - offset = prio - std_prio; + strncpy(buf, std_prio_str, bufsize); if (offset > 0) snprintf(buf + strlen(buf), - bufsize - strlen(buf), " + %d", + bufsize - strlen(buf), " + %" PRIu64, offset); else if (offset < 0) snprintf(buf + strlen(buf), - bufsize - strlen(buf), " - %d", + bufsize - strlen(buf), " - %" PRIu64, -offset); return buf; } @@ -1149,49 +1043,72 @@ static void chain_print_declaration(const struct chain *chain, char priobuf[STD_PRIO_BUFSIZE]; int policy, i; + if (chain->flags & CHAIN_F_BINDING) + return; + nft_print(octx, "\tchain %s {", chain->handle.chain.name); if (nft_output_handle(octx)) nft_print(octx, " # handle %" PRIu64, chain->handle.handle.id); + if (chain->comment) + nft_print(octx, "\n\t\tcomment \"%s\"", chain->comment); nft_print(octx, "\n"); if (chain->flags & CHAIN_F_BASECHAIN) { - nft_print(octx, "\t\ttype %s hook %s", chain->type, - hooknum2str(chain->handle.family, chain->hooknum)); + if (chain->type.str) + nft_print(octx, "\t\ttype %s hook %s", chain->type.str, + hooknum2str(chain->handle.family, chain->hook.num)); + if (chain->dev_array_len == 1) { nft_print(octx, " device \"%s\"", chain->dev_array[0]); } else if (chain->dev_array_len > 1) { nft_print(octx, " devices = { "); for (i = 0; i < chain->dev_array_len; i++) { - nft_print(octx, "%s", chain->dev_array[i]); + nft_print(octx, "\"%s\"", chain->dev_array[i]); if (i + 1 != chain->dev_array_len) nft_print(octx, ", "); } nft_print(octx, " }"); } - nft_print(octx, " priority %s;", - prio2str(octx, priobuf, sizeof(priobuf), - chain->handle.family, chain->hooknum, - chain->priority.expr)); + + if (chain->priority.expr) + nft_print(octx, " priority %s;", + prio2str(octx, priobuf, sizeof(priobuf), + chain->handle.family, chain->hook.num, + chain->priority.expr)); if (chain->policy) { mpz_export_data(&policy, chain->policy->value, BYTEORDER_HOST_ENDIAN, sizeof(int)); nft_print(octx, " policy %s;", chain_policy2str(policy)); } + if (chain->flags & CHAIN_F_HW_OFFLOAD) + nft_print(octx, " flags offload;"); + nft_print(octx, "\n"); } } -static void chain_print(const struct chain *chain, struct output_ctx *octx) +void chain_rules_print(const struct chain *chain, struct output_ctx *octx, + const char *indent) { + unsigned int flags = octx->flags; struct rule *rule; - chain_print_declaration(chain, octx); + if (chain->flags & CHAIN_F_BINDING) + octx->flags &= ~NFT_CTX_OUTPUT_HANDLE; list_for_each_entry(rule, &chain->rules, list) { - nft_print(octx, "\t\t"); + nft_print(octx, "\t\t%s", indent ? : ""); rule_print(rule, octx); nft_print(octx, "\n"); } + + octx->flags = flags; +} + +static void chain_print(const struct chain *chain, struct output_ctx *octx) +{ + chain_print_declaration(chain, octx); + chain_rules_print(chain, octx, NULL); nft_print(octx, "\t}\n"); } @@ -1206,10 +1123,23 @@ void chain_print_plain(const struct chain *chain, struct output_ctx *octx) if (chain->flags & CHAIN_F_BASECHAIN) { mpz_export_data(&policy, chain->policy->value, BYTEORDER_HOST_ENDIAN, sizeof(int)); - nft_print(octx, " { type %s hook %s priority %s; policy %s; }", - chain->type, chain->hookstr, + nft_print(octx, " { type %s hook %s ", + chain->type.str, chain->hook.name); + + if (chain->dev_array_len > 0) { + int i; + + nft_print(octx, "devices = { "); + for (i = 0; i < chain->dev_array_len; i++) { + nft_print(octx, "%s", chain->dev_array[i]); + if (i + 1 != chain->dev_array_len) + nft_print(octx, ", "); + } + nft_print(octx, " } "); + } + nft_print(octx, "priority %s; policy %s; }", prio2str(octx, priobuf, sizeof(priobuf), - chain->handle.family, chain->hooknum, + chain->handle.family, chain->hook.num, chain->priority.expr), chain_policy2str(policy)); } @@ -1222,13 +1152,20 @@ struct table *table_alloc(void) struct table *table; table = xzalloc(sizeof(*table)); + table->location = internal_location; init_list_head(&table->chains); init_list_head(&table->sets); init_list_head(&table->objs); init_list_head(&table->flowtables); + init_list_head(&table->chain_bindings); init_list_head(&table->scope.symbols); table->refcnt = 1; + cache_init(&table->chain_cache); + cache_init(&table->set_cache); + cache_init(&table->obj_cache); + cache_init(&table->ft_cache); + return table; } @@ -1241,17 +1178,38 @@ void table_free(struct table *table) if (--table->refcnt > 0) return; + if (table->comment) + free_const(table->comment); list_for_each_entry_safe(chain, next, &table->chains, list) chain_free(chain); + list_for_each_entry_safe(chain, next, &table->chain_bindings, cache.list) + chain_free(chain); + /* this is implicitly releasing chains in the hashtable cache */ + list_for_each_entry_safe(chain, next, &table->chain_cache.list, cache.list) + chain_free(chain); list_for_each_entry_safe(set, nset, &table->sets, list) set_free(set); + /* this is implicitly releasing sets in the hashtable cache */ + list_for_each_entry_safe(set, nset, &table->set_cache.list, cache.list) + set_free(set); list_for_each_entry_safe(ft, nft, &table->flowtables, list) flowtable_free(ft); + /* this is implicitly releasing flowtables in the hashtable cache */ + list_for_each_entry_safe(ft, nft, &table->ft_cache.list, cache.list) + flowtable_free(ft); list_for_each_entry_safe(obj, nobj, &table->objs, list) obj_free(obj); + /* this is implicitly releasing objs in the hashtable cache */ + list_for_each_entry_safe(obj, nobj, &table->obj_cache.list, cache.list) + obj_free(obj); + handle_free(&table->handle); scope_release(&table->scope); - xfree(table); + cache_free(&table->chain_cache); + cache_free(&table->set_cache); + cache_free(&table->obj_cache); + cache_free(&table->ft_cache); + free(table); } struct table *table_get(struct table *table) @@ -1260,65 +1218,71 @@ struct table *table_get(struct table *table) return table; } -void table_add_hash(struct table *table, struct nft_cache *cache) -{ - list_add_tail(&table->list, &cache->list); -} - -struct table *table_lookup(const struct handle *h, - const struct nft_cache *cache) -{ - struct table *table; - - list_for_each_entry(table, &cache->list, list) { - if (table->handle.family == h->family && - !strcmp(table->handle.table.name, h->table.name)) - return table; - } - return NULL; -} - struct table *table_lookup_fuzzy(const struct handle *h, const struct nft_cache *cache) { struct string_misspell_state st; struct table *table; - string_misspell_init(&st); + if (!h->table.name) + return NULL; - list_for_each_entry(table, &cache->list, list) { - if (!strcmp(table->handle.table.name, h->table.name)) - return table; + string_misspell_init(&st); + list_for_each_entry(table, &cache->table_cache.list, cache.list) { string_misspell_update(table->handle.table.name, h->table.name, table, &st); } return st.obj; } -const char *table_flags_name[TABLE_FLAGS_MAX] = { +static const char *table_flags_name[TABLE_FLAGS_MAX] = { "dormant", + "owner", + "persist", }; -static void table_print_options(const struct table *table, const char **delim, - struct output_ctx *octx) +const char *table_flag_name(uint32_t flag) +{ + if (flag >= TABLE_FLAGS_MAX) + return "unknown"; + + return table_flags_name[flag]; +} + +unsigned int parse_table_flag(const char *name) +{ + int i; + + for (i = 0; i < TABLE_FLAGS_MAX; i++) { + if (!strcmp(name, table_flags_name[i])) + return 1 << i; + } + return 0; +} + +static void table_print_flags(const struct table *table, const char **delim, + struct output_ctx *octx) { uint32_t flags = table->flags; + bool comma = false; int i; - if (flags) { - nft_print(octx, "\tflags "); + if (!table->flags) + return; - for (i = 0; i < TABLE_FLAGS_MAX; i++) { - if (flags & 0x1) - nft_print(octx, "%s", table_flags_name[i]); - flags >>= 1; - if (flags) + nft_print(octx, "\tflags "); + for (i = 0; i < TABLE_FLAGS_MAX; i++) { + if (flags & (1 << i)) { + if (comma) nft_print(octx, ","); + + nft_print(octx, "%s", table_flag_name(i)); + comma = true; } - nft_print(octx, "\n"); - *delim = "\n"; } + nft_print(octx, "\n"); + *delim = "\n"; } static void table_print(const struct table *table, struct output_ctx *octx) @@ -1330,30 +1294,43 @@ static void table_print(const struct table *table, struct output_ctx *octx) const char *delim = ""; const char *family = family2str(table->handle.family); + if (table->has_xt_stmts) + fprintf(octx->error_fp, + "# Warning: table %s %s is managed by iptables-nft, do not touch!\n", + family, table->handle.table.name); + nft_print(octx, "table %s %s {", family, table->handle.table.name); + if (nft_output_handle(octx) || table->flags & TABLE_F_OWNER) + nft_print(octx, " #"); if (nft_output_handle(octx)) - nft_print(octx, " # handle %" PRIu64, table->handle.handle.id); + nft_print(octx, " handle %" PRIu64, table->handle.handle.id); + if (table->flags & TABLE_F_OWNER) + nft_print(octx, " progname %s", get_progname(table->owner)); + nft_print(octx, "\n"); - table_print_options(table, &delim, octx); + table_print_flags(table, &delim, octx); + + if (table->comment) + nft_print(octx, "\tcomment \"%s\"\n", table->comment); - list_for_each_entry(obj, &table->objs, list) { + list_for_each_entry(obj, &table->obj_cache.list, cache.list) { nft_print(octx, "%s", delim); obj_print(obj, octx); delim = "\n"; } - list_for_each_entry(set, &table->sets, list) { + list_for_each_entry(set, &table->set_cache.list, cache.list) { if (set_is_anonymous(set->flags)) continue; nft_print(octx, "%s", delim); set_print(set, octx); delim = "\n"; } - list_for_each_entry(flowtable, &table->flowtables, list) { + list_for_each_entry(flowtable, &table->ft_cache.list, cache.list) { nft_print(octx, "%s", delim); flowtable_print(flowtable, octx); delim = "\n"; } - list_for_each_entry(chain, &table->chains, list) { + list_for_each_entry(chain, &table->chain_cache.list, cache.list) { nft_print(octx, "%s", delim); chain_print(chain, octx); delim = "\n"; @@ -1367,6 +1344,8 @@ struct cmd *cmd_alloc(enum cmd_ops op, enum cmd_obj obj, { struct cmd *cmd; + assert(loc); + cmd = xzalloc(sizeof(*cmd)); init_list_head(&cmd->list); cmd->op = op; @@ -1374,75 +1353,11 @@ struct cmd *cmd_alloc(enum cmd_ops op, enum cmd_obj obj, cmd->handle = *h; cmd->location = *loc; cmd->data = data; - return cmd; -} + cmd->attr = xzalloc_array(NFT_NLATTR_LOC_MAX, + sizeof(struct nlerr_loc)); + cmd->attr_array_len = NFT_NLATTR_LOC_MAX; -void nft_cmd_expand(struct cmd *cmd) -{ - struct list_head new_cmds; - struct flowtable *ft; - struct table *table; - struct chain *chain; - struct rule *rule; - struct set *set; - struct obj *obj; - struct cmd *new; - struct handle h; - - init_list_head(&new_cmds); - - switch (cmd->obj) { - case CMD_OBJ_TABLE: - table = cmd->table; - if (!table) - return; - - list_for_each_entry(chain, &table->chains, list) { - memset(&h, 0, sizeof(h)); - handle_merge(&h, &chain->handle); - new = cmd_alloc(CMD_ADD, CMD_OBJ_CHAIN, &h, - &chain->location, chain_get(chain)); - list_add_tail(&new->list, &new_cmds); - } - list_for_each_entry(obj, &table->objs, list) { - handle_merge(&obj->handle, &table->handle); - memset(&h, 0, sizeof(h)); - handle_merge(&h, &obj->handle); - new = cmd_alloc(CMD_ADD, obj_type_to_cmd(obj->type), &h, - &obj->location, obj_get(obj)); - list_add_tail(&new->list, &new_cmds); - } - list_for_each_entry(set, &table->sets, list) { - handle_merge(&set->handle, &table->handle); - memset(&h, 0, sizeof(h)); - handle_merge(&h, &set->handle); - new = cmd_alloc(CMD_ADD, CMD_OBJ_SET, &h, - &set->location, set_get(set)); - list_add_tail(&new->list, &new_cmds); - } - list_for_each_entry(ft, &table->flowtables, list) { - handle_merge(&ft->handle, &table->handle); - memset(&h, 0, sizeof(h)); - handle_merge(&h, &ft->handle); - new = cmd_alloc(CMD_ADD, CMD_OBJ_FLOWTABLE, &h, - &ft->location, flowtable_get(ft)); - list_add_tail(&new->list, &new_cmds); - } - list_for_each_entry(chain, &table->chains, list) { - list_for_each_entry(rule, &chain->rules, list) { - memset(&h, 0, sizeof(h)); - handle_merge(&h, &rule->handle); - new = cmd_alloc(CMD_ADD, CMD_OBJ_RULE, &h, - &rule->location, - rule_get(rule)); - list_add_tail(&new->list, &new_cmds); - } - } - list_splice(&new_cmds, &cmd->list); - break; - default: - break; - } + return cmd; } struct markup *markup_alloc(uint32_t format) @@ -1457,7 +1372,7 @@ struct markup *markup_alloc(uint32_t format) void markup_free(struct markup *m) { - xfree(m); + free(m); } struct monitor *monitor_alloc(uint32_t format, uint32_t type, const char *event) @@ -1475,19 +1390,27 @@ struct monitor *monitor_alloc(uint32_t format, uint32_t type, const char *event) void monitor_free(struct monitor *m) { - xfree(m->event); - xfree(m); + free_const(m->event); + free(m); } void cmd_free(struct cmd *cmd) { + if (cmd == NULL) + return; + handle_free(&cmd->handle); if (cmd->data != NULL) { switch (cmd->obj) { - case CMD_OBJ_SETELEM: + case CMD_OBJ_ELEMENTS: expr_free(cmd->expr); + if (cmd->elem.set) + set_free(cmd->elem.set); break; case CMD_OBJ_SET: + case CMD_OBJ_MAP: + case CMD_OBJ_METER: + case CMD_OBJ_SETELEMS: set_free(cmd->set); break; case CMD_OBJ_RULE: @@ -1525,68 +1448,66 @@ void cmd_free(struct cmd *cmd) BUG("invalid command object type %u\n", cmd->obj); } } - xfree(cmd->arg); - xfree(cmd); + free(cmd->attr); + free_const(cmd->arg); + free(cmd); } #include <netlink.h> #include <mnl.h> -static int __do_add_setelems(struct netlink_ctx *ctx, struct set *set, - struct expr *expr, uint32_t flags) +static int __do_add_elements(struct netlink_ctx *ctx, struct cmd *cmd, + struct set *set, struct expr *expr, uint32_t flags) { - expr->set_flags |= set->flags; - if (mnl_nft_setelem_add(ctx, set, expr, flags) < 0) + expr_set(expr)->set_flags |= set->flags; + if (mnl_nft_setelem_add(ctx, cmd, set, expr, flags) < 0) return -1; - if (set->init != NULL && - set->flags & NFT_SET_INTERVAL) { - interval_map_decompose(expr); - list_splice_tail_init(&expr->expressions, &set->init->expressions); - set->init->size += expr->size; - expr->size = 0; - } - return 0; } -static int do_add_setelems(struct netlink_ctx *ctx, struct cmd *cmd, +static int do_add_elements(struct netlink_ctx *ctx, struct cmd *cmd, uint32_t flags) { - struct handle *h = &cmd->handle; struct expr *init = cmd->expr; - struct table *table; - struct set *set; - - table = table_lookup(h, &ctx->nft->cache); - set = set_lookup(table, h->set.name); + struct set *set = cmd->elem.set; - if (set->flags & NFT_SET_INTERVAL && - set_to_intervals(ctx->msgs, set, init, true, - ctx->nft->debug_mask, set->automerge, - &ctx->nft->output) < 0) + if (set_is_non_concat_range(set) && + set_to_intervals(set, init, true) < 0) return -1; - return __do_add_setelems(ctx, set, init, flags); + return __do_add_elements(ctx, cmd, set, init, flags); } -static int do_add_set(struct netlink_ctx *ctx, const struct cmd *cmd, +static int do_add_setelems(struct netlink_ctx *ctx, struct cmd *cmd, + uint32_t flags) +{ + struct set *set = cmd->set; + + return __do_add_elements(ctx, cmd, set, set->init, flags); +} + +static int do_add_set(struct netlink_ctx *ctx, struct cmd *cmd, uint32_t flags) { struct set *set = cmd->set; if (set->init != NULL) { - if (set->flags & NFT_SET_INTERVAL && - set_to_intervals(ctx->msgs, set, set->init, true, - ctx->nft->debug_mask, set->automerge, - &ctx->nft->output) < 0) + /* Update set->init->size (NFTNL_SET_DESC_SIZE) before adding + * the set to the kernel. Calling this from do_add_setelems() + * comes too late which might result in spurious ENFILE errors. + */ + if (set_is_non_concat_range(set) && + set_to_intervals(set, set->init, true) < 0) return -1; } + if (mnl_nft_set_add(ctx, cmd, flags) < 0) return -1; - if (set->init != NULL) { - return __do_add_setelems(ctx, set, set->init, flags); - } + + if (set_is_anonymous(set->flags)) + return __do_add_elements(ctx, cmd, set, set->init, flags); + return 0; } @@ -1606,8 +1527,10 @@ static int do_command_add(struct netlink_ctx *ctx, struct cmd *cmd, bool excl) return mnl_nft_rule_add(ctx, cmd, flags | NLM_F_APPEND); case CMD_OBJ_SET: return do_add_set(ctx, cmd, flags); - case CMD_OBJ_SETELEM: + case CMD_OBJ_SETELEMS: return do_add_setelems(ctx, cmd, flags); + case CMD_OBJ_ELEMENTS: + return do_add_elements(ctx, cmd, flags); case CMD_OBJ_COUNTER: case CMD_OBJ_QUOTA: case CMD_OBJ_CT_HELPER: @@ -1654,21 +1577,14 @@ static int do_command_insert(struct netlink_ctx *ctx, struct cmd *cmd) static int do_delete_setelems(struct netlink_ctx *ctx, struct cmd *cmd) { - struct handle *h = &cmd->handle; - struct expr *expr = cmd->expr; - struct table *table; - struct set *set; - - table = table_lookup(h, &ctx->nft->cache); - set = set_lookup(table, h->set.name); + const struct set *set = cmd->elem.set; + struct expr *expr = cmd->elem.expr; - if (set->flags & NFT_SET_INTERVAL && - set_to_intervals(ctx->msgs, set, expr, false, - ctx->nft->debug_mask, set->automerge, - &ctx->nft->output) < 0) + if (set_is_non_concat_range(set) && + set_to_intervals(set, expr, false) < 0) return -1; - if (mnl_nft_setelem_del(ctx, cmd) < 0) + if (mnl_nft_setelem_del(ctx, cmd, &cmd->handle, set, cmd->elem.expr) < 0) return -1; return 0; @@ -1685,7 +1601,7 @@ static int do_command_delete(struct netlink_ctx *ctx, struct cmd *cmd) return mnl_nft_rule_del(ctx, cmd); case CMD_OBJ_SET: return mnl_nft_set_del(ctx, cmd); - case CMD_OBJ_SETELEM: + case CMD_OBJ_ELEMENTS: return do_delete_setelems(ctx, cmd); case CMD_OBJ_COUNTER: return mnl_nft_obj_del(ctx, cmd, NFT_OBJECT_COUNTER); @@ -1710,8 +1626,7 @@ static int do_command_delete(struct netlink_ctx *ctx, struct cmd *cmd) } } -static int do_list_table(struct netlink_ctx *ctx, struct cmd *cmd, - struct table *table) +static int do_list_table(struct netlink_ctx *ctx, struct table *table) { table_print(table, &ctx->nft->output); return 0; @@ -1719,15 +1634,10 @@ static int do_list_table(struct netlink_ctx *ctx, struct cmd *cmd, static int do_list_sets(struct netlink_ctx *ctx, struct cmd *cmd) { - struct print_fmt_options opts = { - .tab = "\t", - .nl = "\n", - .stmt_separator = "\n", - }; struct table *table; struct set *set; - list_for_each_entry(table, &ctx->nft->cache.list, list) { + list_for_each_entry(table, &ctx->nft->cache.table_cache.list, cache.list) { if (cmd->handle.family != NFPROTO_UNSPEC && cmd->handle.family != table->handle.family) continue; @@ -1736,18 +1646,17 @@ static int do_list_sets(struct netlink_ctx *ctx, struct cmd *cmd) family2str(table->handle.family), table->handle.table.name); - list_for_each_entry(set, &table->sets, list) { + list_for_each_entry(set, &table->set_cache.list, cache.list) { if (cmd->obj == CMD_OBJ_SETS && !set_is_literal(set->flags)) continue; if (cmd->obj == CMD_OBJ_METERS && - !set_is_meter(set->flags)) + !set_is_meter_compat(set->flags)) continue; if (cmd->obj == CMD_OBJ_MAPS && !map_is_literal(set->flags)) continue; - set_print_declaration(set, &opts, &ctx->nft->output); - nft_print(&ctx->nft->output, "%s}%s", opts.tab, opts.nl); + set_print(set, &ctx->nft->output); } nft_print(&ctx->nft->output, "}\n"); @@ -1759,9 +1668,10 @@ struct obj *obj_alloc(const struct location *loc) { struct obj *obj; + assert(loc); + obj = xzalloc(sizeof(*obj)); - if (loc != NULL) - obj->location = *loc; + obj->location = *loc; obj->refcnt = 1; return obj; @@ -1777,26 +1687,18 @@ void obj_free(struct obj *obj) { if (--obj->refcnt > 0) return; + free_const(obj->comment); handle_free(&obj->handle); - xfree(obj); -} - -void obj_add_hash(struct obj *obj, struct table *table) -{ - list_add_tail(&obj->list, &table->objs); -} + if (obj->type == NFT_OBJECT_CT_TIMEOUT) { + struct timeout_state *ts, *next; -struct obj *obj_lookup(const struct table *table, const char *name, - uint32_t type) -{ - struct obj *obj; - - list_for_each_entry(obj, &table->objs, list) { - if (!strcmp(obj->handle.obj.name, name) && - obj->type == type) - return obj; + list_for_each_entry_safe(ts, next, &obj->ct_timeout.timeout_list, head) { + list_del(&ts->head); + free_const(ts->timeout_str); + free(ts); + } } - return NULL; + free(obj); } struct obj *obj_lookup_fuzzy(const char *obj_name, @@ -1807,14 +1709,13 @@ struct obj *obj_lookup_fuzzy(const char *obj_name, struct table *table; struct obj *obj; + if (!obj_name) + return NULL; + string_misspell_init(&st); - list_for_each_entry(table, &cache->list, list) { - list_for_each_entry(obj, &table->objs, list) { - if (!strcmp(obj->handle.obj.name, obj_name)) { - *t = table; - return obj; - } + list_for_each_entry(table, &cache->table_cache.list, cache.list) { + list_for_each_entry(obj, &table->obj_cache.list, cache.list) { if (string_misspell_update(obj->handle.obj.name, obj_name, obj, &st)) *t = table; @@ -1825,10 +1726,10 @@ struct obj *obj_lookup_fuzzy(const char *obj_name, static void print_proto_name_proto(uint8_t l4, struct output_ctx *octx) { - const struct protoent *p = getprotobynumber(l4); + char name[NFT_PROTONAME_MAXSIZE]; - if (p) - nft_print(octx, "%s", p->p_name); + if (nft_getprotobynumber(l4, name, sizeof(name))) + nft_print(octx, "%s", name); else nft_print(octx, "%d", l4); } @@ -1843,11 +1744,14 @@ static void print_proto_timeout_policy(uint8_t l4, const uint32_t *timeout, nft_print(octx, "%s%spolicy = { ", opts->tab, opts->tab); for (i = 0; i < timeout_protocol[l4].array_size; i++) { if (timeout[i] != timeout_protocol[l4].dflt_timeout[i]) { + uint64_t timeout_ms; + if (comma) nft_print(octx, ", "); - nft_print(octx, "%s : %u", - timeout_protocol[l4].state_to_name[i], - timeout[i]); + timeout_ms = timeout[i] * 1000u; + nft_print(octx, "%s : ", + timeout_protocol[l4].state_to_name[i]); + time_print(timeout_ms, octx); comma = true; } } @@ -1870,6 +1774,16 @@ static const char *synproxy_timestamp_to_str(const uint32_t flags) return ""; } +static void obj_print_comment(const struct obj *obj, + struct print_fmt_options *opts, + struct output_ctx *octx) +{ + if (obj->comment) + nft_print(octx, "%s%s%scomment \"%s\"", + opts->nl, opts->tab, opts->tab, + obj->comment); +} + static void obj_print_data(const struct obj *obj, struct print_fmt_options *opts, struct output_ctx *octx) @@ -1879,13 +1793,14 @@ static void obj_print_data(const struct obj *obj, nft_print(octx, " %s {", obj->handle.obj.name); if (nft_output_handle(octx)) nft_print(octx, " # handle %" PRIu64, obj->handle.handle.id); - nft_print(octx, "%s%s%s", opts->nl, opts->tab, opts->tab); - if (nft_output_stateless(octx)) { - nft_print(octx, "packets 0 bytes 0"); - break; - } - nft_print(octx, "packets %" PRIu64 " bytes %" PRIu64 "%s", - obj->counter.packets, obj->counter.bytes, opts->nl); + + obj_print_comment(obj, opts, octx); + if (nft_output_stateless(octx)) + nft_print(octx, "%s", opts->nl); + else + nft_print(octx, "%s%s%spackets %" PRIu64 " bytes %" PRIu64 "%s", + opts->nl, opts->tab, opts->tab, + obj->counter.packets, obj->counter.bytes, opts->nl); break; case NFT_OBJECT_QUOTA: { const char *data_unit; @@ -1894,6 +1809,8 @@ static void obj_print_data(const struct obj *obj, nft_print(octx, " %s {", obj->handle.obj.name); if (nft_output_handle(octx)) nft_print(octx, " # handle %" PRIu64, obj->handle.handle.id); + + obj_print_comment(obj, opts, octx); nft_print(octx, "%s%s%s", opts->nl, opts->tab, opts->tab); data_unit = get_rate(obj->quota.bytes, &bytes); nft_print(octx, "%s%" PRIu64 " %s", @@ -1911,6 +1828,8 @@ static void obj_print_data(const struct obj *obj, nft_print(octx, " %s {", obj->handle.obj.name); if (nft_output_handle(octx)) nft_print(octx, " # handle %" PRIu64, obj->handle.handle.id); + + obj_print_comment(obj, opts, octx); nft_print(octx, "%s%s%s", opts->nl, opts->tab, opts->tab); nft_print(octx, "\"%s\"%s", obj->secmark.ctx, opts->nl); break; @@ -1918,6 +1837,8 @@ static void obj_print_data(const struct obj *obj, nft_print(octx, " %s {", obj->handle.obj.name); if (nft_output_handle(octx)) nft_print(octx, " # handle %" PRIu64, obj->handle.handle.id); + + obj_print_comment(obj, opts, octx); nft_print(octx, "%s", opts->nl); nft_print(octx, "%s%stype \"%s\" protocol ", opts->tab, opts->tab, obj->ct_helper.name); @@ -1932,6 +1853,8 @@ static void obj_print_data(const struct obj *obj, nft_print(octx, " %s {", obj->handle.obj.name); if (nft_output_handle(octx)) nft_print(octx, " # handle %" PRIu64, obj->handle.handle.id); + + obj_print_comment(obj, opts, octx); nft_print(octx, "%s", opts->nl); nft_print(octx, "%s%sprotocol ", opts->tab, opts->tab); print_proto_name_proto(obj->ct_timeout.l4proto, octx); @@ -1947,6 +1870,8 @@ static void obj_print_data(const struct obj *obj, nft_print(octx, " %s {", obj->handle.obj.name); if (nft_output_handle(octx)) nft_print(octx, " # handle %" PRIu64, obj->handle.handle.id); + + obj_print_comment(obj, opts, octx); nft_print(octx, "%s", opts->nl); nft_print(octx, "%s%sprotocol ", opts->tab, opts->tab); print_proto_name_proto(obj->ct_expect.l4proto, octx); @@ -1975,6 +1900,8 @@ static void obj_print_data(const struct obj *obj, nft_print(octx, " %s {", obj->handle.obj.name); if (nft_output_handle(octx)) nft_print(octx, " # handle %" PRIu64, obj->handle.handle.id); + + obj_print_comment(obj, opts, octx); nft_print(octx, "%s%s%s", opts->nl, opts->tab, opts->tab); switch (obj->limit.type) { case NFT_LIMIT_PKTS: @@ -2012,6 +1939,8 @@ static void obj_print_data(const struct obj *obj, if (nft_output_handle(octx)) nft_print(octx, " # handle %" PRIu64, obj->handle.handle.id); + obj_print_comment(obj, opts, octx); + if (flags & NF_SYNPROXY_OPT_MSS) { nft_print(octx, "%s%s%s", opts->nl, opts->tab, opts->tab); nft_print(octx, "mss %u", obj->synproxy.mss); @@ -2044,7 +1973,7 @@ static const char * const obj_type_name_array[] = { [NFT_OBJECT_CT_EXPECT] = "ct expectation", }; -const char *obj_type_name(enum stmt_types type) +const char *obj_type_name(unsigned int type) { assert(type <= NFT_OBJECT_MAX && obj_type_name_array[type]); @@ -2062,7 +1991,7 @@ static uint32_t obj_type_cmd_array[NFT_OBJECT_MAX + 1] = { [NFT_OBJECT_CT_EXPECT] = CMD_OBJ_CT_EXPECT, }; -uint32_t obj_type_to_cmd(uint32_t type) +enum cmd_obj obj_type_to_cmd(uint32_t type) { assert(type <= NFT_OBJECT_MAX && obj_type_cmd_array[type]); @@ -2120,7 +2049,7 @@ static int do_list_obj(struct netlink_ctx *ctx, struct cmd *cmd, uint32_t type) struct table *table; struct obj *obj; - list_for_each_entry(table, &ctx->nft->cache.list, list) { + list_for_each_entry(table, &ctx->nft->cache.table_cache.list, cache.list) { if (cmd->handle.family != NFPROTO_UNSPEC && cmd->handle.family != table->handle.family) continue; @@ -2129,14 +2058,14 @@ static int do_list_obj(struct netlink_ctx *ctx, struct cmd *cmd, uint32_t type) strcmp(cmd->handle.table.name, table->handle.table.name)) continue; - if (list_empty(&table->objs)) + if (list_empty(&table->obj_cache.list)) continue; nft_print(&ctx->nft->output, "table %s %s {\n", family2str(table->handle.family), table->handle.table.name); - list_for_each_entry(obj, &table->objs, list) { + list_for_each_entry(obj, &table->obj_cache.list, cache.list) { if (obj->type != type || (cmd->handle.obj.name != NULL && strcmp(cmd->handle.obj.name, obj->handle.obj.name))) @@ -2154,9 +2083,10 @@ struct flowtable *flowtable_alloc(const struct location *loc) { struct flowtable *flowtable; + assert(loc); + flowtable = xzalloc(sizeof(*flowtable)); - if (loc != NULL) - flowtable->location = *loc; + flowtable->location = *loc; flowtable->refcnt = 1; return flowtable; @@ -2180,15 +2110,10 @@ void flowtable_free(struct flowtable *flowtable) if (flowtable->dev_array != NULL) { for (i = 0; i < flowtable->dev_array_len; i++) - xfree(flowtable->dev_array[i]); - xfree(flowtable->dev_array); + free_const(flowtable->dev_array[i]); + free(flowtable->dev_array); } - xfree(flowtable); -} - -void flowtable_add_hash(struct flowtable *flowtable, struct table *table) -{ - list_add_tail(&flowtable->list, &table->flowtables); + free(flowtable); } static void flowtable_print_declaration(const struct flowtable *flowtable, @@ -2211,20 +2136,33 @@ static void flowtable_print_declaration(const struct flowtable *flowtable, if (nft_output_handle(octx)) nft_print(octx, " # handle %" PRIu64, flowtable->handle.handle.id); nft_print(octx, "%s", opts->nl); - nft_print(octx, "%s%shook %s priority %s%s", - opts->tab, opts->tab, - hooknum2str(NFPROTO_NETDEV, flowtable->hooknum), - prio2str(octx, priobuf, sizeof(priobuf), NFPROTO_NETDEV, - flowtable->hooknum, flowtable->priority.expr), - opts->stmt_separator); - - nft_print(octx, "%s%sdevices = { ", opts->tab, opts->tab); - for (i = 0; i < flowtable->dev_array_len; i++) { - nft_print(octx, "%s", flowtable->dev_array[i]); - if (i + 1 != flowtable->dev_array_len) - nft_print(octx, ", "); + + if (flowtable->priority.expr) { + nft_print(octx, "%s%shook %s priority %s%s", + opts->tab, opts->tab, + hooknum2str(NFPROTO_NETDEV, flowtable->hook.num), + prio2str(octx, priobuf, sizeof(priobuf), NFPROTO_NETDEV, + flowtable->hook.num, flowtable->priority.expr), + opts->stmt_separator); } - nft_print(octx, " }%s", opts->stmt_separator); + + if (flowtable->dev_array_len > 0) { + nft_print(octx, "%s%sdevices = { ", opts->tab, opts->tab); + for (i = 0; i < flowtable->dev_array_len; i++) { + nft_print(octx, "\"%s\"", flowtable->dev_array[i]); + if (i + 1 != flowtable->dev_array_len) + nft_print(octx, ", "); + } + nft_print(octx, " }%s", opts->stmt_separator); + } + + if (flowtable->flags & NFT_FLOWTABLE_HW_OFFLOAD) + nft_print(octx, "%s%sflags offload%s", opts->tab, opts->tab, + opts->stmt_separator); + + if (flowtable->flags & NFT_FLOWTABLE_COUNTER) + nft_print(octx, "%s%scounter%s", opts->tab, opts->tab, + opts->stmt_separator); } static void do_flowtable_print(const struct flowtable *flowtable, @@ -2246,17 +2184,21 @@ void flowtable_print(const struct flowtable *s, struct output_ctx *octx) do_flowtable_print(s, &opts, octx); } -struct flowtable *flowtable_lookup(const struct table *table, const char *name) +void flowtable_print_plain(const struct flowtable *ft, struct output_ctx *octx) { - struct flowtable *ft; + struct print_fmt_options opts = { + .tab = "", + .nl = " ", + .table = ft->handle.table.name, + .family = family2str(ft->handle.family), + .stmt_separator = "; ", + }; - list_for_each_entry(ft, &table->flowtables, list) { - if (!strcmp(ft->handle.flowtable.name, name)) - return ft; - } - return NULL; + flowtable_print_declaration(ft, &opts, octx); + nft_print(octx, "}"); } + struct flowtable *flowtable_lookup_fuzzy(const char *ft_name, const struct nft_cache *cache, const struct table **t) @@ -2265,14 +2207,13 @@ struct flowtable *flowtable_lookup_fuzzy(const char *ft_name, struct table *table; struct flowtable *ft; + if (!ft_name) + return NULL; + string_misspell_init(&st); - list_for_each_entry(table, &cache->list, list) { - list_for_each_entry(ft, &table->flowtables, list) { - if (!strcmp(ft->handle.flowtable.name, ft_name)) { - *t = table; - return ft; - } + list_for_each_entry(table, &cache->table_cache.list, cache.list) { + list_for_each_entry(ft, &table->ft_cache.list, cache.list) { if (string_misspell_update(ft->handle.flowtable.name, ft_name, ft, &st)) *t = table; @@ -2286,8 +2227,8 @@ static int do_list_flowtable(struct netlink_ctx *ctx, struct cmd *cmd, { struct flowtable *ft; - ft = flowtable_lookup(table, cmd->handle.flowtable.name); - if (ft == NULL) + ft = ft_cache_find(table, cmd->handle.flowtable.name); + if (!ft) return -1; nft_print(&ctx->nft->output, "table %s %s {\n", @@ -2310,7 +2251,7 @@ static int do_list_flowtables(struct netlink_ctx *ctx, struct cmd *cmd) struct flowtable *flowtable; struct table *table; - list_for_each_entry(table, &ctx->nft->cache.list, list) { + list_for_each_entry(table, &ctx->nft->cache.table_cache.list, cache.list) { if (cmd->handle.family != NFPROTO_UNSPEC && cmd->handle.family != table->handle.family) continue; @@ -2319,7 +2260,7 @@ static int do_list_flowtables(struct netlink_ctx *ctx, struct cmd *cmd) family2str(table->handle.family), table->handle.table.name); - list_for_each_entry(flowtable, &table->flowtables, list) { + list_for_each_entry(flowtable, &table->ft_cache.list, cache.list) { flowtable_print_declaration(flowtable, &opts, &ctx->nft->output); nft_print(&ctx->nft->output, "%s}%s", opts.tab, opts.nl); } @@ -2334,20 +2275,15 @@ static int do_list_ruleset(struct netlink_ctx *ctx, struct cmd *cmd) unsigned int family = cmd->handle.family; struct table *table; - list_for_each_entry(table, &ctx->nft->cache.list, list) { + list_for_each_entry(table, &ctx->nft->cache.table_cache.list, cache.list) { if (family != NFPROTO_UNSPEC && table->handle.family != family) continue; - cmd->handle.family = table->handle.family; - cmd->handle.table.name = table->handle.table.name; - - if (do_list_table(ctx, cmd, table) < 0) + if (do_list_table(ctx, table) < 0) return -1; } - cmd->handle.table.name = NULL; - return 0; } @@ -2355,7 +2291,7 @@ static int do_list_tables(struct netlink_ctx *ctx, struct cmd *cmd) { struct table *table; - list_for_each_entry(table, &ctx->nft->cache.list, list) { + list_for_each_entry(table, &ctx->nft->cache.table_cache.list, cache.list) { if (cmd->handle.family != NFPROTO_UNSPEC && cmd->handle.family != table->handle.family) continue; @@ -2371,9 +2307,14 @@ static int do_list_tables(struct netlink_ctx *ctx, struct cmd *cmd) static void table_print_declaration(struct table *table, struct output_ctx *octx) { - nft_print(octx, "table %s %s {\n", - family2str(table->handle.family), - table->handle.table.name); + const char *family = family2str(table->handle.family); + + if (table->has_xt_stmts) + fprintf(octx->error_fp, + "# Warning: table %s %s is managed by iptables-nft, do not touch!\n", + family, table->handle.table.name); + + nft_print(octx, "table %s %s {\n", family, table->handle.table.name); } static int do_list_chain(struct netlink_ctx *ctx, struct cmd *cmd, @@ -2383,13 +2324,9 @@ static int do_list_chain(struct netlink_ctx *ctx, struct cmd *cmd, table_print_declaration(table, &ctx->nft->output); - list_for_each_entry(chain, &table->chains, list) { - if (chain->handle.family != cmd->handle.family || - strcmp(cmd->handle.chain.name, chain->handle.chain.name) != 0) - continue; - + chain = chain_cache_find(table, cmd->handle.chain.name); + if (chain) chain_print(chain, &ctx->nft->output); - } nft_print(&ctx->nft->output, "}\n"); @@ -2401,14 +2338,14 @@ static int do_list_chains(struct netlink_ctx *ctx, struct cmd *cmd) struct table *table; struct chain *chain; - list_for_each_entry(table, &ctx->nft->cache.list, list) { + list_for_each_entry(table, &ctx->nft->cache.table_cache.list, cache.list) { if (cmd->handle.family != NFPROTO_UNSPEC && cmd->handle.family != table->handle.family) continue; table_print_declaration(table, &ctx->nft->output); - list_for_each_entry(chain, &table->chains, list) { + list_for_each_entry(chain, &table->chain_cache.list, cache.list) { chain_print_declaration(chain, &ctx->nft->output); nft_print(&ctx->nft->output, "\t}\n"); } @@ -2419,9 +2356,15 @@ static int do_list_chains(struct netlink_ctx *ctx, struct cmd *cmd) } static void __do_list_set(struct netlink_ctx *ctx, struct cmd *cmd, - struct table *table, struct set *set) + struct set *set) { + struct table *table = table_alloc(); + + table->handle.table.name = xstrdup(cmd->handle.table.name); + table->handle.family = cmd->handle.family; table_print_declaration(table, &ctx->nft->output); + table_free(table); + set_print(set, &ctx->nft->output); nft_print(&ctx->nft->output, "}\n"); } @@ -2429,17 +2372,26 @@ static void __do_list_set(struct netlink_ctx *ctx, struct cmd *cmd, static int do_list_set(struct netlink_ctx *ctx, struct cmd *cmd, struct table *table) { - struct set *set; + struct set *set = cmd->set; - set = set_lookup(table, cmd->handle.set.name); - if (set == NULL) - return -1; + if (!set) { + set = set_cache_find(table, cmd->handle.set.name); + if (set == NULL) + return -1; + } - __do_list_set(ctx, cmd, table, set); + __do_list_set(ctx, cmd, set); return 0; } +static int do_list_hooks(struct netlink_ctx *ctx, struct cmd *cmd) +{ + const char *devname = cmd->handle.obj.name; + + return mnl_nft_dump_nf_hooks(ctx, cmd->handle.family, devname); +} + static int do_command_list(struct netlink_ctx *ctx, struct cmd *cmd) { struct table *table = NULL; @@ -2447,14 +2399,21 @@ static int do_command_list(struct netlink_ctx *ctx, struct cmd *cmd) if (nft_output_json(&ctx->nft->output)) return do_command_list_json(ctx, cmd); - if (cmd->handle.table.name != NULL) - table = table_lookup(&cmd->handle, &ctx->nft->cache); + if (cmd->handle.table.name != NULL) { + table = table_cache_find(&ctx->nft->cache.table_cache, + cmd->handle.table.name, + cmd->handle.family); + if (!table) { + errno = ENOENT; + return -1; + } + } switch (cmd->obj) { case CMD_OBJ_TABLE: if (!cmd->handle.table.name) return do_list_tables(ctx, cmd); - return do_list_table(ctx, cmd, table); + return do_list_table(ctx, table); case CMD_OBJ_CHAIN: return do_list_chain(ctx, cmd, table); case CMD_OBJ_CHAINS: @@ -2464,6 +2423,8 @@ static int do_command_list(struct netlink_ctx *ctx, struct cmd *cmd) case CMD_OBJ_SET: return do_list_set(ctx, cmd, table); case CMD_OBJ_RULESET: + case CMD_OBJ_RULES: + case CMD_OBJ_RULE: return do_list_ruleset(ctx, cmd); case CMD_OBJ_METERS: return do_list_sets(ctx, cmd); @@ -2483,8 +2444,10 @@ static int do_command_list(struct netlink_ctx *ctx, struct cmd *cmd) case CMD_OBJ_CT_HELPERS: return do_list_obj(ctx, cmd, NFT_OBJECT_CT_HELPER); case CMD_OBJ_CT_TIMEOUT: + case CMD_OBJ_CT_TIMEOUTS: return do_list_obj(ctx, cmd, NFT_OBJECT_CT_TIMEOUT); case CMD_OBJ_CT_EXPECT: + case CMD_OBJ_CT_EXPECTATIONS: return do_list_obj(ctx, cmd, NFT_OBJECT_CT_EXPECT); case CMD_OBJ_LIMIT: case CMD_OBJ_LIMITS: @@ -2499,24 +2462,33 @@ static int do_command_list(struct netlink_ctx *ctx, struct cmd *cmd) return do_list_flowtable(ctx, cmd, table); case CMD_OBJ_FLOWTABLES: return do_list_flowtables(ctx, cmd); - default: - BUG("invalid command object type %u\n", cmd->obj); + case CMD_OBJ_HOOKS: + return do_list_hooks(ctx, cmd); + case CMD_OBJ_MONITOR: + case CMD_OBJ_MARKUP: + case CMD_OBJ_SETELEMS: + case CMD_OBJ_EXPR: + case CMD_OBJ_ELEMENTS: + errno = EOPNOTSUPP; + return -1; + case CMD_OBJ_INVALID: + break; } + BUG("invalid command object type %u\n", cmd->obj); return 0; } -static int do_get_setelems(struct netlink_ctx *ctx, struct cmd *cmd, - struct table *table) +static int do_get_setelems(struct netlink_ctx *ctx, struct cmd *cmd, bool reset) { struct set *set, *new_set; struct expr *init; int err; - set = set_lookup(table, cmd->handle.set.name); + set = cmd->elem.set; /* Create a list of elements based of what we got from command line. */ - if (set->flags & NFT_SET_INTERVAL) + if (set_is_non_concat_range(set)) init = get_set_intervals(set, cmd->expr); else init = cmd->expr; @@ -2525,11 +2497,11 @@ static int do_get_setelems(struct netlink_ctx *ctx, struct cmd *cmd, /* Fetch from kernel the elements that have been requested .*/ err = netlink_get_setelem(ctx, &cmd->handle, &cmd->location, - table, new_set, init); + cmd->elem.set, new_set, init, reset); if (err >= 0) - __do_list_set(ctx, cmd, table, new_set); + __do_list_set(ctx, cmd, new_set); - if (set->flags & NFT_SET_INTERVAL) + if (set_is_non_concat_range(set)) expr_free(init); set_free(new_set); @@ -2539,14 +2511,9 @@ static int do_get_setelems(struct netlink_ctx *ctx, struct cmd *cmd, static int do_command_get(struct netlink_ctx *ctx, struct cmd *cmd) { - struct table *table = NULL; - - if (cmd->handle.table.name != NULL) - table = table_lookup(&cmd->handle, &ctx->nft->cache); - switch (cmd->obj) { - case CMD_OBJ_SETELEM: - return do_get_setelems(ctx, cmd, table); + case CMD_OBJ_ELEMENTS: + return do_get_setelems(ctx, cmd, false); default: BUG("invalid command object type %u\n", cmd->obj); } @@ -2556,39 +2523,14 @@ static int do_command_get(struct netlink_ctx *ctx, struct cmd *cmd) static int do_command_reset(struct netlink_ctx *ctx, struct cmd *cmd) { - struct obj *obj, *next; - struct table *table; - bool dump = false; - uint32_t type; - int ret; - switch (cmd->obj) { - case CMD_OBJ_COUNTERS: - dump = true; - /* fall through */ - case CMD_OBJ_COUNTER: - type = NFT_OBJECT_COUNTER; - break; - case CMD_OBJ_QUOTAS: - dump = true; - /* fall through */ - case CMD_OBJ_QUOTA: - type = NFT_OBJECT_QUOTA; - break; + case CMD_OBJ_ELEMENTS: + return do_get_setelems(ctx, cmd, true); default: - BUG("invalid command object type %u\n", cmd->obj); - } - - ret = netlink_reset_objs(ctx, cmd, type, dump); - list_for_each_entry_safe(obj, next, &ctx->list, list) { - table = table_lookup(&obj->handle, &ctx->nft->cache); - if (!obj_lookup(table, obj->handle.obj.name, obj->type)) - list_move(&obj->list, &table->objs); + break; } - if (ret < 0) - return ret; - return do_list_obj(ctx, cmd, type); + return do_command_list(ctx, cmd); } static int do_command_flush(struct netlink_ctx *ctx, struct cmd *cmd) @@ -2611,12 +2553,14 @@ static int do_command_flush(struct netlink_ctx *ctx, struct cmd *cmd) static int do_command_rename(struct netlink_ctx *ctx, struct cmd *cmd) { - struct table *table = table_lookup(&cmd->handle, &ctx->nft->cache); + struct table *table = table_cache_find(&ctx->nft->cache.table_cache, + cmd->handle.table.name, + cmd->handle.family); const struct chain *chain; switch (cmd->obj) { case CMD_OBJ_CHAIN: - chain = chain_lookup(table, &cmd->handle); + chain = chain_cache_find(table, cmd->handle.chain.name); return mnl_nft_chain_rename(ctx, cmd, chain); default: @@ -2686,6 +2630,7 @@ int do_command(struct netlink_ctx *ctx, struct cmd *cmd) case CMD_REPLACE: return do_command_replace(ctx, cmd); case CMD_DELETE: + case CMD_DESTROY: return do_command_delete(ctx, cmd); case CMD_GET: return do_command_get(ctx, cmd); @@ -2801,49 +2746,78 @@ static void payload_do_merge(struct stmt *sa[], unsigned int n) } /** - * payload_try_merge - try to merge consecutive payload match statements + * stmt_reduce - reduce statements in rule * * @rule: nftables rule * + * This function aims to: + * + * - remove redundant statement, e.g. remove 'meta protocol ip' if family is ip + * - merge consecutive payload match statements + * * Locate sequences of payload match statements referring to adjacent * header locations and merge those using only equality relations. * * As a side-effect, payload match statements are ordered in ascending * order according to the location of the payload. */ -static void payload_try_merge(const struct rule *rule) +static void stmt_reduce(const struct rule *rule) { + struct stmt *stmt, *dstmt = NULL, *next; struct stmt *sa[rule->num_stmts]; - struct stmt *stmt, *next; unsigned int idx = 0; list_for_each_entry_safe(stmt, next, &rule->stmts, list) { + /* delete this redundant statement */ + if (dstmt) { + list_del(&dstmt->list); + stmt_free(dstmt); + dstmt = NULL; + } + /* Must not merge across other statements */ - if (stmt->ops->type != STMT_EXPRESSION) - goto do_merge; + if (stmt->type != STMT_EXPRESSION) { + if (idx >= 2) + payload_do_merge(sa, idx); + idx = 0; + continue; + } if (stmt->expr->etype != EXPR_RELATIONAL) continue; - if (stmt->expr->left->etype != EXPR_PAYLOAD) - continue; if (stmt->expr->right->etype != EXPR_VALUE) continue; - switch (stmt->expr->op) { - case OP_EQ: - case OP_IMPLICIT: - case OP_NEQ: - break; - default: - continue; - } - sa[idx++] = stmt; - continue; -do_merge: - if (idx < 2) - continue; - payload_do_merge(sa, idx); - idx = 0; + if (stmt->expr->left->etype == EXPR_PAYLOAD) { + switch (stmt->expr->op) { + case OP_EQ: + case OP_IMPLICIT: + break; + default: + continue; + } + + sa[idx++] = stmt; + } else if (stmt->expr->left->etype == EXPR_META) { + switch (stmt->expr->op) { + case OP_EQ: + case OP_IMPLICIT: + if (stmt->expr->left->meta.key == NFT_META_PROTOCOL && + !stmt->expr->left->meta.inner_desc) { + uint16_t protocol; + + protocol = mpz_get_uint16(stmt->expr->right->value); + if ((rule->handle.family == NFPROTO_IPV4 && + protocol == ETH_P_IP) || + (rule->handle.family == NFPROTO_IPV6 && + protocol == ETH_P_IPV6)) + dstmt = stmt; + } + break; + default: + break; + } + } } if (idx > 1) @@ -2852,6 +2826,6 @@ do_merge: struct error_record *rule_postprocess(struct rule *rule) { - payload_try_merge(rule); + stmt_reduce(rule); return NULL; } diff --git a/src/scanner.l b/src/scanner.l index 99ee8355..b69d8e81 100644 --- a/src/scanner.l +++ b/src/scanner.l @@ -10,12 +10,15 @@ %{ +#include <nft.h> + #include <limits.h> #include <glob.h> #include <netinet/in.h> #include <arpa/inet.h> #include <linux/types.h> #include <linux/netfilter.h> +#include <sys/stat.h> #include <nftables.h> #include <erec.h> @@ -77,7 +80,6 @@ static void update_pos(struct parser_state *state, struct location *loc, { loc->indesc = state->indesc; loc->first_line = state->indesc->lineno; - loc->last_line = state->indesc->lineno; loc->first_column = state->indesc->column; loc->last_column = state->indesc->column + len - 1; state->indesc->column += len; @@ -86,9 +88,15 @@ static void update_pos(struct parser_state *state, struct location *loc, static void update_offset(struct parser_state *state, struct location *loc, unsigned int len) { + uint32_t line_offset; + state->indesc->token_offset += len; - loc->token_offset = state->indesc->token_offset; - loc->line_offset = state->indesc->line_offset; + if (state->indesc->line_offset > UINT32_MAX) + line_offset = UINT32_MAX; + else + line_offset = state->indesc->line_offset; + + loc->line_offset = line_offset; } static void reset_pos(struct parser_state *state, struct location *loc) @@ -98,6 +106,8 @@ static void reset_pos(struct parser_state *state, struct location *loc) state->indesc->column = 1; } +static void scanner_push_start_cond(void *scanner, enum startcond_type type); + #define YY_USER_ACTION { \ update_pos(yyget_extra(yyscanner), yylloc, yyleng); \ update_offset(yyget_extra(yyscanner), yylloc, yyleng); \ @@ -111,64 +121,64 @@ extern void yyset_column(int, yyscan_t); space [ ] tab \t +newline_crlf \r\n newline \n digit [0-9] hexdigit [0-9a-fA-F] decstring {digit}+ hexstring 0[xX]{hexdigit}+ -numberstring ({decstring}|{hexstring}) letter [a-zA-Z] string ({letter}|[_.])({letter}|{digit}|[/\-_\.])* quotedstring \"[^"]*\" -asteriskstring ({string}\*|{string}\\\*) +asteriskstring ({string}\*|{string}\\\*|\\\*|{string}\\\*{string}) comment #.*$ +comment_line ^[ \t]*#.*\n slash \/ timestring ([0-9]+d)?([0-9]+h)?([0-9]+m)?([0-9]+s)?([0-9]+ms)? hex4 ([[:xdigit:]]{1,4}) +rfc4291_broader (((:{hex4}){2})|(:{ip4addr})) v680 (({hex4}:){7}{hex4}) -v670 ((:)((:{hex4}){7})) -v671 ((({hex4}:){1})((:{hex4}){6})) -v672 ((({hex4}:){2})((:{hex4}){5})) -v673 ((({hex4}:){3})((:{hex4}){4})) -v674 ((({hex4}:){4})((:{hex4}){3})) -v675 ((({hex4}:){5})((:{hex4}){2})) +v670 ((:)((:{hex4}){5}){rfc4291_broader}) +v671 ((({hex4}:){1})((:{hex4}){4}){rfc4291_broader}) +v672 ((({hex4}:){2})((:{hex4}){3}){rfc4291_broader}) +v673 ((({hex4}:){3})((:{hex4}){2}){rfc4291_broader}) +v674 ((({hex4}:){4})((:{hex4}){1}){rfc4291_broader}) +v675 ((({hex4}:){5}){rfc4291_broader}) v676 ((({hex4}:){6})(:{hex4}{1})) v677 ((({hex4}:){7})(:)) v67 ({v670}|{v671}|{v672}|{v673}|{v674}|{v675}|{v676}|{v677}) -v660 ((:)((:{hex4}){6})) -v661 ((({hex4}:){1})((:{hex4}){5})) -v662 ((({hex4}:){2})((:{hex4}){4})) -v663 ((({hex4}:){3})((:{hex4}){3})) -v664 ((({hex4}:){4})((:{hex4}){2})) +v660 ((:)((:{hex4}){4}){rfc4291_broader}) +v661 ((({hex4}:){1})((:{hex4}){3}){rfc4291_broader}) +v662 ((({hex4}:){2})((:{hex4}){2}){rfc4291_broader}) +v663 ((({hex4}:){3})((:{hex4}){1}){rfc4291_broader}) +v664 ((({hex4}:){4}){rfc4291_broader}) v665 ((({hex4}:){5})((:{hex4}){1})) v666 ((({hex4}:){6})(:)) v66 ({v660}|{v661}|{v662}|{v663}|{v664}|{v665}|{v666}) -v650 ((:)((:{hex4}){5})) -v651 ((({hex4}:){1})((:{hex4}){4})) -v652 ((({hex4}:){2})((:{hex4}){3})) -v653 ((({hex4}:){3})((:{hex4}){2})) +v650 ((:)((:{hex4}){3}){rfc4291_broader}) +v651 ((({hex4}:){1})((:{hex4}){2}){rfc4291_broader}) +v652 ((({hex4}:){2})((:{hex4}){1}){rfc4291_broader}) +v653 ((({hex4}:){3}){rfc4291_broader}) v654 ((({hex4}:){4})(:{hex4}{1})) v655 ((({hex4}:){5})(:)) v65 ({v650}|{v651}|{v652}|{v653}|{v654}|{v655}) -v640 ((:)((:{hex4}){4})) -v641 ((({hex4}:){1})((:{hex4}){3})) -v642 ((({hex4}:){2})((:{hex4}){2})) +v640 ((:)((:{hex4}){2}){rfc4291_broader}) +v641 ((({hex4}:){1})((:{hex4}){1}){rfc4291_broader}) +v642 ((({hex4}:){2}){rfc4291_broader}) v643 ((({hex4}:){3})((:{hex4}){1})) v644 ((({hex4}:){4})(:)) v64 ({v640}|{v641}|{v642}|{v643}|{v644}) -v630 ((:)((:{hex4}){3})) -v631 ((({hex4}:){1})((:{hex4}){2})) +v630 ((:)((:{hex4}){1}){rfc4291_broader}) +v631 ((({hex4}:){1}){rfc4291_broader}) v632 ((({hex4}:){2})((:{hex4}){1})) v633 ((({hex4}:){3})(:)) v63 ({v630}|{v631}|{v632}|{v633}) -v620 ((:)((:{hex4}){2})) -v620_rfc4291 ((:)(:{ip4addr})) +v620 ((:){rfc4291_broader}) v621 ((({hex4}:){1})((:{hex4}){1})) v622 ((({hex4}:){2})(:)) -v62_rfc4291 ((:)(:[fF]{4})(:{ip4addr})) -v62 ({v620}|{v621}|{v622}|{v62_rfc4291}|{v620_rfc4291}) +v62 ({v620}|{v621}|{v622}) v610 ((:)(:{hex4}{1})) v611 ((({hex4}:){1})(:)) v61 ({v610}|{v611}) @@ -193,6 +203,62 @@ addrstring ({macaddr}|{ip4addr}|{ip6addr}) %option yylineno %option nodefault %option warn +%option stack +%s SCANSTATE_ARP +%s SCANSTATE_AT +%s SCANSTATE_CT +%s SCANSTATE_COUNTER +%s SCANSTATE_ETH +%s SCANSTATE_GRE +%s SCANSTATE_ICMP +%s SCANSTATE_IGMP +%s SCANSTATE_IP +%s SCANSTATE_IP6 +%s SCANSTATE_LAST +%s SCANSTATE_LIMIT +%s SCANSTATE_META +%s SCANSTATE_POLICY +%s SCANSTATE_QUOTA +%s SCANSTATE_SCTP +%s SCANSTATE_SECMARK +%s SCANSTATE_TCP +%s SCANSTATE_TYPE +%s SCANSTATE_VLAN +%s SCANSTATE_XT +%s SCANSTATE_CMD_DESTROY +%s SCANSTATE_CMD_EXPORT +%s SCANSTATE_CMD_IMPORT +%s SCANSTATE_CMD_LIST +%s SCANSTATE_CMD_MONITOR +%s SCANSTATE_CMD_RESET +%s SCANSTATE_EXPR_AH +%s SCANSTATE_EXPR_COMP +%s SCANSTATE_EXPR_DCCP +%s SCANSTATE_EXPR_DST +%s SCANSTATE_EXPR_ESP +%s SCANSTATE_EXPR_FIB +%s SCANSTATE_EXPR_FRAG +%s SCANSTATE_EXPR_HASH +%s SCANSTATE_EXPR_HBH +%s SCANSTATE_EXPR_IPSEC +%s SCANSTATE_EXPR_MH +%s SCANSTATE_EXPR_NUMGEN +%s SCANSTATE_EXPR_OSF +%s SCANSTATE_EXPR_QUEUE +%s SCANSTATE_EXPR_RT +%s SCANSTATE_EXPR_SCTP_CHUNK +%s SCANSTATE_EXPR_SOCKET +%s SCANSTATE_EXPR_TH +%s SCANSTATE_EXPR_UDP +%s SCANSTATE_EXPR_UDPLITE + +%s SCANSTATE_STMT_DUP +%s SCANSTATE_STMT_FWD +%s SCANSTATE_STMT_LOG +%s SCANSTATE_STMT_NAT +%s SCANSTATE_STMT_REJECT +%s SCANSTATE_STMT_SYNPROXY +%s SCANSTATE_STMT_TPROXY %% @@ -233,7 +299,7 @@ addrstring ({macaddr}|{ip4addr}|{ip6addr}) "/" { return SLASH; } "-" { return DASH; } "*" { return ASTERISK; } -"@" { return AT; } +"@" { scanner_push_start_cond(yyscanner, SCANSTATE_AT); return AT; } "$" { return '$'; } "=" { return '='; } "vmap" { return VMAP; } @@ -247,29 +313,36 @@ addrstring ({macaddr}|{ip4addr}|{ip6addr}) "describe" { return DESCRIBE; } +<SCANSTATE_CMD_LIST,SCANSTATE_CMD_MONITOR>{ + "chains" { return CHAINS; } + "sets" { return SETS; } + "tables" { return TABLES; } +} +<SCANSTATE_CMD_MONITOR>{ + "rules" { return RULES; } + "trace" { return TRACE; } +} "hook" { return HOOK; } "device" { return DEVICE; } "devices" { return DEVICES; } "table" { return TABLE; } -"tables" { return TABLES; } "chain" { return CHAIN; } -"chains" { return CHAINS; } "rule" { return RULE; } -"rules" { return RULES; } -"sets" { return SETS; } "set" { return SET; } "element" { return ELEMENT; } "map" { return MAP; } -"maps" { return MAPS; } "flowtable" { return FLOWTABLE; } "handle" { return HANDLE; } "ruleset" { return RULESET; } -"trace" { return TRACE; } - -"socket" { return SOCKET; } -"transparent" { return TRANSPARENT;} -"tproxy" { return TPROXY; } +"socket" { scanner_push_start_cond(yyscanner, SCANSTATE_EXPR_SOCKET); return SOCKET; } +<SCANSTATE_EXPR_SOCKET>{ + "transparent" { return TRANSPARENT; } + "wildcard" { return WILDCARD; } + "cgroupv2" { return CGROUPV2; } + "level" { return LEVEL; } +} +"tproxy" { scanner_push_start_cond(yyscanner, SCANSTATE_STMT_TPROXY); return TPROXY; } "accept" { return ACCEPT; } "drop" { return DROP; } @@ -277,7 +350,7 @@ addrstring ({macaddr}|{ip4addr}|{ip6addr}) "jump" { return JUMP; } "goto" { return GOTO; } "return" { return RETURN; } -"to" { return TO; } +<SCANSTATE_EXPR_QUEUE,SCANSTATE_STMT_DUP,SCANSTATE_STMT_FWD,SCANSTATE_STMT_NAT,SCANSTATE_STMT_TPROXY,SCANSTATE_IP,SCANSTATE_IP6>"to" { return TO; } /* XXX: SCANSTATE_IP is a workaround */ "inet" { return INET; } "netdev" { return NETDEV; } @@ -289,13 +362,15 @@ addrstring ({macaddr}|{ip4addr}|{ip6addr}) "insert" { return INSERT; } "delete" { return DELETE; } "get" { return GET; } -"list" { return LIST; } -"reset" { return RESET; } +"list" { scanner_push_start_cond(yyscanner, SCANSTATE_CMD_LIST); return LIST; } +"reset" { scanner_push_start_cond(yyscanner, SCANSTATE_CMD_RESET); return RESET; } "flush" { return FLUSH; } "rename" { return RENAME; } -"import" { return IMPORT; } -"export" { return EXPORT; } -"monitor" { return MONITOR; } +"import" { scanner_push_start_cond(yyscanner, SCANSTATE_CMD_IMPORT); return IMPORT; } +"export" { scanner_push_start_cond(yyscanner, SCANSTATE_CMD_EXPORT); return EXPORT; } +"monitor" { scanner_push_start_cond(yyscanner, SCANSTATE_CMD_MONITOR); return MONITOR; } +"destroy" { scanner_push_start_cond(yyscanner, SCANSTATE_CMD_DESTROY); return DESTROY; } + "position" { return POSITION; } "index" { return INDEX; } @@ -310,201 +385,342 @@ addrstring ({macaddr}|{ip4addr}|{ip6addr}) "elements" { return ELEMENTS; } "expires" { return EXPIRES; } -"policy" { return POLICY; } +"policy" { scanner_push_start_cond(yyscanner, SCANSTATE_POLICY); return POLICY; } "size" { return SIZE; } -"performance" { return PERFORMANCE; } -"memory" { return MEMORY; } +<SCANSTATE_POLICY>{ + "performance" { return PERFORMANCE; } + "memory" { return MEMORY; } +} "flow" { return FLOW; } "offload" { return OFFLOAD; } "meter" { return METER; } -"meters" { return METERS; } - -"flowtables" { return FLOWTABLES; } - -"counter" { return COUNTER; } -"name" { return NAME; } -"packets" { return PACKETS; } -"bytes" { return BYTES; } -"avgpkt" { return AVGPKT; } - -"counters" { return COUNTERS; } -"quotas" { return QUOTAS; } -"limits" { return LIMITS; } -"synproxys" { return SYNPROXYS; } - -"log" { return LOG; } -"prefix" { return PREFIX; } -"group" { return GROUP; } -"snaplen" { return SNAPLEN; } -"queue-threshold" { return QUEUE_THRESHOLD; } -"level" { return LEVEL; } - -"queue" { return QUEUE;} -"num" { return QUEUENUM;} -"bypass" { return BYPASS;} -"fanout" { return FANOUT;} - -"limit" { return LIMIT; } -"rate" { return RATE; } -"burst" { return BURST; } -"until" { return UNTIL; } -"over" { return OVER; } - -"quota" { return QUOTA; } -"used" { return USED; } - -"nanosecond" { return NANOSECOND; } -"microsecond" { return MICROSECOND; } -"millisecond" { return MILLISECOND; } -"second" { return SECOND; } -"minute" { return MINUTE; } + +<SCANSTATE_CMD_LIST>{ + "meters" { return METERS; } + "flowtables" { return FLOWTABLES; } + "limits" { return LIMITS; } + "maps" { return MAPS; } + "secmarks" { return SECMARKS; } + "synproxys" { return SYNPROXYS; } + "hooks" { return HOOKS; } +} + +"counter" { scanner_push_start_cond(yyscanner, SCANSTATE_COUNTER); return COUNTER; } +<SCANSTATE_COUNTER,SCANSTATE_LIMIT,SCANSTATE_QUOTA,SCANSTATE_STMT_SYNPROXY,SCANSTATE_EXPR_OSF>"name" { return NAME; } +<SCANSTATE_COUNTER,SCANSTATE_CT,SCANSTATE_LIMIT>"packets" { return PACKETS; } +<SCANSTATE_COUNTER,SCANSTATE_CT,SCANSTATE_LIMIT,SCANSTATE_QUOTA>"bytes" { return BYTES; } + +"last" { scanner_push_start_cond(yyscanner, SCANSTATE_LAST); return LAST; } +<SCANSTATE_LAST>{ + "never" { return NEVER; } +} + +<SCANSTATE_CMD_LIST,SCANSTATE_CMD_RESET>{ + "counters" { return COUNTERS; } + "quotas" { return QUOTAS; } + "rules" { return RULES; } +} + +"log" { scanner_push_start_cond(yyscanner, SCANSTATE_STMT_LOG); return LOG; } +<SCANSTATE_STMT_LOG,SCANSTATE_STMT_NAT,SCANSTATE_IP,SCANSTATE_IP6>"prefix" { return PREFIX; } +<SCANSTATE_STMT_LOG>{ + "snaplen" { return SNAPLEN; } + "queue-threshold" { return QUEUE_THRESHOLD; } + "level" { return LEVEL; } + "group" { return GROUP; } +} + +"queue" { scanner_push_start_cond(yyscanner, SCANSTATE_EXPR_QUEUE); return QUEUE;} +<SCANSTATE_EXPR_QUEUE>{ + "num" { return QUEUENUM;} + "bypass" { return BYPASS;} + "fanout" { return FANOUT;} +} +"limit" { scanner_push_start_cond(yyscanner, SCANSTATE_LIMIT); return LIMIT; } +<SCANSTATE_LIMIT>{ + "rate" { return RATE; } + "burst" { return BURST; } + + /* time_unit */ + "second" { return SECOND; } + "minute" { return MINUTE; } + "week" { return WEEK; } +} +<SCANSTATE_CT,SCANSTATE_LIMIT,SCANSTATE_QUOTA>"over" { return OVER; } + +"quota" { scanner_push_start_cond(yyscanner, SCANSTATE_QUOTA); return QUOTA; } +<SCANSTATE_QUOTA>{ + "until" { return UNTIL; } +} + +<SCANSTATE_QUOTA,SCANSTATE_LAST>"used" { return USED; } + "hour" { return HOUR; } "day" { return DAY; } -"week" { return WEEK; } -"reject" { return _REJECT; } -"with" { return WITH; } -"icmpx" { return ICMPX; } +"reject" { scanner_push_start_cond(yyscanner, SCANSTATE_STMT_REJECT); return _REJECT; } +<SCANSTATE_STMT_REJECT>{ + "with" { return WITH; } + "icmpx" { return ICMPX; } +} -"snat" { return SNAT; } -"dnat" { return DNAT; } -"masquerade" { return MASQUERADE; } -"redirect" { return REDIRECT; } +"snat" { scanner_push_start_cond(yyscanner, SCANSTATE_STMT_NAT); return SNAT; } +"dnat" { scanner_push_start_cond(yyscanner, SCANSTATE_STMT_NAT); return DNAT; } +"masquerade" { scanner_push_start_cond(yyscanner, SCANSTATE_STMT_NAT); return MASQUERADE; } +"redirect" { scanner_push_start_cond(yyscanner, SCANSTATE_STMT_NAT); return REDIRECT; } "random" { return RANDOM; } -"fully-random" { return FULLY_RANDOM; } -"persistent" { return PERSISTENT; } +<SCANSTATE_STMT_NAT>{ + "fully-random" { return FULLY_RANDOM; } + "persistent" { return PERSISTENT; } + "port" { return PORT; } +} -"ll" { return LL_HDR; } -"nh" { return NETWORK_HDR; } -"th" { return TRANSPORT_HDR; } +<SCANSTATE_AT>{ + "ll" { return LL_HDR; } + "nh" { return NETWORK_HDR; } +} +"th" { scanner_push_start_cond(yyscanner, SCANSTATE_EXPR_TH); return TRANSPORT_HDR; } "bridge" { return BRIDGE; } -"ether" { return ETHER; } -"saddr" { return SADDR; } -"daddr" { return DADDR; } -"type" { return TYPE; } +"ether" { scanner_push_start_cond(yyscanner, SCANSTATE_ETH); return ETHER; } +<SCANSTATE_ARP,SCANSTATE_CT,SCANSTATE_ETH,SCANSTATE_IP,SCANSTATE_IP6,SCANSTATE_EXPR_FIB,SCANSTATE_EXPR_IPSEC>{ + "saddr" { return SADDR; } + "daddr" { return DADDR; } +} +"type" { scanner_push_start_cond(yyscanner, SCANSTATE_TYPE); return TYPE; } "typeof" { return TYPEOF; } -"vlan" { return VLAN; } -"id" { return ID; } -"cfi" { return CFI; } -"pcp" { return PCP; } - -"arp" { return ARP; } -"htype" { return HTYPE; } -"ptype" { return PTYPE; } -"hlen" { return HLEN; } -"plen" { return PLEN; } -"operation" { return OPERATION; } - -"ip" { return IP; } -"version" { return HDRVERSION; } -"hdrlength" { return HDRLENGTH; } -"dscp" { return DSCP; } +"vlan" { scanner_push_start_cond(yyscanner, SCANSTATE_VLAN); return VLAN; } +<SCANSTATE_CT,SCANSTATE_EXPR_FRAG,SCANSTATE_VLAN,SCANSTATE_IP,SCANSTATE_ICMP>"id" { return ID; } +<SCANSTATE_VLAN>{ + "cfi" { return CFI; } + "dei" { return DEI; } + "pcp" { return PCP; } +} +"8021ad" { yylval->string = xstrdup(yytext); return STRING; } +"8021q" { yylval->string = xstrdup(yytext); return STRING; } + +"arp" { scanner_push_start_cond(yyscanner, SCANSTATE_ARP); return ARP; } +<SCANSTATE_ARP>{ + "htype" { return HTYPE; } + "ptype" { return PTYPE; } + "hlen" { return HLEN; } + "plen" { return PLEN; } + "operation" { return OPERATION; } +} + +"ip" { scanner_push_start_cond(yyscanner, SCANSTATE_IP); return IP; } +<SCANSTATE_IP,SCANSTATE_IP6,SCANSTATE_EXPR_OSF,SCANSTATE_GRE>{ + "version" { return HDRVERSION; } +} +<SCANSTATE_EXPR_AH,SCANSTATE_EXPR_DST,SCANSTATE_EXPR_HBH,SCANSTATE_EXPR_MH,SCANSTATE_EXPR_RT,SCANSTATE_IP>{ + "hdrlength" { return HDRLENGTH; } +} +<SCANSTATE_IP,SCANSTATE_IP6,SCANSTATE_TYPE>{ + "dscp" { return DSCP; } +} "ecn" { return ECN; } -"length" { return LENGTH; } -"frag-off" { return FRAG_OFF; } -"ttl" { return TTL; } -"protocol" { return PROTOCOL; } -"checksum" { return CHECKSUM; } - -"lsrr" { return LSRR; } -"rr" { return RR; } -"ssrr" { return SSRR; } -"ra" { return RA; } - -"value" { return VALUE; } -"ptr" { return PTR; } - -"echo" { return ECHO; } -"eol" { return EOL; } -"maxseg" { return MAXSEG; } -"noop" { return NOOP; } -"sack" { return SACK; } -"sack0" { return SACK0; } -"sack1" { return SACK1; } -"sack2" { return SACK2; } -"sack3" { return SACK3; } -"sack-permitted" { return SACK_PERMITTED; } -"timestamp" { return TIMESTAMP; } -"time" { return TIME; } +<SCANSTATE_EXPR_UDP,SCANSTATE_IP,SCANSTATE_IP6,SCANSTATE_META,SCANSTATE_TCP,SCANSTATE_SCTP,SCANSTATE_EXPR_SCTP_CHUNK>"length" { return LENGTH; } +<SCANSTATE_EXPR_FRAG,SCANSTATE_IP>{ + "frag-off" { return FRAG_OFF; } +} +<SCANSTATE_EXPR_OSF,SCANSTATE_IP>{ + "ttl" { return TTL; } +} +<SCANSTATE_CT,SCANSTATE_IP,SCANSTATE_META,SCANSTATE_TYPE,SCANSTATE_GRE>"protocol" { return PROTOCOL; } +<SCANSTATE_EXPR_MH,SCANSTATE_EXPR_UDP,SCANSTATE_EXPR_UDPLITE,SCANSTATE_ICMP,SCANSTATE_IGMP,SCANSTATE_IP,SCANSTATE_SCTP,SCANSTATE_TCP>{ + "checksum" { return CHECKSUM; } +} -"kind" { return KIND; } -"count" { return COUNT; } -"left" { return LEFT; } -"right" { return RIGHT; } -"tsval" { return TSVAL; } -"tsecr" { return TSECR; } +<SCANSTATE_IP>{ + "lsrr" { return LSRR; } + "rr" { return RR; } + "ssrr" { return SSRR; } + "ra" { return RA; } -"icmp" { return ICMP; } -"code" { return CODE; } -"sequence" { return SEQUENCE; } -"gateway" { return GATEWAY; } -"mtu" { return MTU; } + "ptr" { return PTR; } + "value" { return VALUE; } -"igmp" { return IGMP; } -"mrt" { return MRT; } + "option" { return OPTION; } + "options" { return OPTIONS; } +} -"ip6" { return IP6; } -"priority" { return PRIORITY; } -"flowlabel" { return FLOWLABEL; } -"nexthdr" { return NEXTHDR; } -"hoplimit" { return HOPLIMIT; } +<SCANSTATE_TCP>{ + /* tcp header fields */ + "ackseq" { return ACKSEQ; } + "doff" { return DOFF; } + "window" { return WINDOW; } + "urgptr" { return URGPTR; } + + /* tcp option types */ + "echo" { return ECHO; } + "eol" { return EOL; } + "maxseg" { return MSS; } + "mss" { return MSS; } + "nop" { return NOP; } + "noop" { return NOP; } + "sack" { return SACK; } + "sack0" { return SACK0; } + "sack1" { return SACK1; } + "sack2" { return SACK2; } + "sack3" { return SACK3; } + "sack-permitted" { return SACK_PERM; } + "sack-perm" { return SACK_PERM; } + "timestamp" { return TIMESTAMP; } + "fastopen" { return FASTOPEN; } + "mptcp" { return MPTCP; } + "md5sig" { return MD5SIG; } + + /* tcp option fields */ + "left" { return LEFT; } + "right" { return RIGHT; } + "count" { return COUNT; } + "tsval" { return TSVAL; } + "tsecr" { return TSECR; } + "subtype" { return SUBTYPE; } + + "options" { return OPTIONS; } + "option" { return OPTION; } +} +"time" { return TIME; } -"icmpv6" { return ICMP6; } -"param-problem" { return PPTR; } -"max-delay" { return MAXDELAY; } +"icmp" { scanner_push_start_cond(yyscanner, SCANSTATE_ICMP); return ICMP; } +"icmpv6" { scanner_push_start_cond(yyscanner, SCANSTATE_ICMP); return ICMP6; } +<SCANSTATE_ICMP>{ + "gateway" { return GATEWAY; } + "code" { return CODE; } + "param-problem" { return PPTR; } + "max-delay" { return MAXDELAY; } + "mtu" { return MTU; } + "taddr" { return TADDR; } + "daddr" { return DADDR; } +} +<SCANSTATE_EXPR_AH,SCANSTATE_EXPR_ESP,SCANSTATE_ICMP,SCANSTATE_TCP>{ + "sequence" { return SEQUENCE; } +} -"ah" { return AH; } -"reserved" { return RESERVED; } -"spi" { return SPI; } +"igmp" { scanner_push_start_cond(yyscanner, SCANSTATE_IGMP); return IGMP; } +<SCANSTATE_IGMP>{ + "mrt" { return MRT; } + "group" { return GROUP; } +} -"esp" { return ESP; } +"ip6" { scanner_push_start_cond(yyscanner, SCANSTATE_IP6); return IP6; } +"priority" { return PRIORITY; } +<SCANSTATE_IP6>{ + "flowlabel" { return FLOWLABEL; } + "hoplimit" { return HOPLIMIT; } +} +<SCANSTATE_EXPR_AH,SCANSTATE_EXPR_COMP,SCANSTATE_EXPR_DST,SCANSTATE_EXPR_FRAG,SCANSTATE_EXPR_HBH,SCANSTATE_EXPR_MH,SCANSTATE_EXPR_RT,SCANSTATE_IP6>{ + "nexthdr" { return NEXTHDR; } +} -"comp" { return COMP; } +"ah" { scanner_push_start_cond(yyscanner, SCANSTATE_EXPR_AH); return AH; } +<SCANSTATE_EXPR_AH,SCANSTATE_EXPR_FRAG,SCANSTATE_EXPR_MH,SCANSTATE_TCP>{ + "reserved" { return RESERVED; } +} +<SCANSTATE_EXPR_AH,SCANSTATE_EXPR_ESP,SCANSTATE_EXPR_IPSEC>"spi" { return SPI; } + +"esp" { scanner_push_start_cond(yyscanner, SCANSTATE_EXPR_ESP); return ESP; } + +"comp" { scanner_push_start_cond(yyscanner, SCANSTATE_EXPR_COMP); return COMP; } +<SCANSTATE_EXPR_COMP>{ + "cpi" { return CPI; } +} "flags" { return FLAGS; } -"cpi" { return CPI; } -"udp" { return UDP; } -"udplite" { return UDPLITE; } -"sport" { return SPORT; } -"dport" { return DPORT; } +"udp" { scanner_push_start_cond(yyscanner, SCANSTATE_EXPR_UDP); return UDP; } +"udplite" { scanner_push_start_cond(yyscanner, SCANSTATE_EXPR_UDPLITE); return UDPLITE; } +<SCANSTATE_EXPR_UDPLITE>{ + "csumcov" { return CSUMCOV; } +} +<SCANSTATE_EXPR_DCCP,SCANSTATE_SCTP,SCANSTATE_TCP,SCANSTATE_EXPR_TH,SCANSTATE_EXPR_UDP,SCANSTATE_EXPR_UDPLITE>{ + "sport" { return SPORT; } +} +<SCANSTATE_CT,SCANSTATE_EXPR_DCCP,SCANSTATE_SCTP,SCANSTATE_TCP,SCANSTATE_EXPR_TH,SCANSTATE_EXPR_UDP,SCANSTATE_EXPR_UDPLITE>{ + "dport" { return DPORT; } +} +<SCANSTATE_EXPR_DCCP>{ + "option" { return OPTION; } +} + +"vxlan" { return VXLAN; } +"vni" { return VNI; } + +"geneve" { return GENEVE; } -"tcp" { return TCP; } -"ackseq" { return ACKSEQ; } -"doff" { return DOFF; } -"window" { return WINDOW; } -"urgptr" { return URGPTR; } -"option" { return OPTION; } +"gre" { scanner_push_start_cond(yyscanner, SCANSTATE_GRE); return GRE; } +"gretap" { scanner_push_start_cond(yyscanner, SCANSTATE_GRE); return GRETAP; } -"dccp" { return DCCP; } +"tcp" { scanner_push_start_cond(yyscanner, SCANSTATE_TCP); return TCP; } -"sctp" { return SCTP; } -"vtag" { return VTAG; } +"dccp" { scanner_push_start_cond(yyscanner, SCANSTATE_EXPR_DCCP); return DCCP; } -"rt" { return RT; } -"rt0" { return RT0; } -"rt2" { return RT2; } -"srh" { return RT4; } -"seg-left" { return SEG_LEFT; } -"addr" { return ADDR; } -"last-entry" { return LAST_ENT; } -"tag" { return TAG; } -"sid" { return SID; } +"sctp" { scanner_push_start_cond(yyscanner, SCANSTATE_SCTP); return SCTP; } -"hbh" { return HBH; } +<SCANSTATE_SCTP>{ + "chunk" { scanner_push_start_cond(yyscanner, SCANSTATE_EXPR_SCTP_CHUNK); return CHUNK; } + "vtag" { return VTAG; } +} -"frag" { return FRAG; } -"reserved2" { return RESERVED2; } -"more-fragments" { return MORE_FRAGMENTS; } +<SCANSTATE_EXPR_SCTP_CHUNK>{ + "data" { return DATA; } + "init" { return INIT; } + "init-ack" { return INIT_ACK; } + "heartbeat" { return HEARTBEAT; } + "heartbeat-ack" { return HEARTBEAT_ACK; } + "abort" { return ABORT; } + "shutdown" { return SHUTDOWN; } + "shutdown-ack" { return SHUTDOWN_ACK; } + "error" { return ERROR; } + "cookie-echo" { return COOKIE_ECHO; } + "cookie-ack" { return COOKIE_ACK; } + "ecne" { return ECNE; } + "cwr" { return CWR; } + "shutdown-complete" { return SHUTDOWN_COMPLETE; } + "asconf-ack" { return ASCONF_ACK; } + "forward-tsn" { return FORWARD_TSN; } + "asconf" { return ASCONF; } + + "tsn" { return TSN; } + "sack" { return SACK; } + "stream" { return STREAM; } + "ssn" { return SSN; } + "ppid" { return PPID; } + "init-tag" { return INIT_TAG; } + "a-rwnd" { return A_RWND; } + "num-outbound-streams" { return NUM_OSTREAMS; } + "num-inbound-streams" { return NUM_ISTREAMS; } + "initial-tsn" { return INIT_TSN; } + "cum-tsn-ack" { return CUM_TSN_ACK; } + "num-gap-ack-blocks" { return NUM_GACK_BLOCKS; } + "num-dup-tsns" { return NUM_DUP_TSNS; } + "lowest-tsn" { return LOWEST_TSN; } + "seqno" { return SEQNO; } + "new-cum-tsn" { return NEW_CUM_TSN; } +} + +"rt" { scanner_push_start_cond(yyscanner, SCANSTATE_EXPR_RT); return RT; } +"rt0" { scanner_push_start_cond(yyscanner, SCANSTATE_EXPR_RT); return RT0; } +"rt2" { scanner_push_start_cond(yyscanner, SCANSTATE_EXPR_RT); return RT2; } +"srh" { scanner_push_start_cond(yyscanner, SCANSTATE_EXPR_RT); return RT4; } +<SCANSTATE_EXPR_RT,SCANSTATE_STMT_NAT,SCANSTATE_IP,SCANSTATE_IP6>"addr" { return ADDR; } + +"hbh" { scanner_push_start_cond(yyscanner, SCANSTATE_EXPR_HBH); return HBH; } + +"frag" { scanner_push_start_cond(yyscanner, SCANSTATE_EXPR_FRAG); return FRAG; } +<SCANSTATE_EXPR_FRAG>{ + "reserved2" { return RESERVED2; } + "more-fragments" { return MORE_FRAGMENTS; } +} -"dst" { return DST; } +"dst" { scanner_push_start_cond(yyscanner, SCANSTATE_EXPR_DST); return DST; } -"mh" { return MH; } +"mh" { scanner_push_start_cond(yyscanner, SCANSTATE_EXPR_MH); return MH; } -"meta" { return META; } +"meta" { scanner_push_start_cond(yyscanner, SCANSTATE_META); return META; } "mark" { return MARK; } "iif" { return IIF; } "iifname" { return IIFNAME; } @@ -526,73 +742,102 @@ addrstring ({macaddr}|{ip4addr}|{ip6addr}) "oifgroup" { return OIFGROUP; } "cgroup" { return CGROUP; } -"classid" { return CLASSID; } -"nexthop" { return NEXTHOP; } - -"ct" { return CT; } -"l3proto" { return L3PROTOCOL; } -"proto-src" { return PROTO_SRC; } -"proto-dst" { return PROTO_DST; } -"zone" { return ZONE; } -"original" { return ORIGINAL; } -"reply" { return REPLY; } -"direction" { return DIRECTION; } -"event" { return EVENT; } -"expectation" { return EXPECTATION; } -"expiration" { return EXPIRATION; } -"helper" { return HELPER; } -"helpers" { return HELPERS; } -"label" { return LABEL; } -"state" { return STATE; } -"status" { return STATUS; } - -"numgen" { return NUMGEN; } -"inc" { return INC; } -"mod" { return MOD; } -"offset" { return OFFSET; } - -"jhash" { return JHASH; } -"symhash" { return SYMHASH; } -"seed" { return SEED; } - -"dup" { return DUP; } -"fwd" { return FWD; } - -"fib" { return FIB; } - -"osf" { return OSF; } - -"synproxy" { return SYNPROXY; } -"mss" { return MSS; } -"wscale" { return WSCALE; } -"sack-perm" { return SACKPERM; } +<SCANSTATE_EXPR_RT>{ + "nexthop" { return NEXTHOP; } + "seg-left" { return SEG_LEFT; } + "mtu" { return MTU; } + "last-entry" { return LAST_ENT; } + "tag" { return TAG; } + "sid" { return SID; } +} +<SCANSTATE_EXPR_RT,SCANSTATE_TYPE>{ + "classid" { return CLASSID; } +} + +"ct" { scanner_push_start_cond(yyscanner, SCANSTATE_CT); return CT; } +<SCANSTATE_CT>{ + "avgpkt" { return AVGPKT; } + "l3proto" { return L3PROTOCOL; } + "proto-src" { return PROTO_SRC; } + "proto-dst" { return PROTO_DST; } + "zone" { return ZONE; } + "original" { return ORIGINAL; } + "reply" { return REPLY; } + "direction" { return DIRECTION; } + "event" { return EVENT; } + "expectation" { return EXPECTATION; } + "expiration" { return EXPIRATION; } + "helper" { return HELPER; } + "helpers" { return HELPERS; } + "label" { return LABEL; } + "state" { return STATE; } + "status" { return STATUS; } + "count" { return COUNT; } +} + +"numgen" { scanner_push_start_cond(yyscanner, SCANSTATE_EXPR_NUMGEN); return NUMGEN; } +<SCANSTATE_EXPR_NUMGEN>{ + "inc" { return INC; } +} + +"jhash" { scanner_push_start_cond(yyscanner, SCANSTATE_EXPR_HASH); return JHASH; } +"symhash" { scanner_push_start_cond(yyscanner, SCANSTATE_EXPR_HASH); return SYMHASH; } + +<SCANSTATE_EXPR_HASH>{ + "seed" { return SEED; } +} +<SCANSTATE_EXPR_HASH,SCANSTATE_EXPR_NUMGEN>{ + "mod" { return MOD; } + "offset" { return OFFSET; } +} +"dup" { scanner_push_start_cond(yyscanner, SCANSTATE_STMT_DUP); return DUP; } +"fwd" { scanner_push_start_cond(yyscanner, SCANSTATE_STMT_FWD); return FWD; } + +"fib" { scanner_push_start_cond(yyscanner, SCANSTATE_EXPR_FIB); return FIB; } + +<SCANSTATE_EXPR_FIB>{ + "check" { return CHECK; } +} + +"osf" { scanner_push_start_cond(yyscanner, SCANSTATE_EXPR_OSF); return OSF; } + +"synproxy" { scanner_push_start_cond(yyscanner, SCANSTATE_STMT_SYNPROXY); return SYNPROXY; } +<SCANSTATE_STMT_SYNPROXY>{ + "wscale" { return WSCALE; } + "maxseg" { return MSS; } + "mss" { return MSS; } + "timestamp" { return TIMESTAMP; } + "sack-permitted" { return SACK_PERM; } + "sack-perm" { return SACK_PERM; } +} "notrack" { return NOTRACK; } -"options" { return OPTIONS; } "all" { return ALL; } -"xml" { return XML; } -"json" { return JSON; } -"vm" { return VM; } +<SCANSTATE_CMD_EXPORT,SCANSTATE_CMD_IMPORT,SCANSTATE_CMD_MONITOR>{ + "xml" { return XML; } + "json" { return JSON; } + "vm" { return VM; } +} "exists" { return EXISTS; } "missing" { return MISSING; } "exthdr" { return EXTHDR; } -"ipsec" { return IPSEC; } -"mode" { return MODE; } -"reqid" { return REQID; } -"spnum" { return SPNUM; } -"transport" { return TRANSPORT; } -"tunnel" { return TUNNEL; } +"ipsec" { scanner_push_start_cond(yyscanner, SCANSTATE_EXPR_IPSEC); return IPSEC; } +<SCANSTATE_EXPR_IPSEC>{ + "reqid" { return REQID; } + "spnum" { return SPNUM; } -"in" { return IN; } -"out" { return OUT; } + "in" { return IN; } + "out" { return OUT; } +} + +"secmark" { scanner_push_start_cond(yyscanner, SCANSTATE_SECMARK); return SECMARK; } -"secmark" { return SECMARK; } -"secmarks" { return SECMARKS; } +"xt" { scanner_push_start_cond(yyscanner, SCANSTATE_XT); return XT; } {addrstring} { yylval->string = xstrdup(yytext); @@ -610,9 +855,9 @@ addrstring ({macaddr}|{ip4addr}|{ip6addr}) return STRING; } -{numberstring} { +{hexstring} { errno = 0; - yylval->val = strtoull(yytext, NULL, 0); + yylval->val = strtoull(yytext, NULL, 16); if (errno != 0) { yylval->string = xstrdup(yytext); return STRING; @@ -620,6 +865,19 @@ addrstring ({macaddr}|{ip4addr}|{ip6addr}) return NUM; } +{decstring} { + int base = yytext[0] == '0' ? 8 : 10; + char *end; + + errno = 0; + yylval->val = strtoull(yytext, &end, base); + if (errno != 0 || *end) { + yylval->string = xstrdup(yytext); + return STRING; + } + return NUM; + } + {classid}/[ \t\n:\-},] { yylval->string = xstrdup(yytext); return STRING; @@ -641,6 +899,8 @@ addrstring ({macaddr}|{ip4addr}|{ip6addr}) return STRING; } +{newline_crlf} { return CRLF; } + \\{newline} { reset_pos(yyget_extra(yyscanner), yylloc); } @@ -652,6 +912,9 @@ addrstring ({macaddr}|{ip4addr}|{ip6addr}) {tab}+ {space}+ +{comment_line} { + reset_pos(yyget_extra(yyscanner), yylloc); + } {comment} <<EOF>> { @@ -668,18 +931,22 @@ addrstring ({macaddr}|{ip4addr}|{ip6addr}) static void scanner_push_indesc(struct parser_state *state, struct input_descriptor *indesc) { - state->indescs[state->indesc_idx] = indesc; - state->indesc = state->indescs[state->indesc_idx++]; + if (!state->indesc) + list_add_tail(&indesc->list, &state->indesc_list); + else + list_add(&indesc->list, &state->indesc->list); + + state->indesc = indesc; } static void scanner_pop_indesc(struct parser_state *state) { - state->indesc_idx--; - - if (state->indesc_idx > 0) - state->indesc = state->indescs[state->indesc_idx - 1]; - else + if (!list_is_first(&state->indesc->list, &state->indesc_list)) { + state->indesc = list_entry(state->indesc->list.prev, + struct input_descriptor, list); + } else { state->indesc = NULL; + } } static void scanner_pop_buffer(yyscan_t scanner) @@ -691,13 +958,15 @@ static void scanner_pop_buffer(yyscan_t scanner) } static void scanner_push_file(struct nft_ctx *nft, void *scanner, - const char *filename, const struct location *loc) + FILE *f, const char *filename, + const struct location *loc, + const struct input_descriptor *parent_indesc) { struct parser_state *state = yyget_extra(scanner); struct input_descriptor *indesc; YY_BUFFER_STATE b; - b = yy_create_buffer(nft->f[state->indesc_idx], YY_BUF_SIZE, scanner); + b = yy_create_buffer(f, YY_BUF_SIZE, scanner); yypush_buffer_state(b, scanner); indesc = xzalloc(sizeof(struct input_descriptor)); @@ -706,33 +975,100 @@ static void scanner_push_file(struct nft_ctx *nft, void *scanner, indesc->location = *loc; indesc->type = INDESC_FILE; indesc->name = xstrdup(filename); + indesc->f = f; + if (!parent_indesc) { + indesc->depth = 1; + } else { + indesc->depth = parent_indesc->depth + 1; + } init_pos(indesc); scanner_push_indesc(state, indesc); - list_add_tail(&indesc->list, &state->indesc_list); +} + +enum nft_include_type { + NFT_INCLUDE, + NFT_CMDLINE, +}; + +static bool __is_useable(unsigned int type, enum nft_include_type t) +{ + type &= S_IFMT; + switch (type) { + case S_IFREG: return true; + case S_IFIFO: + return t == NFT_CMDLINE; /* disallow include /path/to/fifo */ + default: + break; + } + + return false; +} + +/* need to use stat() to, fopen() will block for named fifos */ +static bool filename_is_useable(const char *name) +{ + struct stat sb; + int err; + + err = stat(name, &sb); + if (err) + return false; + + return __is_useable(sb.st_mode, NFT_INCLUDE); +} + +static bool fp_is_useable(FILE *fp, enum nft_include_type t) +{ + int fd = fileno(fp); + struct stat sb; + int err; + + if (fd < 0) + return false; + + err = fstat(fd, &sb); + if (err < 0) + return false; + + return __is_useable(sb.st_mode, t); } static int include_file(struct nft_ctx *nft, void *scanner, - const char *filename, const struct location *loc) + const char *filename, const struct location *loc, + const struct input_descriptor *parent_indesc, + enum nft_include_type includetype) + { struct parser_state *state = yyget_extra(scanner); struct error_record *erec; FILE *f; - if (state->indesc_idx == MAX_INCLUDE_DEPTH) { + if (parent_indesc && parent_indesc->depth == MAX_INCLUDE_DEPTH) { erec = error(loc, "Include nested too deeply, max %u levels", MAX_INCLUDE_DEPTH); goto err; } + if (includetype == NFT_INCLUDE && !filename_is_useable(filename)) { + erec = error(loc, "Not a regular file: \"%s\"\n", filename); + goto err; + } + f = fopen(filename, "r"); if (f == NULL) { erec = error(loc, "Could not open file \"%s\": %s\n", filename, strerror(errno)); goto err; } - nft->f[state->indesc_idx] = f; - scanner_push_file(nft, scanner, filename, loc); + + if (!fp_is_useable(f, includetype)) { + fclose(f); + erec = error(loc, "Not a regular file: \"%s\"\n", filename); + goto err; + } + + scanner_push_file(nft, scanner, f, filename, loc, parent_indesc); return 0; err: erec_queue(erec, state->msgs); @@ -743,6 +1079,7 @@ static int include_glob(struct nft_ctx *nft, void *scanner, const char *pattern, const struct location *loc) { struct parser_state *state = yyget_extra(scanner); + struct input_descriptor *indesc = state->indesc; struct error_record *erec = NULL; bool wildcard = false; glob_t glob_data; @@ -791,7 +1128,7 @@ static int include_glob(struct nft_ctx *nft, void *scanner, const char *pattern, ret = glob(pattern, flags, NULL, &glob_data); if (ret == 0) { char *path; - int len; + size_t len; /* reverse alphabetical order due to stack */ for (i = glob_data.gl_pathc; i > 0; i--) { @@ -803,7 +1140,7 @@ static int include_glob(struct nft_ctx *nft, void *scanner, const char *pattern, if (len == 0 || path[len - 1] == '/') continue; - ret = include_file(nft, scanner, path, loc); + ret = include_file(nft, scanner, path, loc, indesc, NFT_INCLUDE); if (ret != 0) goto err; } @@ -840,7 +1177,7 @@ err: int scanner_read_file(struct nft_ctx *nft, const char *filename, const struct location *loc) { - return include_file(nft, nft->scanner, filename, loc); + return include_file(nft, nft->scanner, filename, loc, NULL, NFT_CMDLINE); } static bool search_in_include_path(const char *filename) @@ -850,39 +1187,58 @@ static bool search_in_include_path(const char *filename) filename[0] != '/'); } +static int include_path_glob(struct nft_ctx *nft, void *scanner, + const char *include_path, const char *filename, + const struct location *loc) +{ + struct parser_state *state = yyget_extra(scanner); + struct error_record *erec; + char buf[PATH_MAX]; + int ret; + + ret = snprintf(buf, sizeof(buf), "%s/%s", include_path, filename); + if (ret < 0 || ret >= PATH_MAX) { + erec = error(loc, "Too long file path \"%s/%s\"\n", + include_path, filename); + erec_queue(erec, state->msgs); + return -1; + } + + ret = include_glob(nft, scanner, buf, loc); + + /* error was already handled */ + if (ret == -1) + return -1; + /* no wildcards and file was processed: break early. */ + if (ret == 0) + return 0; + + /* else 1 (no wildcards) or 2 (wildcards): keep + * searching. + */ + return ret; +} + int scanner_include_file(struct nft_ctx *nft, void *scanner, const char *filename, const struct location *loc) { struct parser_state *state = yyget_extra(scanner); struct error_record *erec; - char buf[PATH_MAX]; unsigned int i; int ret = -1; if (search_in_include_path(filename)) { for (i = 0; i < nft->num_include_paths; i++) { - ret = snprintf(buf, sizeof(buf), "%s/%s", - nft->include_paths[i], filename); - if (ret < 0 || ret >= PATH_MAX) { - erec = error(loc, "Too long file path \"%s/%s\"\n", - nft->include_paths[i], filename); - erec_queue(erec, state->msgs); - return -1; - } - - ret = include_glob(nft, scanner, buf, loc); - - /* error was already handled */ - if (ret == -1) - return -1; - /* no wildcards and file was processed: break early. */ - if (ret == 0) - return 0; - - /* else 1 (no wildcards) or 2 (wildcards): keep - * searching. - */ + ret = include_path_glob(nft, scanner, + nft->include_paths[i], + filename, loc); + if (ret <= 0) + return ret; } + ret = include_path_glob(nft, scanner, DEFAULT_INCLUDE_PATH, + filename, loc); + if (ret <= 0) + return ret; } else { /* an absolute path (starts with '/') */ ret = include_glob(nft, scanner, filename, loc); @@ -906,16 +1262,14 @@ void scanner_push_buffer(void *scanner, const struct input_descriptor *indesc, const char *buffer) { struct parser_state *state = yyget_extra(scanner); + struct input_descriptor *new_indesc; YY_BUFFER_STATE b; - state->indesc = xzalloc(sizeof(struct input_descriptor)); - state->indescs[state->indesc_idx] = state->indesc; - state->indesc_idx++; - - memcpy(state->indesc, indesc, sizeof(*state->indesc)); - state->indesc->data = buffer; - state->indesc->name = NULL; - list_add_tail(&state->indesc->list, &state->indesc_list); + new_indesc = xzalloc(sizeof(struct input_descriptor)); + memcpy(new_indesc, indesc, sizeof(*new_indesc)); + new_indesc->data = buffer; + new_indesc->name = xstrdup(indesc->name); + scanner_push_indesc(state, new_indesc); b = yy_scan_string(buffer, scanner); assert(b != NULL); @@ -929,14 +1283,16 @@ void *scanner_init(struct parser_state *state) yylex_init_extra(state, &scanner); yyset_out(NULL, scanner); + state->startcond_active = xzalloc_array(__SC_MAX, + sizeof(*state->startcond_active)); return scanner; } static void input_descriptor_destroy(const struct input_descriptor *indesc) { if (indesc->name) - xfree(indesc->name); - xfree(indesc); + free_const(indesc->name); + free_const(indesc); } static void input_descriptor_list_destroy(struct parser_state *state) @@ -944,6 +1300,10 @@ static void input_descriptor_list_destroy(struct parser_state *state) struct input_descriptor *indesc, *next; list_for_each_entry_safe(indesc, next, &state->indesc_list, list) { + if (indesc->f) { + fclose(indesc->f); + indesc->f = NULL; + } list_del(&indesc->list); input_descriptor_destroy(indesc); } @@ -953,15 +1313,44 @@ void scanner_destroy(struct nft_ctx *nft) { struct parser_state *state = yyget_extra(nft->scanner); - do { - yypop_buffer_state(nft->scanner); - - if (nft->f[state->indesc_idx]) { - fclose(nft->f[state->indesc_idx]); - nft->f[state->indesc_idx] = NULL; - } - } while (state->indesc_idx--); - input_descriptor_list_destroy(state); + free(state->startcond_active); + yylex_destroy(nft->scanner); } + +static void scanner_push_start_cond(void *scanner, enum startcond_type type) +{ + struct parser_state *state = yyget_extra(scanner); + + state->startcond_type = type; + state->startcond_active[type]++; + + yy_push_state((int)type, scanner); +} + +void scanner_pop_start_cond(void *scanner, enum startcond_type t) +{ + struct parser_state *state = yyget_extra(scanner); + + state->startcond_active[t]--; + + if (state->startcond_type != t) { + state->flex_state_pop++; + return; /* Can't pop just yet! */ + } + + while (state->flex_state_pop) { + state->flex_state_pop--; + state->startcond_type = yy_top_state(scanner); + yy_pop_state(scanner); + + t = state->startcond_type; + if (state->startcond_active[t]) + return; + } + + state->startcond_type = yy_top_state(scanner); + + yy_pop_state(scanner); +} diff --git a/src/sctp_chunk.c b/src/sctp_chunk.c new file mode 100644 index 00000000..24a07e20 --- /dev/null +++ b/src/sctp_chunk.c @@ -0,0 +1,262 @@ +/* + * Copyright Red Hat + * + * 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. + */ + +#include <nft.h> + +#include <exthdr.h> +#include <sctp_chunk.h> + + +#define PHT(__token, __offset, __len) \ + PROTO_HDR_TEMPLATE(__token, &integer_type, BYTEORDER_BIG_ENDIAN, \ + __offset, __len) + +static const struct exthdr_desc sctp_chunk_data = { + .name = "data", + .type = SCTP_CHUNK_TYPE_DATA, + .templates = { + [SCTP_CHUNK_COMMON_TYPE] = PHT("type", 0, 8), + [SCTP_CHUNK_COMMON_FLAGS] = PHT("flags", 8, 8), + [SCTP_CHUNK_COMMON_LENGTH] = PHT("length", 16, 16), + [SCTP_CHUNK_DATA_TSN] = PHT("tsn", 32, 32), + [SCTP_CHUNK_DATA_STREAM] = PHT("stream", 64, 16), + [SCTP_CHUNK_DATA_SSN] = PHT("ssn", 80, 16), + [SCTP_CHUNK_DATA_PPID] = PHT("ppid", 96, 32), + }, +}; + +static const struct exthdr_desc sctp_chunk_init = { + .name = "init", + .type = SCTP_CHUNK_TYPE_INIT, + .templates = { + [SCTP_CHUNK_COMMON_TYPE] = PHT("type", 0, 8), + [SCTP_CHUNK_COMMON_FLAGS] = PHT("flags", 8, 8), + [SCTP_CHUNK_COMMON_LENGTH] = PHT("length", 16, 16), + [SCTP_CHUNK_INIT_TAG] = PHT("init-tag", 32, 32), + [SCTP_CHUNK_INIT_RWND] = PHT("a-rwnd", 64, 32), + [SCTP_CHUNK_INIT_OSTREAMS] = PHT("num-outbound-streams", 96, 16), + [SCTP_CHUNK_INIT_ISTREAMS] = PHT("num-inbound-streams", 112, 16), + [SCTP_CHUNK_INIT_TSN] = PHT("initial-tsn", 128, 32), + }, +}; + +static const struct exthdr_desc sctp_chunk_init_ack = { + .name = "init-ack", + .type = SCTP_CHUNK_TYPE_INIT_ACK, + .templates = { + [SCTP_CHUNK_COMMON_TYPE] = PHT("type", 0, 8), + [SCTP_CHUNK_COMMON_FLAGS] = PHT("flags", 8, 8), + [SCTP_CHUNK_COMMON_LENGTH] = PHT("length", 16, 16), + [SCTP_CHUNK_INIT_TAG] = PHT("init-tag", 32, 32), + [SCTP_CHUNK_INIT_RWND] = PHT("a-rwnd", 64, 32), + [SCTP_CHUNK_INIT_OSTREAMS] = PHT("num-outbound-streams", 96, 16), + [SCTP_CHUNK_INIT_ISTREAMS] = PHT("num-inbound-streams", 112, 16), + [SCTP_CHUNK_INIT_TSN] = PHT("initial-tsn", 128, 32), + }, +}; + +static const struct exthdr_desc sctp_chunk_sack = { + .name = "sack", + .type = SCTP_CHUNK_TYPE_SACK, + .templates = { + [SCTP_CHUNK_COMMON_TYPE] = PHT("type", 0, 8), + [SCTP_CHUNK_COMMON_FLAGS] = PHT("flags", 8, 8), + [SCTP_CHUNK_COMMON_LENGTH] = PHT("length", 16, 16), + [SCTP_CHUNK_SACK_CTSN_ACK] = PHT("cum-tsn-ack", 32, 32), + [SCTP_CHUNK_SACK_RWND] = PHT("a-rwnd", 64, 32), + [SCTP_CHUNK_SACK_GACK_BLOCKS] = PHT("num-gap-ack-blocks", 96, 16), + [SCTP_CHUNK_SACK_DUP_TSNS] = PHT("num-dup-tsns", 112, 16), + }, +}; + +static const struct exthdr_desc sctp_chunk_shutdown = { + .name = "shutdown", + .type = SCTP_CHUNK_TYPE_SHUTDOWN, + .templates = { + [SCTP_CHUNK_COMMON_TYPE] = PHT("type", 0, 8), + [SCTP_CHUNK_COMMON_FLAGS] = PHT("flags", 8, 8), + [SCTP_CHUNK_COMMON_LENGTH] = PHT("length", 16, 16), + [SCTP_CHUNK_SHUTDOWN_CTSN_ACK] = PHT("cum-tsn-ack", 32, 32), + }, +}; + +static const struct exthdr_desc sctp_chunk_ecne = { + .name = "ecne", + .type = SCTP_CHUNK_TYPE_ECNE, + .templates = { + [SCTP_CHUNK_COMMON_TYPE] = PHT("type", 0, 8), + [SCTP_CHUNK_COMMON_FLAGS] = PHT("flags", 8, 8), + [SCTP_CHUNK_COMMON_LENGTH] = PHT("length", 16, 16), + [SCTP_CHUNK_ECNE_CWR_MIN_TSN] = PHT("lowest-tsn", 32, 32), + }, +}; + +static const struct exthdr_desc sctp_chunk_cwr = { + .name = "cwr", + .type = SCTP_CHUNK_TYPE_CWR, + .templates = { + [SCTP_CHUNK_COMMON_TYPE] = PHT("type", 0, 8), + [SCTP_CHUNK_COMMON_FLAGS] = PHT("flags", 8, 8), + [SCTP_CHUNK_COMMON_LENGTH] = PHT("length", 16, 16), + [SCTP_CHUNK_ECNE_CWR_MIN_TSN] = PHT("lowest-tsn", 32, 32), + }, +}; + +static const struct exthdr_desc sctp_chunk_asconf_ack = { + .name = "asconf-ack", + .type = SCTP_CHUNK_TYPE_ASCONF_ACK, + .templates = { + [SCTP_CHUNK_COMMON_TYPE] = PHT("type", 0, 8), + [SCTP_CHUNK_COMMON_FLAGS] = PHT("flags", 8, 8), + [SCTP_CHUNK_COMMON_LENGTH] = PHT("length", 16, 16), + [SCTP_CHUNK_ASCONF_SEQNO] = PHT("seqno", 32, 32), + }, +}; + +static const struct exthdr_desc sctp_chunk_forward_tsn = { + .name = "forward-tsn", + .type = SCTP_CHUNK_TYPE_FORWARD_TSN, + .templates = { + [SCTP_CHUNK_COMMON_TYPE] = PHT("type", 0, 8), + [SCTP_CHUNK_COMMON_FLAGS] = PHT("flags", 8, 8), + [SCTP_CHUNK_COMMON_LENGTH] = PHT("length", 16, 16), + [SCTP_CHUNK_FORWARD_TSN_NCTSN] = PHT("new-cum-tsn", 32, 32), + }, +}; + +static const struct exthdr_desc sctp_chunk_asconf = { + .name = "asconf", + .type = SCTP_CHUNK_TYPE_ASCONF, + .templates = { + [SCTP_CHUNK_COMMON_TYPE] = PHT("type", 0, 8), + [SCTP_CHUNK_COMMON_FLAGS] = PHT("flags", 8, 8), + [SCTP_CHUNK_COMMON_LENGTH] = PHT("length", 16, 16), + [SCTP_CHUNK_ASCONF_SEQNO] = PHT("seqno", 32, 32), + }, +}; + +#define SCTP_CHUNK_DESC_GENERATOR(descname, hname, desctype) \ +static const struct exthdr_desc sctp_chunk_##descname = { \ + .name = #hname, \ + .type = SCTP_CHUNK_TYPE_##desctype, \ + .templates = { \ + [SCTP_CHUNK_COMMON_TYPE] = PHT("type", 0, 8), \ + [SCTP_CHUNK_COMMON_FLAGS] = PHT("flags", 8, 8), \ + [SCTP_CHUNK_COMMON_LENGTH] = PHT("length", 16, 16),\ + }, \ +}; + +SCTP_CHUNK_DESC_GENERATOR(heartbeat, heartbeat, HEARTBEAT) +SCTP_CHUNK_DESC_GENERATOR(heartbeat_ack, heartbeat-ack, HEARTBEAT_ACK) +SCTP_CHUNK_DESC_GENERATOR(abort, abort, ABORT) +SCTP_CHUNK_DESC_GENERATOR(shutdown_ack, shutdown-ack, SHUTDOWN_ACK) +SCTP_CHUNK_DESC_GENERATOR(error, error, ERROR) +SCTP_CHUNK_DESC_GENERATOR(cookie_echo, cookie-echo, COOKIE_ECHO) +SCTP_CHUNK_DESC_GENERATOR(cookie_ack, cookie-ack, COOKIE_ACK) +SCTP_CHUNK_DESC_GENERATOR(shutdown_complete, shutdown-complete, SHUTDOWN_COMPLETE) + +#undef SCTP_CHUNK_DESC_GENERATOR + +static const struct exthdr_desc *sctp_chunk_protocols[] = { + [SCTP_CHUNK_TYPE_DATA] = &sctp_chunk_data, + [SCTP_CHUNK_TYPE_INIT] = &sctp_chunk_init, + [SCTP_CHUNK_TYPE_INIT_ACK] = &sctp_chunk_init_ack, + [SCTP_CHUNK_TYPE_SACK] = &sctp_chunk_sack, + [SCTP_CHUNK_TYPE_HEARTBEAT] = &sctp_chunk_heartbeat, + [SCTP_CHUNK_TYPE_HEARTBEAT_ACK] = &sctp_chunk_heartbeat_ack, + [SCTP_CHUNK_TYPE_ABORT] = &sctp_chunk_abort, + [SCTP_CHUNK_TYPE_SHUTDOWN] = &sctp_chunk_shutdown, + [SCTP_CHUNK_TYPE_SHUTDOWN_ACK] = &sctp_chunk_shutdown_ack, + [SCTP_CHUNK_TYPE_ERROR] = &sctp_chunk_error, + [SCTP_CHUNK_TYPE_COOKIE_ECHO] = &sctp_chunk_cookie_echo, + [SCTP_CHUNK_TYPE_COOKIE_ACK] = &sctp_chunk_cookie_ack, + [SCTP_CHUNK_TYPE_ECNE] = &sctp_chunk_ecne, + [SCTP_CHUNK_TYPE_CWR] = &sctp_chunk_cwr, + [SCTP_CHUNK_TYPE_SHUTDOWN_COMPLETE] = &sctp_chunk_shutdown_complete, + [SCTP_CHUNK_TYPE_ASCONF_ACK] = &sctp_chunk_asconf_ack, + [SCTP_CHUNK_TYPE_FORWARD_TSN] = &sctp_chunk_forward_tsn, + [SCTP_CHUNK_TYPE_ASCONF] = &sctp_chunk_asconf, +}; + +const struct exthdr_desc *sctp_chunk_protocol_find(const char *name) +{ + unsigned int i; + + for (i = 0; i < array_size(sctp_chunk_protocols); i++) { + if (sctp_chunk_protocols[i] && + !strcmp(sctp_chunk_protocols[i]->name, name)) + return sctp_chunk_protocols[i]; + } + return NULL; +} + +struct expr *sctp_chunk_expr_alloc(const struct location *loc, + unsigned int type, unsigned int field) +{ + const struct proto_hdr_template *tmpl; + const struct exthdr_desc *desc = NULL; + struct expr *expr; + + if (type < array_size(sctp_chunk_protocols)) + desc = sctp_chunk_protocols[type]; + + if (!desc) + return NULL; + + tmpl = &desc->templates[field]; + if (!tmpl) + return NULL; + + expr = expr_alloc(loc, EXPR_EXTHDR, tmpl->dtype, + BYTEORDER_BIG_ENDIAN, tmpl->len); + expr->exthdr.desc = desc; + expr->exthdr.tmpl = tmpl; + expr->exthdr.op = NFT_EXTHDR_OP_SCTP; + expr->exthdr.raw_type = desc->type; + expr->exthdr.offset = tmpl->offset; + + return expr; +} + +void sctp_chunk_init_raw(struct expr *expr, uint8_t type, unsigned int off, + unsigned int len, uint32_t flags) +{ + const struct proto_hdr_template *tmpl; + unsigned int i; + + assert(expr->etype == EXPR_EXTHDR); + + expr->len = len; + expr->exthdr.flags = flags; + expr->exthdr.offset = off; + expr->exthdr.op = NFT_EXTHDR_OP_SCTP; + + if (flags & NFT_EXTHDR_F_PRESENT) + datatype_set(expr, &boolean_type); + else + datatype_set(expr, &integer_type); + + if (type >= array_size(sctp_chunk_protocols)) + return; + + expr->exthdr.desc = sctp_chunk_protocols[type]; + expr->exthdr.flags = flags; + assert(expr->exthdr.desc != NULL); + + for (i = 0; i < array_size(expr->exthdr.desc->templates); ++i) { + tmpl = &expr->exthdr.desc->templates[i]; + if (tmpl->offset != off || tmpl->len != len) + continue; + + if ((flags & NFT_EXTHDR_F_PRESENT) == 0) + datatype_set(expr, tmpl->dtype); + + expr->exthdr.tmpl = tmpl; + break; + } +} diff --git a/src/segtree.c b/src/segtree.c index e8e32412..70b4416c 100644 --- a/src/segtree.c +++ b/src/segtree.c @@ -8,8 +8,8 @@ * Development of this code funded by Astaro AG (http://www.astaro.com/) */ -#include <stdlib.h> -#include <string.h> +#include <nft.h> + #include <inttypes.h> #include <arpa/inet.h> @@ -19,604 +19,38 @@ #include <expression.h> #include <gmputil.h> #include <utils.h> -#include <rbtree.h> - -/** - * struct seg_tree - segment tree - * - * @root: the rbtree's root - * @type: the datatype of the dimension - * @dwidth: width of the dimension - * @byteorder: byteorder of elements - * @debug_mask: display debugging information - */ -struct seg_tree { - struct rb_root root; - const struct datatype *keytype; - unsigned int keylen; - const struct datatype *datatype; - unsigned int datalen; - enum byteorder byteorder; - unsigned int debug_mask; -}; - -enum elementary_interval_flags { - EI_F_INTERVAL_END = 0x1, - EI_F_INTERVAL_OPEN = 0x2, -}; - -/** - * struct elementary_interval - elementary interval [left, right] - * - * @rb_node: seg_tree rb node - * @list: list node for linearized tree - * @left: left endpoint - * @right: right endpoint - * @size: interval size (right - left) - * @flags: flags - * @expr: associated expression - */ -struct elementary_interval { - union { - struct rb_node rb_node; - struct list_head list; - }; - - mpz_t left; - mpz_t right; - mpz_t size; - - enum elementary_interval_flags flags; - struct expr *expr; -}; - -static void seg_tree_init(struct seg_tree *tree, const struct set *set, - struct expr *init, unsigned int debug_mask) -{ - struct expr *first; - - first = list_entry(init->expressions.next, struct expr, list); - tree->root = RB_ROOT; - tree->keytype = set->key->dtype; - tree->keylen = set->key->len; - tree->datatype = NULL; - tree->datalen = 0; - if (set->data) { - tree->datatype = set->data->dtype; - tree->datalen = set->data->len; - } - tree->byteorder = first->byteorder; - tree->debug_mask = debug_mask; -} - -static struct elementary_interval *ei_alloc(const mpz_t left, const mpz_t right, - struct expr *expr, - enum elementary_interval_flags flags) -{ - struct elementary_interval *ei; - - ei = xzalloc(sizeof(*ei)); - mpz_init_set(ei->left, left); - mpz_init_set(ei->right, right); - mpz_init(ei->size); - mpz_sub(ei->size, right, left); - if (expr != NULL) - ei->expr = expr_get(expr); - ei->flags = flags; - return ei; -} -static void ei_destroy(struct elementary_interval *ei) +static enum byteorder get_key_byteorder(const struct expr *e) { - mpz_clear(ei->left); - mpz_clear(ei->right); - mpz_clear(ei->size); - if (ei->expr != NULL) - expr_free(ei->expr); - xfree(ei); -} - -/** - * ei_lookup - find elementary interval containing point p - * - * @tree: segment tree - * @p: the point - */ -static struct elementary_interval *ei_lookup(struct seg_tree *tree, const mpz_t p) -{ - struct rb_node *n = tree->root.rb_node; - struct elementary_interval *ei; - - while (n != NULL) { - ei = rb_entry(n, struct elementary_interval, rb_node); - - if (mpz_cmp(p, ei->left) >= 0 && - mpz_cmp(p, ei->right) <= 0) - return ei; - else if (mpz_cmp(p, ei->left) <= 0) - n = n->rb_left; - else if (mpz_cmp(p, ei->right) > 0) - n = n->rb_right; - } - return NULL; -} - -static void ei_remove(struct seg_tree *tree, struct elementary_interval *ei) -{ - rb_erase(&ei->rb_node, &tree->root); -} - -static void __ei_insert(struct seg_tree *tree, struct elementary_interval *new) -{ - struct rb_node **p = &tree->root.rb_node; - struct rb_node *parent = NULL; - struct elementary_interval *ei; - - while (*p != NULL) { - parent = *p; - ei = rb_entry(parent, struct elementary_interval, rb_node); - - if (mpz_cmp(new->left, ei->left) >= 0 && - mpz_cmp(new->left, ei->right) <= 0) - break; - else if (mpz_cmp(new->left, ei->left) <= 0) - p = &(*p)->rb_left; - else if (mpz_cmp(new->left, ei->left) > 0) - p = &(*p)->rb_right; - } - - rb_link_node(&new->rb_node, parent, p); - rb_insert_color(&new->rb_node, &tree->root); -} - -static bool segtree_debug(unsigned int debug_mask) -{ - if (debug_mask & NFT_DEBUG_SEGTREE) - return true; - - return false; -} - -/** - * ei_insert - insert an elementary interval into the tree - * - * @tree: the seg_tree - * @new: the elementary interval - * - * New entries take precedence over existing ones. Insertions are assumed to - * be ordered by descending interval size, meaning the new interval never - * extends over more than two existing intervals. - */ -static int ei_insert(struct list_head *msgs, struct seg_tree *tree, - struct elementary_interval *new, bool merge) -{ - struct elementary_interval *lei, *rei; - mpz_t p; - - mpz_init2(p, tree->keylen); - - /* - * Lookup the intervals containing the left and right endpoints. - */ - lei = ei_lookup(tree, new->left); - rei = ei_lookup(tree, new->right); - - if (segtree_debug(tree->debug_mask)) - pr_gmp_debug("insert: [%Zx %Zx]\n", new->left, new->right); - - if (lei != NULL && rei != NULL && lei == rei) { - if (!merge) - goto err; - /* - * The new interval is entirely contained in the same interval, - * split it into two parts: - * - * [lei_left, new_left) and (new_right, rei_right] + enum datatypes basetype = expr_basetype(e)->type; + + switch (basetype) { + case TYPE_INTEGER: + /* For ranges, integers MUST be in BYTEORDER_BIG_ENDIAN. + * If the LHS (lookup key, e.g. 'meta mark', is host endian, + * a byteorder expression is injected to convert the register + * content before lookup. */ - if (segtree_debug(tree->debug_mask)) - pr_gmp_debug("split [%Zx %Zx]\n", lei->left, lei->right); - - ei_remove(tree, lei); - - mpz_sub_ui(p, new->left, 1); - if (mpz_cmp(lei->left, p) <= 0) - __ei_insert(tree, ei_alloc(lei->left, p, lei->expr, 0)); - - mpz_add_ui(p, new->right, 1); - if (mpz_cmp(p, rei->right) < 0) - __ei_insert(tree, ei_alloc(p, rei->right, lei->expr, 0)); - ei_destroy(lei); - } else { - if (lei != NULL) { - if (!merge) - goto err; - /* - * Left endpoint is within lei, adjust it so we have: - * - * [lei_left, new_left)[new_left, new_right] - */ - if (segtree_debug(tree->debug_mask)) { - pr_gmp_debug("adjust left [%Zx %Zx]\n", - lei->left, lei->right); - } - - mpz_sub_ui(lei->right, new->left, 1); - mpz_sub(lei->size, lei->right, lei->left); - if (mpz_sgn(lei->size) < 0) { - ei_remove(tree, lei); - ei_destroy(lei); - } - } - if (rei != NULL) { - if (!merge) - goto err; - /* - * Right endpoint is within rei, adjust it so we have: - * - * [new_left, new_right](new_right, rei_right] - */ - if (segtree_debug(tree->debug_mask)) { - pr_gmp_debug("adjust right [%Zx %Zx]\n", - rei->left, rei->right); - } - - mpz_add_ui(rei->left, new->right, 1); - mpz_sub(rei->size, rei->right, rei->left); - if (mpz_sgn(rei->size) < 0) { - ei_remove(tree, rei); - ei_destroy(rei); - } - } - } - - __ei_insert(tree, new); - - mpz_clear(p); - - return 0; -err: - errno = EEXIST; - return expr_binary_error(msgs, lei->expr, new->expr, - "conflicting intervals specified"); -} - -/* - * Sort intervals according to their priority, which is defined inversely to - * their size. - * - * The beginning of the interval is used as secondary sorting criterion. This - * makes sure that overlapping ranges with equal priority are next to each - * other, allowing to easily detect unsolvable conflicts during insertion. - * - * Note: unsolvable conflicts can only occur when using ranges or two identical - * prefix specifications. - */ -static int interval_cmp(const void *p1, const void *p2) -{ - const struct elementary_interval *e1 = *(void * const *)p1; - const struct elementary_interval *e2 = *(void * const *)p2; - mpz_t d; - int ret; - - mpz_init(d); - - mpz_sub(d, e2->size, e1->size); - if (mpz_cmp_ui(d, 0)) - ret = mpz_sgn(d); - else - ret = mpz_cmp(e1->left, e2->left); - - mpz_clear(d); - return ret; -} - -static unsigned int expr_to_intervals(const struct expr *set, - unsigned int keylen, - struct elementary_interval **intervals) -{ - struct elementary_interval *ei; - struct expr *i, *next; - unsigned int n; - mpz_t low, high; - - mpz_init2(low, keylen); - mpz_init2(high, keylen); - - /* - * Convert elements to intervals. - */ - n = 0; - list_for_each_entry_safe(i, next, &set->expressions, list) { - range_expr_value_low(low, i); - range_expr_value_high(high, i); - ei = ei_alloc(low, high, i, 0); - intervals[n++] = ei; - } - mpz_clear(high); - mpz_clear(low); - - return n; -} - -static bool intervals_match(const struct elementary_interval *e1, - const struct elementary_interval *e2) -{ - return mpz_cmp(e1->left, e2->left) == 0 && - mpz_cmp(e1->right, e2->right) == 0; -} - -/* This function checks for overlaps in two ways: - * - * 1) A new interval end intersects an existing interval. - * 2) New intervals that are larger than existing ones, that don't intersect - * at all, but that wrap the existing ones. - */ -static bool interval_overlap(const struct elementary_interval *e1, - const struct elementary_interval *e2) -{ - if (intervals_match(e1, e2)) - return false; - - return (mpz_cmp(e1->left, e2->left) >= 0 && - mpz_cmp(e1->left, e2->right) <= 0) || - (mpz_cmp(e1->right, e2->left) >= 0 && - mpz_cmp(e1->right, e2->right) <= 0) || - (mpz_cmp(e1->left, e2->left) <= 0 && - mpz_cmp(e1->right, e2->right) >= 0); -} - -static int set_overlap(struct list_head *msgs, const struct set *set, - struct expr *init, unsigned int keylen, bool add) -{ - struct elementary_interval *new_intervals[init->size]; - struct elementary_interval *intervals[set->init->size]; - unsigned int n, m, i, j; - int ret = 0; - - n = expr_to_intervals(init, keylen, new_intervals); - m = expr_to_intervals(set->init, keylen, intervals); - - for (i = 0; i < n; i++) { - bool found = false; - - for (j = 0; j < m; j++) { - if (add && interval_overlap(new_intervals[i], - intervals[j])) { - expr_error(msgs, new_intervals[i]->expr, - "interval overlaps with an existing one"); - errno = EEXIST; - ret = -1; - goto out; - } else if (!add && intervals_match(new_intervals[i], - intervals[j])) { - found = true; - break; - } - } - if (!add && !found) { - expr_error(msgs, new_intervals[i]->expr, - "interval not found in set"); - errno = ENOENT; - ret = -1; - break; - } - } -out: - for (i = 0; i < n; i++) - ei_destroy(new_intervals[i]); - for (i = 0; i < m; i++) - ei_destroy(intervals[i]); - - return ret; -} - -static int set_to_segtree(struct list_head *msgs, struct set *set, - struct expr *init, struct seg_tree *tree, - bool add, bool merge) -{ - struct elementary_interval *intervals[init->size]; - struct expr *i, *next; - unsigned int n; - int err; - - /* We are updating an existing set with new elements, check if the new - * interval overlaps with any of the existing ones. - */ - if (set->init && set->init != init) { - err = set_overlap(msgs, set, init, tree->keylen, add); - if (err < 0) - return err; - } - - n = expr_to_intervals(init, tree->keylen, intervals); - - list_for_each_entry_safe(i, next, &init->expressions, list) { - list_del(&i->list); - expr_free(i); - } - - /* - * Sort intervals by priority. - */ - qsort(intervals, n, sizeof(intervals[0]), interval_cmp); - - /* - * Insert elements into tree - */ - for (n = 0; n < init->size; n++) { - err = ei_insert(msgs, tree, intervals[n], merge); - if (err < 0) - return err; - } - - return 0; -} - -static bool segtree_needs_first_segment(const struct set *set, - const struct expr *init, bool add) -{ - if (add && !set->root) { - /* Add the first segment in four situations: - * - * 1) This is an anonymous set. - * 2) This set exists and it is empty. - * 3) New empty set and, separately, new elements are added. - * 4) This set is created with a number of initial elements. - */ - if ((set_is_anonymous(set->flags)) || - (set->init && set->init->size == 0) || - (set->init == NULL && init) || - (set->init == init)) { - return true; - } - } - /* This is an update for a set that already contains elements, so don't - * add the first non-matching elements otherwise we hit EEXIST. - */ - return false; -} - -static void segtree_linearize(struct list_head *list, const struct set *set, - const struct expr *init, struct seg_tree *tree, - bool add, bool merge) -{ - bool needs_first_segment = segtree_needs_first_segment(set, init, add); - struct elementary_interval *ei, *nei, *prev = NULL; - struct rb_node *node, *next; - mpz_t p, q; - - mpz_init2(p, tree->keylen); - mpz_init2(q, tree->keylen); - - /* - * Convert the tree of open intervals to half-closed map expressions. - */ - rb_for_each_entry_safe(ei, node, next, &tree->root, rb_node) { - if (segtree_debug(tree->debug_mask)) - pr_gmp_debug("iter: [%Zx %Zx]\n", ei->left, ei->right); - - if (prev == NULL) { - /* - * If the first segment doesn't begin at zero, insert a - * non-matching segment to cover [0, first_left). - */ - if (needs_first_segment && mpz_cmp_ui(ei->left, 0)) { - mpz_set_ui(p, 0); - mpz_sub_ui(q, ei->left, 1); - nei = ei_alloc(p, q, NULL, EI_F_INTERVAL_END); - list_add_tail(&nei->list, list); - } - } else { - /* - * If the previous segment doesn't end directly left to - * this one, insert a non-matching segment to cover - * (prev_right, ei_left). - */ - mpz_add_ui(p, prev->right, 1); - if (mpz_cmp(p, ei->left) < 0 || - (!(set->flags & NFT_SET_ANONYMOUS) && !merge)) { - mpz_sub_ui(q, ei->left, 1); - nei = ei_alloc(p, q, NULL, EI_F_INTERVAL_END); - list_add_tail(&nei->list, list); - } else if (add && merge && - ei->expr->etype != EXPR_MAPPING) { - /* Merge contiguous segments only in case of - * new additions. - */ - mpz_set(prev->right, ei->right); - ei_remove(tree, ei); - ei_destroy(ei); - continue; - } - } - - ei_remove(tree, ei); - list_add_tail(&ei->list, list); - - prev = ei; - } - - /* - * If the last segment doesn't end at the right side of the dimension, - * insert a non-matching segment to cover (last_right, end]. - */ - if (mpz_scan0(prev->right, 0) != tree->keylen) { - mpz_add_ui(p, prev->right, 1); - mpz_bitmask(q, tree->keylen); - nei = ei_alloc(p, q, NULL, EI_F_INTERVAL_END); - list_add_tail(&nei->list, list); - } else { - prev->flags |= EI_F_INTERVAL_OPEN; - } - - mpz_clear(p); - mpz_clear(q); -} - -static void set_insert_interval(struct expr *set, struct seg_tree *tree, - const struct elementary_interval *ei) -{ - struct expr *expr; - - expr = constant_expr_alloc(&internal_location, tree->keytype, - tree->byteorder, tree->keylen, NULL); - mpz_set(expr->value, ei->left); - expr = set_elem_expr_alloc(&internal_location, expr); - - if (ei->expr != NULL) { - if (ei->expr->etype == EXPR_MAPPING) { - if (ei->expr->left->comment) - expr->comment = xstrdup(ei->expr->left->comment); - if (ei->expr->left->timeout) - expr->timeout = ei->expr->left->timeout; - expr = mapping_expr_alloc(&ei->expr->location, expr, - expr_get(ei->expr->right)); - } else { - if (ei->expr->comment) - expr->comment = xstrdup(ei->expr->comment); - if (ei->expr->timeout) - expr->timeout = ei->expr->timeout; - } + return BYTEORDER_BIG_ENDIAN; + case TYPE_STRING: + return BYTEORDER_HOST_ENDIAN; + default: + break; } - if (ei->flags & EI_F_INTERVAL_END) - expr->flags |= EXPR_F_INTERVAL_END; - if (ei->flags & EI_F_INTERVAL_OPEN) - expr->elem_flags |= NFTNL_SET_ELEM_F_INTERVAL_OPEN; - - compound_expr_add(set, expr); + return BYTEORDER_INVALID; } -int set_to_intervals(struct list_head *errs, struct set *set, - struct expr *init, bool add, unsigned int debug_mask, - bool merge, struct output_ctx *octx) +static void interval_expr_copy(struct expr *dst, struct expr *src) { - struct elementary_interval *ei, *next; - struct seg_tree tree; - LIST_HEAD(list); - - seg_tree_init(&tree, set, init, debug_mask); - if (set_to_segtree(errs, set, init, &tree, add, merge) < 0) - return -1; - segtree_linearize(&list, set, init, &tree, add, merge); - - init->size = 0; - list_for_each_entry_safe(ei, next, &list, list) { - if (segtree_debug(tree.debug_mask)) { - pr_gmp_debug("list: [%.*Zx %.*Zx]\n", - 2 * tree.keylen / BITS_PER_BYTE, ei->left, - 2 * tree.keylen / BITS_PER_BYTE, ei->right); - } - set_insert_interval(init, &tree, ei); - ei_destroy(ei); - } - - if (segtree_debug(tree.debug_mask)) { - expr_print(init, octx); - pr_gmp_debug("\n"); - } - - return 0; + if (src->comment) + dst->comment = xstrdup(src->comment); + if (src->timeout) + dst->timeout = src->timeout; + if (src->expiration) + dst->expiration = src->expiration; + + list_splice_init(&src->stmt_list, &dst->stmt_list); } static void set_elem_add(const struct set *set, struct expr *init, mpz_t value, @@ -635,6 +69,7 @@ static void set_elem_add(const struct set *set, struct expr *init, mpz_t value, struct expr *get_set_intervals(const struct set *set, const struct expr *init) { + enum byteorder byteorder = get_key_byteorder(set->key); struct expr *new_init; mpz_t low, high; struct expr *i; @@ -642,13 +77,21 @@ struct expr *get_set_intervals(const struct set *set, const struct expr *init) mpz_init2(low, set->key->len); mpz_init2(high, set->key->len); - new_init = list_expr_alloc(&internal_location); + new_init = set_expr_alloc(&internal_location, NULL); - list_for_each_entry(i, &init->expressions, list) { + list_for_each_entry(i, &expr_set(init)->expressions, list) { switch (i->key->etype) { case EXPR_VALUE: set_elem_add(set, new_init, i->key->value, - i->flags, i->byteorder); + i->flags, byteorder); + break; + case EXPR_CONCAT: + compound_expr_add(new_init, expr_clone(i)); + i->flags |= EXPR_F_INTERVAL_END; + compound_expr_add(new_init, expr_clone(i)); + break; + case EXPR_SET_ELEM_CATCHALL: + compound_expr_add(new_init, expr_clone(i)); break; default: range_expr_value_low(low, i); @@ -667,79 +110,125 @@ struct expr *get_set_intervals(const struct set *set, const struct expr *init) return new_init; } -static struct expr *get_set_interval_find(const struct table *table, - const char *set_name, +static struct expr *expr_value(struct expr *expr) +{ + switch (expr->etype) { + case EXPR_MAPPING: + return expr->left->key; + case EXPR_SET_ELEM: + return expr->key; + case EXPR_VALUE: + return expr; + default: + BUG("invalid expression type %s\n", expr_name(expr)); + } +} + +static struct expr *get_set_interval_find(const struct set *cache_set, struct expr *left, struct expr *right) { + const struct set *set = cache_set; struct expr *range = NULL; - struct set *set; - mpz_t low, high; - struct expr *i; + struct expr *i, *key; + mpz_t val; - set = set_lookup(table, set_name); - mpz_init2(low, set->key->len); - mpz_init2(high, set->key->len); + mpz_init2(val, set->key->len); - list_for_each_entry(i, &set->init->expressions, list) { - switch (i->key->etype) { + list_for_each_entry(i, &expr_set(set->init)->expressions, list) { + key = expr_value(i); + switch (key->etype) { + case EXPR_VALUE: + if (expr_basetype(i->key)->type != TYPE_STRING) + break; + /* string type, check if its a range (wildcard). */ + /* fall-through */ + case EXPR_PREFIX: case EXPR_RANGE: - range_expr_value_low(low, i); - range_expr_value_high(high, i); - if (mpz_cmp(left->key->value, low) >= 0 && - mpz_cmp(right->key->value, high) <= 0) { - range = range_expr_alloc(&internal_location, - expr_clone(left->key), - expr_clone(right->key)); - goto out; - } - break; + range_expr_value_low(val, i); + if (left && mpz_cmp(expr_value(left)->value, val)) + break; + + range_expr_value_high(val, i); + if (right && mpz_cmp(expr_value(right)->value, val)) + break; + + range = expr_clone(i); + goto out; default: break; } } out: - mpz_clear(low); - mpz_clear(high); + mpz_clear(val); return range; } -static struct expr *get_set_interval_end(const struct table *table, - const char *set_name, - struct expr *left) +static struct expr *__expr_to_set_elem(struct expr *low, struct expr *expr) { - struct expr *i, *range = NULL; - struct set *set; - mpz_t low, high; + struct expr *elem = set_elem_expr_alloc(&low->location, expr); - set = set_lookup(table, set_name); - mpz_init2(low, set->key->len); - mpz_init2(high, set->key->len); + if (low->etype == EXPR_MAPPING) { + interval_expr_copy(elem, low->left); - list_for_each_entry(i, &set->init->expressions, list) { - switch (i->key->etype) { - case EXPR_RANGE: - range_expr_value_low(low, i); - if (mpz_cmp(low, left->key->value) == 0) { - range = range_expr_alloc(&internal_location, - expr_clone(left->key), - expr_clone(i->key->right)); - goto out; - } - break; - default: - break; - } + elem = mapping_expr_alloc(&low->location, elem, + expr_clone(low->right)); + } else { + interval_expr_copy(elem, low); } -out: - mpz_clear(low); - mpz_clear(high); + elem->flags |= EXPR_F_KERNEL; - return range; + return elem; +} + +static struct expr *expr_to_set_elem(struct expr *e) +{ + unsigned int len = div_round_up(e->len, BITS_PER_BYTE); + unsigned int str_len; + char data[len + 1]; + struct expr *expr; + + if (expr_basetype(expr_value(e))->type != TYPE_STRING) + return expr_clone(e); + + mpz_export_data(data, expr_value(e)->value, BYTEORDER_BIG_ENDIAN, len); + + str_len = strnlen(data, len); + if (str_len >= len || str_len == 0) + return expr_clone(e); + + data[str_len] = '*'; + + expr = constant_expr_alloc(&e->location, e->dtype, + BYTEORDER_HOST_ENDIAN, + (str_len + 1) * BITS_PER_BYTE, data); + + return __expr_to_set_elem(e, expr); +} + +static void set_compound_expr_add(struct expr *compound, struct expr *expr, struct expr *orig) +{ + struct expr *elem; + + switch (expr->etype) { + case EXPR_SET_ELEM: + list_splice_init(&orig->stmt_list, &expr->stmt_list); + compound_expr_add(compound, expr); + break; + case EXPR_MAPPING: + list_splice_init(&orig->left->stmt_list, &expr->left->stmt_list); + compound_expr_add(compound, expr); + break; + default: + elem = set_elem_expr_alloc(&orig->location, expr); + list_splice_init(&orig->stmt_list, &elem->stmt_list); + compound_expr_add(compound, elem); + break; + } } -int get_set_decompose(struct table *table, struct set *set) +int get_set_decompose(struct set *cache_set, struct set *set) { struct expr *i, *next, *range; struct expr *left = NULL; @@ -747,41 +236,46 @@ int get_set_decompose(struct table *table, struct set *set) new_init = set_expr_alloc(&internal_location, set); - list_for_each_entry_safe(i, next, &set->init->expressions, list) { + list_for_each_entry_safe(i, next, &expr_set(set->init)->expressions, list) { if (i->flags & EXPR_F_INTERVAL_END && left) { list_del(&left->list); list_del(&i->list); mpz_sub_ui(i->key->value, i->key->value, 1); - range = get_set_interval_find(table, set->handle.set.name, - left, i); + range = get_set_interval_find(cache_set, left, i); if (!range) { + expr_free(left); + expr_free(i); expr_free(new_init); errno = ENOENT; return -1; } - compound_expr_add(new_init, range); + set_compound_expr_add(new_init, range, left); + + expr_free(left); + expr_free(i); + left = NULL; } else { if (left) { - range = get_set_interval_end(table, - set->handle.set.name, - left); + range = get_set_interval_find(cache_set, + left, NULL); + if (range) - compound_expr_add(new_init, range); + set_compound_expr_add(new_init, range, left); else - compound_expr_add(new_init, - expr_clone(left)); + set_compound_expr_add(new_init, + expr_to_set_elem(left), left); } left = i; } } if (left) { - range = get_set_interval_end(table, set->handle.set.name, left); + range = get_set_interval_find(cache_set, left, NULL); if (range) - compound_expr_add(new_init, range); + set_compound_expr_add(new_init, range, left); else - compound_expr_add(new_init, expr_clone(left)); + set_compound_expr_add(new_init, expr_to_set_elem(left), left); } expr_free(set->init); @@ -803,24 +297,15 @@ static bool range_is_prefix(const mpz_t range) return ret; } -static struct expr *expr_value(struct expr *expr) -{ - switch (expr->etype) { - case EXPR_MAPPING: - return expr->left->key; - case EXPR_SET_ELEM: - return expr->key; - default: - BUG("invalid expression type %s\n", expr_name(expr)); - } -} - static int expr_value_cmp(const void *p1, const void *p2) { struct expr *e1 = *(void * const *)p1; struct expr *e2 = *(void * const *)p2; int ret; + if (expr_value(e1)->etype == EXPR_CONCAT) + return -1; + ret = mpz_cmp(expr_value(e1)->value, expr_value(e2)->value); if (ret == 0) { if (e1->flags & EXPR_F_INTERVAL_END) @@ -832,26 +317,273 @@ static int expr_value_cmp(const void *p1, const void *p2) return ret; } +/* Given start and end elements of a range, check if it can be represented as + * a single netmask, and if so, how long, by returning zero or a positive value. + */ +static int range_mask_len(const mpz_t start, const mpz_t end, unsigned int len) +{ + mpz_t tmp_start, tmp_end; + int ret; + + mpz_init_set(tmp_start, start); + mpz_init_set(tmp_end, end); + + while (mpz_cmp(tmp_start, tmp_end) <= 0 && + !mpz_tstbit(tmp_start, 0) && mpz_tstbit(tmp_end, 0) && + len--) { + mpz_fdiv_q_2exp(tmp_start, tmp_start, 1); + mpz_fdiv_q_2exp(tmp_end, tmp_end, 1); + } + + ret = !mpz_cmp(tmp_start, tmp_end) ? (int)len : -1; + + mpz_clear(tmp_start); + mpz_clear(tmp_end); + + return ret; +} + +/* Given a set with two elements (start and end), transform them into a + * concatenation of ranges. That is, from a list of start expressions and a list + * of end expressions, form a list of start - end expressions. + */ +void concat_range_aggregate(struct expr *set) +{ + struct expr *i, *start = NULL, *end, *r1, *r2, *next, *r1_next, *tmp; + struct list_head *r2_next; + int prefix_len, free_r1; + mpz_t range, p; + + list_for_each_entry_safe(i, next, &expr_set(set)->expressions, list) { + if (!start) { + start = i; + continue; + } + end = i; + + /* Walk over r1 (start expression) and r2 (end) in parallel, + * form ranges between corresponding r1 and r2 expressions, + * store them by replacing r2 expressions, and free r1 + * expressions. + */ + r2 = list_first_entry(&expr_concat(expr_value(end))->expressions, + struct expr, list); + list_for_each_entry_safe(r1, r1_next, + &expr_concat(expr_value(start))->expressions, + list) { + bool string_type = false; + + mpz_init(range); + mpz_init(p); + + r2_next = r2->list.next; + free_r1 = 0; + + if (!mpz_cmp(r1->value, r2->value)) { + free_r1 = 1; + goto next; + } + + if (expr_basetype(r1)->type == TYPE_STRING && + expr_basetype(r2)->type == TYPE_STRING) { + string_type = true; + mpz_switch_byteorder(r1->value, r1->len / BITS_PER_BYTE); + mpz_switch_byteorder(r2->value, r2->len / BITS_PER_BYTE); + } + + mpz_sub(range, r2->value, r1->value); + mpz_sub_ui(range, range, 1); + mpz_and(p, r1->value, range); + + /* Check if we are forced, or if it's anyway preferable, + * to express the range as a wildcard string, or two points + * instead of a netmask. + */ + prefix_len = range_mask_len(r1->value, r2->value, + r1->len); + if (string_type) { + mpz_switch_byteorder(r1->value, r1->len / BITS_PER_BYTE); + mpz_switch_byteorder(r2->value, r2->len / BITS_PER_BYTE); + } + + if (prefix_len >= 0 && + (prefix_len % BITS_PER_BYTE) == 0 && + string_type) { + unsigned int str_len = prefix_len / BITS_PER_BYTE; + char data[str_len + 2]; + + mpz_export_data(data, r1->value, BYTEORDER_HOST_ENDIAN, str_len); + data[str_len] = '*'; + + tmp = constant_expr_alloc(&r1->location, r1->dtype, + BYTEORDER_HOST_ENDIAN, + (str_len + 1) * BITS_PER_BYTE, data); + tmp->len = r2->len; + list_replace(&r2->list, &tmp->list); + r2_next = tmp->list.next; + expr_free(r2); + free_r1 = 1; + goto next; + } + + if (prefix_len < 0 || + !datatype_prefix_notation(r1->dtype)) { + tmp = range_expr_alloc(&r1->location, r1, + r2); + + list_replace(&r2->list, &tmp->list); + r2_next = tmp->list.next; + } else { + tmp = prefix_expr_alloc(&r1->location, r1, + prefix_len); + tmp->len = r2->len; + + list_replace(&r2->list, &tmp->list); + r2_next = tmp->list.next; + expr_free(r2); + } + +next: + mpz_clear(p); + mpz_clear(range); + + r2 = list_entry(r2_next, typeof(*r2), list); + compound_expr_remove(start, r1); + + if (free_r1) + expr_free(r1); + } + + compound_expr_remove(set, start); + expr_free(start); + start = NULL; + } +} + +static struct expr *interval_to_prefix(struct expr *low, struct expr *i, const mpz_t range) +{ + unsigned int prefix_len; + struct expr *prefix; + + prefix_len = expr_value(i)->len - mpz_scan0(range, 0); + prefix = prefix_expr_alloc(&low->location, + expr_clone(expr_value(low)), + prefix_len); + prefix->len = expr_value(i)->len; + + return __expr_to_set_elem(low, prefix); +} + +static struct expr *interval_to_string(struct expr *low, struct expr *i, const mpz_t range) +{ + unsigned int len = div_round_up(i->len, BITS_PER_BYTE); + unsigned int prefix_len, str_len; + char data[len + 2]; + struct expr *expr; + + prefix_len = expr_value(i)->len - mpz_scan0(range, 0); + + if (prefix_len > i->len || prefix_len % BITS_PER_BYTE) + return interval_to_prefix(low, i, range); + + mpz_export_data(data, expr_value(low)->value, BYTEORDER_BIG_ENDIAN, len); + + str_len = strnlen(data, len); + if (str_len >= len || str_len == 0) + return interval_to_prefix(low, i, range); + + data[str_len] = '*'; + + expr = constant_expr_alloc(&low->location, low->dtype, + BYTEORDER_HOST_ENDIAN, + len * BITS_PER_BYTE, data); + + return __expr_to_set_elem(low, expr); +} + +static struct expr *interval_to_range(struct expr *low, struct expr *i, mpz_t range) +{ + struct expr *tmp; + + tmp = constant_expr_alloc(&low->location, low->dtype, + low->byteorder, expr_value(low)->len, + NULL); + + mpz_add(range, range, expr_value(low)->value); + mpz_set(tmp->value, range); + + tmp = range_expr_alloc(&low->location, + expr_clone(expr_value(low)), + tmp); + + return __expr_to_set_elem(low, tmp); +} + +static void +add_interval(struct expr *set, struct expr *low, struct expr *i) +{ + struct expr *expr; + mpz_t range, p; + + mpz_init(range); + mpz_init(p); + + mpz_sub(range, expr_value(i)->value, expr_value(low)->value); + if (i->etype != EXPR_VALUE) + mpz_sub_ui(range, range, 1); + + mpz_and(p, expr_value(low)->value, range); + + if (!mpz_cmp_ui(range, 0)) { + if (expr_basetype(low)->type == TYPE_STRING) + mpz_switch_byteorder(expr_value(low)->value, + expr_value(low)->len / BITS_PER_BYTE); + low->flags |= EXPR_F_KERNEL; + expr = expr_get(low); + } else if (range_is_prefix(range) && !mpz_cmp_ui(p, 0)) { + + if (datatype_prefix_notation(i->dtype)) + expr = interval_to_prefix(low, i, range); + else if (expr_basetype(i)->type == TYPE_STRING) + expr = interval_to_string(low, i, range); + else + expr = interval_to_range(low, i, range); + } else + expr = interval_to_range(low, i, range); + + compound_expr_add(set, expr); + + mpz_clear(range); + mpz_clear(p); +} + void interval_map_decompose(struct expr *set) { + struct expr *i, *next, *low = NULL, *end, *catchall = NULL, *key; struct expr **elements, **ranges; - struct expr *i, *next, *low = NULL, *end; unsigned int n, m, size; - mpz_t range, p; bool interval; - if (set->size == 0) + if (expr_set(set)->size == 0) return; - elements = xmalloc_array(set->size, sizeof(struct expr *)); - ranges = xmalloc_array(set->size * 2, sizeof(struct expr *)); - - mpz_init(range); - mpz_init(p); + elements = xmalloc_array(expr_set(set)->size, sizeof(struct expr *)); + ranges = xmalloc_array(expr_set(set)->size * 2, sizeof(struct expr *)); /* Sort elements */ n = 0; - list_for_each_entry_safe(i, next, &set->expressions, list) { + list_for_each_entry_safe(i, next, &expr_set(set)->expressions, list) { + key = NULL; + if (i->etype == EXPR_SET_ELEM) + key = i->key; + else if (i->etype == EXPR_MAPPING) + key = i->left->key; + + if (key && key->etype == EXPR_SET_ELEM_CATCHALL) { + list_del(&i->list); + catchall = i; + continue; + } compound_expr_remove(set, i); elements[n++] = i; } @@ -896,83 +628,7 @@ void interval_map_decompose(struct expr *set) } } - mpz_sub(range, expr_value(i)->value, expr_value(low)->value); - mpz_sub_ui(range, range, 1); - - mpz_and(p, expr_value(low)->value, range); - - if (!mpz_cmp_ui(range, 0)) - compound_expr_add(set, expr_get(low)); - else if ((!range_is_prefix(range) || - !(i->dtype->flags & DTYPE_F_PREFIX)) || - mpz_cmp_ui(p, 0)) { - struct expr *tmp; - - tmp = constant_expr_alloc(&low->location, low->dtype, - low->byteorder, expr_value(low)->len, - NULL); - - mpz_add(range, range, expr_value(low)->value); - mpz_set(tmp->value, range); - - tmp = range_expr_alloc(&low->location, - expr_clone(expr_value(low)), - tmp); - tmp = set_elem_expr_alloc(&low->location, tmp); - - if (low->etype == EXPR_MAPPING) { - if (low->left->comment) - tmp->comment = xstrdup(low->left->comment); - if (low->left->timeout) - tmp->timeout = low->left->timeout; - if (low->left->expiration) - tmp->expiration = low->left->expiration; - - tmp = mapping_expr_alloc(&tmp->location, tmp, - expr_clone(low->right)); - } else { - if (low->comment) - tmp->comment = xstrdup(low->comment); - if (low->timeout) - tmp->timeout = low->timeout; - if (low->expiration) - tmp->expiration = low->expiration; - } - - compound_expr_add(set, tmp); - } else { - struct expr *prefix; - unsigned int prefix_len; - - prefix_len = expr_value(i)->len - mpz_scan0(range, 0); - prefix = prefix_expr_alloc(&low->location, - expr_clone(expr_value(low)), - prefix_len); - prefix->len = expr_value(i)->len; - - prefix = set_elem_expr_alloc(&low->location, prefix); - - if (low->etype == EXPR_MAPPING) { - if (low->left->comment) - prefix->comment = xstrdup(low->left->comment); - if (low->left->timeout) - prefix->timeout = low->left->timeout; - if (low->left->expiration) - prefix->expiration = low->left->expiration; - - prefix = mapping_expr_alloc(&low->location, prefix, - expr_clone(low->right)); - } else { - if (low->comment) - prefix->comment = xstrdup(low->comment); - if (low->timeout) - prefix->timeout = low->timeout; - if (low->left->expiration) - prefix->expiration = low->expiration; - } - - compound_expr_add(set, prefix); - } + add_interval(set, low, i); if (i->flags & EXPR_F_INTERVAL_END) { expr_free(low); @@ -986,23 +642,23 @@ void interval_map_decompose(struct expr *set) i = constant_expr_alloc(&low->location, low->dtype, low->byteorder, expr_value(low)->len, NULL); - mpz_init_bitmask(i->value, i->len); + mpz_bitmask(i->value, i->len); if (!mpz_cmp(i->value, expr_value(low)->value)) { - expr_free(i); - i = low; + compound_expr_add(set, low); } else { - i = range_expr_alloc(&low->location, expr_value(low), i); - i = set_elem_expr_alloc(&low->location, i); - if (low->etype == EXPR_MAPPING) - i = mapping_expr_alloc(&i->location, i, low->right); + add_interval(set, low, i); + expr_free(low); } - compound_expr_add(set, i); + expr_free(i); + out: - mpz_clear(range); - mpz_clear(p); + if (catchall) { + catchall->flags |= EXPR_F_KERNEL; + compound_expr_add(set, catchall); + } - xfree(ranges); - xfree(elements); + free(ranges); + free(elements); } diff --git a/src/socket.c b/src/socket.c index d78a163a..8a149e63 100644 --- a/src/socket.c +++ b/src/socket.c @@ -4,10 +4,12 @@ * Copyright (c) 2018 Máté Eckl <ecklm94@gmail.com> * * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. + * it under the terms of the GNU General Public License version 2 (or any + * later) as published by the Free Software Foundation. */ +#include <nft.h> + #include <nftables.h> #include <expression.h> #include <socket.h> @@ -26,21 +28,37 @@ const struct socket_template socket_templates[] = { .len = 4 * BITS_PER_BYTE, .byteorder = BYTEORDER_HOST_ENDIAN, }, + [NFT_SOCKET_WILDCARD] = { + .token = "wildcard", + .dtype = &integer_type, + .len = BITS_PER_BYTE, + .byteorder = BYTEORDER_HOST_ENDIAN, + }, + [NFT_SOCKET_CGROUPV2] = { + .token = "cgroupv2", + .dtype = &cgroupv2_type, + .len = 8 * BITS_PER_BYTE, + .byteorder = BYTEORDER_HOST_ENDIAN, + }, }; static void socket_expr_print(const struct expr *expr, struct output_ctx *octx) { nft_print(octx, "socket %s", socket_templates[expr->socket.key].token); + if (expr->socket.key == NFT_SOCKET_CGROUPV2) + nft_print(octx, " level %u", expr->socket.level); } static bool socket_expr_cmp(const struct expr *e1, const struct expr *e2) { - return e1->socket.key == e2->socket.key; + return e1->socket.key == e2->socket.key && + e1->socket.level == e2->socket.level; } static void socket_expr_clone(struct expr *new, const struct expr *expr) { new->socket.key = expr->socket.key; + new->socket.level = expr->socket.level; } #define NFTNL_UDATA_SOCKET_KEY 0 @@ -89,7 +107,7 @@ static struct expr *socket_expr_parse_udata(const struct nftnl_udata *attr) key = nftnl_udata_get_u32(ud[NFTNL_UDATA_SOCKET_KEY]); - return socket_expr_alloc(&internal_location, key); + return socket_expr_alloc(&internal_location, key, 0); } const struct expr_ops socket_expr_ops = { @@ -103,7 +121,8 @@ const struct expr_ops socket_expr_ops = { .parse_udata = socket_expr_parse_udata, }; -struct expr *socket_expr_alloc(const struct location *loc, enum nft_socket_keys key) +struct expr *socket_expr_alloc(const struct location *loc, + enum nft_socket_keys key, uint32_t level) { const struct socket_template *tmpl = &socket_templates[key]; struct expr *expr; @@ -111,6 +130,7 @@ struct expr *socket_expr_alloc(const struct location *loc, enum nft_socket_keys expr = expr_alloc(loc, EXPR_SOCKET, tmpl->dtype, tmpl->byteorder, tmpl->len); expr->socket.key = key; + expr->socket.level = level; return expr; } diff --git a/src/statement.c b/src/statement.c index be35bcef..695b57a6 100644 --- a/src/statement.c +++ b/src/statement.c @@ -8,19 +8,21 @@ * Development of this code funded by Astaro AG (http://www.astaro.com/) */ +#include <nft.h> + #include <stddef.h> -#include <stdlib.h> #include <stdio.h> -#include <stdint.h> #include <inttypes.h> -#include <string.h> #include <syslog.h> +#include <rule.h> #include <arpa/inet.h> #include <linux/netfilter.h> +#include <linux/netfilter/nf_log.h> #include <netinet/ip_icmp.h> #include <netinet/icmp6.h> #include <statement.h> +#include <tcpopt.h> #include <utils.h> #include <list.h> #include <xt.h> @@ -31,25 +33,28 @@ #include <linux/netfilter/nf_log.h> #include <linux/netfilter/nf_synproxy.h> -struct stmt *stmt_alloc(const struct location *loc, - const struct stmt_ops *ops) +struct stmt *stmt_alloc(const struct location *loc, const struct stmt_ops *ops) { struct stmt *stmt; stmt = xzalloc(sizeof(*stmt)); init_list_head(&stmt->list); stmt->location = *loc; - stmt->ops = ops; + stmt->type = ops->type; return stmt; } void stmt_free(struct stmt *stmt) { + const struct stmt_ops *ops; + if (stmt == NULL) return; - if (stmt->ops->destroy) - stmt->ops->destroy(stmt); - xfree(stmt); + + ops = stmt_ops(stmt); + if (ops->destroy) + ops->destroy(stmt); + free(stmt); } void stmt_list_free(struct list_head *list) @@ -64,7 +69,9 @@ void stmt_list_free(struct list_head *list) void stmt_print(const struct stmt *stmt, struct output_ctx *octx) { - stmt->ops->print(stmt, octx); + const struct stmt_ops *ops = stmt_ops(stmt); + + ops->print(stmt, octx); } static void expr_stmt_print(const struct stmt *stmt, struct output_ctx *octx) @@ -111,6 +118,50 @@ struct stmt *verdict_stmt_alloc(const struct location *loc, struct expr *expr) return stmt; } +static const char *chain_verdict(const struct expr *expr) +{ + switch (expr->verdict) { + case NFT_JUMP: + return "jump"; + case NFT_GOTO: + return "goto"; + default: + BUG("unknown chain verdict"); + } +} + +static void chain_stmt_print(const struct stmt *stmt, struct output_ctx *octx) +{ + nft_print(octx, "%s {\n", chain_verdict(stmt->chain.expr)); + chain_rules_print(stmt->chain.chain, octx, "\t"); + nft_print(octx, "\t\t}"); +} + +static void chain_stmt_destroy(struct stmt *stmt) +{ + expr_free(stmt->chain.expr); +} + +static const struct stmt_ops chain_stmt_ops = { + .type = STMT_CHAIN, + .name = "chain", + .print = chain_stmt_print, + .destroy = chain_stmt_destroy, +}; + +struct stmt *chain_stmt_alloc(const struct location *loc, struct chain *chain, + enum nft_verdicts verdict) +{ + struct stmt *stmt; + + stmt = stmt_alloc(loc, &chain_stmt_ops); + stmt->chain.chain = chain; + stmt->chain.expr = verdict_expr_alloc(loc, verdict, NULL); + stmt->chain.expr->chain_id = chain->handle.chain_id; + + return stmt; +} + static void meter_stmt_print(const struct stmt *stmt, struct output_ctx *octx) { unsigned int flags = octx->flags; @@ -137,7 +188,7 @@ static void meter_stmt_destroy(struct stmt *stmt) expr_free(stmt->meter.key); expr_free(stmt->meter.set); stmt_free(stmt->meter.stmt); - xfree(stmt->meter.name); + free_const(stmt->meter.name); } static const struct stmt_ops meter_stmt_ops = { @@ -155,7 +206,7 @@ struct stmt *meter_stmt_alloc(const struct location *loc) static void connlimit_stmt_print(const struct stmt *stmt, struct output_ctx *octx) { - nft_print(octx, "ct count %s%u ", + nft_print(octx, "ct count %s%u", stmt->connlimit.flags ? "over " : "", stmt->connlimit.count); } @@ -202,6 +253,37 @@ struct stmt *counter_stmt_alloc(const struct location *loc) return stmt; } +static void last_stmt_print(const struct stmt *stmt, struct output_ctx *octx) +{ + nft_print(octx, "last"); + + if (nft_output_stateless(octx)) + return; + + nft_print(octx, " used "); + + if (stmt->last.set) + time_print(stmt->last.used, octx); + else + nft_print(octx, "never"); +} + +static const struct stmt_ops last_stmt_ops = { + .type = STMT_LAST, + .name = "last", + .print = last_stmt_print, + .json = last_stmt_json, +}; + +struct stmt *last_stmt_alloc(const struct location *loc) +{ + struct stmt *stmt; + + stmt = stmt_alloc(loc, &last_stmt_ops); + stmt->flags |= STMT_F_STATEFUL; + return stmt; +} + static const char *objref_type[NFT_OBJECT_MAX + 1] = { [NFT_OBJECT_COUNTER] = "counter", [NFT_OBJECT_QUOTA] = "quota", @@ -338,7 +420,7 @@ static void log_stmt_print(const struct stmt *stmt, struct output_ctx *octx) static void log_stmt_destroy(struct stmt *stmt) { - xfree(stmt->log.prefix); + free_const(stmt->log.prefix); } static const struct stmt_ops log_stmt_ops = { @@ -404,9 +486,7 @@ static void limit_stmt_print(const struct stmt *stmt, struct output_ctx *octx) nft_print(octx, "limit rate %s%" PRIu64 "/%s", inv ? "over " : "", stmt->limit.rate, get_unit(stmt->limit.unit)); - if (stmt->limit.burst && stmt->limit.burst != 5) - nft_print(octx, " burst %u packets", - stmt->limit.burst); + nft_print(octx, " burst %u packets", stmt->limit.burst); break; case NFT_LIMIT_PKT_BYTES: data_unit = get_rate(stmt->limit.rate, &rate); @@ -414,7 +494,7 @@ static void limit_stmt_print(const struct stmt *stmt, struct output_ctx *octx) nft_print(octx, "limit rate %s%" PRIu64 " %s/%s", inv ? "over " : "", rate, data_unit, get_unit(stmt->limit.unit)); - if (stmt->limit.burst > 0) { + if (stmt->limit.burst != 0) { uint64_t burst; data_unit = get_rate(stmt->limit.burst, &burst); @@ -443,20 +523,25 @@ struct stmt *limit_stmt_alloc(const struct location *loc) static void queue_stmt_print(const struct stmt *stmt, struct output_ctx *octx) { - const char *delim = " "; + struct expr *e = stmt->queue.queue; + const char *delim = " flags "; nft_print(octx, "queue"); - if (stmt->queue.queue != NULL) { - nft_print(octx, " num "); - expr_print(stmt->queue.queue, octx); - } + if (stmt->queue.flags & NFT_QUEUE_FLAG_BYPASS) { nft_print(octx, "%sbypass", delim); delim = ","; } + if (stmt->queue.flags & NFT_QUEUE_FLAG_CPU_FANOUT) nft_print(octx, "%sfanout", delim); + if (e) { + nft_print(octx, " to "); + expr_print(stmt->queue.queue, octx); + } else { + nft_print(octx, " to 0"); + } } static void queue_stmt_destroy(struct stmt *stmt) @@ -472,9 +557,15 @@ static const struct stmt_ops queue_stmt_ops = { .destroy = queue_stmt_destroy, }; -struct stmt *queue_stmt_alloc(const struct location *loc) +struct stmt *queue_stmt_alloc(const struct location *loc, struct expr *e, uint16_t flags) { - return stmt_alloc(loc, &queue_stmt_ops); + struct stmt *stmt; + + stmt = stmt_alloc(loc, &queue_stmt_ops); + stmt->queue.queue = e; + stmt->queue.flags = flags; + + return stmt; } static void quota_stmt_print(const struct stmt *stmt, struct output_ctx *octx) @@ -519,7 +610,7 @@ static void reject_stmt_print(const struct stmt *stmt, struct output_ctx *octx) case NFT_REJECT_ICMPX_UNREACH: if (stmt->reject.icmp_code == NFT_REJECT_ICMPX_PORT_UNREACH) break; - nft_print(octx, " with icmpx type "); + nft_print(octx, " with icmpx "); expr_print(stmt->reject.expr, octx); break; case NFT_REJECT_ICMP_UNREACH: @@ -528,14 +619,14 @@ static void reject_stmt_print(const struct stmt *stmt, struct output_ctx *octx) if (!stmt->reject.verbose_print && stmt->reject.icmp_code == ICMP_PORT_UNREACH) break; - nft_print(octx, " with icmp type "); + nft_print(octx, " with icmp "); expr_print(stmt->reject.expr, octx); break; case NFPROTO_IPV6: if (!stmt->reject.verbose_print && stmt->reject.icmp_code == ICMP6_DST_UNREACH_NOPORT) break; - nft_print(octx, " with icmpv6 type "); + nft_print(octx, " with icmpv6 "); expr_print(stmt->reject.expr, octx); break; } @@ -607,6 +698,9 @@ static void nat_stmt_print(const struct stmt *stmt, struct output_ctx *octx) break; } + if (stmt->nat.type_flags & STMT_NAT_F_PREFIX) + nft_print(octx, " prefix"); + nft_print(octx, " to"); } @@ -675,15 +769,16 @@ const char * const set_stmt_op_names[] = { static void set_stmt_print(const struct stmt *stmt, struct output_ctx *octx) { unsigned int flags = octx->flags; + struct stmt *this; nft_print(octx, "%s ", set_stmt_op_names[stmt->set.op]); expr_print(stmt->set.set, octx); nft_print(octx, " { "); expr_print(stmt->set.key, octx); - if (stmt->set.stmt) { + list_for_each_entry(this, &stmt->set.stmt_list, list) { nft_print(octx, " "); octx->flags |= NFT_CTX_OUTPUT_STATELESS; - stmt_print(stmt->set.stmt, octx); + stmt_print(this, octx); octx->flags = flags; } nft_print(octx, " }"); @@ -691,9 +786,12 @@ static void set_stmt_print(const struct stmt *stmt, struct output_ctx *octx) static void set_stmt_destroy(struct stmt *stmt) { + struct stmt *this, *next; + expr_free(stmt->set.key); expr_free(stmt->set.set); - stmt_free(stmt->set.stmt); + list_for_each_entry_safe(this, next, &stmt->set.stmt_list, list) + stmt_free(this); } static const struct stmt_ops set_stmt_ops = { @@ -706,21 +804,27 @@ static const struct stmt_ops set_stmt_ops = { struct stmt *set_stmt_alloc(const struct location *loc) { - return stmt_alloc(loc, &set_stmt_ops); + struct stmt *stmt; + + stmt = stmt_alloc(loc, &set_stmt_ops); + init_list_head(&stmt->set.stmt_list); + + return stmt; } static void map_stmt_print(const struct stmt *stmt, struct output_ctx *octx) { unsigned int flags = octx->flags; + struct stmt *this; nft_print(octx, "%s ", set_stmt_op_names[stmt->map.op]); expr_print(stmt->map.set, octx); nft_print(octx, " { "); expr_print(stmt->map.key, octx); - if (stmt->map.stmt) { + list_for_each_entry(this, &stmt->map.stmt_list, list) { nft_print(octx, " "); octx->flags |= NFT_CTX_OUTPUT_STATELESS; - stmt_print(stmt->map.stmt, octx); + stmt_print(this, octx); octx->flags = flags; } nft_print(octx, " : "); @@ -730,10 +834,13 @@ static void map_stmt_print(const struct stmt *stmt, struct output_ctx *octx) static void map_stmt_destroy(struct stmt *stmt) { + struct stmt *this, *next; + expr_free(stmt->map.key); expr_free(stmt->map.data); expr_free(stmt->map.set); - stmt_free(stmt->map.stmt); + list_for_each_entry_safe(this, next, &stmt->map.stmt_list, list) + stmt_free(this); } static const struct stmt_ops map_stmt_ops = { @@ -741,11 +848,17 @@ static const struct stmt_ops map_stmt_ops = { .name = "map", .print = map_stmt_print, .destroy = map_stmt_destroy, + .json = map_stmt_json, }; struct stmt *map_stmt_alloc(const struct location *loc) { - return stmt_alloc(loc, &map_stmt_ops); + struct stmt *stmt; + + stmt = stmt_alloc(loc, &map_stmt_ops); + init_list_head(&stmt->map.stmt_list); + + return stmt; } static void dup_stmt_print(const struct stmt *stmt, struct output_ctx *octx) @@ -827,6 +940,37 @@ struct stmt *fwd_stmt_alloc(const struct location *loc) return stmt_alloc(loc, &fwd_stmt_ops); } +static void optstrip_stmt_print(const struct stmt *stmt, struct output_ctx *octx) +{ + const struct expr *expr = stmt->optstrip.expr; + + nft_print(octx, "reset "); + expr_print(expr, octx); +} + +static void optstrip_stmt_destroy(struct stmt *stmt) +{ + expr_free(stmt->optstrip.expr); +} + +static const struct stmt_ops optstrip_stmt_ops = { + .type = STMT_OPTSTRIP, + .name = "optstrip", + .print = optstrip_stmt_print, + .json = optstrip_stmt_json, + .destroy = optstrip_stmt_destroy, +}; + +struct stmt *optstrip_stmt_alloc(const struct location *loc, struct expr *e) +{ + struct stmt *stmt = stmt_alloc(loc, &optstrip_stmt_ops); + + e->exthdr.flags |= NFT_EXTHDR_F_PRESENT; + stmt->optstrip.expr = e; + + return stmt; +} + static void tproxy_stmt_print(const struct stmt *stmt, struct output_ctx *octx) { nft_print(octx, "tproxy"); @@ -846,7 +990,7 @@ static void tproxy_stmt_print(const struct stmt *stmt, struct output_ctx *octx) expr_print(stmt->tproxy.addr, octx); } } - if (stmt->tproxy.port && stmt->tproxy.port->etype == EXPR_VALUE) { + if (stmt->tproxy.port) { if (!stmt->tproxy.addr) nft_print(octx, " "); nft_print(octx, ":"); @@ -883,6 +1027,7 @@ static const struct stmt_ops xt_stmt_ops = { .name = "xt", .print = xt_stmt_print, .destroy = xt_stmt_destroy, + .json = xt_stmt_json, }; struct stmt *xt_stmt_alloc(const struct location *loc) @@ -939,3 +1084,59 @@ struct stmt *synproxy_stmt_alloc(const struct location *loc) { return stmt_alloc(loc, &synproxy_stmt_ops); } + +/* For src/optimize.c */ +static struct stmt_ops invalid_stmt_ops = { + .type = STMT_INVALID, + .name = "unsupported", +}; + +static const struct stmt_ops *__stmt_ops_by_type(enum stmt_types type) +{ + switch (type) { + case STMT_INVALID: return &invalid_stmt_ops; + case STMT_EXPRESSION: return &expr_stmt_ops; + case STMT_VERDICT: return &verdict_stmt_ops; + case STMT_METER: return &meter_stmt_ops; + case STMT_COUNTER: return &counter_stmt_ops; + case STMT_PAYLOAD: return &payload_stmt_ops; + case STMT_META: return &meta_stmt_ops; + case STMT_LIMIT: return &limit_stmt_ops; + case STMT_LOG: return &log_stmt_ops; + case STMT_REJECT: return &reject_stmt_ops; + case STMT_NAT: return &nat_stmt_ops; + case STMT_TPROXY: return &tproxy_stmt_ops; + case STMT_QUEUE: return &queue_stmt_ops; + case STMT_CT: return &ct_stmt_ops; + case STMT_SET: return &set_stmt_ops; + case STMT_DUP: return &dup_stmt_ops; + case STMT_FWD: return &fwd_stmt_ops; + case STMT_XT: return &xt_stmt_ops; + case STMT_QUOTA: return "a_stmt_ops; + case STMT_NOTRACK: return ¬rack_stmt_ops; + case STMT_OBJREF: return &objref_stmt_ops; + case STMT_EXTHDR: return &exthdr_stmt_ops; + case STMT_FLOW_OFFLOAD: return &flow_offload_stmt_ops; + case STMT_CONNLIMIT: return &connlimit_stmt_ops; + case STMT_MAP: return &map_stmt_ops; + case STMT_SYNPROXY: return &synproxy_stmt_ops; + case STMT_CHAIN: return &chain_stmt_ops; + case STMT_OPTSTRIP: return &optstrip_stmt_ops; + case STMT_LAST: return &last_stmt_ops; + default: + break; + } + + return NULL; +} + +const struct stmt_ops *stmt_ops(const struct stmt *stmt) +{ + const struct stmt_ops *ops; + + ops = __stmt_ops_by_type(stmt->type); + if (!ops) + BUG("Unknown statement type %d\n", stmt->type); + + return ops; +} diff --git a/src/tcpopt.c b/src/tcpopt.c index ec305d94..d8139337 100644 --- a/src/tcpopt.c +++ b/src/tcpopt.c @@ -1,8 +1,7 @@ +#include <nft.h> + #include <stddef.h> -#include <stdlib.h> #include <stdio.h> -#include <stdint.h> -#include <string.h> #include <netinet/in.h> #include <netinet/ip6.h> #include <netinet/tcp.h> @@ -20,221 +19,297 @@ static const struct proto_hdr_template tcpopt_unknown_template = __offset, __len) static const struct exthdr_desc tcpopt_eol = { .name = "eol", - .type = TCPOPT_EOL, + .type = TCPOPT_KIND_EOL, .templates = { - [TCPOPTHDR_FIELD_KIND] = PHT("kind", 0, 8), + [TCPOPT_COMMON_KIND] = PHT("kind", 0, 8), }, }; static const struct exthdr_desc tcpopt_nop = { - .name = "noop", - .type = TCPOPT_NOP, + .name = "nop", + .type = TCPOPT_KIND_NOP, .templates = { - [TCPOPTHDR_FIELD_KIND] = PHT("kind", 0, 8), + [TCPOPT_COMMON_KIND] = PHT("kind", 0, 8), }, }; static const struct exthdr_desc tcptopt_maxseg = { .name = "maxseg", - .type = TCPOPT_MAXSEG, + .type = TCPOPT_KIND_MAXSEG, .templates = { - [TCPOPTHDR_FIELD_KIND] = PHT("kind", 0, 8), - [TCPOPTHDR_FIELD_LENGTH] = PHT("length", 8, 8), - [TCPOPTHDR_FIELD_SIZE] = PHT("size", 16, 16), + [TCPOPT_MAXSEG_KIND] = PHT("kind", 0, 8), + [TCPOPT_MAXSEG_LENGTH] = PHT("length", 8, 8), + [TCPOPT_MAXSEG_SIZE] = PHT("size", 16, 16), }, }; static const struct exthdr_desc tcpopt_window = { .name = "window", - .type = TCPOPT_WINDOW, + .type = TCPOPT_KIND_WINDOW, .templates = { - [TCPOPTHDR_FIELD_KIND] = PHT("kind", 0, 8), - [TCPOPTHDR_FIELD_LENGTH] = PHT("length", 8, 8), - [TCPOPTHDR_FIELD_COUNT] = PHT("count", 16, 8), + [TCPOPT_WINDOW_KIND] = PHT("kind", 0, 8), + [TCPOPT_WINDOW_LENGTH] = PHT("length", 8, 8), + [TCPOPT_WINDOW_COUNT] = PHT("count", 16, 8), }, }; static const struct exthdr_desc tcpopt_sack_permitted = { - .name = "sack-permitted", - .type = TCPOPT_SACK_PERMITTED, + .name = "sack-perm", + .type = TCPOPT_KIND_SACK_PERMITTED, .templates = { - [TCPOPTHDR_FIELD_KIND] = PHT("kind", 0, 8), - [TCPOPTHDR_FIELD_LENGTH] = PHT("length", 8, 8), + [TCPOPT_COMMON_KIND] = PHT("kind", 0, 8), + [TCPOPT_COMMON_LENGTH] = PHT("length", 8, 8), }, }; static const struct exthdr_desc tcpopt_sack = { .name = "sack", - .type = TCPOPT_SACK, + .type = TCPOPT_KIND_SACK, .templates = { - [TCPOPTHDR_FIELD_KIND] = PHT("kind", 0, 8), - [TCPOPTHDR_FIELD_LENGTH] = PHT("length", 8, 8), - [TCPOPTHDR_FIELD_LEFT] = PHT("left", 16, 32), - [TCPOPTHDR_FIELD_RIGHT] = PHT("right", 48, 32), + [TCPOPT_SACK_KIND] = PHT("kind", 0, 8), + [TCPOPT_SACK_LENGTH] = PHT("length", 8, 8), + [TCPOPT_SACK_LEFT] = PHT("left", 16, 32), + [TCPOPT_SACK_RIGHT] = PHT("right", 48, 32), + [TCPOPT_SACK_LEFT1] = PHT("left", 80, 32), + [TCPOPT_SACK_RIGHT1] = PHT("right", 112, 32), + [TCPOPT_SACK_LEFT2] = PHT("left", 144, 32), + [TCPOPT_SACK_RIGHT2] = PHT("right", 176, 32), + [TCPOPT_SACK_LEFT3] = PHT("left", 208, 32), + [TCPOPT_SACK_RIGHT3] = PHT("right", 240, 32), }, }; static const struct exthdr_desc tcpopt_timestamp = { .name = "timestamp", - .type = TCPOPT_TIMESTAMP, + .type = TCPOPT_KIND_TIMESTAMP, .templates = { - [TCPOPTHDR_FIELD_KIND] = PHT("kind", 0, 8), - [TCPOPTHDR_FIELD_LENGTH] = PHT("length", 8, 8), - [TCPOPTHDR_FIELD_TSVAL] = PHT("tsval", 16, 32), - [TCPOPTHDR_FIELD_TSECR] = PHT("tsecr", 48, 32), + [TCPOPT_TS_KIND] = PHT("kind", 0, 8), + [TCPOPT_TS_LENGTH] = PHT("length", 8, 8), + [TCPOPT_TS_TSVAL] = PHT("tsval", 16, 32), + [TCPOPT_TS_TSECR] = PHT("tsecr", 48, 32), }, }; -#undef PHT -#define TCPOPT_OBSOLETE ((struct exthdr_desc *)NULL) -#define TCPOPT_ECHO 6 -#define TCPOPT_ECHO_REPLY 7 -static const struct exthdr_desc *tcpopt_protocols[] = { - [TCPOPT_EOL] = &tcpopt_eol, - [TCPOPT_NOP] = &tcpopt_nop, - [TCPOPT_MAXSEG] = &tcptopt_maxseg, - [TCPOPT_WINDOW] = &tcpopt_window, - [TCPOPT_SACK_PERMITTED] = &tcpopt_sack_permitted, - [TCPOPT_SACK] = &tcpopt_sack, - [TCPOPT_ECHO] = TCPOPT_OBSOLETE, - [TCPOPT_ECHO_REPLY] = TCPOPT_OBSOLETE, - [TCPOPT_TIMESTAMP] = &tcpopt_timestamp, +static const struct exthdr_desc tcpopt_fastopen = { + .name = "fastopen", + .type = TCPOPT_KIND_FASTOPEN, + .templates = { + [TCPOPT_COMMON_KIND] = PHT("kind", 0, 8), + [TCPOPT_COMMON_LENGTH] = PHT("length", 8, 8), + }, }; -static unsigned int calc_offset(const struct exthdr_desc *desc, - const struct proto_hdr_template *tmpl, - unsigned int num) -{ - if (!desc || tmpl == &tcpopt_unknown_template) - return 0; - - switch (desc->type) { - case TCPOPT_SACK: - /* Make sure, offset calculations only apply to left and right - * fields - */ - return (tmpl->offset < 16) ? 0 : num * 64; - default: - return 0; - } -} +static const struct exthdr_desc tcpopt_md5sig = { + .name = "md5sig", + .type = TCPOPT_KIND_MD5SIG, + .templates = { + [TCPOPT_COMMON_KIND] = PHT("kind", 0, 8), + [TCPOPT_COMMON_LENGTH] = PHT("length", 8, 8), + }, +}; +static const struct symbol_table mptcp_subtype_tbl = { + .base = BASE_DECIMAL, + .symbols = { + SYMBOL("mp-capable", 0), + SYMBOL("mp-join", 1), + SYMBOL("dss", 2), + SYMBOL("add-addr", 3), + SYMBOL("remove-addr", 4), + SYMBOL("mp-prio", 5), + SYMBOL("mp-fail", 6), + SYMBOL("mp-fastclose", 7), + SYMBOL("mp-tcprst", 8), + SYMBOL_LIST_END + }, +}; -static unsigned int calc_offset_reverse(const struct exthdr_desc *desc, - const struct proto_hdr_template *tmpl, - unsigned int offset) -{ - if (!desc || tmpl == &tcpopt_unknown_template) - return offset; - - switch (desc->type) { - case TCPOPT_SACK: - /* We can safely ignore the first left/right field */ - return offset < 80 ? offset : (offset % 64); - default: - return offset; - } -} +/* alias of integer_type to parse mptcp subtypes */ +const struct datatype mptcpopt_subtype = { + .type = TYPE_INTEGER, + .name = "integer", + .desc = "mptcp option subtype", + .size = 4, + .basetype = &integer_type, + .sym_tbl = &mptcp_subtype_tbl, +}; -const struct exthdr_desc *tcpopthdr_protocols[__TCPOPTHDR_MAX] = { - [TCPOPTHDR_EOL] = &tcpopt_eol, - [TCPOPTHDR_NOOP] = &tcpopt_nop, - [TCPOPTHDR_MAXSEG] = &tcptopt_maxseg, - [TCPOPTHDR_WINDOW] = &tcpopt_window, - [TCPOPTHDR_SACK_PERMITTED] = &tcpopt_sack_permitted, - [TCPOPTHDR_SACK0] = &tcpopt_sack, - [TCPOPTHDR_SACK1] = &tcpopt_sack, - [TCPOPTHDR_SACK2] = &tcpopt_sack, - [TCPOPTHDR_SACK3] = &tcpopt_sack, - [TCPOPTHDR_ECHO] = TCPOPT_OBSOLETE, - [TCPOPTHDR_ECHO_REPLY] = TCPOPT_OBSOLETE, - [TCPOPTHDR_TIMESTAMP] = &tcpopt_timestamp, +static const struct exthdr_desc tcpopt_mptcp = { + .name = "mptcp", + .type = TCPOPT_KIND_MPTCP, + .templates = { + [TCPOPT_MPTCP_KIND] = PHT("kind", 0, 8), + [TCPOPT_MPTCP_LENGTH] = PHT("length", 8, 8), + [TCPOPT_MPTCP_SUBTYPE] = PROTO_HDR_TEMPLATE("subtype", + &mptcpopt_subtype, + BYTEORDER_BIG_ENDIAN, + 16, 4), + }, }; -static uint8_t tcpopt_optnum[] = { - [TCPOPTHDR_SACK0] = 0, - [TCPOPTHDR_SACK1] = 1, - [TCPOPTHDR_SACK2] = 2, - [TCPOPTHDR_SACK3] = 3, +static const struct exthdr_desc tcpopt_fallback = { + .templates = { + [TCPOPT_COMMON_KIND] = PHT("kind", 0, 8), + [TCPOPT_COMMON_LENGTH] = PHT("length", 8, 8), + }, +}; +#undef PHT + +const struct exthdr_desc *tcpopt_protocols[] = { + [TCPOPT_KIND_EOL] = &tcpopt_eol, + [TCPOPT_KIND_NOP] = &tcpopt_nop, + [TCPOPT_KIND_MAXSEG] = &tcptopt_maxseg, + [TCPOPT_KIND_WINDOW] = &tcpopt_window, + [TCPOPT_KIND_SACK_PERMITTED] = &tcpopt_sack_permitted, + [TCPOPT_KIND_SACK] = &tcpopt_sack, + [TCPOPT_KIND_TIMESTAMP] = &tcpopt_timestamp, + [TCPOPT_KIND_MD5SIG] = &tcpopt_md5sig, + [TCPOPT_KIND_MPTCP] = &tcpopt_mptcp, + [TCPOPT_KIND_FASTOPEN] = &tcpopt_fastopen, }; -static uint8_t tcpopt_find_optnum(uint8_t optnum) +static void tcpopt_assign_tmpl(struct expr *expr, + const struct proto_hdr_template *tmpl, + const struct exthdr_desc *desc) { - if (optnum > TCPOPTHDR_SACK3) - return 0; + expr->exthdr.op = NFT_EXTHDR_OP_TCPOPT; - return tcpopt_optnum[optnum]; + expr->exthdr.desc = desc; + expr->exthdr.tmpl = tmpl; + expr->exthdr.offset = tmpl->offset; } -struct expr *tcpopt_expr_alloc(const struct location *loc, uint8_t type, - uint8_t field) +/** + * tcpopt_expr_alloc - allocate tcp option extension expression + * + * @loc: location from parser + * @kind: raw tcp option value to find in packet + * @field: highlevel field to find in the option if @kind is present in packet + * + * Allocate a new tcp option expression. + * @kind is the raw option value to find in the packet. + * Exception: SACK may use extra OOB data that is mangled here. + * + * @field is the optional field to extract from the @type option. + */ +struct expr *tcpopt_expr_alloc(const struct location *loc, + unsigned int kind, + unsigned int field) { const struct proto_hdr_template *tmpl; - const struct exthdr_desc *desc; + const struct exthdr_desc *desc = NULL; struct expr *expr; - uint8_t optnum; - desc = tcpopthdr_protocols[type]; + switch (kind) { + case TCPOPT_KIND_SACK1: + kind = TCPOPT_KIND_SACK; + if (field == TCPOPT_SACK_LEFT) + field = TCPOPT_SACK_LEFT1; + else if (field == TCPOPT_SACK_RIGHT) + field = TCPOPT_SACK_RIGHT1; + break; + case TCPOPT_KIND_SACK2: + kind = TCPOPT_KIND_SACK; + if (field == TCPOPT_SACK_LEFT) + field = TCPOPT_SACK_LEFT2; + else if (field == TCPOPT_SACK_RIGHT) + field = TCPOPT_SACK_RIGHT2; + break; + case TCPOPT_KIND_SACK3: + kind = TCPOPT_KIND_SACK; + if (field == TCPOPT_SACK_LEFT) + field = TCPOPT_SACK_LEFT3; + else if (field == TCPOPT_SACK_RIGHT) + field = TCPOPT_SACK_RIGHT3; + break; + } + + if (kind < array_size(tcpopt_protocols)) + desc = tcpopt_protocols[kind]; + + if (!desc) { + if (kind > 255) + return NULL; + + desc = &tcpopt_fallback; + + switch (field) { + case TCPOPT_COMMON_KIND: + case TCPOPT_COMMON_LENGTH: + tmpl = &desc->templates[field]; + break; + default: + tmpl = &tcpopt_unknown_template; + break; + } + + expr = expr_alloc(loc, EXPR_EXTHDR, &integer_type, + BYTEORDER_BIG_ENDIAN, 8); + + expr->exthdr.raw_type = kind; + tcpopt_assign_tmpl(expr, tmpl, desc); + return expr; + } + tmpl = &desc->templates[field]; - if (!tmpl) + if (!tmpl || !tmpl->dtype) return NULL; - optnum = tcpopt_find_optnum(type); - expr = expr_alloc(loc, EXPR_EXTHDR, tmpl->dtype, BYTEORDER_BIG_ENDIAN, tmpl->len); - expr->exthdr.desc = desc; - expr->exthdr.tmpl = tmpl; - expr->exthdr.op = NFT_EXTHDR_OP_TCPOPT; - expr->exthdr.offset = calc_offset(desc, tmpl, optnum); + + expr->exthdr.raw_type = desc->type; + tcpopt_assign_tmpl(expr, tmpl, desc); return expr; } -void tcpopt_init_raw(struct expr *expr, uint8_t type, unsigned int offset, +void tcpopt_init_raw(struct expr *expr, uint8_t type, unsigned int off, unsigned int len, uint32_t flags) { const struct proto_hdr_template *tmpl; - unsigned int i, off; + unsigned int i; assert(expr->etype == EXPR_EXTHDR); expr->len = len; expr->exthdr.flags = flags; - expr->exthdr.offset = offset; + expr->exthdr.offset = off; + expr->exthdr.op = NFT_EXTHDR_OP_TCPOPT; + expr->exthdr.tmpl = &tcpopt_unknown_template; + + if (flags & NFT_EXTHDR_F_PRESENT) + datatype_set(expr, &boolean_type); + else + datatype_set(expr, &integer_type); + + if (type >= array_size(tcpopt_protocols) || + !tcpopt_protocols[type]) + return; - assert(type < array_size(tcpopt_protocols)); expr->exthdr.desc = tcpopt_protocols[type]; expr->exthdr.flags = flags; - assert(expr->exthdr.desc != TCPOPT_OBSOLETE); + assert(expr->exthdr.desc != NULL); for (i = 0; i < array_size(expr->exthdr.desc->templates); ++i) { tmpl = &expr->exthdr.desc->templates[i]; - /* We have to reverse calculate the offset for the sack options - * at this point - */ - off = calc_offset_reverse(expr->exthdr.desc, tmpl, offset); if (tmpl->offset != off || tmpl->len != len) continue; - if (flags & NFT_EXTHDR_F_PRESENT) - datatype_set(expr, &boolean_type); - else + if ((flags & NFT_EXTHDR_F_PRESENT) == 0) datatype_set(expr, tmpl->dtype); + expr->exthdr.tmpl = tmpl; - expr->exthdr.op = NFT_EXTHDR_OP_TCPOPT; break; } } -bool tcpopt_find_template(struct expr *expr, const struct expr *mask, - unsigned int *shift) +bool tcpopt_find_template(struct expr *expr, unsigned int offset, unsigned int len) { if (expr->exthdr.tmpl != &tcpopt_unknown_template) return false; - tcpopt_init_raw(expr, expr->exthdr.desc->type, expr->exthdr.offset, - expr->len, 0); + tcpopt_init_raw(expr, expr->exthdr.desc->type, offset, len, 0); if (expr->exthdr.tmpl == &tcpopt_unknown_template) return false; diff --git a/src/trace.c b/src/trace.c new file mode 100644 index 00000000..b2709510 --- /dev/null +++ b/src/trace.c @@ -0,0 +1,462 @@ +#include <nft.h> +#include <trace.h> + +#include <libnftnl/trace.h> + +#include <errno.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <inttypes.h> + +#include <linux/netfilter/nfnetlink.h> +#include <linux/netfilter/nf_tables.h> +#include <linux/netfilter.h> + +#include <nftables.h> +#include <mnl.h> +#include <parser.h> +#include <netlink.h> +#include <expression.h> +#include <statement.h> +#include <utils.h> + +#define nft_mon_print(monh, ...) nft_print(&monh->ctx->nft->output, __VA_ARGS__) + +static void trace_print_hdr(const struct nftnl_trace *nlt, + struct output_ctx *octx) +{ + nft_print(octx, "trace id %08x %s ", + nftnl_trace_get_u32(nlt, NFTNL_TRACE_ID), + family2str(nftnl_trace_get_u32(nlt, NFTNL_TRACE_FAMILY))); + if (nftnl_trace_is_set(nlt, NFTNL_TRACE_TABLE)) + nft_print(octx, "%s ", + nftnl_trace_get_str(nlt, NFTNL_TRACE_TABLE)); + if (nftnl_trace_is_set(nlt, NFTNL_TRACE_CHAIN)) + nft_print(octx, "%s ", + nftnl_trace_get_str(nlt, NFTNL_TRACE_CHAIN)); +} + +static void trace_print_expr(const struct nftnl_trace *nlt, unsigned int attr, + struct expr *lhs, struct output_ctx *octx) +{ + struct expr *rhs, *rel; + const void *data; + uint32_t len; + + data = nftnl_trace_get_data(nlt, attr, &len); + rhs = constant_expr_alloc(&netlink_location, + lhs->dtype, lhs->byteorder, + len * BITS_PER_BYTE, data); + rel = relational_expr_alloc(&netlink_location, OP_EQ, lhs, rhs); + + expr_print(rel, octx); + nft_print(octx, " "); + expr_free(rel); +} + +static void trace_print_verdict(const struct nftnl_trace *nlt, + struct output_ctx *octx) +{ + struct expr *chain_expr = NULL; + const char *chain = NULL; + unsigned int verdict; + struct expr *expr; + + verdict = nftnl_trace_get_u32(nlt, NFTNL_TRACE_VERDICT); + if (nftnl_trace_is_set(nlt, NFTNL_TRACE_JUMP_TARGET)) { + chain = xstrdup(nftnl_trace_get_str(nlt, NFTNL_TRACE_JUMP_TARGET)); + chain_expr = constant_expr_alloc(&netlink_location, + &string_type, + BYTEORDER_HOST_ENDIAN, + strlen(chain) * BITS_PER_BYTE, + chain); + } + expr = verdict_expr_alloc(&netlink_location, verdict, chain_expr); + + nft_print(octx, "verdict "); + expr_print(expr, octx); + expr_free(expr); +} + +static void trace_print_policy(const struct nftnl_trace *nlt, + struct output_ctx *octx) +{ + unsigned int policy; + struct expr *expr; + + policy = nftnl_trace_get_u32(nlt, NFTNL_TRACE_POLICY); + + expr = verdict_expr_alloc(&netlink_location, policy, NULL); + + nft_print(octx, "policy "); + expr_print(expr, octx); + expr_free(expr); +} + +static struct rule *trace_lookup_rule(const struct nftnl_trace *nlt, + uint64_t rule_handle, + struct nft_cache *cache) +{ + struct chain *chain; + struct table *table; + struct handle h; + + h.family = nftnl_trace_get_u32(nlt, NFTNL_TRACE_FAMILY); + h.table.name = nftnl_trace_get_str(nlt, NFTNL_TRACE_TABLE); + h.chain.name = nftnl_trace_get_str(nlt, NFTNL_TRACE_CHAIN); + + if (!h.table.name) + return NULL; + + table = table_cache_find(&cache->table_cache, h.table.name, h.family); + if (!table) + return NULL; + + chain = chain_cache_find(table, h.chain.name); + if (!chain) + return NULL; + + return rule_lookup(chain, rule_handle); +} + +static void trace_print_rule(const struct nftnl_trace *nlt, + struct output_ctx *octx, struct nft_cache *cache) +{ + uint64_t rule_handle; + struct rule *rule; + + rule_handle = nftnl_trace_get_u64(nlt, NFTNL_TRACE_RULE_HANDLE); + rule = trace_lookup_rule(nlt, rule_handle, cache); + + trace_print_hdr(nlt, octx); + + if (rule) { + nft_print(octx, "rule "); + rule_print(rule, octx); + } else { + nft_print(octx, "unknown rule handle %" PRIu64, rule_handle); + } + + nft_print(octx, " ("); + trace_print_verdict(nlt, octx); + nft_print(octx, ")\n"); +} + +static void trace_gen_stmts(struct list_head *stmts, + struct proto_ctx *ctx, struct payload_dep_ctx *pctx, + const struct nftnl_trace *nlt, unsigned int attr, + enum proto_bases base) +{ + struct list_head unordered = LIST_HEAD_INIT(unordered); + struct list_head list; + struct expr *rel, *lhs, *rhs, *tmp, *nexpr; + struct stmt *stmt; + const struct proto_desc *desc; + const void *hdr; + uint32_t hlen; + unsigned int n; + + if (!nftnl_trace_is_set(nlt, attr)) + return; + hdr = nftnl_trace_get_data(nlt, attr, &hlen); + + lhs = payload_expr_alloc(&netlink_location, NULL, 0); + payload_init_raw(lhs, base, 0, hlen * BITS_PER_BYTE); + rhs = constant_expr_alloc(&netlink_location, + &invalid_type, BYTEORDER_INVALID, + hlen * BITS_PER_BYTE, hdr); + +restart: + init_list_head(&list); + payload_expr_expand(&list, lhs, ctx); + expr_free(lhs); + + desc = NULL; + list_for_each_entry_safe(lhs, nexpr, &list, list) { + if (desc && desc != ctx->protocol[base].desc) { + /* Chained protocols */ + lhs->payload.offset = 0; + if (ctx->protocol[base].desc == NULL) + break; + goto restart; + } + + tmp = constant_expr_splice(rhs, lhs->len); + expr_set_type(tmp, lhs->dtype, lhs->byteorder); + if (tmp->byteorder == BYTEORDER_HOST_ENDIAN) + mpz_switch_byteorder(tmp->value, tmp->len / BITS_PER_BYTE); + + /* Skip unknown and filtered expressions */ + desc = lhs->payload.desc; + if (lhs->dtype == &invalid_type || + lhs->payload.tmpl == &proto_unknown_template || + desc->checksum_key == payload_hdr_field(lhs) || + desc->format.filter & (1 << payload_hdr_field(lhs))) { + expr_free(lhs); + expr_free(tmp); + continue; + } + + rel = relational_expr_alloc(&lhs->location, OP_EQ, lhs, tmp); + stmt = expr_stmt_alloc(&rel->location, rel); + list_add_tail(&stmt->list, &unordered); + + desc = ctx->protocol[base].desc; + relational_expr_pctx_update(ctx, rel); + } + + expr_free(rhs); + + n = 0; +next: + list_for_each_entry(stmt, &unordered, list) { + enum proto_bases b = base; + + rel = stmt->expr; + lhs = rel->left; + + /* Move statements to result list in defined order */ + desc = lhs->payload.desc; + if (desc->format.order[n] && + desc->format.order[n] != payload_hdr_field(lhs)) + continue; + + list_move_tail(&stmt->list, stmts); + n++; + + if (payload_is_stacked(desc, rel)) + b--; + + /* Don't strip 'icmp type' from payload dump. */ + if (pctx->icmp_type == 0) + payload_dependency_kill(pctx, lhs, ctx->family); + if (lhs->flags & EXPR_F_PROTOCOL) + payload_dependency_store(pctx, stmt, b); + + goto next; + } +} + +static struct expr *trace_alloc_list(const struct datatype *dtype, + enum byteorder byteorder, + unsigned int len, const void *data) +{ + struct expr *list_expr; + unsigned int i; + mpz_t value; + uint32_t v; + + if (len != sizeof(v)) + return constant_expr_alloc(&netlink_location, + dtype, byteorder, + len * BITS_PER_BYTE, data); + + list_expr = list_expr_alloc(&netlink_location); + + mpz_init2(value, 32); + mpz_import_data(value, data, byteorder, len); + v = mpz_get_uint32(value); + if (v == 0) { + mpz_clear(value); + return NULL; + } + + for (i = 0; i < 32; i++) { + uint32_t bitv = v & (1 << i); + + if (bitv == 0) + continue; + + compound_expr_add(list_expr, + constant_expr_alloc(&netlink_location, + dtype, byteorder, + len * BITS_PER_BYTE, + &bitv)); + } + + mpz_clear(value); + return list_expr; +} + +static void trace_print_ct_expr(const struct nftnl_trace *nlt, unsigned int attr, + enum nft_ct_keys key, struct output_ctx *octx) +{ + struct expr *lhs, *rhs, *rel; + const void *data; + uint32_t len; + + data = nftnl_trace_get_data(nlt, attr, &len); + lhs = ct_expr_alloc(&netlink_location, key, -1); + + switch (key) { + case NFT_CT_STATUS: + rhs = trace_alloc_list(lhs->dtype, lhs->byteorder, len, data); + if (!rhs) { + expr_free(lhs); + return; + } + rel = binop_expr_alloc(&netlink_location, OP_IMPLICIT, lhs, rhs); + break; + case NFT_CT_DIRECTION: + case NFT_CT_STATE: + case NFT_CT_ID: + /* fallthrough */ + default: + rhs = constant_expr_alloc(&netlink_location, + lhs->dtype, lhs->byteorder, + len * BITS_PER_BYTE, data); + rel = relational_expr_alloc(&netlink_location, OP_IMPLICIT, lhs, rhs); + break; + } + + expr_print(rel, octx); + nft_print(octx, " "); + expr_free(rel); +} + +static void trace_print_ct(const struct nftnl_trace *nlt, + struct output_ctx *octx) +{ + bool ct = nftnl_trace_is_set(nlt, NFTNL_TRACE_CT_STATE); + + if (!ct) + return; + + trace_print_hdr(nlt, octx); + + nft_print(octx, "conntrack: "); + + if (nftnl_trace_is_set(nlt, NFTNL_TRACE_CT_DIRECTION)) + trace_print_ct_expr(nlt, NFTNL_TRACE_CT_DIRECTION, + NFT_CT_DIRECTION, octx); + + if (nftnl_trace_is_set(nlt, NFTNL_TRACE_CT_STATE)) + trace_print_ct_expr(nlt, NFTNL_TRACE_CT_STATE, + NFT_CT_STATE, octx); + + if (nftnl_trace_is_set(nlt, NFTNL_TRACE_CT_STATUS)) + trace_print_ct_expr(nlt, NFTNL_TRACE_CT_STATUS, + NFT_CT_STATUS, octx); + + if (nftnl_trace_is_set(nlt, NFTNL_TRACE_CT_ID)) + trace_print_ct_expr(nlt, NFTNL_TRACE_CT_ID, + NFT_CT_ID, octx); + + nft_print(octx, "\n"); +} + +static void trace_print_packet(const struct nftnl_trace *nlt, + struct output_ctx *octx) +{ + struct list_head stmts = LIST_HEAD_INIT(stmts); + const struct proto_desc *ll_desc; + struct payload_dep_ctx pctx = {}; + struct proto_ctx ctx; + uint16_t dev_type; + uint32_t nfproto; + struct stmt *stmt, *next; + + trace_print_ct(nlt, octx); + trace_print_hdr(nlt, octx); + + nft_print(octx, "packet: "); + if (nftnl_trace_is_set(nlt, NFTNL_TRACE_IIF)) + trace_print_expr(nlt, NFTNL_TRACE_IIF, + meta_expr_alloc(&netlink_location, + NFT_META_IIF), octx); + if (nftnl_trace_is_set(nlt, NFTNL_TRACE_OIF)) + trace_print_expr(nlt, NFTNL_TRACE_OIF, + meta_expr_alloc(&netlink_location, + NFT_META_OIF), octx); + + proto_ctx_init(&ctx, nftnl_trace_get_u32(nlt, NFTNL_TRACE_FAMILY), 0, false); + ll_desc = ctx.protocol[PROTO_BASE_LL_HDR].desc; + if ((ll_desc == &proto_inet || ll_desc == &proto_netdev) && + nftnl_trace_is_set(nlt, NFTNL_TRACE_NFPROTO)) { + nfproto = nftnl_trace_get_u32(nlt, NFTNL_TRACE_NFPROTO); + + proto_ctx_update(&ctx, PROTO_BASE_LL_HDR, &netlink_location, NULL); + proto_ctx_update(&ctx, PROTO_BASE_NETWORK_HDR, &netlink_location, + proto_find_upper(ll_desc, nfproto)); + } + if (ctx.protocol[PROTO_BASE_LL_HDR].desc == NULL && + nftnl_trace_is_set(nlt, NFTNL_TRACE_IIFTYPE)) { + dev_type = nftnl_trace_get_u16(nlt, NFTNL_TRACE_IIFTYPE); + proto_ctx_update(&ctx, PROTO_BASE_LL_HDR, &netlink_location, + proto_dev_desc(dev_type)); + } + + trace_gen_stmts(&stmts, &ctx, &pctx, nlt, NFTNL_TRACE_LL_HEADER, + PROTO_BASE_LL_HDR); + trace_gen_stmts(&stmts, &ctx, &pctx, nlt, NFTNL_TRACE_NETWORK_HEADER, + PROTO_BASE_NETWORK_HDR); + trace_gen_stmts(&stmts, &ctx, &pctx, nlt, NFTNL_TRACE_TRANSPORT_HEADER, + PROTO_BASE_TRANSPORT_HDR); + + list_for_each_entry_safe(stmt, next, &stmts, list) { + stmt_print(stmt, octx); + nft_print(octx, " "); + stmt_free(stmt); + } + nft_print(octx, "\n"); +} + +int netlink_events_trace_cb(const struct nlmsghdr *nlh, int type, + struct netlink_mon_handler *monh) +{ + struct nftnl_trace *nlt; + + assert(type == NFT_MSG_TRACE); + + nlt = nftnl_trace_alloc(); + if (!nlt) + memory_allocation_error(); + + if (nftnl_trace_nlmsg_parse(nlh, nlt) < 0) + netlink_abi_error(); + + if (nftnl_trace_is_set(nlt, NFTNL_TRACE_LL_HEADER) || + nftnl_trace_is_set(nlt, NFTNL_TRACE_NETWORK_HEADER)) + trace_print_packet(nlt, &monh->ctx->nft->output); + + switch (nftnl_trace_get_u32(nlt, NFTNL_TRACE_TYPE)) { + case NFT_TRACETYPE_RULE: + if (nftnl_trace_is_set(nlt, NFTNL_TRACE_RULE_HANDLE)) + trace_print_rule(nlt, &monh->ctx->nft->output, + &monh->ctx->nft->cache); + break; + case NFT_TRACETYPE_POLICY: + trace_print_hdr(nlt, &monh->ctx->nft->output); + + if (nftnl_trace_is_set(nlt, NFTNL_TRACE_POLICY)) { + trace_print_policy(nlt, &monh->ctx->nft->output); + nft_mon_print(monh, " "); + } + + if (nftnl_trace_is_set(nlt, NFTNL_TRACE_MARK)) + trace_print_expr(nlt, NFTNL_TRACE_MARK, + meta_expr_alloc(&netlink_location, + NFT_META_MARK), + &monh->ctx->nft->output); + nft_mon_print(monh, "\n"); + break; + case NFT_TRACETYPE_RETURN: + trace_print_hdr(nlt, &monh->ctx->nft->output); + + if (nftnl_trace_is_set(nlt, NFTNL_TRACE_VERDICT)) { + trace_print_verdict(nlt, &monh->ctx->nft->output); + nft_mon_print(monh, " "); + } + + if (nftnl_trace_is_set(nlt, NFTNL_TRACE_MARK)) + trace_print_expr(nlt, NFTNL_TRACE_MARK, + meta_expr_alloc(&netlink_location, + NFT_META_MARK), + &monh->ctx->nft->output); + nft_mon_print(monh, "\n"); + break; + } + + nftnl_trace_free(nlt); + return MNL_CB_OK; +} diff --git a/src/utils.c b/src/utils.c index 47f5b791..2aa1eb4e 100644 --- a/src/utils.c +++ b/src/utils.c @@ -8,12 +8,12 @@ * Development of this code funded by Astaro AG (http://www.astaro.com/) */ +#include <nft.h> + #include <stddef.h> -#include <stdlib.h> #include <stdarg.h> #include <stdio.h> #include <unistd.h> -#include <string.h> #include <nftables.h> #include <utils.h> @@ -24,11 +24,6 @@ void __noreturn __memory_allocation_error(const char *filename, uint32_t line) exit(NFT_EXIT_NOMEM); } -void xfree(const void *ptr) -{ - free((void *)ptr); -} - void *xmalloc(size_t size) { void *ptr; @@ -50,6 +45,16 @@ void *xmalloc_array(size_t nmemb, size_t size) return xmalloc(nmemb * size); } +void *xzalloc_array(size_t nmemb, size_t size) +{ + void *ptr; + + ptr = xmalloc_array(nmemb, size); + memset(ptr, 0, nmemb * size); + + return ptr; +} + void *xrealloc(void *ptr, size_t size) { ptr = realloc(ptr, size); @@ -90,3 +95,8 @@ void xstrunescape(const char *in, char *out) } out[k++] = '\0'; } + +int round_pow_2(unsigned int n) +{ + return 1UL << fls(n - 1); +} @@ -1,11 +1,15 @@ /* * XFRM (ipsec) expression * + * Copyright (c) Red Hat GmbH. Author: Florian Westphal <fw@strlen.de> + * * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. + * it under the terms of the GNU General Public License version 2 (or any + * later) as published by the Free Software Foundation. */ +#include <nft.h> + #include <nftables.h> #include <erec.h> #include <expression.h> @@ -13,7 +17,6 @@ #include <datatype.h> #include <gmputil.h> #include <utils.h> -#include <string.h> #include <netinet/ip.h> #include <linux/netfilter.h> @@ -2,14 +2,14 @@ * Copyright (c) 2013-2015 Pablo Neira Ayuso <pablo@netfilter.org> * Copyright (c) 2015 Arturo Borrero Gonzalez <arturo@debian.org> * - * This program is free software; you can redistribute it and/or modifyi - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. + * 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. */ -#include <stdlib.h> +#include <nft.h> + #include <time.h> -#include <string.h> #include <net/if.h> #include <getopt.h> #include <ctype.h> /* for isspace */ @@ -28,85 +28,108 @@ #ifdef HAVE_LIBXTABLES #include <xtables.h> + +static void *xt_entry_alloc(const struct xt_stmt *xt, uint32_t af); #endif void xt_stmt_xlate(const struct stmt *stmt, struct output_ctx *octx) { + static const char *typename[NFT_XT_MAX] = { + [NFT_XT_MATCH] = "match", + [NFT_XT_TARGET] = "target", + [NFT_XT_WATCHER] = "watcher", + }; + int rc = 0; #ifdef HAVE_LIBXTABLES struct xt_xlate *xl = xt_xlate_alloc(10240); + struct xtables_target *tg; + struct xt_entry_target *t; + struct xtables_match *mt; + struct xt_entry_match *m; + size_t size; + void *entry; + + xtables_set_nfproto(stmt->xt.family); + entry = xt_entry_alloc(&stmt->xt, stmt->xt.family); switch (stmt->xt.type) { case NFT_XT_MATCH: - if (stmt->xt.match->xlate) { + mt = xtables_find_match(stmt->xt.name, XTF_TRY_LOAD, NULL); + if (!mt) { + fprintf(octx->error_fp, + "# Warning: XT match %s not found\n", + stmt->xt.name); + break; + } + size = XT_ALIGN(sizeof(*m)) + stmt->xt.infolen; + + m = xzalloc(size); + memcpy(&m->data, stmt->xt.info, stmt->xt.infolen); + + m->u.match_size = size; + m->u.user.revision = stmt->xt.rev; + + if (mt->xlate) { struct xt_xlate_mt_params params = { - .ip = stmt->xt.entry, - .match = stmt->xt.match->m, - .numeric = 0, + .ip = entry, + .match = m, + .numeric = 1, }; - stmt->xt.match->xlate(xl, ¶ms); - nft_print(octx, "%s", xt_xlate_get(xl)); - } else if (stmt->xt.match->print) { - printf("#"); - stmt->xt.match->print(&stmt->xt.entry, - stmt->xt.match->m, 0); + rc = mt->xlate(xl, ¶ms); } + free(m); break; case NFT_XT_WATCHER: case NFT_XT_TARGET: - if (stmt->xt.target->xlate) { + tg = xtables_find_target(stmt->xt.name, XTF_TRY_LOAD); + if (!tg) { + fprintf(octx->error_fp, + "# Warning: XT target %s not found\n", + stmt->xt.name); + break; + } + size = XT_ALIGN(sizeof(*t)) + stmt->xt.infolen; + + t = xzalloc(size); + memcpy(&t->data, stmt->xt.info, stmt->xt.infolen); + + t->u.target_size = size; + t->u.user.revision = stmt->xt.rev; + + strcpy(t->u.user.name, tg->name); + + if (tg->xlate) { struct xt_xlate_tg_params params = { - .ip = stmt->xt.entry, - .target = stmt->xt.target->t, - .numeric = 0, + .ip = entry, + .target = t, + .numeric = 1, }; - stmt->xt.target->xlate(xl, ¶ms); - nft_print(octx, "%s", xt_xlate_get(xl)); - } else if (stmt->xt.target->print) { - printf("#"); - stmt->xt.target->print(NULL, stmt->xt.target->t, 0); + rc = tg->xlate(xl, ¶ms); } - break; - default: + free(t); break; } + if (rc == 1) + nft_print(octx, "%s", xt_xlate_get(xl)); xt_xlate_free(xl); -#else - nft_print(octx, "# xt_%s", stmt->xt.name); + free(entry); #endif + if (!rc) + nft_print(octx, "xt %s \"%s\"", + typename[stmt->xt.type], stmt->xt.name); } void xt_stmt_destroy(struct stmt *stmt) { -#ifdef HAVE_LIBXTABLES - switch (stmt->xt.type) { - case NFT_XT_MATCH: - if (!stmt->xt.match) - break; - if (stmt->xt.match->m) - xfree(stmt->xt.match->m); - xfree(stmt->xt.match); - break; - case NFT_XT_WATCHER: - case NFT_XT_TARGET: - if (!stmt->xt.target) - break; - if (stmt->xt.target->t) - xfree(stmt->xt.target->t); - xfree(stmt->xt.target); - break; - default: - break; - } -#endif - xfree(stmt->xt.entry); - xfree(stmt->xt.name); + free_const(stmt->xt.name); + free(stmt->xt.info); } #ifdef HAVE_LIBXTABLES -static void *xt_entry_alloc(struct xt_stmt *xt, uint32_t af) +static void *xt_entry_alloc(const struct xt_stmt *xt, uint32_t af) { union nft_entry { struct ipt_entry ipt; @@ -173,24 +196,6 @@ static uint32_t xt_proto(const struct proto_ctx *pctx) return 0; } - -static struct xtables_target *xt_target_clone(struct xtables_target *t) -{ - struct xtables_target *clone; - - clone = xzalloc(sizeof(struct xtables_target)); - memcpy(clone, t, sizeof(struct xtables_target)); - return clone; -} - -static struct xtables_match *xt_match_clone(struct xtables_match *m) -{ - struct xtables_match *clone; - - clone = xzalloc(sizeof(struct xtables_match)); - memcpy(clone, m, sizeof(struct xtables_match)); - return clone; -} #endif /* @@ -201,89 +206,48 @@ void netlink_parse_match(struct netlink_parse_ctx *ctx, const struct location *loc, const struct nftnl_expr *nle) { - struct stmt *stmt; - const char *name; -#ifdef HAVE_LIBXTABLES - struct xtables_match *mt; const char *mtinfo; - struct xt_entry_match *m; + struct stmt *stmt; uint32_t mt_len; - xtables_set_nfproto(ctx->table->handle.family); - - name = nftnl_expr_get_str(nle, NFTNL_EXPR_MT_NAME); - - mt = xtables_find_match(name, XTF_TRY_LOAD, NULL); - if (!mt) { - fprintf(stderr, "XT match %s not found\n", name); - return; - } mtinfo = nftnl_expr_get(nle, NFTNL_EXPR_MT_INFO, &mt_len); - m = xzalloc(sizeof(struct xt_entry_match) + mt_len); - memcpy(&m->data, mtinfo, mt_len); - - m->u.match_size = mt_len + XT_ALIGN(sizeof(struct xt_entry_match)); - m->u.user.revision = nftnl_expr_get_u32(nle, NFTNL_EXPR_MT_REV); - stmt = xt_stmt_alloc(loc); - stmt->xt.name = strdup(name); + stmt->xt.name = strdup(nftnl_expr_get_str(nle, NFTNL_EXPR_MT_NAME)); stmt->xt.type = NFT_XT_MATCH; - stmt->xt.match = xt_match_clone(mt); - stmt->xt.match->m = m; -#else - name = nftnl_expr_get_str(nle, NFTNL_EXPR_MT_NAME); + stmt->xt.rev = nftnl_expr_get_u32(nle, NFTNL_EXPR_MT_REV); + stmt->xt.family = ctx->table->handle.family; - stmt = xt_stmt_alloc(loc); - stmt->xt.name = strdup(name); - stmt->xt.type = NFT_XT_MATCH; -#endif - list_add_tail(&stmt->list, &ctx->rule->stmts); + stmt->xt.infolen = mt_len; + stmt->xt.info = xmalloc(mt_len); + memcpy(stmt->xt.info, mtinfo, mt_len); + + ctx->table->has_xt_stmts = true; + rule_stmt_append(ctx->rule, stmt); } void netlink_parse_target(struct netlink_parse_ctx *ctx, const struct location *loc, const struct nftnl_expr *nle) { - struct stmt *stmt; - const char *name; -#ifdef HAVE_LIBXTABLES - struct xtables_target *tg; const void *tginfo; - struct xt_entry_target *t; - size_t size; + struct stmt *stmt; uint32_t tg_len; - xtables_set_nfproto(ctx->table->handle.family); - - name = nftnl_expr_get_str(nle, NFTNL_EXPR_TG_NAME); - tg = xtables_find_target(name, XTF_TRY_LOAD); - if (!tg) { - fprintf(stderr, "XT target %s not found\n", name); - return; - } tginfo = nftnl_expr_get(nle, NFTNL_EXPR_TG_INFO, &tg_len); - size = XT_ALIGN(sizeof(struct xt_entry_target)) + tg_len; - t = xzalloc(size); - memcpy(&t->data, tginfo, tg_len); - t->u.target_size = size; - t->u.user.revision = nftnl_expr_get_u32(nle, NFTNL_EXPR_TG_REV); - strcpy(t->u.user.name, tg->name); - stmt = xt_stmt_alloc(loc); - stmt->xt.name = strdup(name); + stmt->xt.name = strdup(nftnl_expr_get_str(nle, NFTNL_EXPR_TG_NAME)); stmt->xt.type = NFT_XT_TARGET; - stmt->xt.target = xt_target_clone(tg); - stmt->xt.target->t = t; -#else - name = nftnl_expr_get_str(nle, NFTNL_EXPR_TG_NAME); + stmt->xt.rev = nftnl_expr_get_u32(nle, NFTNL_EXPR_TG_REV); + stmt->xt.family = ctx->table->handle.family; - stmt = xt_stmt_alloc(loc); - stmt->xt.name = strdup(name); - stmt->xt.type = NFT_XT_TARGET; -#endif - list_add_tail(&stmt->list, &ctx->rule->stmts); + stmt->xt.infolen = tg_len; + stmt->xt.info = xmalloc(tg_len); + memcpy(stmt->xt.info, tginfo, tg_len); + + ctx->table->has_xt_stmts = true; + rule_stmt_append(ctx->rule, stmt); } #ifdef HAVE_LIBXTABLES @@ -305,11 +269,12 @@ static bool is_watcher(uint32_t family, struct stmt *stmt) void stmt_xt_postprocess(struct rule_pp_ctx *rctx, struct stmt *stmt, struct rule *rule) { - if (is_watcher(rctx->pctx.family, stmt)) + struct dl_proto_ctx *dl = dl_proto_ctx(rctx); + + if (is_watcher(dl->pctx.family, stmt)) stmt->xt.type = NFT_XT_WATCHER; - stmt->xt.proto = xt_proto(&rctx->pctx); - stmt->xt.entry = xt_entry_alloc(&stmt->xt, rctx->pctx.family); + stmt->xt.proto = xt_proto(&dl->pctx); } static int nft_xt_compatible_revision(const char *name, uint8_t rev, int opt) @@ -321,7 +286,7 @@ static int nft_xt_compatible_revision(const char *name, uint8_t rev, int opt) struct nfgenmsg *nfg; int ret = 0; - switch (rev) { + switch (opt) { case IPT_SO_GET_REVISION_MATCH: family = NFPROTO_IPV4; type = 0; @@ -383,7 +348,7 @@ err: } static struct option original_opts[] = { - { NULL }, + { }, }; static struct xtables_globals xt_nft_globals = { @@ -395,7 +360,18 @@ static struct xtables_globals xt_nft_globals = { void xt_init(void) { - /* Default to IPv4, but this changes in runtime */ - xtables_init_all(&xt_nft_globals, NFPROTO_IPV4); + static bool init_once; + + if (!init_once) { + /* libxtables is full of global variables and cannot be used + * concurrently by multiple threads. Hence, it's fine that the + * "init_once" guard is not thread-safe either. + * Don't link against xtables if you want thread safety. + */ + init_once = true; + + /* Default to IPv4, but this changes in runtime */ + xtables_init_all(&xt_nft_globals, NFPROTO_IPV4); + } } #endif |