diff options
Diffstat (limited to '')
-rw-r--r-- | src/cache.c | 1295 |
1 files changed, 1295 insertions, 0 deletions
diff --git a/src/cache.c b/src/cache.c new file mode 100644 index 0000000..4e89fe1 --- /dev/null +++ b/src/cache.c @@ -0,0 +1,1295 @@ +/* + * Copyright (c) 2019 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 <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_CHAIN | + NFT_CACHE_SET | + NFT_CACHE_OBJECT | + NFT_CACHE_FLOWTABLE; + 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_ELEMENTS: + flags |= NFT_CACHE_TABLE | + NFT_CACHE_CHAIN | + NFT_CACHE_SET | + NFT_CACHE_OBJECT | + 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; + + if (cmd->handle.index.id || + cmd->handle.position.id) + flags |= NFT_CACHE_RULE | NFT_CACHE_UPDATE; + break; + default: + break; + } + + return flags; +} + +static unsigned int evaluate_cache_del(struct cmd *cmd, unsigned int flags) +{ + switch (cmd->obj) { + case CMD_OBJ_ELEMENTS: + flags |= NFT_CACHE_SETELEM_MAYBE; + break; + default: + break; + } + + return flags; +} + +static unsigned int evaluate_cache_get(struct cmd *cmd, unsigned int flags) +{ + switch (cmd->obj) { + case CMD_OBJ_ELEMENTS: + flags |= NFT_CACHE_TABLE | + NFT_CACHE_SET | + NFT_CACHE_SETELEM; + break; + default: + break; + } + + return 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) + xfree(obj); + } + xfree(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; + break; + default: + break; + } + + return flags; +} + +static unsigned int evaluate_cache_rename(struct cmd *cmd, unsigned int flags) +{ + switch (cmd->obj) { + case CMD_OBJ_CHAIN: + flags |= NFT_CACHE_CHAIN; + break; + default: + break; + } + + return flags; +} + +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: + if (filter && cmd->handle.table.name) { + filter->list.family = cmd->handle.family; + filter->list.table = cmd->handle.table.name; + } + flags |= NFT_CACHE_FULL; + break; + case CMD_OBJ_CHAIN: + if (filter && 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 (filter && 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 if (nft_output_terse(&nft->output)) + flags |= NFT_CACHE_FULL | NFT_CACHE_TERSE; + 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 (filter && + 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_RULESET: + if (nft_output_terse(&nft->output)) + flags |= NFT_CACHE_FULL | NFT_CACHE_TERSE; + else + flags |= NFT_CACHE_FULL; + break; + default: + flags |= NFT_CACHE_FULL; + break; + } + flags |= NFT_CACHE_REFRESH; + + 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_RULES: + case CMD_OBJ_RULE: + if (filter) { + 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; + } + flags |= NFT_CACHE_SET | NFT_CACHE_FLOWTABLE | + NFT_CACHE_OBJECT | NFT_CACHE_CHAIN; + break; + case CMD_OBJ_ELEMENTS: + case CMD_OBJ_SET: + case CMD_OBJ_MAP: + flags |= NFT_CACHE_SET; + break; + default: + flags |= NFT_CACHE_TABLE; + break; + } + + 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; +} + +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; + struct cmd *cmd; + + list_for_each_entry(cmd, cmds, list) { + if (nft_handle_validate(cmd, msgs) < 0) + return -1; + + if (filter->list.table && cmd->op != CMD_LIST) + memset(&filter->list, 0, sizeof(filter->list)); + + 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; + break; + case CMD_DELETE: + case CMD_DESTROY: + flags |= NFT_CACHE_TABLE | + NFT_CACHE_CHAIN | + NFT_CACHE_SET | + NFT_CACHE_FLOWTABLE | + NFT_CACHE_OBJECT; + + flags = evaluate_cache_del(cmd, flags); + break; + case CMD_GET: + flags = evaluate_cache_get(cmd, flags); + break; + case CMD_RESET: + flags |= evaluate_cache_reset(cmd, flags, filter); + break; + case CMD_LIST: + flags |= evaluate_cache_list(nft, cmd, flags, filter); + break; + case CMD_MONITOR: + flags |= NFT_CACHE_FULL; + break; + case CMD_FLUSH: + 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; + } + } + *pflags = flags; + + return 0; +} + +void table_cache_add(struct table *table, struct nft_cache *cache) +{ + uint32_t hash; + + hash = djb_hash(table->handle.table.name) % NFT_CACHE_HSIZE; + cache_add(&table->cache, &cache->table_cache, hash); +} + +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; + + if (!name) + return NULL; + + 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; + uint32_t hash, family; + struct chain *chain; + + 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); + hash = djb_hash(chain_name) % NFT_CACHE_HSIZE; + 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, hash); + } + + 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) +{ + uint32_t hash; + + hash = djb_hash(chain->handle.chain.name) % NFT_CACHE_HSIZE; + cache_add(&chain->cache, &table->chain_cache, hash); +} + +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; + + 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; +} + +int rule_cache_dump(struct netlink_ctx *ctx, const struct handle *h, + const struct nft_cache_filter *filter, + bool dump, bool reset) +{ + struct nftnl_rule_list *rule_cache; + const char *table = NULL; + const char *chain = NULL; + uint64_t rule_handle = 0; + + if (filter) { + table = filter->list.table; + chain = filter->list.chain; + rule_handle = filter->list.rule_handle; + } + + rule_cache = mnl_nft_rule_dump(ctx, h->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; + uint32_t hash; + + 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); + hash = djb_hash(set_name) % NFT_CACHE_HSIZE; + cache_add(&set->cache, &ctx->table->set_cache, hash); + + 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) +{ + uint32_t hash; + + hash = djb_hash(set->handle.set.name) % NFT_CACHE_HSIZE; + cache_add(&set->cache, &table->set_cache, hash); +} + +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; + + 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_name; + struct obj *obj; + uint32_t hash; + + obj = netlink_delinearize_obj(ctx->nlctx, nlo); + if (!obj) + return -1; + + obj_name = nftnl_obj_get_str(nlo, NFTNL_OBJ_NAME); + hash = djb_hash(obj_name) % NFT_CACHE_HSIZE; + cache_add(&obj->cache, &ctx->table->obj_cache, hash); + + 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 table *table) +{ + struct nftnl_obj_list *obj_list; + + obj_list = mnl_nft_obj_dump(ctx, table->handle.family, + table->handle.table.name, NULL, + 0, true, false); + 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) +{ + uint32_t hash; + + hash = djb_hash(obj->handle.obj.name) % NFT_CACHE_HSIZE; + cache_add(&obj->cache, &table->obj_cache, hash); +} + +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; + + 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; + uint32_t hash; + + 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); + hash = djb_hash(ft_name) % NFT_CACHE_HSIZE; + cache_add(&ft->cache, &ctx->table->ft_cache, hash); + + 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) +{ + uint32_t hash; + + hash = djb_hash(ft->handle.flowtable.name) % NFT_CACHE_HSIZE; + cache_add(&ft->cache, &table->ft_cache, hash); +} + +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; + + 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, true, false); + + 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 = (typeof(filter.list)) { + .table = table->handle.table.name, + .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; + 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_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, false); + 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, false); + 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) { + obj_list = obj_cache_dump(ctx, table); + if (!obj_list) { + ret = -1; + goto cache_fails; + } + ret = obj_cache_init(ctx, table, obj_list); + + nftnl_obj_list_free(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 (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) +{ + return cache->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) && + 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) +{ + xfree(cache->ht); +} + +void cache_add(struct cache_item *item, struct cache *cache, uint32_t hash) +{ + 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); +} |