/*
* 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 <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <string.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 <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] = 180,
};
struct timeout_protocol timeout_protocol[IPPROTO_MAX] = {
[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;
}
static int cache_init_tables(struct netlink_ctx *ctx, struct handle *h,
struct nft_cache *cache)
{
int ret;
ret = netlink_list_tables(ctx, h);
if (ret < 0)
return -1;
list_splice_tail_init(&ctx->list, &cache->list);
return 0;
}
static int cache_init_objects(struct netlink_ctx *ctx, unsigned int flags)
{
struct rule *rule, *nrule;
struct table *table;
struct chain *chain;
struct set *set;
int ret;
list_for_each_entry(table, &ctx->nft->cache.list, list) {
if (flags & NFT_CACHE_SET_BIT) {
ret = netlink_list_sets(ctx, &table->handle);
list_splice_tail_init(&ctx->list, &table->sets);
if (ret < 0)
return -1;
}
if (flags & NFT_CACHE_SETELEM_BIT) {
list_for_each_entry(set, &table->sets, list) {
ret = netlink_list_setelems(ctx, &set->handle,
set);
if (ret < 0)
return -1;
}
}
if (flags & NFT_CACHE_CHAIN_BIT) {
ret = netlink_list_chains(ctx, &table->handle);
if (ret < 0)
return -1;
list_splice_tail_init(&ctx->list, &table->chains);
}
if (flags & NFT_CACHE_FLOWTABLE_BIT) {
ret = netlink_list_flowtables(ctx, &table->handle);
if (ret < 0)
return -1;
list_splice_tail_init(&ctx->list, &table->flowtables);
}
if (flags & NFT_CACHE_OBJECT_BIT) {
ret = netlink_list_objs(ctx, &table->handle);
if (ret < 0)
return -1;
list_splice_tail_init(&ctx->list, &table->objs);
}
if (flags & NFT_CACHE_RULE_BIT) {
ret = netlink_list_rules(ctx, &table->handle);
list_for_each_entry_safe(rule, nrule, &ctx->list, list) {
chain = chain_lookup(table, &rule->handle);
list_move_tail(&rule->list, &chain->rules);
}
if (ret < 0)
return -1;
}
}
return 0;
}
static int cache_init(struct netlink_ctx *ctx, unsigned int flags)
{
struct handle handle = {
.family = NFPROTO_UNSPEC,
};
int ret;
if (flags == NFT_CACHE_EMPTY)
return 0;
/* assume NFT_CACHE_TABLE is always set. */
ret = cache_init_tables(ctx, &handle, &ctx->nft->cache);
if (ret < 0)
return ret;
ret = cache_init_objects(ctx, flags);
if (ret < 0)
return ret;
return 0;
}
static bool cache_is_complete(struct nft_cache *cache, unsigned int flags)
{
return (cache->flags & flags) == flags;
}
static bool cache_is_updated(struct nft_cache *cache, uint16_t genid)
{
return genid && genid == cache->genid;
}
bool cache_needs_update(struct nft_cache *cache)
{
return cache->flags & NFT_CACHE_UPDATE;
}
int cache_update(struct nft_ctx *nft, unsigned int flags, struct list_head *msgs)
{
struct netlink_ctx ctx = {
.list = LIST_HEAD_INIT(ctx.list),
.nft = nft,
.msgs = msgs,
};
struct nft_cache *cache = &nft->cache;
uint32_t genid, genid_stop, oldflags;
int ret;
replay:
ctx.seqnum = cache->seqnum++;
genid = mnl_genid_get(&ctx);
if (cache_is_complete(cache, flags) &&
cache_is_updated(cache, genid))
return 0;
if (cache->genid)
cache_release(cache);
if (flags & NFT_CACHE_FLUSHED) {
oldflags = flags;
flags = NFT_CACHE_EMPTY;
if (oldflags & NFT_CACHE_UPDATE)
flags |= NFT_CACHE_UPDATE;
goto skip;
}
ret = cache_init(&ctx, flags);
if (ret < 0) {
cache_release(cache);
if (errno == EINTR) {
nft->nf_sock = nft_mnl_socket_reopen(nft->nf_sock);
goto replay;
}
return -1;
}
genid_stop = mnl_genid_get(&ctx);
if (genid != genid_stop) {
cache_release(cache);
goto replay;
}
skip:
cache->genid = genid;
cache->flags = flags;
return 0;
}
static void __cache_flush(struct list_head *table_list)
{
struct table *table, *next;
list_for_each_entry_safe(table, next, table_list, list) {
list_del(&table->list);
table_free(table);
}
}
void cache_release(struct nft_cache *cache)
{
__cache_flush(&cache->list);
cache->genid = 0;
cache->flags = NFT_CACHE_EMPTY;
}
/* internal ID to uniquely identify a set in the batch */
static uint32_t set_id;
struct set *set_alloc(const struct location *loc)
{
struct set *set;
set = xzalloc(sizeof(*set));
set->refcnt = 1;
set->handle.set_id = ++set_id;
if (loc != NULL)
set->location = *loc;
return set;
}
struct set *set_clone(const struct set *set)
{
struct set *new_set;
new_set = set_alloc(NULL);
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);
new_set->datatype = datatype_get(set->datatype);
new_set->datalen = set->datalen;
new_set->objtype = set->objtype;
new_set->policy = set->policy;
new_set->automerge = set->automerge;
new_set->desc.size = set->desc.size;
return new_set;
}
struct set *set_get(struct set *set)
{
set->refcnt++;
return set;
}
void set_free(struct set *set)
{
if (--set->refcnt > 0)
return;
if (set->init != NULL)
expr_free(set->init);
handle_free(&set->handle);
expr_free(set->key);
set_datatype_destroy(set->datatype);
xfree(set);
}
void set_add_hash(struct set *set, struct table *table)
{
list_add_tail(&set->list, &table->sets);
}
struct set *set_lookup(const struct table *table, const char *name)
{
struct set *set;
list_for_each_entry(set, &table->sets, list) {
if (!strcmp(set->handle.set.name, name))
return set;
}
return NULL;
}
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->list, list) {
list_for_each_entry(set, &table->sets, list) {
if (set_is_anonymous(set->flags))
continue;
if (!strcmp(set->handle.set.name, set_name)) {
*t = table;
return set;
}
if (string_misspell_update(set->handle.set.name,
set_name, set, &st))
*t = table;
}
}
return st.obj;
}
struct set *set_lookup_global(uint32_t family, const char *table,
const char *name, struct nft_cache *cache)
{
struct handle h;
struct table *t;
h.family = family;
h.table.name = table;
t = table_lookup(&h, cache);
if (t == NULL)
return NULL;
return set_lookup(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_declaration(const struct set *set,
struct print_fmt_options *opts,
struct output_ctx *octx)
{
const char *delim = "";
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", opts->nl);
nft_print(octx, "%s%stype %s",
opts->tab, opts->tab, set->key->dtype->name);
if (set_is_datamap(set->flags))
nft_print(octx, " : %s", set->datatype->name);
else if (set_is_objmap(set->flags))
nft_print(octx, " : %s", obj_type_name(set->objtype));
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 (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);
}
}
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;
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;
}
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",
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;
}
struct chain *chain_alloc(const char *name)
{
struct chain *chain;
chain = xzalloc(sizeof(*chain));
chain->refcnt = 1;
init_list_head(&chain->rules);
init_list_head(&chain->scope.symbols);
if (name != NULL)
chain->handle.chain.name = xstrdup(name);
chain->policy = NULL;
return chain;
}
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);
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);
}
void chain_add_hash(struct chain *chain, struct table *table)
{
list_add_tail(&chain->list, &table->chains);
}
struct chain *chain_lookup(const struct table *table, const struct handle *h)
{
struct chain *chain;
list_for_each_entry(chain, &table->chains, list) {
if (!strcmp(chain->handle.chain.name, h->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;
string_misspell_init(&st);
list_for_each_entry(table, &cache->list, list) {
list_for_each_entry(chain, &table->chains, list) {
if (!strcmp(chain->handle.chain.name, h->chain.name)) {
*t = table;
return chain;
}
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";
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";
default:
break;
}
break;
case NFPROTO_NETDEV:
switch (hooknum) {
case NF_NETDEV_INGRESS:
return "ingress";
}
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;
};
const static 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" },
};
const static 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)
return true;
}
break;
case NF_IP_PRI_NAT_SRC:
switch(family) {
case NFPROTO_INET:
case NFPROTO_IPV4:
case NFPROTO_IPV6:
if (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;
nft_print(octx, "\tchain %s {", chain->handle.chain.name);
if (nft_output_handle(octx))
nft_print(octx, " # handle %" PRIu64, chain->handle.handle.id);
nft_print(octx, "\n");
if (chain->flags & CHAIN_F_BASECHAIN) {
nft_print(octx, "\t\ttype %s hook %s", chain->type,
hooknum2str(chain->handle.family, chain->hooknum));
if (chain->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->hooknum,
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));
}
nft_print(octx, "\n");
}
}
static void chain_print(const struct chain *chain, struct output_ctx *octx)
{
struct rule *rule;
chain_print_declaration(chain, octx);
list_for_each_entry(rule, &chain->rules, list) {
nft_print(octx, "\t\t");
rule_print(rule, octx);
nft_print(octx, "\n");
}
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 priority %s; policy %s; }",
chain->type, chain->hookstr,
prio2str(octx, priobuf, sizeof(priobuf),
chain->handle.family, chain->hooknum,
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));
init_list_head(&table->chains);
init_list_head(&table->sets);
init_list_head(&table->objs);
init_list_head(&table->flowtables);
init_list_head(&table->scope.symbols);
table->refcnt = 1;
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;
list_for_each_entry_safe(chain, next, &table->chains, list)
chain_free(chain);
list_for_each_entry_safe(set, nset, &table->sets, list)
set_free(set);
list_for_each_entry_safe(ft, nft, &table->flowtables, list)
flowtable_free(ft);
list_for_each_entry_safe(obj, nobj, &table->objs, list)
obj_free(obj);
handle_free(&table->handle);
scope_release(&table->scope);
xfree(table);
}
struct table *table_get(struct table *table)
{
table->refcnt++;
return table;
}
void table_add_hash(struct table *table, struct nft_cache *cache)
{
list_add_tail(&table->list, &cache->list);
}
struct table *table_lookup(const struct handle *h,
const struct nft_cache *cache)
{
struct table *table;
list_for_each_entry(table, &cache->list, list) {
if (table->handle.family == h->family &&
!strcmp(table->handle.table.name, h->table.name))
return table;
}
return NULL;
}
struct table *table_lookup_fuzzy(const struct handle *h,
const struct nft_cache *cache)
{
struct string_misspell_state st;
struct table *table;
string_misspell_init(&st);
list_for_each_entry(table, &cache->list, list) {
if (!strcmp(table->handle.table.name, h->table.name))
return table;
string_misspell_update(table->handle.table.name,
h->table.name, table, &st);
}
return st.obj;
}
const char *table_flags_name[TABLE_FLAGS_MAX] = {
"dormant",
};
static void table_print_options(const struct table *table, const char **delim,
struct output_ctx *octx)
{
uint32_t flags = table->flags;
int i;
if (flags) {
nft_print(octx, "\tflags ");
for (i = 0; i < TABLE_FLAGS_MAX; i++) {
if (flags & 0x1)
nft_print(octx, "%s", table_flags_name[i]);
flags >>= 1;
if (flags)
nft_print(octx, ",");
}
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);
nft_print(octx, "table %s %s {", family, table->handle.table.name);
if (nft_output_handle(octx))
nft_print(octx, " # handle %" PRIu64, table->handle.handle.id);
nft_print(octx, "\n");
table_print_options(table, &delim, octx);
list_for_each_entry(obj, &table->objs, list) {
nft_print(octx, "%s", delim);
obj_print(obj, octx);
delim = "\n";
}
list_for_each_entry(set, &table->sets, list) {
if (set_is_anonymous(set->flags))
continue;
nft_print(octx, "%s", delim);
set_print(set, octx);
delim = "\n";
}
list_for_each_entry(flowtable, &table->flowtables, list) {
nft_print(octx, "%s", delim);
flowtable_print(flowtable, octx);
delim = "\n";
}
list_for_each_entry(chain, &table->chains, 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;
cmd = xzalloc(sizeof(*cmd));
init_list_head(&cmd->list);
cmd->op = op;
cmd->obj = obj;
cmd->handle = *h;
cmd->location = *loc;
cmd->data = data;
return cmd;
}
void nft_cmd_expand(struct cmd *cmd)
{
struct list_head new_cmds;
struct flowtable *ft;
struct table *table;
struct chain *chain;
struct rule *rule;
struct set *set;
struct obj *obj;
struct cmd *new;
struct handle h;
init_list_head(&new_cmds);
switch (cmd->obj) {
case CMD_OBJ_TABLE:
table = cmd->table;
if (!table)
return;
list_for_each_entry(chain, &table->chains, list) {
memset(&h, 0, sizeof(h));
handle_merge(&h, &chain->handle);
new = cmd_alloc(CMD_ADD, CMD_OBJ_CHAIN, &h,
&chain->location, chain_get(chain));
list_add_tail(&new->list, &new_cmds);
}
list_for_each_entry(obj, &table->objs, list) {
handle_merge(&obj->handle, &table->handle);
memset(&h, 0, sizeof(h));
handle_merge(&h, &obj->handle);
new = cmd_alloc(CMD_ADD, obj_type_to_cmd(obj->type), &h,
&obj->location, obj_get(obj));
list_add_tail(&new->list, &new_cmds);
}
list_for_each_entry(set, &table->sets, list) {
handle_merge(&set->handle, &table->handle);
memset(&h, 0, sizeof(h));
handle_merge(&h, &set->handle);
new = cmd_alloc(CMD_ADD, CMD_OBJ_SET, &h,
&set->location, set_get(set));
list_add_tail(&new->list, &new_cmds);
}
list_for_each_entry(ft, &table->flowtables, list) {
handle_merge(&ft->handle, &table->handle);
memset(&h, 0, sizeof(h));
handle_merge(&h, &ft->handle);
new = cmd_alloc(CMD_ADD, CMD_OBJ_FLOWTABLE, &h,
&ft->location, flowtable_get(ft));
list_add_tail(&new->list, &new_cmds);
}
list_for_each_entry(chain, &table->chains, list) {
list_for_each_entry(rule, &chain->rules, list) {
memset(&h, 0, sizeof(h));
handle_merge(&h, &rule->handle);
new = cmd_alloc(CMD_ADD, CMD_OBJ_RULE, &h,
&rule->location,
rule_get(rule));
list_add_tail(&new->list, &new_cmds);
}
}
list_splice(&new_cmds, &cmd->list);
break;
default:
break;
}
}
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_SETELEM:
expr_free(cmd->expr);
break;
case CMD_OBJ_SET:
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->arg);
xfree(cmd);
}
#include <netlink.h>
#include <mnl.h>
static int __do_add_setelems(struct netlink_ctx *ctx, struct set *set,
struct expr *expr, uint32_t flags)
{
expr->set_flags |= set->flags;
if (mnl_nft_setelem_add(ctx, set, expr, flags) < 0)
return -1;
if (set->init != NULL &&
set->flags & NFT_SET_INTERVAL) {
interval_map_decompose(expr);
list_splice_tail_init(&expr->expressions, &set->init->expressions);
set->init->size += expr->size;
expr->size = 0;
}
return 0;
}
static int do_add_setelems(struct netlink_ctx *ctx, struct cmd *cmd,
uint32_t flags)
{
struct handle *h = &cmd->handle;
struct expr *init = cmd->expr;
struct table *table;
struct set *set;
table = table_lookup(h, &ctx->nft->cache);
set = set_lookup(table, h->set.name);
if (set->flags & NFT_SET_INTERVAL &&
set_to_intervals(ctx->msgs, set, init, true,
ctx->nft->debug_mask, set->automerge,
&ctx->nft->output) < 0)
return -1;
return __do_add_setelems(ctx, set, init, flags);
}
static int do_add_set(struct netlink_ctx *ctx, const struct cmd *cmd,
uint32_t flags)
{
struct set *set = cmd->set;
if (set->init != NULL) {
if (set->flags & NFT_SET_INTERVAL &&
set_to_intervals(ctx->msgs, set, set->init, true,
ctx->nft->debug_mask, set->automerge,
&ctx->nft->output) < 0)
return -1;
}
if (mnl_nft_set_add(ctx, cmd, flags) < 0)
return -1;
if (set->init != NULL) {
return __do_add_setelems(ctx, set, set->init, flags);
}
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_SETELEM:
return do_add_setelems(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 handle *h = &cmd->handle;
struct expr *expr = cmd->expr;
struct table *table;
struct set *set;
table = table_lookup(h, &ctx->nft->cache);
set = set_lookup(table, h->set.name);
if (set->flags & NFT_SET_INTERVAL &&
set_to_intervals(ctx->msgs, set, expr, false,
ctx->nft->debug_mask, set->automerge,
&ctx->nft->output) < 0)
return -1;
if (mnl_nft_setelem_del(ctx, cmd) < 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_SETELEM:
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 cmd *cmd,
struct table *table)
{
table_print(table, &ctx->nft->output);
return 0;
}
static int do_list_sets(struct netlink_ctx *ctx, struct cmd *cmd)
{
struct print_fmt_options opts = {
.tab = "\t",
.nl = "\n",
.stmt_separator = "\n",
};
struct table *table;
struct set *set;
list_for_each_entry(table, &ctx->nft->cache.list, list) {
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->sets, 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_declaration(set, &opts, &ctx->nft->output);
nft_print(&ctx->nft->output, "%s}%s", opts.tab, opts.nl);
}
nft_print(&ctx->nft->output, "}\n");
}
return 0;
}
struct obj *obj_alloc(const struct location *loc)
{
struct obj *obj;
obj = xzalloc(sizeof(*obj));
if (loc != NULL)
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;
handle_free(&obj->handle);
xfree(obj);
}
void obj_add_hash(struct obj *obj, struct table *table)
{
list_add_tail(&obj->list, &table->objs);
}
struct obj *obj_lookup(const struct table *table, const char *name,
uint32_t type)
{
struct obj *obj;
list_for_each_entry(obj, &table->objs, list) {
if (!strcmp(obj->handle.obj.name, name) &&
obj->type == type)
return obj;
}
return NULL;
}
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->list, list) {
list_for_each_entry(obj, &table->objs, list) {
if (!strcmp(obj->handle.obj.name, obj_name)) {
*t = table;
return obj;
}
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)
{
const struct protoent *p = getprotobynumber(l4);
if (p)
nft_print(octx, "%s", p->p_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]) {
if (comma)
nft_print(octx, ", ");
nft_print(octx, "%s : %u",
timeout_protocol[l4].state_to_name[i],
timeout[i]);
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_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);
nft_print(octx, "%s%s%s", opts->nl, opts->tab, opts->tab);
if (nft_output_stateless(octx)) {
nft_print(octx, "packets 0 bytes 0");
break;
}
nft_print(octx, "packets %" PRIu64 " bytes %" PRIu64 "%s",
obj->counter.packets, obj->counter.bytes, opts->nl);
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);
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);
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);
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);
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);
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);
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);
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(enum stmt_types 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,
};
uint32_t 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.list, 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->objs))
continue;
nft_print(&ctx->nft->output, "table %s %s {\n",
family2str(table->handle.family),
table->handle.table.name);
list_for_each_entry(obj, &table->objs, list) {
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;
flowtable = xzalloc(sizeof(*flowtable));
if (loc != NULL)
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);
}
void flowtable_add_hash(struct flowtable *flowtable, struct table *table)
{
list_add_tail(&flowtable->list, &table->flowtables);
}
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->hooknum),
prio2str(octx, priobuf, sizeof(priobuf), NFPROTO_NETDEV,
flowtable->hooknum, flowtable->priority.expr),
opts->stmt_separator);
nft_print(octx, "%s%sdevices = { ", opts->tab, opts->tab);
for (i = 0; i < flowtable->dev_array_len; i++) {
nft_print(octx, "%s", flowtable->dev_array[i]);
if (i + 1 != flowtable->dev_array_len)
nft_print(octx, ", ");
}
nft_print(octx, " }%s", 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(const struct table *table, const char *name)
{
struct flowtable *ft;
list_for_each_entry(ft, &table->flowtables, list) {
if (!strcmp(ft->handle.flowtable.name, name))
return ft;
}
return NULL;
}
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->list, list) {
list_for_each_entry(ft, &table->flowtables, list) {
if (!strcmp(ft->handle.flowtable.name, ft_name)) {
*t = table;
return ft;
}
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 = flowtable_lookup(table, cmd->handle.flowtable.name);
if (ft == NULL)
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.list, 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->flowtables, 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.list, list) {
if (family != NFPROTO_UNSPEC &&
table->handle.family != family)
continue;
cmd->handle.family = table->handle.family;
cmd->handle.table.name = table->handle.table.name;
if (do_list_table(ctx, cmd, table) < 0)
return -1;
}
cmd->handle.table.name = NULL;
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.list, 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)
{
nft_print(octx, "table %s %s {\n",
family2str(table->handle.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);
list_for_each_entry(chain, &table->chains, list) {
if (chain->handle.family != cmd->handle.family ||
strcmp(cmd->handle.chain.name, chain->handle.chain.name) != 0)
continue;
chain_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.list, list) {
if (cmd->handle.family != NFPROTO_UNSPEC &&
cmd->handle.family != table->handle.family)
continue;
table_print_declaration(table, &ctx->nft->output);
list_for_each_entry(chain, &table->chains, list) {
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 table *table, struct set *set)
{
table_print_declaration(table, &ctx->nft->output);
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;
set = set_lookup(table, cmd->handle.set.name);
if (set == NULL)
return -1;
__do_list_set(ctx, cmd, table, set);
return 0;
}
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_lookup(&cmd->handle, &ctx->nft->cache);
switch (cmd->obj) {
case CMD_OBJ_TABLE:
if (!cmd->handle.table.name)
return do_list_tables(ctx, cmd);
return do_list_table(ctx, cmd, table);
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:
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:
return do_list_obj(ctx, cmd, NFT_OBJECT_CT_TIMEOUT);
case CMD_OBJ_CT_EXPECT:
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);
default:
BUG("invalid command object type %u\n", cmd->obj);
}
return 0;
}
static int do_get_setelems(struct netlink_ctx *ctx, struct cmd *cmd,
struct table *table)
{
struct set *set, *new_set;
struct expr *init;
int err;
set = set_lookup(table, cmd->handle.set.name);
/* Create a list of elements based of what we got from command line. */
if (set->flags & NFT_SET_INTERVAL)
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,
table, new_set, init);
if (err >= 0)
__do_list_set(ctx, cmd, table, new_set);
if (set->flags & NFT_SET_INTERVAL)
expr_free(init);
set_free(new_set);
return err;
}
static int do_command_get(struct netlink_ctx *ctx, struct cmd *cmd)
{
struct table *table = NULL;
if (cmd->handle.table.name != NULL)
table = table_lookup(&cmd->handle, &ctx->nft->cache);
switch (cmd->obj) {
case CMD_OBJ_SETELEM:
return do_get_setelems(ctx, cmd, table);
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;
default:
BUG("invalid command object type %u\n", cmd->obj);
}
ret = netlink_reset_objs(ctx, cmd, type, dump);
list_for_each_entry_safe(obj, next, &ctx->list, list) {
table = table_lookup(&obj->handle, &ctx->nft->cache);
list_move(&obj->list, &table->objs);
}
if (ret < 0)
return ret;
return do_list_obj(ctx, cmd, type);
}
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_lookup(&cmd->handle, &ctx->nft->cache);
const struct chain *chain;
switch (cmd->obj) {
case CMD_OBJ_CHAIN:
chain = chain_lookup(table, &cmd->handle);
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:
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);
}
}
/**
* payload_try_merge - try to merge consecutive payload match statements
*
* @rule: nftables rule
*
* Locate sequences of payload match statements referring to adjacent
* header locations and merge those using only equality relations.
*
* As a side-effect, payload match statements are ordered in ascending
* order according to the location of the payload.
*/
static void payload_try_merge(const struct rule *rule)
{
struct stmt *sa[rule->num_stmts];
struct stmt *stmt, *next;
unsigned int idx = 0;
list_for_each_entry_safe(stmt, next, &rule->stmts, list) {
/* Must not merge across other statements */
if (stmt->ops->type != STMT_EXPRESSION)
goto do_merge;
if (stmt->expr->etype != EXPR_RELATIONAL)
continue;
if (stmt->expr->left->etype != EXPR_PAYLOAD)
continue;
if (stmt->expr->right->etype != EXPR_VALUE)
continue;
switch (stmt->expr->op) {
case OP_EQ:
case OP_IMPLICIT:
case OP_NEQ:
break;
default:
continue;
}
sa[idx++] = stmt;
continue;
do_merge:
if (idx < 2)
continue;
payload_do_merge(sa, idx);
idx = 0;
}
if (idx > 1)
payload_do_merge(sa, idx);
}
struct error_record *rule_postprocess(struct rule *rule)
{
payload_try_merge(rule);
return NULL;
}