diff options
Diffstat (limited to 'src/cmd.c')
-rw-r--r-- | src/cmd.c | 407 |
1 files changed, 375 insertions, 32 deletions
@@ -1,3 +1,13 @@ +/* + * 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> @@ -5,36 +15,82 @@ #include <utils.h> #include <iface.h> #include <errno.h> -#include <stdlib.h> #include <cache.h> -#include <string.h> + +void cmd_add_loc(struct cmd *cmd, uint16_t offset, 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].offset = offset; + cmd->attr[cmd->num_attrs].location = loc; + cmd->num_attrs++; +} static int nft_cmd_enoent_table(struct netlink_ctx *ctx, const struct cmd *cmd, - struct location *loc) + 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?", + 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, - struct location *loc) + const struct location *loc) { - const struct table *table; + 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 table ‘%s’ in family %s?", + 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); @@ -42,29 +98,29 @@ static int nft_cmd_enoent_chain(struct netlink_ctx *ctx, const struct cmd *cmd, } static int nft_cmd_enoent_rule(struct netlink_ctx *ctx, const struct cmd *cmd, - struct location *loc) + const struct location *loc) { unsigned int flags = NFT_CACHE_TABLE | NFT_CACHE_CHAIN; - const struct table *table; + const struct table *table = NULL; struct chain *chain; - if (cache_update(ctx->nft, flags, ctx->msgs) < 0) + if (nft_cache_update(ctx->nft, flags, ctx->msgs, NULL) < 0) return 0; - table = table_lookup_fuzzy(&cmd->handle, &ctx->nft->cache); - if (table && strcmp(cmd->handle.table.name, table->handle.table.name)) { - netlink_io_error(ctx, loc, "%s; did you mean table ‘%s’ in family %s?", - strerror(ENOENT), table->handle.table.name, - family2str(table->handle.family)); + 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; - } else if (!table) { + + if (!chain) return 0; - } - chain = chain_lookup_fuzzy(&cmd->handle, &ctx->nft->cache, &table); - if (chain && strcmp(cmd->handle.chain.name, chain->handle.chain.name)) { - netlink_io_error(ctx, loc, "%s; did you mean chain ‘%s’ in table %s ‘%s’?", + 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), @@ -76,16 +132,30 @@ static int nft_cmd_enoent_rule(struct netlink_ctx *ctx, const struct cmd *cmd, } static int nft_cmd_enoent_set(struct netlink_ctx *ctx, const struct cmd *cmd, - struct location *loc) + const struct location *loc) { - const struct table *table; + 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’?", + 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, @@ -95,16 +165,30 @@ static int nft_cmd_enoent_set(struct netlink_ctx *ctx, const struct cmd *cmd, } static int nft_cmd_enoent_obj(struct netlink_ctx *ctx, const struct cmd *cmd, - struct location *loc) + const struct location *loc) { - const struct table *table; + 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’?", + 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); @@ -112,17 +196,32 @@ static int nft_cmd_enoent_obj(struct netlink_ctx *ctx, const struct cmd *cmd, } static int nft_cmd_enoent_flowtable(struct netlink_ctx *ctx, - const struct cmd *cmd, struct location *loc) + const struct cmd *cmd, + const struct location *loc) { - const struct table *table; + 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’?", + 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); @@ -130,7 +229,7 @@ static int nft_cmd_enoent_flowtable(struct netlink_ctx *ctx, } static void nft_cmd_enoent(struct netlink_ctx *ctx, const struct cmd *cmd, - struct location *loc, int err) + const struct location *loc, int err) { int ret = 0; @@ -170,11 +269,58 @@ static void nft_cmd_enoent(struct netlink_ctx *ctx, const struct cmd *cmd, 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) { - struct location *loc = NULL; - int i; + const struct location *loc = NULL; + uint32_t i; for (i = 0; i < cmd->num_attrs; i++) { if (!cmd->attr[i].offset) @@ -192,6 +338,203 @@ void nft_cmd_error(struct netlink_ctx *ctx, struct cmd *cmd, 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); + } +} + +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; + } +} + +bool nft_cmd_collapse(struct list_head *cmds) +{ + struct cmd *cmd, *next, *elems = NULL; + struct expr *expr, *enext; + bool collapse = false; + + list_for_each_entry_safe(cmd, next, cmds, list) { + if (cmd->op != CMD_ADD && + cmd->op != CMD_CREATE) { + elems = NULL; + continue; + } + + if (cmd->obj != CMD_OBJ_ELEMENTS) { + elems = NULL; + continue; + } + + if (cmd->expr->etype == EXPR_VARIABLE) + continue; + + if (!elems) { + elems = cmd; + continue; + } + + if (cmd->op != elems->op) { + elems = cmd; + continue; + } + + if (elems->handle.family != cmd->handle.family || + strcmp(elems->handle.table.name, cmd->handle.table.name) || + strcmp(elems->handle.set.name, cmd->handle.set.name)) { + elems = cmd; + continue; + } + + collapse = true; + list_for_each_entry_safe(expr, enext, &cmd->expr->expressions, list) { + expr->cmd = cmd; + list_move_tail(&expr->list, &elems->expr->expressions); + } + elems->expr->size += cmd->expr->size; + list_move_tail(&cmd->list, &elems->collapse_list); + } + + return collapse; +} + +void nft_cmd_uncollapse(struct list_head *cmds) +{ + struct cmd *cmd, *cmd_next, *collapse_cmd, *collapse_cmd_next; + struct expr *expr, *next; + + list_for_each_entry_safe(cmd, cmd_next, cmds, list) { + if (list_empty(&cmd->collapse_list)) + continue; + + assert(cmd->obj == CMD_OBJ_ELEMENTS); + + list_for_each_entry_safe(expr, next, &cmd->expr->expressions, list) { + if (!expr->cmd) + continue; + + list_move_tail(&expr->list, &expr->cmd->expr->expressions); + cmd->expr->size--; + expr->cmd = NULL; + } + + list_for_each_entry_safe(collapse_cmd, collapse_cmd_next, &cmd->collapse_list, list) { + if (cmd->elem.set) + collapse_cmd->elem.set = set_get(cmd->elem.set); + + list_add(&collapse_cmd->list, &cmd->list); + } + } +} |