diff options
Diffstat (limited to 'src/rule.c')
-rw-r--r-- | src/rule.c | 2799 |
1 files changed, 2799 insertions, 0 deletions
diff --git a/src/rule.c b/src/rule.c new file mode 100644 index 0000000..739b7a5 --- /dev/null +++ b/src/rule.c @@ -0,0 +1,2799 @@ +/* + * Copyright (c) 2008-2012 Patrick McHardy <kaber@trash.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 as + * published by the Free Software Foundation. + * + * Development of this code funded by Astaro AG (http://www.astaro.com/) + */ + +#include <nft.h> + +#include <stddef.h> +#include <stdio.h> +#include <inttypes.h> +#include <errno.h> + +#include <statement.h> +#include <rule.h> +#include <utils.h> +#include <netdb.h> +#include <netlink.h> +#include <mnl.h> +#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> +#include <netinet/ip.h> +#include <linux/netfilter.h> +#include <linux/netfilter_arp.h> +#include <linux/netfilter_ipv4.h> +#include <linux/netfilter/nf_synproxy.h> +#include <net/if.h> +#include <linux/netfilter_bridge.h> + +static const char *const tcp_state_to_name[] = { + [NFTNL_CTTIMEOUT_TCP_SYN_SENT] = "syn_sent", + [NFTNL_CTTIMEOUT_TCP_SYN_RECV] = "syn_recv", + [NFTNL_CTTIMEOUT_TCP_ESTABLISHED] = "established", + [NFTNL_CTTIMEOUT_TCP_FIN_WAIT] = "fin_wait", + [NFTNL_CTTIMEOUT_TCP_CLOSE_WAIT] = "close_wait", + [NFTNL_CTTIMEOUT_TCP_LAST_ACK] = "last_ack", + [NFTNL_CTTIMEOUT_TCP_TIME_WAIT] = "time_wait", + [NFTNL_CTTIMEOUT_TCP_CLOSE] = "close", + [NFTNL_CTTIMEOUT_TCP_SYN_SENT2] = "syn_sent2", + [NFTNL_CTTIMEOUT_TCP_RETRANS] = "retrans", + [NFTNL_CTTIMEOUT_TCP_UNACK] = "unack", +}; + +static const char *const udp_state_to_name[] = { + [NFTNL_CTTIMEOUT_UDP_UNREPLIED] = "unreplied", + [NFTNL_CTTIMEOUT_UDP_REPLIED] = "replied", +}; + +static uint32_t tcp_dflt_timeout[] = { + [NFTNL_CTTIMEOUT_TCP_SYN_SENT] = 120, + [NFTNL_CTTIMEOUT_TCP_SYN_RECV] = 60, + [NFTNL_CTTIMEOUT_TCP_ESTABLISHED] = 432000, + [NFTNL_CTTIMEOUT_TCP_FIN_WAIT] = 120, + [NFTNL_CTTIMEOUT_TCP_CLOSE_WAIT] = 60, + [NFTNL_CTTIMEOUT_TCP_LAST_ACK] = 30, + [NFTNL_CTTIMEOUT_TCP_TIME_WAIT] = 120, + [NFTNL_CTTIMEOUT_TCP_CLOSE] = 10, + [NFTNL_CTTIMEOUT_TCP_SYN_SENT2] = 120, + [NFTNL_CTTIMEOUT_TCP_RETRANS] = 300, + [NFTNL_CTTIMEOUT_TCP_UNACK] = 300, +}; + +static uint32_t udp_dflt_timeout[] = { + [NFTNL_CTTIMEOUT_UDP_UNREPLIED] = 30, + [NFTNL_CTTIMEOUT_UDP_REPLIED] = 120, +}; + +struct timeout_protocol timeout_protocol[UINT8_MAX + 1] = { + [IPPROTO_TCP] = { + .array_size = NFTNL_CTTIMEOUT_TCP_MAX, + .state_to_name = tcp_state_to_name, + .dflt_timeout = tcp_dflt_timeout, + }, + [IPPROTO_UDP] = { + .array_size = NFTNL_CTTIMEOUT_UDP_MAX, + .state_to_name = udp_state_to_name, + .dflt_timeout = udp_dflt_timeout, + }, +}; + +int timeout_str2num(uint16_t l4proto, struct timeout_state *ts) +{ + unsigned int i; + + for (i = 0; i < timeout_protocol[l4proto].array_size; i++) { + if (!strcmp(timeout_protocol[l4proto].state_to_name[i], ts->timeout_str)) { + ts->timeout_index = i; + return 0; + } + } + return -1; +} + +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); +} + +void handle_merge(struct handle *dst, const struct handle *src) +{ + if (dst->family == 0) + dst->family = src->family; + if (dst->table.name == NULL && src->table.name != NULL) { + dst->table.name = xstrdup(src->table.name); + dst->table.location = src->table.location; + } + if (dst->chain.name == NULL && src->chain.name != NULL) { + dst->chain.name = xstrdup(src->chain.name); + dst->chain.location = src->chain.location; + } + if (dst->set.name == NULL && src->set.name != NULL) { + dst->set.name = xstrdup(src->set.name); + dst->set.location = src->set.location; + } + if (dst->flowtable.name == NULL && src->flowtable.name != NULL) + dst->flowtable.name = xstrdup(src->flowtable.name); + if (dst->obj.name == NULL && src->obj.name != NULL) + dst->obj.name = xstrdup(src->obj.name); + if (dst->handle.id == 0) + dst->handle = src->handle; + if (dst->position.id == 0) + dst->position = src->position; + if (dst->index.id == 0) + dst->index = src->index; +} + +/* internal ID to uniquely identify a set in the batch */ +static uint32_t set_id; + +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; + set->location = *loc; + + init_list_head(&set->stmt_list); + + return set; +} + +struct set *set_clone(const struct set *set) +{ + struct set *new_set; + + 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; + new_set->timeout = set->timeout; + new_set->key = expr_clone(set->key); + if (set->data) + new_set->data = expr_clone(set->data); + new_set->objtype = set->objtype; + new_set->policy = set->policy; + new_set->automerge = set->automerge; + new_set->desc = set->desc; + init_list_head(&new_set->stmt_list); + + return new_set; +} + +struct set *set_get(struct set *set) +{ + set->refcnt++; + return set; +} + +void set_free(struct set *set) +{ + struct stmt *stmt, *next; + + if (--set->refcnt > 0) + return; + + expr_free(set->init); + if (set->comment) + xfree(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); +} + +struct set *set_lookup_fuzzy(const char *set_name, + const struct nft_cache *cache, + const struct table **t) +{ + struct string_misspell_state st; + struct table *table; + struct set *set; + + string_misspell_init(&st); + + 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 (string_misspell_update(set->handle.set.name, + set_name, set, &st)) + *t = table; + } + } + return st.obj; +} + +struct set *set_lookup_global(uint32_t family, const char *table, + const char *name, struct nft_cache *cache) +{ + struct table *t; + + t = table_cache_find(&cache->table_cache, table, family); + if (t == NULL) + return NULL; + + return set_cache_find(t, name); +} + +struct print_fmt_options { + const char *tab; + const char *nl; + const char *table; + const char *family; + const char *stmt_separator; +}; + +const char *set_policy2str(uint32_t policy) +{ + switch (policy) { + case NFT_SET_POL_PERFORMANCE: + return "performance"; + case NFT_SET_POL_MEMORY: + return "memory"; + default: + return "unknown"; + } +} + +static void set_print_key(const struct expr *expr, struct output_ctx *octx) +{ + const struct datatype *dtype = expr->dtype; + + if (dtype->size || dtype->type == TYPE_VERDICT) + nft_print(octx, "%s", dtype->name); + else + expr_print(expr, octx); +} + +static void set_print_key_and_data(const struct set *set, struct output_ctx *octx) +{ + bool use_typeof = set->key_typeof_valid; + + nft_print(octx, "%s ", use_typeof ? "typeof" : "type"); + + if (use_typeof) + expr_print(set->key, octx); + else + set_print_key(set->key, octx); + + 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 + set_print_key(set->data, octx); + } else if (set_is_objmap(set->flags)) { + nft_print(octx, " : %s", obj_type_name(set->objtype)); + } +} + +static void set_print_declaration(const struct set *set, + struct print_fmt_options *opts, + struct output_ctx *octx) +{ + const char *delim = ""; + struct stmt *stmt; + const char *type; + uint32_t flags; + + if (set_is_meter(set->flags)) + type = "meter"; + else if (set_is_map(set->flags)) + type = "map"; + else + type = "set"; + + nft_print(octx, "%s%s", opts->tab, type); + + if (opts->family != NULL) + nft_print(octx, " %s", opts->family); + + if (opts->table != NULL) + nft_print(octx, " %s", opts->table); + + nft_print(octx, " %s {", set->handle.set.name); + + if (nft_output_handle(octx)) + nft_print(octx, " # handle %" PRIu64, set->handle.handle.id); + nft_print(octx, "%s%s%s", opts->nl, opts->tab, opts->tab); + + set_print_key_and_data(set, octx); + + nft_print(octx, "%s", opts->stmt_separator); + + if (!(set->flags & (NFT_SET_CONSTANT))) { + if (set->policy != NFT_SET_POL_PERFORMANCE) { + nft_print(octx, "%s%spolicy %s%s", + opts->tab, opts->tab, + set_policy2str(set->policy), + opts->stmt_separator); + } + + if (set->desc.size > 0) { + nft_print(octx, "%s%ssize %u%s", + opts->tab, opts->tab, + set->desc.size, + opts->stmt_separator); + } + } + + flags = set->flags; + /* "timeout" flag is redundant if a default timeout exists */ + if (set->timeout) + flags &= ~NFT_SET_TIMEOUT; + + if (flags & (NFT_SET_CONSTANT | NFT_SET_INTERVAL | NFT_SET_TIMEOUT | NFT_SET_EVAL)) { + nft_print(octx, "%s%sflags ", opts->tab, opts->tab); + if (set->flags & NFT_SET_CONSTANT) { + nft_print(octx, "%sconstant", delim); + delim = ","; + } + if (set->flags & NFT_SET_EVAL) { + nft_print(octx, "%sdynamic", delim); + delim = ","; + } + if (set->flags & NFT_SET_INTERVAL) { + nft_print(octx, "%sinterval", delim); + delim = ","; + } + if (set->flags & NFT_SET_TIMEOUT) { + nft_print(octx, "%stimeout", delim); + delim = ","; + } + 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); + + if (set->timeout) { + nft_print(octx, "%s%stimeout ", opts->tab, opts->tab); + time_print(set->timeout, octx); + nft_print(octx, "%s", opts->stmt_separator); + } + if (set->gc_int) { + nft_print(octx, "%s%sgc-interval ", opts->tab, opts->tab); + 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, + struct output_ctx *octx) +{ + set_print_declaration(set, opts, octx); + + if ((set_is_meter(set->flags) && nft_output_stateless(octx)) || + nft_output_terse(octx)) { + nft_print(octx, "%s}%s", opts->tab, opts->nl); + return; + } + + if (set->init != NULL && set->init->size > 0) { + nft_print(octx, "%s%selements = ", opts->tab, opts->tab); + expr_print(set->init, octx); + nft_print(octx, "%s", opts->nl); + } + nft_print(octx, "%s}%s", opts->tab, opts->nl); +} + +void set_print(const struct set *s, struct output_ctx *octx) +{ + struct print_fmt_options opts = { + .tab = "\t", + .nl = "\n", + .stmt_separator = "\n", + }; + + do_set_print(s, &opts, octx); +} + +void set_print_plain(const struct set *s, struct output_ctx *octx) +{ + struct print_fmt_options opts = { + .tab = "", + .nl = " ", + .table = s->handle.table.name, + .family = family2str(s->handle.family), + .stmt_separator = "; ", + }; + + do_set_print(s, &opts, octx); +} + +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); + init_list_head(&rule->stmts); + rule->refcnt = 1; + if (h != NULL) + rule->handle = *h; + + return rule; +} + +struct rule *rule_get(struct rule *rule) +{ + rule->refcnt++; + return rule; +} + +void rule_free(struct rule *rule) +{ + if (--rule->refcnt > 0) + return; + stmt_list_free(&rule->stmts); + handle_free(&rule->handle); + xfree(rule->comment); + xfree(rule); +} + +void rule_print(const struct rule *rule, struct output_ctx *octx) +{ + const struct stmt *stmt; + + list_for_each_entry(stmt, &rule->stmts, list) { + stmt->ops->print(stmt, octx); + if (!list_is_last(&stmt->list, &rule->stmts)) + nft_print(octx, " "); + } + + if (rule->comment) + nft_print(octx, " comment \"%s\"", rule->comment); + + if (nft_output_handle(octx)) + nft_print(octx, " # handle %" PRIu64, rule->handle.handle.id); +} + +struct rule *rule_lookup(const struct chain *chain, uint64_t handle) +{ + struct rule *rule; + + list_for_each_entry(rule, &chain->rules, list) { + if (rule->handle.handle.id == handle) + return rule; + } + return NULL; +} + +struct rule *rule_lookup_by_index(const struct chain *chain, uint64_t index) +{ + struct rule *rule; + + list_for_each_entry(rule, &chain->rules, list) { + if (!--index) + return rule; + } + 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)); + + init_list_head(&scope->symbols); + + return scope; +} + +struct scope *scope_init(struct scope *scope, const struct scope *parent) +{ + scope->parent = parent; + return scope; +} + +void scope_release(const struct scope *scope) +{ + struct symbol *sym, *next; + + list_for_each_entry_safe(sym, next, &scope->symbols, list) { + assert(sym->refcnt == 1); + list_del(&sym->list); + xfree(sym->identifier); + expr_free(sym->expr); + xfree(sym); + } +} + +void scope_free(struct scope *scope) +{ + scope_release(scope); + xfree(scope); +} + +void symbol_bind(struct scope *scope, const char *identifier, struct expr *expr) +{ + struct symbol *sym; + + sym = xzalloc(sizeof(*sym)); + sym->identifier = xstrdup(identifier); + sym->expr = expr; + sym->refcnt = 1; + + list_add(&sym->list, &scope->symbols); +} + +struct symbol *symbol_get(const struct scope *scope, const char *identifier) +{ + struct symbol *sym; + + sym = symbol_lookup(scope, identifier); + if (!sym) + return NULL; + + sym->refcnt++; + + return sym; +} + +static void symbol_put(struct symbol *sym) +{ + if (--sym->refcnt == 0) { + xfree(sym->identifier); + expr_free(sym->expr); + xfree(sym); + } +} + +static void symbol_remove(struct symbol *sym) +{ + list_del(&sym->list); + symbol_put(sym); +} + +int symbol_unbind(const struct scope *scope, const char *identifier) +{ + struct symbol *sym, *next; + + list_for_each_entry_safe(sym, next, &scope->symbols, list) { + if (!strcmp(sym->identifier, identifier)) + symbol_remove(sym); + } + + return 0; +} + +struct symbol *symbol_lookup(const struct scope *scope, const char *identifier) +{ + struct symbol *sym; + + while (scope != NULL) { + list_for_each_entry(sym, &scope->symbols, list) { + if (!strcmp(sym->identifier, identifier)) + return sym; + } + scope = scope->parent; + } + return NULL; +} + +struct symbol *symbol_lookup_fuzzy(const struct scope *scope, + const char *identifier) +{ + struct string_misspell_state st; + struct symbol *sym; + + string_misspell_init(&st); + + while (scope != NULL) { + list_for_each_entry(sym, &scope->symbols, list) + string_misspell_update(sym->identifier, identifier, + sym, &st); + + scope = scope->parent; + } + return st.obj; +} + +static const char * const chain_type_str_array[] = { + "filter", + "nat", + "route", + NULL, +}; + +const char *chain_type_name_lookup(const char *name) +{ + int i; + + for (i = 0; chain_type_str_array[i]; i++) { + if (!strcmp(name, chain_type_str_array[i])) + return chain_type_str_array[i]; + } + + return NULL; +} + +static const char * const chain_hookname_str_array[] = { + "prerouting", + "input", + "forward", + "postrouting", + "output", + "ingress", + "egress", + NULL, +}; + +const char *chain_hookname_lookup(const char *name) +{ + int i; + + for (i = 0; chain_hookname_str_array[i]; i++) { + if (!strcmp(name, chain_hookname_str_array[i])) + return chain_hookname_str_array[i]; + } + + return NULL; +} + +/* 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); + + chain->policy = NULL; + return chain; +} + +struct chain *chain_get(struct chain *chain) +{ + chain->refcnt++; + return chain; +} + +void chain_free(struct chain *chain) +{ + struct rule *rule, *next; + int i; + + if (--chain->refcnt > 0) + return; + list_for_each_entry_safe(rule, next, &chain->rules, list) + rule_free(rule); + handle_free(&chain->handle); + scope_release(&chain->scope); + xfree(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); + expr_free(chain->priority.expr); + expr_free(chain->policy); + xfree(chain->comment); + xfree(chain); +} + +struct chain *chain_binding_lookup(const struct table *table, + const char *chain_name) +{ + struct chain *chain; + + list_for_each_entry(chain, &table->chain_bindings, cache.list) { + if (!strcmp(chain->handle.chain.name, chain_name)) + return chain; + } + return NULL; +} + +struct chain *chain_lookup_fuzzy(const struct handle *h, + const struct nft_cache *cache, + const struct table **t) +{ + struct string_misspell_state st; + struct table *table; + struct chain *chain; + + if (!h->chain.name) + return NULL; + + string_misspell_init(&st); + + 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; + } + } + return st.obj; +} + +const char *family2str(unsigned int family) +{ + switch (family) { + case NFPROTO_IPV4: + return "ip"; + case NFPROTO_IPV6: + return "ip6"; + case NFPROTO_INET: + return "inet"; + case NFPROTO_NETDEV: + return "netdev"; + case NFPROTO_ARP: + return "arp"; + case NFPROTO_BRIDGE: + return "bridge"; + default: + break; + } + return "unknown"; +} + +const char *hooknum2str(unsigned int family, unsigned int hooknum) +{ + switch (family) { + case NFPROTO_IPV4: + case NFPROTO_BRIDGE: + case NFPROTO_IPV6: + case NFPROTO_INET: + switch (hooknum) { + case NF_INET_PRE_ROUTING: + return "prerouting"; + case NF_INET_LOCAL_IN: + return "input"; + case NF_INET_FORWARD: + return "forward"; + case NF_INET_POST_ROUTING: + return "postrouting"; + case NF_INET_LOCAL_OUT: + return "output"; + case NF_INET_INGRESS: + return "ingress"; + default: + break; + }; + break; + case NFPROTO_ARP: + switch (hooknum) { + case NF_ARP_IN: + return "input"; + case NF_ARP_FORWARD: + return "forward"; + case NF_ARP_OUT: + return "output"; + case __NF_ARP_INGRESS: + return "ingress"; + default: + break; + } + break; + case NFPROTO_NETDEV: + switch (hooknum) { + case NF_NETDEV_INGRESS: + return "ingress"; + case NF_NETDEV_EGRESS: + return "egress"; + } + break; + default: + break; + }; + + return "unknown"; +} + +const char *chain_policy2str(uint32_t policy) +{ + switch (policy) { + case NF_DROP: + return "drop"; + case NF_ACCEPT: + return "accept"; + } + return "unknown"; +} + +struct prio_tag { + int val; + const char *str; +}; + +static const struct prio_tag std_prios[] = { + { NF_IP_PRI_RAW, "raw" }, + { NF_IP_PRI_MANGLE, "mangle" }, + { NF_IP_PRI_NAT_DST, "dstnat" }, + { NF_IP_PRI_FILTER, "filter" }, + { NF_IP_PRI_SECURITY, "security" }, + { NF_IP_PRI_NAT_SRC, "srcnat" }, +}; + +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" }, + { NF_BR_PRI_NAT_SRC, "srcnat" }, +}; + +static bool std_prio_family_hook_compat(int prio, int family, int hook) +{ + /* bridge family has different values */ + if (family == NFPROTO_BRIDGE) { + switch (prio) { + case NF_BR_PRI_NAT_DST_BRIDGED: + if (hook == NF_BR_PRE_ROUTING) + return true; + break; + case NF_BR_PRI_FILTER_BRIDGED: + return true; + case NF_BR_PRI_NAT_DST_OTHER: + if (hook == NF_BR_LOCAL_OUT) + return true; + break; + case NF_BR_PRI_NAT_SRC: + if (hook == NF_BR_POST_ROUTING) + return true; + } + return false; + } + switch(prio) { + case NF_IP_PRI_FILTER: + switch (family) { + case NFPROTO_INET: + case NFPROTO_IPV4: + case NFPROTO_IPV6: + case NFPROTO_ARP: + case NFPROTO_NETDEV: + return true; + } + break; + case NF_IP_PRI_RAW: + case NF_IP_PRI_MANGLE: + case NF_IP_PRI_SECURITY: + switch (family) { + case NFPROTO_INET: + case NFPROTO_IPV4: + case NFPROTO_IPV6: + return true; + } + break; + case NF_IP_PRI_NAT_DST: + switch(family) { + case NFPROTO_INET: + case NFPROTO_IPV4: + case NFPROTO_IPV6: + if (hook == NF_INET_PRE_ROUTING || + hook == NF_INET_LOCAL_OUT) + return true; + } + break; + case NF_IP_PRI_NAT_SRC: + switch(family) { + case NFPROTO_INET: + case NFPROTO_IPV4: + case NFPROTO_IPV6: + if (hook == NF_INET_LOCAL_IN || + hook == NF_INET_POST_ROUTING) + return true; + } + } + return false; +} + +int std_prio_lookup(const char *std_prio_name, int family, int hook) +{ + const struct prio_tag *prio_arr; + size_t i, arr_size; + + if (family == NFPROTO_BRIDGE) { + prio_arr = bridge_std_prios; + arr_size = array_size(bridge_std_prios); + } else { + prio_arr = std_prios; + arr_size = array_size(std_prios); + } + + for (i = 0; i < arr_size; ++i) { + if (strcmp(prio_arr[i].str, std_prio_name) == 0 && + std_prio_family_hook_compat(prio_arr[i].val, family, hook)) + return prio_arr[i].val; + } + return NF_IP_PRI_LAST; +} + +static const char *prio2str(const struct output_ctx *octx, + char *buf, size_t bufsize, int family, int hook, + const struct expr *expr) +{ + const struct prio_tag *prio_arr; + int std_prio, offset, prio; + const char *std_prio_str; + const int reach = 10; + size_t i, arr_size; + + mpz_export_data(&prio, expr->value, BYTEORDER_HOST_ENDIAN, sizeof(int)); + if (family == NFPROTO_BRIDGE) { + prio_arr = bridge_std_prios; + arr_size = array_size(bridge_std_prios); + } else { + prio_arr = std_prios; + arr_size = array_size(std_prios); + } + + if (!nft_output_numeric_prio(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) { + 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", + offset); + else if (offset < 0) + snprintf(buf + strlen(buf), + bufsize - strlen(buf), " - %d", + -offset); + return buf; + } + } + } + snprintf(buf, bufsize, "%d", prio); + return buf; +} + +static void chain_print_declaration(const struct chain *chain, + struct output_ctx *octx) +{ + 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.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]); + 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->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"); + } +} + +void chain_rules_print(const struct chain *chain, struct output_ctx *octx, + const char *indent) +{ + unsigned int flags = octx->flags; + struct rule *rule; + + 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%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"); +} + +void chain_print_plain(const struct chain *chain, struct output_ctx *octx) +{ + char priobuf[STD_PRIO_BUFSIZE]; + int policy; + + nft_print(octx, "chain %s %s %s", family2str(chain->handle.family), + chain->handle.table.name, chain->handle.chain.name); + + 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 ", + 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->hook.num, + chain->priority.expr), + chain_policy2str(policy)); + } + if (nft_output_handle(octx)) + nft_print(octx, " # handle %" PRIu64, chain->handle.handle.id); +} + +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; +} + +void table_free(struct table *table) +{ + struct chain *chain, *next; + struct flowtable *ft, *nft; + struct set *set, *nset; + struct obj *obj, *nobj; + + if (--table->refcnt > 0) + return; + if (table->comment) + xfree(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); + cache_free(&table->chain_cache); + cache_free(&table->set_cache); + cache_free(&table->obj_cache); + cache_free(&table->ft_cache); + xfree(table); +} + +struct table *table_get(struct table *table) +{ + table->refcnt++; + return table; +} + +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); + + 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; +} + +static const char *table_flags_name[TABLE_FLAGS_MAX] = { + "dormant", + "owner", +}; + +const char *table_flag_name(uint32_t flag) +{ + if (flag >= TABLE_FLAGS_MAX) + return "unknown"; + + return table_flags_name[flag]; +} + +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 (!table->flags) + return; + + 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"; +} + +static void table_print(const struct table *table, struct output_ctx *octx) +{ + struct flowtable *flowtable; + struct chain *chain; + struct obj *obj; + struct set *set; + 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); + if (table->flags & TABLE_F_OWNER) + nft_print(octx, " progname %s", get_progname(table->owner)); + + nft_print(octx, "\n"); + table_print_flags(table, &delim, octx); + + if (table->comment) + nft_print(octx, "\tcomment \"%s\"\n", table->comment); + + 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->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->ft_cache.list, cache.list) { + nft_print(octx, "%s", delim); + flowtable_print(flowtable, octx); + delim = "\n"; + } + list_for_each_entry(chain, &table->chain_cache.list, cache.list) { + nft_print(octx, "%s", delim); + chain_print(chain, octx); + delim = "\n"; + } + nft_print(octx, "}\n"); +} + +struct cmd *cmd_alloc(enum cmd_ops op, enum cmd_obj obj, + const struct handle *h, const struct location *loc, + void *data) +{ + struct cmd *cmd; + + assert(loc); + + cmd = xzalloc(sizeof(*cmd)); + init_list_head(&cmd->list); + cmd->op = op; + cmd->obj = obj; + cmd->handle = *h; + cmd->location = *loc; + cmd->data = data; + cmd->attr = xzalloc_array(NFT_NLATTR_LOC_MAX, + sizeof(struct nlerr_loc)); + cmd->attr_array_len = NFT_NLATTR_LOC_MAX; + init_list_head(&cmd->collapse_list); + + return cmd; +} + +struct markup *markup_alloc(uint32_t format) +{ + struct markup *markup; + + markup = xmalloc(sizeof(struct markup)); + markup->format = format; + + return markup; +} + +void markup_free(struct markup *m) +{ + xfree(m); +} + +struct monitor *monitor_alloc(uint32_t format, uint32_t type, const char *event) +{ + struct monitor *mon; + + mon = xmalloc(sizeof(struct monitor)); + mon->format = format; + mon->type = type; + mon->event = event; + mon->flags = 0; + + return mon; +} + +void monitor_free(struct monitor *m) +{ + xfree(m->event); + xfree(m); +} + +void cmd_free(struct cmd *cmd) +{ + handle_free(&cmd->handle); + if (cmd->data != NULL) { + switch (cmd->obj) { + 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: + rule_free(cmd->rule); + break; + case CMD_OBJ_CHAIN: + chain_free(cmd->chain); + break; + case CMD_OBJ_TABLE: + table_free(cmd->table); + break; + case CMD_OBJ_EXPR: + expr_free(cmd->expr); + break; + case CMD_OBJ_MONITOR: + monitor_free(cmd->monitor); + break; + case CMD_OBJ_MARKUP: + markup_free(cmd->markup); + break; + case CMD_OBJ_COUNTER: + case CMD_OBJ_QUOTA: + case CMD_OBJ_CT_HELPER: + case CMD_OBJ_CT_TIMEOUT: + case CMD_OBJ_CT_EXPECT: + case CMD_OBJ_LIMIT: + case CMD_OBJ_SECMARK: + case CMD_OBJ_SYNPROXY: + obj_free(cmd->object); + break; + case CMD_OBJ_FLOWTABLE: + flowtable_free(cmd->flowtable); + break; + default: + BUG("invalid command object type %u\n", cmd->obj); + } + } + xfree(cmd->attr); + xfree(cmd->arg); + xfree(cmd); +} + +#include <netlink.h> +#include <mnl.h> + +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, cmd, set, expr, flags) < 0) + return -1; + + return 0; +} + +static int do_add_elements(struct netlink_ctx *ctx, struct cmd *cmd, + uint32_t flags) +{ + struct expr *init = cmd->expr; + struct set *set = cmd->elem.set; + + if (set_is_non_concat_range(set) && + set_to_intervals(set, init, true) < 0) + return -1; + + return __do_add_elements(ctx, cmd, set, init, flags); +} + +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) { + /* 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_is_anonymous(set->flags)) + return __do_add_elements(ctx, cmd, set, set->init, flags); + + return 0; +} + +static int do_command_add(struct netlink_ctx *ctx, struct cmd *cmd, bool excl) +{ + uint32_t flags = excl ? NLM_F_EXCL : 0; + + if (nft_output_echo(&ctx->nft->output)) + flags |= NLM_F_ECHO; + + switch (cmd->obj) { + case CMD_OBJ_TABLE: + return mnl_nft_table_add(ctx, cmd, flags); + case CMD_OBJ_CHAIN: + return mnl_nft_chain_add(ctx, cmd, flags); + case CMD_OBJ_RULE: + 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_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: + case CMD_OBJ_CT_TIMEOUT: + case CMD_OBJ_CT_EXPECT: + case CMD_OBJ_LIMIT: + case CMD_OBJ_SECMARK: + case CMD_OBJ_SYNPROXY: + return mnl_nft_obj_add(ctx, cmd, flags); + case CMD_OBJ_FLOWTABLE: + return mnl_nft_flowtable_add(ctx, cmd, flags); + default: + BUG("invalid command object type %u\n", cmd->obj); + } + return 0; +} + +static int do_command_replace(struct netlink_ctx *ctx, struct cmd *cmd) +{ + switch (cmd->obj) { + case CMD_OBJ_RULE: + return mnl_nft_rule_replace(ctx, cmd); + default: + BUG("invalid command object type %u\n", cmd->obj); + } + return 0; +} + +static int do_command_insert(struct netlink_ctx *ctx, struct cmd *cmd) +{ + uint32_t flags = 0; + + if (nft_output_echo(&ctx->nft->output)) + flags |= NLM_F_ECHO; + + switch (cmd->obj) { + case CMD_OBJ_RULE: + return mnl_nft_rule_add(ctx, cmd, flags); + default: + BUG("invalid command object type %u\n", cmd->obj); + } + return 0; +} + +static int do_delete_setelems(struct netlink_ctx *ctx, struct cmd *cmd) +{ + struct expr *expr = cmd->elem.expr; + struct set *set = cmd->elem.set; + + if (set_is_non_concat_range(set) && + set_to_intervals(set, expr, false) < 0) + return -1; + + if (mnl_nft_setelem_del(ctx, cmd, &cmd->handle, cmd->elem.expr) < 0) + return -1; + + return 0; +} + +static int do_command_delete(struct netlink_ctx *ctx, struct cmd *cmd) +{ + switch (cmd->obj) { + case CMD_OBJ_TABLE: + return mnl_nft_table_del(ctx, cmd); + case CMD_OBJ_CHAIN: + return mnl_nft_chain_del(ctx, cmd); + case CMD_OBJ_RULE: + return mnl_nft_rule_del(ctx, cmd); + case CMD_OBJ_SET: + return mnl_nft_set_del(ctx, cmd); + case CMD_OBJ_ELEMENTS: + return do_delete_setelems(ctx, cmd); + case CMD_OBJ_COUNTER: + return mnl_nft_obj_del(ctx, cmd, NFT_OBJECT_COUNTER); + case CMD_OBJ_QUOTA: + return mnl_nft_obj_del(ctx, cmd, NFT_OBJECT_QUOTA); + case CMD_OBJ_CT_HELPER: + return mnl_nft_obj_del(ctx, cmd, NFT_OBJECT_CT_HELPER); + case CMD_OBJ_CT_TIMEOUT: + return mnl_nft_obj_del(ctx, cmd, NFT_OBJECT_CT_TIMEOUT); + case CMD_OBJ_CT_EXPECT: + return mnl_nft_obj_del(ctx, cmd, NFT_OBJECT_CT_EXPECT); + case CMD_OBJ_LIMIT: + return mnl_nft_obj_del(ctx, cmd, NFT_OBJECT_LIMIT); + case CMD_OBJ_SECMARK: + return mnl_nft_obj_del(ctx, cmd, NFT_OBJECT_SECMARK); + case CMD_OBJ_SYNPROXY: + return mnl_nft_obj_del(ctx, cmd, NFT_OBJECT_SYNPROXY); + case CMD_OBJ_FLOWTABLE: + return mnl_nft_flowtable_del(ctx, cmd); + default: + BUG("invalid command object type %u\n", cmd->obj); + } +} + +static int do_list_table(struct netlink_ctx *ctx, struct table *table) +{ + table_print(table, &ctx->nft->output); + return 0; +} + +static int do_list_sets(struct netlink_ctx *ctx, struct cmd *cmd) +{ + struct table *table; + struct set *set; + + 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; + + nft_print(&ctx->nft->output, "table %s %s {\n", + family2str(table->handle.family), + table->handle.table.name); + + 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)) + continue; + if (cmd->obj == CMD_OBJ_MAPS && + !map_is_literal(set->flags)) + continue; + set_print(set, &ctx->nft->output); + } + + nft_print(&ctx->nft->output, "}\n"); + } + return 0; +} + +struct obj *obj_alloc(const struct location *loc) +{ + struct obj *obj; + + assert(loc); + + obj = xzalloc(sizeof(*obj)); + obj->location = *loc; + + obj->refcnt = 1; + return obj; +} + +struct obj *obj_get(struct obj *obj) +{ + obj->refcnt++; + return obj; +} + +void obj_free(struct obj *obj) +{ + if (--obj->refcnt > 0) + return; + xfree(obj->comment); + handle_free(&obj->handle); + if (obj->type == NFT_OBJECT_CT_TIMEOUT) { + struct timeout_state *ts, *next; + + list_for_each_entry_safe(ts, next, &obj->ct_timeout.timeout_list, head) { + list_del(&ts->head); + xfree(ts->timeout_str); + xfree(ts); + } + } + xfree(obj); +} + +struct obj *obj_lookup_fuzzy(const char *obj_name, + const struct nft_cache *cache, + const struct table **t) +{ + struct string_misspell_state st; + struct table *table; + struct obj *obj; + + string_misspell_init(&st); + + 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; + } + } + return st.obj; +} + +static void print_proto_name_proto(uint8_t l4, struct output_ctx *octx) +{ + char name[NFT_PROTONAME_MAXSIZE]; + + if (nft_getprotobynumber(l4, name, sizeof(name))) + nft_print(octx, "%s", name); + else + nft_print(octx, "%d", l4); +} + +static void print_proto_timeout_policy(uint8_t l4, const uint32_t *timeout, + struct print_fmt_options *opts, + struct output_ctx *octx) +{ + bool comma = false; + unsigned int i; + + 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, ", "); + timeout_ms = timeout[i] * 1000u; + nft_print(octx, "%s : ", + timeout_protocol[l4].state_to_name[i]); + time_print(timeout_ms, octx); + comma = true; + } + } + nft_print(octx, " }%s", opts->stmt_separator); +} + +static const char *synproxy_sack_to_str(const uint32_t flags) +{ + if (flags & NF_SYNPROXY_OPT_SACK_PERM) + return "sack-perm"; + + return ""; +} + +static const char *synproxy_timestamp_to_str(const uint32_t flags) +{ + if (flags & NF_SYNPROXY_OPT_TIMESTAMP) + return "timestamp"; + + 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) +{ + switch (obj->type) { + case NFT_OBJECT_COUNTER: + 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); + 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; + uint64_t bytes; + + 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", + obj->quota.flags & NFT_QUOTA_F_INV ? "over " : "", + bytes, data_unit); + if (!nft_output_stateless(octx) && obj->quota.used) { + data_unit = get_rate(obj->quota.used, &bytes); + nft_print(octx, " used %" PRIu64 " %s", + bytes, data_unit); + } + nft_print(octx, "%s", opts->nl); + } + break; + case NFT_OBJECT_SECMARK: + 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; + case NFT_OBJECT_CT_HELPER: + 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); + print_proto_name_proto(obj->ct_helper.l4proto, octx); + nft_print(octx, "%s", opts->stmt_separator); + nft_print(octx, "%s%sl3proto %s%s", + opts->tab, opts->tab, + family2str(obj->ct_helper.l3proto), + opts->stmt_separator); + break; + case NFT_OBJECT_CT_TIMEOUT: + 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); + nft_print(octx, "%s", opts->stmt_separator); + nft_print(octx, "%s%sl3proto %s%s", + opts->tab, opts->tab, + family2str(obj->ct_timeout.l3proto), + opts->stmt_separator); + print_proto_timeout_policy(obj->ct_timeout.l4proto, + obj->ct_timeout.timeout, opts, octx); + break; + case NFT_OBJECT_CT_EXPECT: + 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); + nft_print(octx, "%s", opts->stmt_separator); + nft_print(octx, "%s%sdport %d%s", + opts->tab, opts->tab, + obj->ct_expect.dport, + opts->stmt_separator); + nft_print(octx, "%s%stimeout ", opts->tab, opts->tab); + time_print(obj->ct_expect.timeout, octx); + nft_print(octx, "%s", opts->stmt_separator); + nft_print(octx, "%s%ssize %d%s", + opts->tab, opts->tab, + obj->ct_expect.size, + opts->stmt_separator); + nft_print(octx, "%s%sl3proto %s%s", + opts->tab, opts->tab, + family2str(obj->ct_expect.l3proto), + opts->stmt_separator); + break; + case NFT_OBJECT_LIMIT: { + bool inv = obj->limit.flags & NFT_LIMIT_F_INV; + const char *data_unit; + uint64_t rate; + + 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: + nft_print(octx, "rate %s%" PRIu64 "/%s", + inv ? "over " : "", obj->limit.rate, + get_unit(obj->limit.unit)); + if (obj->limit.burst > 0 && obj->limit.burst != 5) + nft_print(octx, " burst %u packets", + obj->limit.burst); + break; + case NFT_LIMIT_PKT_BYTES: + data_unit = get_rate(obj->limit.rate, &rate); + + nft_print(octx, "rate %s%" PRIu64 " %s/%s", + inv ? "over " : "", rate, data_unit, + get_unit(obj->limit.unit)); + if (obj->limit.burst > 0) { + uint64_t burst; + + data_unit = get_rate(obj->limit.burst, &burst); + nft_print(octx, " burst %"PRIu64" %s", + burst, data_unit); + } + break; + } + nft_print(octx, "%s", opts->nl); + } + break; + case NFT_OBJECT_SYNPROXY: { + uint32_t flags = obj->synproxy.flags; + const char *sack_str = synproxy_sack_to_str(flags); + const char *ts_str = synproxy_timestamp_to_str(flags); + + 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); + + 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); + } + if (flags & NF_SYNPROXY_OPT_WSCALE) { + nft_print(octx, "%s%s%s", opts->nl, opts->tab, opts->tab); + nft_print(octx, "wscale %u", obj->synproxy.wscale); + } + if (flags & (NF_SYNPROXY_OPT_TIMESTAMP | NF_SYNPROXY_OPT_SACK_PERM)) { + nft_print(octx, "%s%s%s", opts->nl, opts->tab, opts->tab); + nft_print(octx, "%s %s", ts_str, sack_str); + } + nft_print(octx, "%s", opts->stmt_separator); + } + break; + default: + nft_print(octx, " unknown {%s", opts->nl); + break; + } +} + +static const char * const obj_type_name_array[] = { + [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_SYNPROXY] = "synproxy", + [NFT_OBJECT_CT_EXPECT] = "ct expectation", +}; + +const char *obj_type_name(unsigned int type) +{ + assert(type <= NFT_OBJECT_MAX && obj_type_name_array[type]); + + return obj_type_name_array[type]; +} + +static uint32_t obj_type_cmd_array[NFT_OBJECT_MAX + 1] = { + [NFT_OBJECT_COUNTER] = CMD_OBJ_COUNTER, + [NFT_OBJECT_QUOTA] = CMD_OBJ_QUOTA, + [NFT_OBJECT_CT_HELPER] = CMD_OBJ_CT_HELPER, + [NFT_OBJECT_LIMIT] = CMD_OBJ_LIMIT, + [NFT_OBJECT_CT_TIMEOUT] = CMD_OBJ_CT_TIMEOUT, + [NFT_OBJECT_SECMARK] = CMD_OBJ_SECMARK, + [NFT_OBJECT_SYNPROXY] = CMD_OBJ_SYNPROXY, + [NFT_OBJECT_CT_EXPECT] = CMD_OBJ_CT_EXPECT, +}; + +enum cmd_obj obj_type_to_cmd(uint32_t type) +{ + assert(type <= NFT_OBJECT_MAX && obj_type_cmd_array[type]); + + return obj_type_cmd_array[type]; +} + +static void obj_print_declaration(const struct obj *obj, + struct print_fmt_options *opts, + struct output_ctx *octx) +{ + nft_print(octx, "%s%s", opts->tab, obj_type_name(obj->type)); + + if (opts->family != NULL) + nft_print(octx, " %s", opts->family); + + if (opts->table != NULL) + nft_print(octx, " %s", opts->table); + + obj_print_data(obj, opts, octx); + + nft_print(octx, "%s}%s", opts->tab, opts->nl); +} + +void obj_print(const struct obj *obj, struct output_ctx *octx) +{ + struct print_fmt_options opts = { + .tab = "\t", + .nl = "\n", + .stmt_separator = "\n", + }; + + obj_print_declaration(obj, &opts, octx); +} + +void obj_print_plain(const struct obj *obj, struct output_ctx *octx) +{ + struct print_fmt_options opts = { + .tab = "", + .nl = " ", + .table = obj->handle.table.name, + .family = family2str(obj->handle.family), + .stmt_separator = "; ", + }; + + obj_print_declaration(obj, &opts, octx); +} + +static int do_list_obj(struct netlink_ctx *ctx, struct cmd *cmd, uint32_t type) +{ + struct print_fmt_options opts = { + .tab = "\t", + .nl = "\n", + .stmt_separator = "\n", + }; + struct table *table; + struct obj *obj; + + 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; + + if (cmd->handle.table.name != NULL && + strcmp(cmd->handle.table.name, table->handle.table.name)) + continue; + + 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->obj_cache.list, cache.list) { + if (obj->type != type || + (cmd->handle.obj.name != NULL && + strcmp(cmd->handle.obj.name, obj->handle.obj.name))) + continue; + + obj_print_declaration(obj, &opts, &ctx->nft->output); + } + + nft_print(&ctx->nft->output, "}\n"); + } + return 0; +} + +struct flowtable *flowtable_alloc(const struct location *loc) +{ + struct flowtable *flowtable; + + assert(loc); + + flowtable = xzalloc(sizeof(*flowtable)); + flowtable->location = *loc; + + flowtable->refcnt = 1; + return flowtable; +} + +struct flowtable *flowtable_get(struct flowtable *flowtable) +{ + flowtable->refcnt++; + return flowtable; +} + +void flowtable_free(struct flowtable *flowtable) +{ + int i; + + if (--flowtable->refcnt > 0) + return; + handle_free(&flowtable->handle); + expr_free(flowtable->priority.expr); + expr_free(flowtable->dev_expr); + + if (flowtable->dev_array != NULL) { + for (i = 0; i < flowtable->dev_array_len; i++) + xfree(flowtable->dev_array[i]); + xfree(flowtable->dev_array); + } + xfree(flowtable); +} + +static void flowtable_print_declaration(const struct flowtable *flowtable, + struct print_fmt_options *opts, + struct output_ctx *octx) +{ + char priobuf[STD_PRIO_BUFSIZE]; + int i; + + nft_print(octx, "%sflowtable", opts->tab); + + if (opts->family != NULL) + nft_print(octx, " %s", opts->family); + + if (opts->table != NULL) + nft_print(octx, " %s", opts->table); + + nft_print(octx, " %s {", flowtable->handle.flowtable.name); + + 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->hook.num), + prio2str(octx, priobuf, sizeof(priobuf), NFPROTO_NETDEV, + flowtable->hook.num, flowtable->priority.expr), + 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, + struct print_fmt_options *opts, + struct output_ctx *octx) +{ + flowtable_print_declaration(flowtable, opts, octx); + nft_print(octx, "%s}%s", opts->tab, opts->nl); +} + +void flowtable_print(const struct flowtable *s, struct output_ctx *octx) +{ + struct print_fmt_options opts = { + .tab = "\t", + .nl = "\n", + .stmt_separator = "\n", + }; + + do_flowtable_print(s, &opts, octx); +} + +struct flowtable *flowtable_lookup_fuzzy(const char *ft_name, + const struct nft_cache *cache, + const struct table **t) +{ + struct string_misspell_state st; + struct table *table; + struct flowtable *ft; + + string_misspell_init(&st); + + 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; + } + } + return st.obj; +} + +static int do_list_flowtable(struct netlink_ctx *ctx, struct cmd *cmd, + struct table *table) +{ + struct flowtable *ft; + + ft = ft_cache_find(table, cmd->handle.flowtable.name); + if (!ft) + return -1; + + nft_print(&ctx->nft->output, "table %s %s {\n", + family2str(table->handle.family), + table->handle.table.name); + + flowtable_print(ft, &ctx->nft->output); + nft_print(&ctx->nft->output, "}\n"); + + return 0; +} + +static int do_list_flowtables(struct netlink_ctx *ctx, struct cmd *cmd) +{ + struct print_fmt_options opts = { + .tab = "\t", + .nl = "\n", + .stmt_separator = "\n", + }; + struct flowtable *flowtable; + struct table *table; + + 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; + + nft_print(&ctx->nft->output, "table %s %s {\n", + family2str(table->handle.family), + table->handle.table.name); + + 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); + } + + nft_print(&ctx->nft->output, "}\n"); + } + return 0; +} + +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.table_cache.list, cache.list) { + if (family != NFPROTO_UNSPEC && + table->handle.family != family) + continue; + + if (do_list_table(ctx, table) < 0) + return -1; + } + + return 0; +} + +static int do_list_tables(struct netlink_ctx *ctx, struct cmd *cmd) +{ + struct table *table; + + 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; + + nft_print(&ctx->nft->output, "table %s %s\n", + family2str(table->handle.family), + table->handle.table.name); + } + + return 0; +} + +static void table_print_declaration(struct table *table, + struct output_ctx *octx) +{ + 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, + struct table *table) +{ + struct chain *chain; + + table_print_declaration(table, &ctx->nft->output); + + chain = chain_cache_find(table, cmd->handle.chain.name); + if (chain) + chain_print(chain, &ctx->nft->output); + + nft_print(&ctx->nft->output, "}\n"); + + return 0; +} + +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.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->chain_cache.list, cache.list) { + chain_print_declaration(chain, &ctx->nft->output); + nft_print(&ctx->nft->output, "\t}\n"); + } + nft_print(&ctx->nft->output, "}\n"); + } + + return 0; +} + +static void __do_list_set(struct netlink_ctx *ctx, struct cmd *cmd, + 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"); +} + +static int do_list_set(struct netlink_ctx *ctx, struct cmd *cmd, + struct table *table) +{ + struct set *set = cmd->set; + + if (!set) { + set = set_cache_find(table, cmd->handle.set.name); + if (set == NULL) + return -1; + } + + __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; + int hooknum = -1; + + if (cmd->handle.chain.name) + hooknum = cmd->handle.chain_id; + + return mnl_nft_dump_nf_hooks(ctx, cmd->handle.family, hooknum, devname); +} + +static int do_command_list(struct netlink_ctx *ctx, struct cmd *cmd) +{ + struct table *table = NULL; + + if (nft_output_json(&ctx->nft->output)) + return do_command_list_json(ctx, cmd); + + if (cmd->handle.table.name != NULL) + table = table_cache_find(&ctx->nft->cache.table_cache, + cmd->handle.table.name, + cmd->handle.family); + switch (cmd->obj) { + case CMD_OBJ_TABLE: + if (!cmd->handle.table.name) + return do_list_tables(ctx, cmd); + return do_list_table(ctx, table); + case CMD_OBJ_CHAIN: + return do_list_chain(ctx, cmd, table); + case CMD_OBJ_CHAINS: + return do_list_chains(ctx, cmd); + case CMD_OBJ_SETS: + return do_list_sets(ctx, 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); + case CMD_OBJ_METER: + return do_list_set(ctx, cmd, table); + case CMD_OBJ_MAPS: + return do_list_sets(ctx, cmd); + case CMD_OBJ_MAP: + return do_list_set(ctx, cmd, table); + case CMD_OBJ_COUNTER: + case CMD_OBJ_COUNTERS: + return do_list_obj(ctx, cmd, NFT_OBJECT_COUNTER); + case CMD_OBJ_QUOTA: + case CMD_OBJ_QUOTAS: + return do_list_obj(ctx, cmd, NFT_OBJECT_QUOTA); + case CMD_OBJ_CT_HELPER: + 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: + return do_list_obj(ctx, cmd, NFT_OBJECT_LIMIT); + case CMD_OBJ_SECMARK: + case CMD_OBJ_SECMARKS: + return do_list_obj(ctx, cmd, NFT_OBJECT_SECMARK); + case CMD_OBJ_SYNPROXY: + case CMD_OBJ_SYNPROXYS: + return do_list_obj(ctx, cmd, NFT_OBJECT_SYNPROXY); + case CMD_OBJ_FLOWTABLE: + return do_list_flowtable(ctx, cmd, table); + case CMD_OBJ_FLOWTABLES: + return do_list_flowtables(ctx, cmd); + case CMD_OBJ_HOOKS: + return do_list_hooks(ctx, cmd); + default: + BUG("invalid command object type %u\n", cmd->obj); + } + + return 0; +} + +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 = cmd->elem.set; + + /* Create a list of elements based of what we got from command line. */ + if (set_is_non_concat_range(set)) + init = get_set_intervals(set, cmd->expr); + else + init = cmd->expr; + + new_set = set_clone(set); + + /* Fetch from kernel the elements that have been requested .*/ + err = netlink_get_setelem(ctx, &cmd->handle, &cmd->location, + cmd->elem.set, new_set, init, reset); + if (err >= 0) + __do_list_set(ctx, cmd, new_set); + + if (set_is_non_concat_range(set)) + expr_free(init); + + set_free(new_set); + + return err; +} + +static int do_command_get(struct netlink_ctx *ctx, struct cmd *cmd) +{ + switch (cmd->obj) { + case CMD_OBJ_ELEMENTS: + return do_get_setelems(ctx, cmd, false); + default: + BUG("invalid command object type %u\n", cmd->obj); + } + + return 0; +} + +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_RULES: + ret = netlink_reset_rules(ctx, cmd, true); + if (ret < 0) + return ret; + + return do_command_list(ctx, cmd); + case CMD_OBJ_RULE: + return netlink_reset_rules(ctx, cmd, false); + case CMD_OBJ_ELEMENTS: + return do_get_setelems(ctx, cmd, true); + case CMD_OBJ_SET: + case CMD_OBJ_MAP: + ret = netlink_list_setelems(ctx, &cmd->handle, cmd->set, true); + if (ret < 0) + return ret; + + return do_command_list(ctx, cmd); + 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_cache_find(&ctx->nft->cache.table_cache, + obj->handle.table.name, + obj->handle.family); + if (!obj_cache_find(table, obj->handle.obj.name, obj->type)) { + list_del(&obj->list); + obj_cache_add(obj, table); + } + } + if (ret < 0) + return ret; + + return do_command_list(ctx, cmd); +} + +static int do_command_flush(struct netlink_ctx *ctx, struct cmd *cmd) +{ + switch (cmd->obj) { + case CMD_OBJ_TABLE: + case CMD_OBJ_CHAIN: + return mnl_nft_rule_del(ctx, cmd); + case CMD_OBJ_SET: + case CMD_OBJ_MAP: + case CMD_OBJ_METER: + return mnl_nft_setelem_flush(ctx, cmd); + case CMD_OBJ_RULESET: + return mnl_nft_table_del(ctx, cmd); + default: + BUG("invalid command object type %u\n", cmd->obj); + } + return 0; +} + +static int do_command_rename(struct netlink_ctx *ctx, struct cmd *cmd) +{ + 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_cache_find(table, cmd->handle.chain.name); + + return mnl_nft_chain_rename(ctx, cmd, chain); + default: + BUG("invalid command object type %u\n", cmd->obj); + } + return 0; +} + +static int do_command_monitor(struct netlink_ctx *ctx, struct cmd *cmd) +{ + struct netlink_mon_handler monhandler = { + .monitor_flags = cmd->monitor->flags, + .format = cmd->monitor->format, + .ctx = ctx, + .loc = &cmd->location, + .cache = &ctx->nft->cache, + .debug_mask = ctx->nft->debug_mask, + }; + + if (nft_output_json(&ctx->nft->output)) + monhandler.format = NFTNL_OUTPUT_JSON; + + return netlink_monitor(&monhandler, ctx->nft->nf_sock); +} + +static int do_command_describe(struct netlink_ctx *ctx, struct cmd *cmd, + struct output_ctx *octx) +{ + expr_describe(cmd->expr, octx); + return 0; +} + +struct cmd *cmd_alloc_obj_ct(enum cmd_ops op, int type, const struct handle *h, + const struct location *loc, struct obj *obj) +{ + enum cmd_obj cmd_obj; + + if (obj) + obj->type = type; + + switch (type) { + case NFT_OBJECT_CT_HELPER: + cmd_obj = CMD_OBJ_CT_HELPER; + break; + case NFT_OBJECT_CT_TIMEOUT: + cmd_obj = CMD_OBJ_CT_TIMEOUT; + break; + case NFT_OBJECT_CT_EXPECT: + cmd_obj = CMD_OBJ_CT_EXPECT; + break; + default: + BUG("missing type mapping"); + } + + return cmd_alloc(op, cmd_obj, h, loc, obj); +} + +int do_command(struct netlink_ctx *ctx, struct cmd *cmd) +{ + switch (cmd->op) { + case CMD_ADD: + return do_command_add(ctx, cmd, false); + case CMD_CREATE: + return do_command_add(ctx, cmd, true); + case CMD_INSERT: + return do_command_insert(ctx, 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); + case CMD_LIST: + return do_command_list(ctx, cmd); + case CMD_RESET: + return do_command_reset(ctx, cmd); + case CMD_FLUSH: + return do_command_flush(ctx, cmd); + case CMD_RENAME: + return do_command_rename(ctx, cmd); + case CMD_IMPORT: + case CMD_EXPORT: + errno = EOPNOTSUPP; + return -1; + case CMD_MONITOR: + return do_command_monitor(ctx, cmd); + case CMD_DESCRIBE: + return do_command_describe(ctx, cmd, &ctx->nft->output); + default: + BUG("invalid command object type %u\n", cmd->obj); + } +} + +static int payload_match_stmt_cmp(const void *p1, const void *p2) +{ + const struct stmt *s1 = *(struct stmt * const *)p1; + const struct stmt *s2 = *(struct stmt * const *)p2; + const struct expr *e1 = s1->expr, *e2 = s2->expr; + int d; + + d = e1->left->payload.base - e2->left->payload.base; + if (d != 0) + return d; + return e1->left->payload.offset - e2->left->payload.offset; +} + +static bool relational_ops_match(const struct expr *e1, const struct expr *e2) +{ + enum ops op1, op2; + + op1 = e1->op == OP_IMPLICIT ? OP_EQ : e1->op; + op2 = e2->op == OP_IMPLICIT ? OP_EQ : e2->op; + + return op1 == op2; +} + +static void payload_do_merge(struct stmt *sa[], unsigned int n) +{ + struct expr *last, *this, *expr1, *expr2; + struct stmt *stmt; + unsigned int i, j; + + qsort(sa, n, sizeof(sa[0]), payload_match_stmt_cmp); + + last = sa[0]->expr; + for (j = 0, i = 1; i < n; i++) { + stmt = sa[i]; + this = stmt->expr; + + if (!payload_can_merge(last->left, this->left) || + !relational_ops_match(last, this)) { + last = this; + j = i; + continue; + } + + expr1 = payload_expr_join(last->left, this->left); + expr2 = constant_expr_join(last->right, this->right); + + /* We can merge last into this, but we can't replace + * the statement associated with this if it does contain + * a higher level protocol. + * + * ether type ip ip saddr X ether saddr Y + * ... can be changed to + * ether type ip ether saddr Y ip saddr X + * ... but not + * ip saddr X ether type ip ether saddr Y + * + * The latter form means we perform ip saddr test before + * ensuring ip dependency, plus it makes decoding harder + * since we don't know the type of the network header + * right away. + * + * So, if we're about to replace a statement + * containing a protocol identifier, just swap this and last + * and replace the other one (i.e., replace 'load ether type ip' + * with the combined 'load both ether type and saddr') and not + * the other way around. + */ + if (this->left->flags & EXPR_F_PROTOCOL) { + struct expr *tmp = last; + + last = this; + this = tmp; + + expr1->flags |= EXPR_F_PROTOCOL; + stmt = sa[j]; + assert(stmt->expr == this); + j = i; + } + + expr_free(last->left); + last->left = expr1; + + expr_free(last->right); + last->right = expr2; + + list_del(&stmt->list); + stmt_free(stmt); + } +} + +/** + * 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 stmt_reduce(const struct rule *rule) +{ + struct stmt *stmt, *dstmt = NULL, *next; + struct stmt *sa[rule->num_stmts]; + 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) { + if (idx >= 2) + payload_do_merge(sa, idx); + idx = 0; + continue; + } + + if (stmt->expr->etype != EXPR_RELATIONAL) + continue; + if (stmt->expr->right->etype != EXPR_VALUE) + continue; + + if (stmt->expr->left->etype == EXPR_PAYLOAD) { + switch (stmt->expr->op) { + case OP_EQ: + case OP_IMPLICIT: + case OP_NEQ: + 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) + payload_do_merge(sa, idx); +} + +struct error_record *rule_postprocess(struct rule *rule) +{ + stmt_reduce(rule); + return NULL; +} |