summaryrefslogtreecommitdiffstats
path: root/src/evaluate.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-09 13:08:37 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-09 13:08:37 +0000
commit971e619d8602fa52b1bfcb3ea65b7ab96be85318 (patch)
tree26feb2498c72b796e07b86349d17f544046de279 /src/evaluate.c
parentInitial commit. (diff)
downloadnftables-971e619d8602fa52b1bfcb3ea65b7ab96be85318.tar.xz
nftables-971e619d8602fa52b1bfcb3ea65b7ab96be85318.zip
Adding upstream version 1.0.9.upstream/1.0.9upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/evaluate.c')
-rw-r--r--src/evaluate.c5857
1 files changed, 5857 insertions, 0 deletions
diff --git a/src/evaluate.c b/src/evaluate.c
new file mode 100644
index 0000000..2196e92
--- /dev/null
+++ b/src/evaluate.c
@@ -0,0 +1,5857 @@
+/*
+ * Copyright (c) 2008 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 <arpa/inet.h>
+#include <linux/netfilter.h>
+#include <linux/netfilter_arp.h>
+#include <linux/netfilter/nf_tables.h>
+#include <linux/netfilter/nf_synproxy.h>
+#include <linux/netfilter/nf_nat.h>
+#include <linux/netfilter/nf_log.h>
+#include <linux/netfilter_ipv4.h>
+#include <netinet/ip_icmp.h>
+#include <netinet/icmp6.h>
+#include <net/ethernet.h>
+#include <net/if.h>
+#include <errno.h>
+
+#include <expression.h>
+#include <statement.h>
+#include <intervals.h>
+#include <netlink.h>
+#include <time.h>
+#include <rule.h>
+#include <cache.h>
+#include <erec.h>
+#include <gmputil.h>
+#include <utils.h>
+#include <xt.h>
+
+struct proto_ctx *eval_proto_ctx(struct eval_ctx *ctx)
+{
+ uint8_t idx = ctx->inner_desc ? 1 : 0;
+
+ return &ctx->_pctx[idx];
+}
+
+static int expr_evaluate(struct eval_ctx *ctx, struct expr **expr);
+
+static const char * const byteorder_names[] = {
+ [BYTEORDER_INVALID] = "invalid",
+ [BYTEORDER_HOST_ENDIAN] = "host endian",
+ [BYTEORDER_BIG_ENDIAN] = "big endian",
+};
+
+#define chain_error(ctx, s1, fmt, args...) \
+ __stmt_binary_error(ctx, &(s1)->location, NULL, fmt, ## args)
+#define monitor_error(ctx, s1, fmt, args...) \
+ __stmt_binary_error(ctx, &(s1)->location, NULL, fmt, ## args)
+#define cmd_error(ctx, loc, fmt, args...) \
+ __stmt_binary_error(ctx, loc, NULL, fmt, ## args)
+
+static int __fmtstring(3, 4) set_error(struct eval_ctx *ctx,
+ const struct set *set,
+ const char *fmt, ...)
+{
+ struct error_record *erec;
+ va_list ap;
+
+ va_start(ap, fmt);
+ erec = erec_vcreate(EREC_ERROR, &set->location, fmt, ap);
+ va_end(ap);
+ erec_queue(erec, ctx->msgs);
+ return -1;
+}
+
+static void key_fix_dtype_byteorder(struct expr *key)
+{
+ const struct datatype *dtype = key->dtype;
+
+ if (dtype->byteorder == key->byteorder)
+ return;
+
+ __datatype_set(key, set_datatype_alloc(dtype, key->byteorder));
+}
+
+static int set_evaluate(struct eval_ctx *ctx, struct set *set);
+static struct expr *implicit_set_declaration(struct eval_ctx *ctx,
+ const char *name,
+ struct expr *key,
+ struct expr *data,
+ struct expr *expr)
+{
+ struct cmd *cmd;
+ struct set *set;
+ struct handle h;
+
+ if (set_is_datamap(expr->set_flags))
+ key_fix_dtype_byteorder(key);
+
+ set = set_alloc(&expr->location);
+ set->flags = NFT_SET_ANONYMOUS | expr->set_flags;
+ set->handle.set.name = xstrdup(name);
+ set->key = key;
+ set->data = data;
+ set->init = expr;
+ set->automerge = set->flags & NFT_SET_INTERVAL;
+
+ if (ctx->table != NULL)
+ list_add_tail(&set->list, &ctx->table->sets);
+ else {
+ handle_merge(&set->handle, &ctx->cmd->handle);
+ memset(&h, 0, sizeof(h));
+ handle_merge(&h, &set->handle);
+ h.set.location = expr->location;
+ cmd = cmd_alloc(CMD_ADD, CMD_OBJ_SET, &h, &expr->location, set);
+ cmd->location = set->location;
+ list_add_tail(&cmd->list, &ctx->cmd->list);
+ }
+
+ set_evaluate(ctx, set);
+
+ return set_ref_expr_alloc(&expr->location, set);
+}
+
+static enum ops byteorder_conversion_op(struct expr *expr,
+ enum byteorder byteorder)
+{
+ switch (expr->byteorder) {
+ case BYTEORDER_HOST_ENDIAN:
+ if (byteorder == BYTEORDER_BIG_ENDIAN)
+ return OP_HTON;
+ break;
+ case BYTEORDER_BIG_ENDIAN:
+ if (byteorder == BYTEORDER_HOST_ENDIAN)
+ return OP_NTOH;
+ break;
+ default:
+ break;
+ }
+ BUG("invalid byte order conversion %u => %u\n",
+ expr->byteorder, byteorder);
+}
+
+static int byteorder_conversion(struct eval_ctx *ctx, struct expr **expr,
+ enum byteorder byteorder)
+{
+ enum datatypes basetype;
+ enum ops op;
+
+ assert(!expr_is_constant(*expr) || expr_is_singleton(*expr));
+
+ if ((*expr)->byteorder == byteorder)
+ return 0;
+
+ /* Conversion for EXPR_CONCAT is handled for single composing ranges */
+ if ((*expr)->etype == EXPR_CONCAT) {
+ struct expr *i, *next, *unary;
+
+ list_for_each_entry_safe(i, next, &(*expr)->expressions, list) {
+ if (i->byteorder == BYTEORDER_BIG_ENDIAN)
+ continue;
+
+ basetype = expr_basetype(i)->type;
+ if (basetype == TYPE_STRING)
+ continue;
+
+ assert(basetype == TYPE_INTEGER);
+
+ op = byteorder_conversion_op(i, byteorder);
+ unary = unary_expr_alloc(&i->location, op, i);
+ if (expr_evaluate(ctx, &unary) < 0)
+ return -1;
+
+ list_replace(&i->list, &unary->list);
+ }
+
+ return 0;
+ }
+
+ basetype = expr_basetype(*expr)->type;
+ switch (basetype) {
+ case TYPE_INTEGER:
+ break;
+ case TYPE_STRING:
+ return 0;
+ default:
+ return expr_error(ctx->msgs, *expr,
+ "Byteorder mismatch: %s expected %s, %s got %s",
+ byteorder_names[byteorder],
+ expr_name(*expr),
+ byteorder_names[(*expr)->byteorder]);
+ }
+
+ if (expr_is_constant(*expr) || div_round_up((*expr)->len, BITS_PER_BYTE) < 2)
+ (*expr)->byteorder = byteorder;
+ else {
+ op = byteorder_conversion_op(*expr, byteorder);
+ *expr = unary_expr_alloc(&(*expr)->location, op, *expr);
+ if (expr_evaluate(ctx, expr) < 0)
+ return -1;
+ }
+ return 0;
+}
+
+static int table_not_found(struct eval_ctx *ctx)
+{
+ struct table *table;
+
+ table = table_lookup_fuzzy(&ctx->cmd->handle, &ctx->nft->cache);
+ if (table == NULL)
+ return cmd_error(ctx, &ctx->cmd->handle.table.location,
+ "%s", strerror(ENOENT));
+
+ return cmd_error(ctx, &ctx->cmd->handle.table.location,
+ "%s; did you mean table ‘%s’ in family %s?",
+ strerror(ENOENT), table->handle.table.name,
+ family2str(table->handle.family));
+}
+
+static int chain_not_found(struct eval_ctx *ctx)
+{
+ const struct table *table;
+ struct chain *chain;
+
+ chain = chain_lookup_fuzzy(&ctx->cmd->handle, &ctx->nft->cache, &table);
+ if (chain == NULL)
+ return cmd_error(ctx, &ctx->cmd->handle.chain.location,
+ "%s", strerror(ENOENT));
+
+ return cmd_error(ctx, &ctx->cmd->handle.chain.location,
+ "%s; did you mean chain ‘%s’ in table %s ‘%s’?",
+ strerror(ENOENT), chain->handle.chain.name,
+ family2str(chain->handle.family),
+ table->handle.table.name);
+}
+
+static int set_not_found(struct eval_ctx *ctx, const struct location *loc,
+ const char *set_name)
+{
+ const struct table *table;
+ struct set *set;
+
+ set = set_lookup_fuzzy(set_name, &ctx->nft->cache, &table);
+ if (set == NULL)
+ return cmd_error(ctx, loc, "%s", strerror(ENOENT));
+
+ return cmd_error(ctx, loc,
+ "%s; did you mean %s ‘%s’ in table %s ‘%s’?",
+ strerror(ENOENT),
+ set_is_map(set->flags) ? "map" : "set",
+ set->handle.set.name,
+ family2str(set->handle.family),
+ table->handle.table.name);
+}
+
+static int flowtable_not_found(struct eval_ctx *ctx, const struct location *loc,
+ const char *ft_name)
+{
+ const struct table *table;
+ struct flowtable *ft;
+
+ ft = flowtable_lookup_fuzzy(ft_name, &ctx->nft->cache, &table);
+ if (!ft)
+ return cmd_error(ctx, loc, "%s", strerror(ENOENT));
+
+ return cmd_error(ctx, loc,
+ "%s; did you mean flowtable ‘%s’ in table %s ‘%s’?",
+ strerror(ENOENT), ft->handle.flowtable.name,
+ family2str(ft->handle.family),
+ table->handle.table.name);
+}
+
+/*
+ * Symbol expression: parse symbol and evaluate resulting expression.
+ */
+static int expr_evaluate_symbol(struct eval_ctx *ctx, struct expr **expr)
+{
+ struct parse_ctx parse_ctx = {
+ .tbl = &ctx->nft->output.tbl,
+ .input = &ctx->nft->input,
+ };
+ struct error_record *erec;
+ struct table *table;
+ struct set *set;
+ struct expr *new;
+
+ switch ((*expr)->symtype) {
+ case SYMBOL_VALUE:
+ datatype_set(*expr, ctx->ectx.dtype);
+ erec = symbol_parse(&parse_ctx, *expr, &new);
+ if (erec != NULL) {
+ erec_queue(erec, ctx->msgs);
+ return -1;
+ }
+ break;
+ case SYMBOL_SET:
+ table = table_cache_find(&ctx->nft->cache.table_cache,
+ ctx->cmd->handle.table.name,
+ ctx->cmd->handle.family);
+ if (table == NULL)
+ return table_not_found(ctx);
+
+ set = set_cache_find(table, (*expr)->identifier);
+ if (set == NULL || !set->key)
+ return set_not_found(ctx, &(*expr)->location,
+ (*expr)->identifier);
+
+ new = set_ref_expr_alloc(&(*expr)->location, set);
+ break;
+ }
+
+ expr_free(*expr);
+ *expr = new;
+
+ return expr_evaluate(ctx, expr);
+}
+
+static int expr_evaluate_string(struct eval_ctx *ctx, struct expr **exprp)
+{
+ struct expr *expr = *exprp;
+ unsigned int len = div_round_up(expr->len, BITS_PER_BYTE), datalen;
+ struct expr *value, *prefix;
+ int data_len = ctx->ectx.len > 0 ? ctx->ectx.len : len + 1;
+ char data[data_len];
+
+ if (ctx->ectx.len > 0) {
+ if (expr->len > ctx->ectx.len)
+ return expr_error(ctx->msgs, expr,
+ "String exceeds maximum length of %u",
+ ctx->ectx.len / BITS_PER_BYTE);
+ expr->len = ctx->ectx.len;
+ }
+
+ memset(data + len, 0, data_len - len);
+ mpz_export_data(data, expr->value, BYTEORDER_HOST_ENDIAN, len);
+
+ if (strlen(data) == 0)
+ return expr_error(ctx->msgs, expr,
+ "Empty string is not allowed");
+
+ datalen = strlen(data) - 1;
+ if (data[datalen] != '*') {
+ /* We need to reallocate the constant expression with the right
+ * expression length to avoid problems on big endian.
+ */
+ value = constant_expr_alloc(&expr->location, ctx->ectx.dtype,
+ BYTEORDER_HOST_ENDIAN,
+ expr->len, data);
+ expr_free(expr);
+ *exprp = value;
+ return 0;
+ }
+
+ if (datalen == 0)
+ return expr_error(ctx->msgs, expr,
+ "All-wildcard strings are not supported");
+
+ if (data[datalen - 1] == '\\') {
+ char unescaped_str[data_len];
+
+ memset(unescaped_str, 0, sizeof(unescaped_str));
+ xstrunescape(data, unescaped_str);
+
+ value = constant_expr_alloc(&expr->location, ctx->ectx.dtype,
+ BYTEORDER_HOST_ENDIAN,
+ expr->len, unescaped_str);
+ expr_free(expr);
+ *exprp = value;
+ return 0;
+ }
+
+ data[datalen] = 0;
+ value = constant_expr_alloc(&expr->location, ctx->ectx.dtype,
+ BYTEORDER_HOST_ENDIAN,
+ expr->len, data);
+
+ prefix = prefix_expr_alloc(&expr->location, value,
+ datalen * BITS_PER_BYTE);
+ datatype_set(prefix, ctx->ectx.dtype);
+ prefix->flags |= EXPR_F_CONSTANT;
+ prefix->byteorder = BYTEORDER_HOST_ENDIAN;
+ prefix->len = expr->len;
+
+ expr_free(expr);
+ *exprp = prefix;
+ return 0;
+}
+
+static int expr_evaluate_integer(struct eval_ctx *ctx, struct expr **exprp)
+{
+ struct expr *expr = *exprp;
+ char *valstr, *rangestr;
+ uint32_t masklen;
+ mpz_t mask;
+
+ if (ctx->ectx.maxval > 0 &&
+ mpz_cmp_ui(expr->value, ctx->ectx.maxval) > 0) {
+ valstr = mpz_get_str(NULL, 10, expr->value);
+ expr_error(ctx->msgs, expr,
+ "Value %s exceeds valid range 0-%u",
+ valstr, ctx->ectx.maxval);
+ free(valstr);
+ return -1;
+ }
+
+ if (ctx->stmt_len > ctx->ectx.len)
+ masklen = ctx->stmt_len;
+ else
+ masklen = ctx->ectx.len;
+
+ mpz_init_bitmask(mask, masklen);
+ if (mpz_cmp(expr->value, mask) > 0) {
+ valstr = mpz_get_str(NULL, 10, expr->value);
+ rangestr = mpz_get_str(NULL, 10, mask);
+ expr_error(ctx->msgs, expr,
+ "Value %s exceeds valid range 0-%s",
+ valstr, rangestr);
+ free(valstr);
+ free(rangestr);
+ mpz_clear(mask);
+ return -1;
+ }
+ expr->byteorder = ctx->ectx.byteorder;
+ expr->len = masklen;
+ mpz_clear(mask);
+ return 0;
+}
+
+static int expr_evaluate_value(struct eval_ctx *ctx, struct expr **expr)
+{
+ switch (expr_basetype(*expr)->type) {
+ case TYPE_INTEGER:
+ if (expr_evaluate_integer(ctx, expr) < 0)
+ return -1;
+ break;
+ case TYPE_STRING:
+ if (expr_evaluate_string(ctx, expr) < 0)
+ return -1;
+ break;
+ default:
+ BUG("invalid basetype %s\n", expr_basetype(*expr)->name);
+ }
+ return 0;
+}
+
+/*
+ * Primary expressions determine the datatype context.
+ */
+static int expr_evaluate_primary(struct eval_ctx *ctx, struct expr **expr)
+{
+ __expr_set_context(&ctx->ectx, (*expr)->dtype, (*expr)->byteorder,
+ (*expr)->len, 0);
+ return 0;
+}
+
+static int
+conflict_resolution_gen_dependency(struct eval_ctx *ctx, int protocol,
+ const struct expr *expr,
+ struct stmt **res)
+{
+ enum proto_bases base = expr->payload.base;
+ const struct proto_hdr_template *tmpl;
+ const struct proto_desc *desc = NULL;
+ struct expr *dep, *left, *right;
+ struct proto_ctx *pctx;
+ struct stmt *stmt;
+
+ assert(expr->payload.base == PROTO_BASE_LL_HDR);
+
+ pctx = eval_proto_ctx(ctx);
+ desc = pctx->protocol[base].desc;
+ tmpl = &desc->templates[desc->protocol_key];
+ left = payload_expr_alloc(&expr->location, desc, desc->protocol_key);
+
+ right = constant_expr_alloc(&expr->location, tmpl->dtype,
+ tmpl->dtype->byteorder, tmpl->len,
+ constant_data_ptr(protocol, tmpl->len));
+
+ dep = relational_expr_alloc(&expr->location, OP_EQ, left, right);
+ stmt = expr_stmt_alloc(&dep->location, dep);
+ if (stmt_evaluate(ctx, stmt) < 0)
+ return expr_error(ctx->msgs, expr,
+ "dependency statement is invalid");
+
+ if (ctx->inner_desc)
+ left->payload.inner_desc = ctx->inner_desc;
+
+ *res = stmt;
+ return 0;
+}
+
+static uint8_t expr_offset_shift(const struct expr *expr, unsigned int offset,
+ unsigned int *extra_len)
+{
+ unsigned int new_offset, len;
+ int shift;
+
+ new_offset = offset % BITS_PER_BYTE;
+ len = round_up(expr->len, BITS_PER_BYTE);
+ shift = len - (new_offset + expr->len);
+ while (shift < 0) {
+ shift += BITS_PER_BYTE;
+ *extra_len += BITS_PER_BYTE;
+ }
+ return shift;
+}
+
+static void expr_evaluate_bits(struct eval_ctx *ctx, struct expr **exprp)
+{
+ struct expr *expr = *exprp, *and, *mask, *rshift, *off;
+ unsigned masklen, len = expr->len, extra_len = 0;
+ enum byteorder byteorder;
+ uint8_t shift;
+ mpz_t bitmask;
+
+ switch (expr->etype) {
+ case EXPR_PAYLOAD:
+ shift = expr_offset_shift(expr, expr->payload.offset,
+ &extra_len);
+ break;
+ case EXPR_EXTHDR:
+ shift = expr_offset_shift(expr, expr->exthdr.offset,
+ &extra_len);
+ break;
+ default:
+ BUG("Unknown expression %s\n", expr_name(expr));
+ }
+
+ masklen = len + shift;
+ assert(masklen <= NFT_REG_SIZE * BITS_PER_BYTE);
+
+ mpz_init2(bitmask, masklen);
+ mpz_bitmask(bitmask, len);
+ mpz_lshift_ui(bitmask, shift);
+
+ mask = constant_expr_alloc(&expr->location, expr_basetype(expr),
+ BYTEORDER_HOST_ENDIAN, masklen, NULL);
+ mpz_set(mask->value, bitmask);
+ mpz_clear(bitmask);
+
+ and = binop_expr_alloc(&expr->location, OP_AND, expr, mask);
+ and->dtype = expr->dtype;
+ and->byteorder = expr->byteorder;
+ and->len = masklen;
+
+ if (shift) {
+ if (ctx->stmt_len > 0 && div_round_up(masklen, BITS_PER_BYTE) > 1) {
+ int op = byteorder_conversion_op(expr, BYTEORDER_HOST_ENDIAN);
+ and = unary_expr_alloc(&expr->location, op, and);
+ and->len = masklen;
+ byteorder = BYTEORDER_HOST_ENDIAN;
+ } else {
+ byteorder = expr->byteorder;
+ }
+
+ off = constant_expr_alloc(&expr->location,
+ expr_basetype(expr),
+ BYTEORDER_HOST_ENDIAN,
+ sizeof(shift), &shift);
+
+ rshift = binop_expr_alloc(&expr->location, OP_RSHIFT, and, off);
+ rshift->dtype = expr->dtype;
+ rshift->byteorder = byteorder;
+ rshift->len = masklen;
+
+ *exprp = rshift;
+ } else
+ *exprp = and;
+
+ if (extra_len)
+ expr->len += extra_len;
+}
+
+static int __expr_evaluate_exthdr(struct eval_ctx *ctx, struct expr **exprp)
+{
+ struct expr *expr = *exprp;
+
+ if (expr->exthdr.flags & NFT_EXTHDR_F_PRESENT)
+ datatype_set(expr, &boolean_type);
+
+ if (expr_evaluate_primary(ctx, exprp) < 0)
+ return -1;
+
+ if (expr->exthdr.offset % BITS_PER_BYTE != 0 ||
+ expr->len % BITS_PER_BYTE != 0)
+ expr_evaluate_bits(ctx, exprp);
+
+ switch (expr->exthdr.op) {
+ case NFT_EXTHDR_OP_TCPOPT: {
+ static const unsigned int max_tcpoptlen = (15 * 4 - 20) * BITS_PER_BYTE;
+ unsigned int totlen;
+
+ totlen = expr->exthdr.tmpl->len + expr->exthdr.offset;
+
+ if (totlen > max_tcpoptlen)
+ return expr_error(ctx->msgs, expr,
+ "offset and size %u exceeds max tcp headerlen (%u)",
+ totlen, max_tcpoptlen);
+ break;
+ }
+ case NFT_EXTHDR_OP_IPV4: {
+ static const unsigned int max_ipoptlen = 40 * BITS_PER_BYTE;
+ unsigned int totlen;
+
+ totlen = expr->exthdr.offset + expr->exthdr.tmpl->len;
+
+ if (totlen > max_ipoptlen)
+ return expr_error(ctx->msgs, expr,
+ "offset and size %u exceeds max ip option len (%u)",
+ totlen, max_ipoptlen);
+ break;
+ }
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+/*
+ * Exthdr expression: check whether dependencies are fulfilled, otherwise
+ * generate the necessary relational expression and prepend it to the current
+ * statement.
+ */
+static int expr_evaluate_exthdr(struct eval_ctx *ctx, struct expr **exprp)
+{
+ const struct proto_desc *base, *dependency = NULL;
+ enum proto_bases pb = PROTO_BASE_NETWORK_HDR;
+ struct expr *expr = *exprp;
+ struct proto_ctx *pctx;
+ struct stmt *nstmt;
+
+ switch (expr->exthdr.op) {
+ case NFT_EXTHDR_OP_TCPOPT:
+ case NFT_EXTHDR_OP_SCTP:
+ case NFT_EXTHDR_OP_DCCP:
+ return __expr_evaluate_exthdr(ctx, exprp);
+ case NFT_EXTHDR_OP_IPV4:
+ dependency = &proto_ip;
+ break;
+ case NFT_EXTHDR_OP_IPV6:
+ default:
+ dependency = &proto_ip6;
+ break;
+ }
+
+ assert(dependency);
+
+ pctx = eval_proto_ctx(ctx);
+ base = pctx->protocol[pb].desc;
+ if (base == dependency)
+ return __expr_evaluate_exthdr(ctx, exprp);
+
+ if (base)
+ return expr_error(ctx->msgs, expr,
+ "cannot use exthdr with %s", base->name);
+
+ if (exthdr_gen_dependency(ctx, expr, dependency, pb - 1, &nstmt) < 0)
+ return -1;
+
+ list_add(&nstmt->list, &ctx->rule->stmts);
+
+ return __expr_evaluate_exthdr(ctx, exprp);
+}
+
+/* dependency supersede.
+ *
+ * 'inet' is a 'phony' l2 dependency used by NFPROTO_INET to fulfil network
+ * header dependency, i.e. ensure that 'ip saddr 1.2.3.4' only sees ip headers.
+ *
+ * If a match expression that depends on a particular L2 header, e.g. ethernet,
+ * is used, we thus get a conflict since we already have a l2 header dependency.
+ *
+ * But in the inet case we can just ignore the conflict since only another
+ * restriction is added, and these are not mutually exclusive.
+ *
+ * Example: inet filter in ip saddr 1.2.3.4 ether saddr a:b:c:d:e:f
+ *
+ * ip saddr adds meta dependency on ipv4 packets
+ * ether saddr adds another dependency on ethernet frames.
+ */
+static int meta_iiftype_gen_dependency(struct eval_ctx *ctx,
+ struct expr *payload, struct stmt **res)
+{
+ struct stmt *nstmt;
+ uint16_t type;
+
+ if (proto_dev_type(payload->payload.desc, &type) < 0)
+ return expr_error(ctx->msgs, payload,
+ "protocol specification is invalid "
+ "for this family");
+
+ nstmt = meta_stmt_meta_iiftype(&payload->location, type);
+ if (stmt_evaluate(ctx, nstmt) < 0)
+ return expr_error(ctx->msgs, payload,
+ "dependency statement is invalid");
+
+ if (ctx->inner_desc)
+ nstmt->expr->left->meta.inner_desc = ctx->inner_desc;
+
+ *res = nstmt;
+ return 0;
+}
+
+static bool proto_is_dummy(const struct proto_desc *desc)
+{
+ return desc == &proto_inet || desc == &proto_netdev;
+}
+
+static int resolve_protocol_conflict(struct eval_ctx *ctx,
+ const struct proto_desc *desc,
+ struct expr *payload)
+{
+ enum proto_bases base = payload->payload.base;
+ struct stmt *nstmt = NULL;
+ struct proto_ctx *pctx;
+ int link, err;
+
+ pctx = eval_proto_ctx(ctx);
+
+ if (payload->payload.base == PROTO_BASE_LL_HDR) {
+ if (proto_is_dummy(desc)) {
+ if (ctx->inner_desc) {
+ proto_ctx_update(pctx, PROTO_BASE_LL_HDR, &payload->location, &proto_eth);
+ } else {
+ err = meta_iiftype_gen_dependency(ctx, payload, &nstmt);
+ if (err < 0)
+ return err;
+
+ desc = payload->payload.desc;
+ rule_stmt_insert_at(ctx->rule, nstmt, ctx->stmt);
+ }
+ } else {
+ unsigned int i;
+
+ /* payload desc stored in the L2 header stack? No conflict. */
+ for (i = 0; i < pctx->stacked_ll_count; i++) {
+ if (pctx->stacked_ll[i] == payload->payload.desc)
+ return 0;
+ }
+ }
+ }
+
+ assert(base <= PROTO_BASE_MAX);
+ /* This payload and the existing context don't match, conflict. */
+ if (pctx->protocol[base + 1].desc != NULL)
+ return 1;
+
+ link = proto_find_num(desc, payload->payload.desc);
+ if (link < 0 ||
+ conflict_resolution_gen_dependency(ctx, link, payload, &nstmt) < 0)
+ return 1;
+
+ if (base == PROTO_BASE_LL_HDR) {
+ unsigned int i;
+
+ for (i = 0; i < pctx->stacked_ll_count; i++)
+ payload->payload.offset += pctx->stacked_ll[i]->length;
+ }
+
+ rule_stmt_insert_at(ctx->rule, nstmt, ctx->stmt);
+
+ return 0;
+}
+
+/*
+ * Payload expression: check whether dependencies are fulfilled, otherwise
+ * generate the necessary relational expression and prepend it to the current
+ * statement.
+ */
+static int __expr_evaluate_payload(struct eval_ctx *ctx, struct expr *expr)
+{
+ struct expr *payload = expr;
+ enum proto_bases base = payload->payload.base;
+ const struct proto_desc *desc;
+ struct proto_ctx *pctx;
+ struct stmt *nstmt;
+ int err;
+
+ if (expr->etype == EXPR_PAYLOAD && expr->payload.is_raw)
+ return 0;
+
+ pctx = eval_proto_ctx(ctx);
+ desc = pctx->protocol[base].desc;
+ if (desc == NULL) {
+ if (payload_gen_dependency(ctx, payload, &nstmt) < 0)
+ return -1;
+
+ rule_stmt_insert_at(ctx->rule, nstmt, ctx->stmt);
+
+ desc = pctx->protocol[base].desc;
+
+ if (desc == expr->payload.desc)
+ goto check_icmp;
+
+ if (base == PROTO_BASE_LL_HDR) {
+ int link;
+
+ link = proto_find_num(desc, payload->payload.desc);
+ if (link < 0 ||
+ conflict_resolution_gen_dependency(ctx, link, payload, &nstmt) < 0)
+ return expr_error(ctx->msgs, payload,
+ "conflicting protocols specified: %s vs. %s",
+ desc->name,
+ payload->payload.desc->name);
+
+ payload->payload.offset += pctx->stacked_ll[0]->length;
+ rule_stmt_insert_at(ctx->rule, nstmt, ctx->stmt);
+ return 1;
+ }
+ goto check_icmp;
+ }
+
+ if (payload->payload.base == desc->base &&
+ proto_ctx_is_ambiguous(pctx, base)) {
+ desc = proto_ctx_find_conflict(pctx, base, payload->payload.desc);
+ assert(desc);
+
+ return expr_error(ctx->msgs, payload,
+ "conflicting protocols specified: %s vs. %s",
+ desc->name,
+ payload->payload.desc->name);
+ }
+
+ /* No conflict: Same payload protocol as context, adjust offset
+ * if needed.
+ */
+ if (desc == payload->payload.desc) {
+ const struct proto_hdr_template *tmpl;
+
+ if (desc->base == PROTO_BASE_LL_HDR) {
+ unsigned int i;
+
+ for (i = 0; i < pctx->stacked_ll_count; i++)
+ payload->payload.offset += pctx->stacked_ll[i]->length;
+ }
+check_icmp:
+ if (desc != &proto_icmp && desc != &proto_icmp6)
+ return 0;
+
+ tmpl = expr->payload.tmpl;
+
+ if (!tmpl || !tmpl->icmp_dep)
+ return 0;
+
+ if (payload_gen_icmp_dependency(ctx, expr, &nstmt) < 0)
+ return -1;
+
+ if (nstmt)
+ rule_stmt_insert_at(ctx->rule, nstmt, ctx->stmt);
+
+ return 0;
+ }
+ /* If we already have context and this payload is on the same
+ * base, try to resolve the protocol conflict.
+ */
+ if (payload->payload.base == desc->base) {
+ err = resolve_protocol_conflict(ctx, desc, payload);
+ if (err <= 0)
+ return err;
+
+ desc = pctx->protocol[base].desc;
+ if (desc == payload->payload.desc)
+ return 0;
+ }
+ return expr_error(ctx->msgs, payload,
+ "conflicting protocols specified: %s vs. %s",
+ pctx->protocol[base].desc->name,
+ payload->payload.desc->name);
+}
+
+static bool payload_needs_adjustment(const struct expr *expr)
+{
+ return expr->payload.offset % BITS_PER_BYTE != 0 ||
+ expr->len % BITS_PER_BYTE != 0;
+}
+
+static int expr_evaluate_payload(struct eval_ctx *ctx, struct expr **exprp)
+{
+ struct expr *expr = *exprp;
+
+ if (expr->payload.evaluated)
+ return 0;
+
+ if (__expr_evaluate_payload(ctx, expr) < 0)
+ return -1;
+
+ if (expr_evaluate_primary(ctx, exprp) < 0)
+ return -1;
+
+ if (payload_needs_adjustment(expr))
+ expr_evaluate_bits(ctx, exprp);
+
+ expr->payload.evaluated = true;
+
+ return 0;
+}
+
+static int expr_evaluate_inner(struct eval_ctx *ctx, struct expr **exprp)
+{
+ struct proto_ctx *pctx = eval_proto_ctx(ctx);
+ const struct proto_desc *desc = NULL;
+ struct expr *expr = *exprp;
+ int ret;
+
+ assert(expr->etype == EXPR_PAYLOAD);
+
+ pctx = eval_proto_ctx(ctx);
+ desc = pctx->protocol[PROTO_BASE_TRANSPORT_HDR].desc;
+
+ if (desc == NULL &&
+ expr->payload.inner_desc->base < PROTO_BASE_INNER_HDR) {
+ struct stmt *nstmt;
+
+ if (payload_gen_inner_dependency(ctx, expr, &nstmt) < 0)
+ return -1;
+
+ rule_stmt_insert_at(ctx->rule, nstmt, ctx->stmt);
+
+ proto_ctx_update(pctx, PROTO_BASE_TRANSPORT_HDR, &expr->location, expr->payload.inner_desc);
+ }
+
+ if (expr->payload.inner_desc->base == PROTO_BASE_INNER_HDR) {
+ desc = pctx->protocol[expr->payload.inner_desc->base - 1].desc;
+ if (!desc) {
+ return expr_error(ctx->msgs, expr,
+ "no transport protocol specified");
+ }
+
+ if (proto_find_num(desc, expr->payload.inner_desc) < 0) {
+ return expr_error(ctx->msgs, expr,
+ "unexpected transport protocol %s",
+ desc->name);
+ }
+
+ proto_ctx_update(pctx, expr->payload.inner_desc->base, &expr->location,
+ expr->payload.inner_desc);
+ }
+
+ if (expr->payload.base != PROTO_BASE_INNER_HDR)
+ ctx->inner_desc = expr->payload.inner_desc;
+
+ ret = expr_evaluate_payload(ctx, exprp);
+
+ return ret;
+}
+
+static int expr_evaluate_payload_inner(struct eval_ctx *ctx, struct expr **exprp)
+{
+ int ret;
+
+ if ((*exprp)->payload.inner_desc)
+ ret = expr_evaluate_inner(ctx, exprp);
+ else
+ ret = expr_evaluate_payload(ctx, exprp);
+
+ return ret;
+}
+
+/*
+ * RT expression: validate protocol dependencies.
+ */
+static int expr_evaluate_rt(struct eval_ctx *ctx, struct expr **expr)
+{
+ static const char emsg[] = "cannot determine ip protocol version, use \"ip nexthop\" or \"ip6 nexthop\" instead";
+ struct expr *rt = *expr;
+ struct proto_ctx *pctx;
+
+ pctx = eval_proto_ctx(ctx);
+ rt_expr_update_type(pctx, rt);
+
+ switch (rt->rt.key) {
+ case NFT_RT_NEXTHOP4:
+ if (rt->dtype != &ipaddr_type)
+ return expr_error(ctx->msgs, rt, "%s", emsg);
+ if (pctx->family == NFPROTO_IPV6)
+ return expr_error(ctx->msgs, rt, "%s nexthop will not match", "ip");
+ break;
+ case NFT_RT_NEXTHOP6:
+ if (rt->dtype != &ip6addr_type)
+ return expr_error(ctx->msgs, rt, "%s", emsg);
+ if (pctx->family == NFPROTO_IPV4)
+ return expr_error(ctx->msgs, rt, "%s nexthop will not match", "ip6");
+ break;
+ default:
+ break;
+ }
+
+ return expr_evaluate_primary(ctx, expr);
+}
+
+static int ct_gen_nh_dependency(struct eval_ctx *ctx, struct expr *ct)
+{
+ const struct proto_desc *base, *base_now;
+ struct expr *left, *right, *dep;
+ struct stmt *nstmt = NULL;
+ struct proto_ctx *pctx;
+
+ pctx = eval_proto_ctx(ctx);
+ base_now = pctx->protocol[PROTO_BASE_NETWORK_HDR].desc;
+
+ switch (ct->ct.nfproto) {
+ case NFPROTO_IPV4:
+ base = &proto_ip;
+ break;
+ case NFPROTO_IPV6:
+ base = &proto_ip6;
+ break;
+ default:
+ base = pctx->protocol[PROTO_BASE_NETWORK_HDR].desc;
+ if (base == &proto_ip)
+ ct->ct.nfproto = NFPROTO_IPV4;
+ else if (base == &proto_ip)
+ ct->ct.nfproto = NFPROTO_IPV6;
+
+ if (base)
+ break;
+
+ return expr_error(ctx->msgs, ct,
+ "cannot determine ip protocol version, use \"ip %1$caddr\" or \"ip6 %1$caddr\" instead",
+ ct->ct.key == NFT_CT_SRC ? 's' : 'd');
+ }
+
+ /* no additional dependency needed? */
+ if (base == base_now)
+ return 0;
+
+ if (base_now && base_now != base)
+ return expr_error(ctx->msgs, ct,
+ "conflicting dependencies: %s vs. %s\n",
+ base->name,
+ pctx->protocol[PROTO_BASE_NETWORK_HDR].desc->name);
+ switch (pctx->family) {
+ case NFPROTO_IPV4:
+ case NFPROTO_IPV6:
+ return 0;
+ }
+
+ left = ct_expr_alloc(&ct->location, NFT_CT_L3PROTOCOL, ct->ct.direction);
+
+ right = constant_expr_alloc(&ct->location, left->dtype,
+ left->dtype->byteorder, left->len,
+ constant_data_ptr(ct->ct.nfproto, left->len));
+ dep = relational_expr_alloc(&ct->location, OP_EQ, left, right);
+
+ relational_expr_pctx_update(pctx, dep);
+
+ nstmt = expr_stmt_alloc(&dep->location, dep);
+ rule_stmt_insert_at(ctx->rule, nstmt, ctx->stmt);
+
+ return 0;
+}
+
+/*
+ * CT expression: update the protocol dependant types bases on the protocol
+ * context.
+ */
+static int expr_evaluate_ct(struct eval_ctx *ctx, struct expr **expr)
+{
+ const struct proto_desc *base, *error;
+ struct expr *ct = *expr;
+ struct proto_ctx *pctx;
+
+ pctx = eval_proto_ctx(ctx);
+ base = pctx->protocol[PROTO_BASE_NETWORK_HDR].desc;
+
+ switch (ct->ct.key) {
+ case NFT_CT_SRC:
+ case NFT_CT_DST:
+ ct_gen_nh_dependency(ctx, ct);
+ break;
+ case NFT_CT_SRC_IP:
+ case NFT_CT_DST_IP:
+ if (base == &proto_ip6) {
+ error = &proto_ip;
+ goto err_conflict;
+ }
+ break;
+ case NFT_CT_SRC_IP6:
+ case NFT_CT_DST_IP6:
+ if (base == &proto_ip) {
+ error = &proto_ip6;
+ goto err_conflict;
+ }
+ break;
+ default:
+ break;
+ }
+
+ ct_expr_update_type(pctx, ct);
+
+ return expr_evaluate_primary(ctx, expr);
+
+err_conflict:
+ return stmt_binary_error(ctx, ct,
+ &pctx->protocol[PROTO_BASE_NETWORK_HDR],
+ "conflicting protocols specified: %s vs. %s",
+ base->name, error->name);
+}
+
+/*
+ * Prefix expression: the argument must be a constant value of integer or
+ * string base type; the prefix length must be less than or equal to the type
+ * width.
+ */
+static int expr_evaluate_prefix(struct eval_ctx *ctx, struct expr **expr)
+{
+ struct expr *prefix = *expr, *base, *and, *mask;
+
+ if (expr_evaluate(ctx, &prefix->prefix) < 0)
+ return -1;
+ base = prefix->prefix;
+
+ if (!expr_is_constant(base))
+ return expr_error(ctx->msgs, prefix,
+ "Prefix expression is undefined for "
+ "non-constant expressions");
+
+ switch (expr_basetype(base)->type) {
+ case TYPE_INTEGER:
+ case TYPE_STRING:
+ break;
+ default:
+ return expr_error(ctx->msgs, prefix,
+ "Prefix expression is undefined for "
+ "%s types", base->dtype->desc);
+ }
+
+ if (prefix->prefix_len > base->len)
+ return expr_error(ctx->msgs, prefix,
+ "Prefix length %u is invalid for type "
+ "of %u bits width",
+ prefix->prefix_len, base->len);
+
+ /* Clear the uncovered bits of the base value */
+ mask = constant_expr_alloc(&prefix->location, expr_basetype(base),
+ BYTEORDER_HOST_ENDIAN, base->len, NULL);
+ switch (expr_basetype(base)->type) {
+ case TYPE_INTEGER:
+ mpz_prefixmask(mask->value, base->len, prefix->prefix_len);
+ break;
+ case TYPE_STRING:
+ mpz_bitmask(mask->value, prefix->prefix_len);
+ break;
+ }
+ and = binop_expr_alloc(&prefix->location, OP_AND, base, mask);
+ prefix->prefix = and;
+ if (expr_evaluate(ctx, &prefix->prefix) < 0)
+ return -1;
+ base = prefix->prefix;
+ assert(expr_is_constant(base));
+
+ prefix->dtype = base->dtype;
+ prefix->byteorder = base->byteorder;
+ prefix->len = base->len;
+ prefix->flags |= EXPR_F_CONSTANT;
+ return 0;
+}
+
+/*
+ * Range expression: both sides must be constants of integer base type.
+ */
+static int expr_evaluate_range_expr(struct eval_ctx *ctx,
+ const struct expr *range,
+ struct expr **expr)
+{
+ if (expr_evaluate(ctx, expr) < 0)
+ return -1;
+
+ if (expr_basetype(*expr)->type != TYPE_INTEGER)
+ return expr_binary_error(ctx->msgs, *expr, range,
+ "Range expression is undefined for "
+ "%s types", (*expr)->dtype->desc);
+ if (!expr_is_constant(*expr))
+ return expr_binary_error(ctx->msgs, *expr, range,
+ "Range is not constant");
+ return 0;
+}
+
+static int __expr_evaluate_range(struct eval_ctx *ctx, struct expr **expr)
+{
+ struct expr *range = *expr;
+
+ if (expr_evaluate_range_expr(ctx, range, &range->left) < 0)
+ return -1;
+ if (expr_evaluate_range_expr(ctx, range, &range->right) < 0)
+ return -1;
+
+ return 0;
+}
+
+static int expr_evaluate_range(struct eval_ctx *ctx, struct expr **expr)
+{
+ struct expr *range = *expr, *left, *right;
+ int rc;
+
+ rc = __expr_evaluate_range(ctx, expr);
+ if (rc)
+ return rc;
+
+ left = range->left;
+ right = range->right;
+
+ if (mpz_cmp(left->value, right->value) > 0)
+ return expr_error(ctx->msgs, range,
+ "Range has zero or negative size");
+ datatype_set(range, left->dtype);
+ range->flags |= EXPR_F_CONSTANT;
+ return 0;
+}
+
+/*
+ * Unary expressions: unary expressions are only generated internally for
+ * byteorder conversion of non-constant numerical expressions.
+ */
+static int expr_evaluate_unary(struct eval_ctx *ctx, struct expr **expr)
+{
+ struct expr *unary = *expr, *arg = unary->arg;
+ enum byteorder byteorder;
+
+ /* unary expression arguments has already been evaluated. */
+
+ assert(!expr_is_constant(arg));
+ assert(expr_basetype(arg)->type == TYPE_INTEGER);
+ assert(arg->etype != EXPR_UNARY);
+
+ switch (unary->op) {
+ case OP_HTON:
+ assert(arg->byteorder == BYTEORDER_HOST_ENDIAN);
+ byteorder = BYTEORDER_BIG_ENDIAN;
+ break;
+ case OP_NTOH:
+ assert(arg->byteorder == BYTEORDER_BIG_ENDIAN);
+ byteorder = BYTEORDER_HOST_ENDIAN;
+ break;
+ default:
+ BUG("invalid unary operation %u\n", unary->op);
+ }
+
+ unary->dtype = arg->dtype;
+ unary->byteorder = byteorder;
+ unary->len = arg->len;
+ return 0;
+}
+
+/*
+ * Binops
+ */
+static int constant_binop_simplify(struct eval_ctx *ctx, struct expr **expr)
+{
+ struct expr *op = *expr, *left = (*expr)->left, *right = (*expr)->right;
+ struct expr *new;
+ mpz_t val, mask;
+
+ assert(left->etype == EXPR_VALUE);
+ assert(right->etype == EXPR_VALUE);
+ assert(left->byteorder == right->byteorder);
+
+ mpz_init2(val, op->len);
+ mpz_init_bitmask(mask, op->len);
+
+ switch (op->op) {
+ case OP_AND:
+ mpz_and(val, left->value, right->value);
+ mpz_and(val, val, mask);
+ break;
+ case OP_XOR:
+ mpz_xor(val, left->value, right->value);
+ mpz_and(val, val, mask);
+ break;
+ case OP_OR:
+ mpz_ior(val, left->value, right->value);
+ mpz_and(val, val, mask);
+ break;
+ case OP_LSHIFT:
+ assert(left->byteorder == BYTEORDER_HOST_ENDIAN);
+ mpz_set(val, left->value);
+ mpz_lshift_ui(val, mpz_get_uint32(right->value));
+ mpz_and(val, val, mask);
+ break;
+ case OP_RSHIFT:
+ assert(left->byteorder == BYTEORDER_HOST_ENDIAN);
+ mpz_set(val, left->value);
+ mpz_and(val, val, mask);
+ mpz_rshift_ui(val, mpz_get_uint32(right->value));
+ break;
+ default:
+ BUG("invalid binary operation %u\n", op->op);
+ }
+
+ new = constant_expr_alloc(&op->location, op->dtype, op->byteorder,
+ op->len, NULL);
+ mpz_set(new->value, val);
+
+ expr_free(*expr);
+ *expr = new;
+
+ mpz_clear(mask);
+ mpz_clear(val);
+
+ return expr_evaluate(ctx, expr);
+}
+
+static int expr_evaluate_shift(struct eval_ctx *ctx, struct expr **expr)
+{
+ struct expr *op = *expr, *left = op->left, *right = op->right;
+ unsigned int shift = mpz_get_uint32(right->value);
+ unsigned int max_shift_len;
+
+ if (ctx->stmt_len > left->len)
+ max_shift_len = ctx->stmt_len;
+ else
+ max_shift_len = left->len;
+
+ if (shift >= max_shift_len)
+ return expr_binary_error(ctx->msgs, right, left,
+ "%s shift of %u bits is undefined for type of %u bits width",
+ op->op == OP_LSHIFT ? "Left" : "Right",
+ shift, max_shift_len);
+
+ /* Both sides need to be in host byte order */
+ if (byteorder_conversion(ctx, &op->left, BYTEORDER_HOST_ENDIAN) < 0)
+ return -1;
+ left = op->left;
+ if (byteorder_conversion(ctx, &op->right, BYTEORDER_HOST_ENDIAN) < 0)
+ return -1;
+
+ datatype_set(op, &integer_type);
+ op->byteorder = BYTEORDER_HOST_ENDIAN;
+ op->len = max_shift_len;
+
+ if (expr_is_constant(left))
+ return constant_binop_simplify(ctx, expr);
+ return 0;
+}
+
+static int expr_evaluate_bitwise(struct eval_ctx *ctx, struct expr **expr)
+{
+ struct expr *op = *expr, *left = op->left;
+ const struct datatype *dtype;
+ enum byteorder byteorder;
+ unsigned int max_len;
+
+ if (ctx->stmt_len > left->len) {
+ max_len = ctx->stmt_len;
+ byteorder = BYTEORDER_HOST_ENDIAN;
+ dtype = &integer_type;
+
+ /* Both sides need to be in host byte order */
+ if (byteorder_conversion(ctx, &op->left, BYTEORDER_HOST_ENDIAN) < 0)
+ return -1;
+
+ left = op->left;
+ } else {
+ max_len = left->len;
+ byteorder = left->byteorder;
+ dtype = left->dtype;
+ }
+
+ if (byteorder_conversion(ctx, &op->right, byteorder) < 0)
+ return -1;
+
+ datatype_set(op, dtype);
+ op->byteorder = byteorder;
+ op->len = max_len;
+
+ if (expr_is_constant(left))
+ return constant_binop_simplify(ctx, expr);
+ return 0;
+}
+
+/*
+ * Binop expression: both sides must be of integer base type. The left
+ * hand side may be either constant or non-constant; in case its constant
+ * it must be a singleton. The ride hand side must always be a constant
+ * singleton.
+ */
+static int expr_evaluate_binop(struct eval_ctx *ctx, struct expr **expr)
+{
+ struct expr *op = *expr, *left, *right;
+ const char *sym = expr_op_symbols[op->op];
+ unsigned int max_shift_len = ctx->ectx.len;
+
+ if (expr_evaluate(ctx, &op->left) < 0)
+ return -1;
+ left = op->left;
+
+ if (op->op == OP_LSHIFT || op->op == OP_RSHIFT) {
+ if (left->len > max_shift_len)
+ max_shift_len = left->len;
+
+ __expr_set_context(&ctx->ectx, &integer_type,
+ left->byteorder, max_shift_len, 0);
+ }
+
+ if (expr_evaluate(ctx, &op->right) < 0)
+ return -1;
+ right = op->right;
+
+ switch (expr_basetype(left)->type) {
+ case TYPE_INTEGER:
+ case TYPE_STRING:
+ break;
+ default:
+ return expr_binary_error(ctx->msgs, left, op,
+ "Binary operation (%s) is undefined "
+ "for %s types",
+ sym, left->dtype->desc);
+ }
+
+ if (expr_is_constant(left) && !expr_is_singleton(left))
+ return expr_binary_error(ctx->msgs, left, op,
+ "Binary operation (%s) is undefined "
+ "for %s expressions",
+ sym, expr_name(left));
+
+ if (!expr_is_constant(right))
+ return expr_binary_error(ctx->msgs, right, op,
+ "Right hand side of binary operation "
+ "(%s) must be constant", sym);
+
+ if (!expr_is_singleton(right))
+ return expr_binary_error(ctx->msgs, left, op,
+ "Binary operation (%s) is undefined "
+ "for %s expressions",
+ sym, expr_name(right));
+
+ /* The grammar guarantees this */
+ assert(datatype_equal(expr_basetype(left), expr_basetype(right)));
+
+ switch (op->op) {
+ case OP_LSHIFT:
+ case OP_RSHIFT:
+ return expr_evaluate_shift(ctx, expr);
+ case OP_AND:
+ case OP_XOR:
+ case OP_OR:
+ return expr_evaluate_bitwise(ctx, expr);
+ default:
+ BUG("invalid binary operation %u\n", op->op);
+ }
+}
+
+static int list_member_evaluate(struct eval_ctx *ctx, struct expr **expr)
+{
+ struct expr *next = list_entry((*expr)->list.next, struct expr, list);
+ int err;
+
+ assert(*expr != next);
+ list_del(&(*expr)->list);
+ err = expr_evaluate(ctx, expr);
+ list_add_tail(&(*expr)->list, &next->list);
+ return err;
+}
+
+static int expr_evaluate_concat(struct eval_ctx *ctx, struct expr **expr)
+{
+ const struct datatype *dtype = ctx->ectx.dtype, *tmp;
+ uint32_t type = dtype ? dtype->type : 0, ntype = 0;
+ int off = dtype ? dtype->subtypes : 0;
+ unsigned int flags = EXPR_F_CONSTANT | EXPR_F_SINGLETON;
+ const struct list_head *expressions = NULL;
+ struct expr *i, *next, *key = NULL;
+ const struct expr *key_ctx = NULL;
+ bool runaway = false;
+ uint32_t size = 0;
+
+ if (ctx->ectx.key && ctx->ectx.key->etype == EXPR_CONCAT) {
+ key_ctx = ctx->ectx.key;
+ assert(!list_empty(&ctx->ectx.key->expressions));
+ key = list_first_entry(&ctx->ectx.key->expressions, struct expr, list);
+ expressions = &ctx->ectx.key->expressions;
+ }
+
+ list_for_each_entry_safe(i, next, &(*expr)->expressions, list) {
+ enum byteorder bo = BYTEORDER_INVALID;
+ unsigned dsize_bytes, dsize = 0;
+
+ if (runaway) {
+ return expr_binary_error(ctx->msgs, *expr, key_ctx,
+ "too many concatenation components");
+ }
+
+ if (i->etype == EXPR_CT &&
+ (i->ct.key == NFT_CT_SRC ||
+ i->ct.key == NFT_CT_DST))
+ return expr_error(ctx->msgs, i,
+ "specify either ip or ip6 for address matching");
+
+ if (expr_is_constant(*expr) && dtype && off == 0)
+ return expr_binary_error(ctx->msgs, i, *expr,
+ "unexpected concat component, "
+ "expecting %s",
+ dtype->desc);
+
+ if (key) {
+ tmp = key->dtype;
+ dsize = key->len;
+ bo = key->byteorder;
+ off--;
+ } else if (dtype == NULL || off == 0) {
+ tmp = datatype_lookup(TYPE_INVALID);
+ } else {
+ tmp = concat_subtype_lookup(type, --off);
+ dsize = tmp->size;
+ bo = tmp->byteorder;
+ }
+
+ __expr_set_context(&ctx->ectx, tmp, bo, dsize, 0);
+
+ if (list_member_evaluate(ctx, &i) < 0)
+ return -1;
+ flags &= i->flags;
+
+ if (!key && i->dtype->type == TYPE_INTEGER) {
+ struct datatype *clone;
+
+ clone = datatype_clone(i->dtype);
+ clone->size = i->len;
+ clone->byteorder = i->byteorder;
+ __datatype_set(i, clone);
+ }
+
+ if (dtype == NULL && i->dtype->size == 0)
+ return expr_binary_error(ctx->msgs, i, *expr,
+ "can not use variable sized "
+ "data types (%s) in concat "
+ "expressions",
+ i->dtype->name);
+ if (dsize == 0) /* reload after evaluation or clone above */
+ dsize = i->dtype->size;
+
+ ntype = concat_subtype_add(ntype, i->dtype->type);
+
+ dsize_bytes = div_round_up(dsize, BITS_PER_BYTE);
+ (*expr)->field_len[(*expr)->field_count++] = dsize_bytes;
+ size += netlink_padded_len(dsize);
+ if (key && expressions) {
+ if (list_is_last(&key->list, expressions))
+ runaway = true;
+
+ key = list_next_entry(key, list);
+ }
+
+ ctx->inner_desc = NULL;
+ }
+
+ (*expr)->flags |= flags;
+ __datatype_set(*expr, concat_type_alloc(ntype));
+ (*expr)->len = size;
+
+ if (off > 0)
+ return expr_error(ctx->msgs, *expr,
+ "datatype mismatch, expected %s, "
+ "expression has type %s",
+ dtype->desc, (*expr)->dtype->desc);
+
+ expr_set_context(&ctx->ectx, (*expr)->dtype, (*expr)->len);
+ if (!key_ctx)
+ ctx->ectx.key = *expr;
+ else
+ ctx->ectx.key = key_ctx;
+
+ return 0;
+}
+
+static int expr_evaluate_list(struct eval_ctx *ctx, struct expr **expr)
+{
+ struct expr *list = *expr, *new, *i, *next;
+ mpz_t val;
+
+ mpz_init_set_ui(val, 0);
+ list_for_each_entry_safe(i, next, &list->expressions, list) {
+ if (list_member_evaluate(ctx, &i) < 0)
+ return -1;
+ if (i->etype != EXPR_VALUE)
+ return expr_error(ctx->msgs, i,
+ "List member must be a constant "
+ "value");
+ if (i->dtype->basetype->type != TYPE_BITMASK)
+ return expr_error(ctx->msgs, i,
+ "Basetype of type %s is not bitmask",
+ i->dtype->desc);
+ mpz_ior(val, val, i->value);
+ }
+
+ new = constant_expr_alloc(&list->location, ctx->ectx.dtype,
+ BYTEORDER_HOST_ENDIAN, ctx->ectx.len, NULL);
+ mpz_set(new->value, val);
+ mpz_clear(val);
+
+ expr_free(*expr);
+ *expr = new;
+ return 0;
+}
+
+static int __expr_evaluate_set_elem(struct eval_ctx *ctx, struct expr *elem)
+{
+ int num_elem_exprs = 0, num_set_exprs = 0;
+ struct set *set = ctx->set;
+ struct stmt *stmt;
+
+ list_for_each_entry(stmt, &elem->stmt_list, list)
+ num_elem_exprs++;
+ list_for_each_entry(stmt, &set->stmt_list, list)
+ num_set_exprs++;
+
+ if (num_elem_exprs > 0) {
+ struct stmt *set_stmt, *elem_stmt;
+
+ if (num_set_exprs > 0 && num_elem_exprs != num_set_exprs) {
+ return expr_error(ctx->msgs, elem,
+ "number of statements mismatch, set expects %d "
+ "but element has %d", num_set_exprs,
+ num_elem_exprs);
+ } else if (num_set_exprs == 0) {
+ if (!(set->flags & NFT_SET_ANONYMOUS) &&
+ !(set->flags & NFT_SET_EVAL)) {
+ elem_stmt = list_first_entry(&elem->stmt_list, struct stmt, list);
+ return stmt_error(ctx, elem_stmt,
+ "missing statement in %s declaration",
+ set_is_map(set->flags) ? "map" : "set");
+ }
+ return 0;
+ }
+
+ set_stmt = list_first_entry(&set->stmt_list, struct stmt, list);
+
+ list_for_each_entry(elem_stmt, &elem->stmt_list, list) {
+ if (set_stmt->ops != elem_stmt->ops) {
+ return stmt_error(ctx, elem_stmt,
+ "statement mismatch, element expects %s, "
+ "but %s has type %s",
+ elem_stmt->ops->name,
+ set_is_map(set->flags) ? "map" : "set",
+ set_stmt->ops->name);
+ }
+ set_stmt = list_next_entry(set_stmt, list);
+ }
+ }
+
+ return 0;
+}
+
+static int expr_evaluate_set_elem(struct eval_ctx *ctx, struct expr **expr)
+{
+ struct expr *elem = *expr;
+ const struct expr *key;
+
+ if (ctx->set) {
+ if (__expr_evaluate_set_elem(ctx, elem) < 0)
+ return -1;
+
+ key = ctx->set->key;
+ __expr_set_context(&ctx->ectx, key->dtype, key->byteorder, key->len, 0);
+ ctx->ectx.key = key;
+ }
+
+ if (expr_evaluate(ctx, &elem->key) < 0)
+ return -1;
+
+ if (ctx->set &&
+ !(ctx->set->flags & (NFT_SET_ANONYMOUS | NFT_SET_INTERVAL))) {
+ switch (elem->key->etype) {
+ case EXPR_PREFIX:
+ case EXPR_RANGE:
+ key = elem->key;
+ goto err_missing_flag;
+ case EXPR_CONCAT:
+ list_for_each_entry(key, &elem->key->expressions, list) {
+ switch (key->etype) {
+ case EXPR_PREFIX:
+ case EXPR_RANGE:
+ goto err_missing_flag;
+ default:
+ break;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ datatype_set(elem, elem->key->dtype);
+ elem->len = elem->key->len;
+ elem->flags = elem->key->flags;
+
+ return 0;
+
+err_missing_flag:
+ return expr_error(ctx->msgs, key,
+ "You must add 'flags interval' to your %s declaration if you want to add %s elements",
+ set_is_map(ctx->set->flags) ? "map" : "set", expr_name(key));
+}
+
+static const struct expr *expr_set_elem(const struct expr *expr)
+{
+ if (expr->etype == EXPR_MAPPING)
+ return expr->left;
+
+ return expr;
+}
+
+static int interval_set_eval(struct eval_ctx *ctx, struct set *set,
+ struct expr *init)
+{
+ int ret;
+
+ if (!init)
+ return 0;
+
+ ret = 0;
+ switch (ctx->cmd->op) {
+ case CMD_CREATE:
+ case CMD_ADD:
+ case CMD_INSERT:
+ if (set->automerge) {
+ ret = set_automerge(ctx->msgs, ctx->cmd, set, init,
+ ctx->nft->debug_mask);
+ } else {
+ ret = set_overlap(ctx->msgs, set, init);
+ }
+ break;
+ case CMD_DELETE:
+ case CMD_DESTROY:
+ ret = set_delete(ctx->msgs, ctx->cmd, set, init,
+ ctx->nft->debug_mask);
+ break;
+ case CMD_GET:
+ break;
+ default:
+ BUG("unhandled op %d\n", ctx->cmd->op);
+ break;
+ }
+
+ return ret;
+}
+
+static void expr_evaluate_set_ref(struct eval_ctx *ctx, struct expr *expr)
+{
+ struct set *set = expr->set;
+
+ expr_set_context(&ctx->ectx, set->key->dtype, set->key->len);
+ ctx->ectx.key = set->key;
+}
+
+static int expr_evaluate_set(struct eval_ctx *ctx, struct expr **expr)
+{
+ struct expr *set = *expr, *i, *next;
+ const struct expr *elem;
+
+ list_for_each_entry_safe(i, next, &set->expressions, list) {
+ if (list_member_evaluate(ctx, &i) < 0)
+ return -1;
+
+ if (i->etype == EXPR_MAPPING &&
+ i->left->etype == EXPR_SET_ELEM &&
+ i->left->key->etype == EXPR_SET) {
+ struct expr *new, *j;
+
+ list_for_each_entry(j, &i->left->key->expressions, list) {
+ new = mapping_expr_alloc(&i->location,
+ expr_get(j),
+ expr_get(i->right));
+ list_add_tail(&new->list, &set->expressions);
+ set->size++;
+ }
+ list_del(&i->list);
+ expr_free(i);
+ continue;
+ }
+
+ elem = expr_set_elem(i);
+
+ if (elem->etype == EXPR_SET_ELEM &&
+ elem->key->etype == EXPR_SET_REF)
+ return expr_error(ctx->msgs, i,
+ "Set reference cannot be part of another set");
+
+ if (elem->etype == EXPR_SET_ELEM &&
+ elem->key->etype == EXPR_SET) {
+ struct expr *new = expr_get(elem->key);
+
+ set->set_flags |= elem->key->set_flags;
+ list_replace(&i->list, &new->list);
+ expr_free(i);
+ i = new;
+ elem = expr_set_elem(i);
+ }
+
+ if (!expr_is_constant(i))
+ return expr_error(ctx->msgs, i,
+ "Set member is not constant");
+
+ if (i->etype == EXPR_SET) {
+ /* Merge recursive set definitions */
+ list_splice_tail_init(&i->expressions, &i->list);
+ list_del(&i->list);
+ set->size += i->size - 1;
+ set->set_flags |= i->set_flags;
+ expr_free(i);
+ } else if (!expr_is_singleton(i)) {
+ set->set_flags |= NFT_SET_INTERVAL;
+ if (elem->key->etype == EXPR_CONCAT)
+ set->set_flags |= NFT_SET_CONCAT;
+ }
+ }
+
+ if (ctx->set) {
+ if (ctx->set->flags & NFT_SET_CONCAT)
+ set->set_flags |= NFT_SET_CONCAT;
+ }
+
+ set->set_flags |= NFT_SET_CONSTANT;
+
+ datatype_set(set, ctx->ectx.dtype);
+ set->len = ctx->ectx.len;
+ set->flags |= EXPR_F_CONSTANT;
+
+ return 0;
+}
+
+static int binop_transfer(struct eval_ctx *ctx, struct expr **expr);
+
+static void map_set_concat_info(struct expr *map)
+{
+ map->mappings->set->flags |= map->mappings->set->init->set_flags;
+
+ if (map->mappings->set->flags & NFT_SET_INTERVAL &&
+ map->map->etype == EXPR_CONCAT) {
+ memcpy(&map->mappings->set->desc.field_len, &map->map->field_len,
+ sizeof(map->mappings->set->desc.field_len));
+ map->mappings->set->desc.field_count = map->map->field_count;
+ map->mappings->flags |= NFT_SET_CONCAT;
+ }
+}
+
+static void __mapping_expr_expand(struct expr *i)
+{
+ struct expr *j, *range, *next;
+
+ assert(i->etype == EXPR_MAPPING);
+ switch (i->right->etype) {
+ case EXPR_VALUE:
+ range = range_expr_alloc(&i->location, expr_get(i->right), expr_get(i->right));
+ expr_free(i->right);
+ i->right = range;
+ break;
+ case EXPR_CONCAT:
+ list_for_each_entry_safe(j, next, &i->right->expressions, list) {
+ if (j->etype != EXPR_VALUE)
+ continue;
+
+ range = range_expr_alloc(&j->location, expr_get(j), expr_get(j));
+ list_replace(&j->list, &range->list);
+ expr_free(j);
+ }
+ i->right->flags &= ~EXPR_F_SINGLETON;
+ break;
+ default:
+ break;
+ }
+}
+
+static int mapping_expr_expand(struct eval_ctx *ctx)
+{
+ struct expr *i;
+
+ if (!set_is_anonymous(ctx->set->flags))
+ return 0;
+
+ list_for_each_entry(i, &ctx->set->init->expressions, list) {
+ if (i->etype != EXPR_MAPPING)
+ return expr_error(ctx->msgs, i,
+ "expected mapping, not %s", expr_name(i));
+ __mapping_expr_expand(i);
+ }
+
+ return 0;
+}
+
+static bool datatype_compatible(const struct datatype *a, const struct datatype *b)
+{
+ return (a->type == TYPE_MARK &&
+ datatype_equal(datatype_basetype(a), datatype_basetype(b))) ||
+ datatype_equal(a, b);
+}
+
+static int expr_evaluate_map(struct eval_ctx *ctx, struct expr **expr)
+{
+ struct expr *map = *expr, *mappings;
+ struct expr_ctx ectx = ctx->ectx;
+ struct expr *key, *data;
+
+ if (map->map->etype == EXPR_CT &&
+ (map->map->ct.key == NFT_CT_SRC ||
+ map->map->ct.key == NFT_CT_DST))
+ return expr_error(ctx->msgs, map->map,
+ "specify either ip or ip6 for address matching");
+ else if (map->map->etype == EXPR_CONCAT) {
+ struct expr *i;
+
+ list_for_each_entry(i, &map->map->expressions, list) {
+ if (i->etype == EXPR_CT &&
+ (i->ct.key == NFT_CT_SRC ||
+ i->ct.key == NFT_CT_DST))
+ return expr_error(ctx->msgs, i,
+ "specify either ip or ip6 for address matching");
+ }
+ }
+
+ expr_set_context(&ctx->ectx, NULL, 0);
+ ctx->stmt_len = 0;
+ if (expr_evaluate(ctx, &map->map) < 0)
+ return -1;
+ if (expr_is_constant(map->map))
+ return expr_error(ctx->msgs, map->map,
+ "Map expression can not be constant");
+
+ mappings = map->mappings;
+ mappings->set_flags |= NFT_SET_MAP;
+
+ switch (map->mappings->etype) {
+ case EXPR_SET:
+ if (ctx->ectx.key && ctx->ectx.key->etype == EXPR_CONCAT) {
+ key = expr_clone(ctx->ectx.key);
+ } else {
+ key = constant_expr_alloc(&map->location,
+ ctx->ectx.dtype,
+ ctx->ectx.byteorder,
+ ctx->ectx.len, NULL);
+ }
+
+ if (ectx.dtype->type == TYPE_VERDICT) {
+ data = verdict_expr_alloc(&netlink_location, 0, NULL);
+ } else {
+ const struct datatype *dtype;
+
+ dtype = set_datatype_alloc(ectx.dtype, ectx.byteorder);
+ data = constant_expr_alloc(&netlink_location, dtype,
+ dtype->byteorder, ectx.len, NULL);
+ datatype_free(dtype);
+ }
+
+ mappings = implicit_set_declaration(ctx, "__map%d",
+ key, data,
+ mappings);
+
+ if (ectx.len && mappings->set->data->len != ectx.len)
+ BUG("%d vs %d\n", mappings->set->data->len, ectx.len);
+
+ map->mappings = mappings;
+
+ ctx->set = mappings->set;
+ if (expr_evaluate(ctx, &map->mappings->set->init) < 0)
+ return -1;
+
+ if (set_is_interval(map->mappings->set->init->set_flags) &&
+ !(map->mappings->set->init->set_flags & NFT_SET_CONCAT) &&
+ interval_set_eval(ctx, ctx->set, map->mappings->set->init) < 0)
+ return -1;
+
+ expr_set_context(&ctx->ectx, ctx->set->key->dtype, ctx->set->key->len);
+ if (binop_transfer(ctx, expr) < 0)
+ return -1;
+
+ if (ctx->set->data->flags & EXPR_F_INTERVAL) {
+ ctx->set->data->len *= 2;
+
+ if (mapping_expr_expand(ctx))
+ return -1;
+ }
+
+ ctx->set->key->len = ctx->ectx.len;
+ ctx->set = NULL;
+ map = *expr;
+
+ map_set_concat_info(map);
+ break;
+ case EXPR_SYMBOL:
+ if (expr_evaluate(ctx, &map->mappings) < 0)
+ return -1;
+ if (map->mappings->etype != EXPR_SET_REF ||
+ !set_is_datamap(map->mappings->set->flags))
+ return expr_error(ctx->msgs, map->mappings,
+ "Expression is not a map");
+ break;
+ case EXPR_SET_REF:
+ /* symbol has been already evaluated to set reference */
+ break;
+ default:
+ BUG("invalid mapping expression %s\n",
+ expr_name(map->mappings));
+ }
+
+ if (!datatype_compatible(map->mappings->set->key->dtype, map->map->dtype))
+ return expr_binary_error(ctx->msgs, map->mappings, map->map,
+ "datatype mismatch, map expects %s, "
+ "mapping expression has type %s",
+ map->mappings->set->key->dtype->desc,
+ map->map->dtype->desc);
+
+ datatype_set(map, map->mappings->set->data->dtype);
+ map->flags |= EXPR_F_CONSTANT;
+
+ /* Data for range lookups needs to be in big endian order */
+ if (map->mappings->set->flags & NFT_SET_INTERVAL &&
+ byteorder_conversion(ctx, &map->map, BYTEORDER_BIG_ENDIAN) < 0)
+ return -1;
+
+ return 0;
+}
+
+static bool data_mapping_has_interval(struct expr *data)
+{
+ struct expr *i;
+
+ if (data->etype == EXPR_RANGE ||
+ data->etype == EXPR_PREFIX)
+ return true;
+
+ if (data->etype != EXPR_CONCAT)
+ return false;
+
+ list_for_each_entry(i, &data->expressions, list) {
+ if (i->etype == EXPR_RANGE ||
+ i->etype == EXPR_PREFIX)
+ return true;
+ }
+
+ return false;
+}
+
+static int expr_evaluate_mapping(struct eval_ctx *ctx, struct expr **expr)
+{
+ struct expr *mapping = *expr;
+ struct set *set = ctx->set;
+ uint32_t datalen;
+
+ if (set == NULL)
+ return expr_error(ctx->msgs, mapping,
+ "mapping outside of map context");
+ if (!set_is_map(set->flags))
+ return set_error(ctx, set, "set is not a map");
+
+ expr_set_context(&ctx->ectx, set->key->dtype, set->key->len);
+ if (expr_evaluate(ctx, &mapping->left) < 0)
+ return -1;
+ if (!expr_is_constant(mapping->left))
+ return expr_error(ctx->msgs, mapping->left,
+ "Key must be a constant");
+ mapping->flags |= mapping->left->flags & EXPR_F_SINGLETON;
+
+ assert(set->data != NULL);
+ if (!set_is_anonymous(set->flags) &&
+ set->data->flags & EXPR_F_INTERVAL)
+ datalen = set->data->len / 2;
+ else
+ datalen = set->data->len;
+ __expr_set_context(&ctx->ectx, set->data->dtype,
+ set->data->byteorder, datalen, 0);
+
+ if (expr_evaluate(ctx, &mapping->right) < 0)
+ return -1;
+ if (!expr_is_constant(mapping->right))
+ return expr_error(ctx->msgs, mapping->right,
+ "Value must be a constant");
+
+ if (set_is_anonymous(set->flags) &&
+ data_mapping_has_interval(mapping->right))
+ set->data->flags |= EXPR_F_INTERVAL;
+
+ if (!set_is_anonymous(set->flags) &&
+ set->data->flags & EXPR_F_INTERVAL)
+ __mapping_expr_expand(mapping);
+
+ if (!(set->data->flags & EXPR_F_INTERVAL) &&
+ !expr_is_singleton(mapping->right))
+ return expr_error(ctx->msgs, mapping->right,
+ "Value must be a singleton");
+
+ mapping->flags |= EXPR_F_CONSTANT;
+ return 0;
+}
+
+/* We got datatype context via statement. If the basetype is compatible, set
+ * this expression datatype to the one of the statement to make it datatype
+ * compatible. This is a more conservative approach than enabling datatype
+ * compatibility between two different datatypes whose basetype is the same,
+ * let's revisit this later once users come with valid usecases to generalize
+ * this.
+ */
+static void expr_dtype_integer_compatible(struct eval_ctx *ctx,
+ struct expr *expr)
+{
+ if (ctx->ectx.dtype &&
+ ctx->ectx.dtype->basetype == &integer_type &&
+ ctx->ectx.len == 4 * BITS_PER_BYTE) {
+ datatype_set(expr, ctx->ectx.dtype);
+ expr->len = ctx->ectx.len;
+ }
+}
+
+static int expr_evaluate_numgen(struct eval_ctx *ctx, struct expr **exprp)
+{
+ struct expr *expr = *exprp;
+ unsigned int maxval;
+
+ expr_dtype_integer_compatible(ctx, expr);
+
+ maxval = expr->numgen.mod + expr->numgen.offset - 1;
+ __expr_set_context(&ctx->ectx, expr->dtype, expr->byteorder, expr->len,
+ maxval);
+ return 0;
+}
+
+static int expr_evaluate_hash(struct eval_ctx *ctx, struct expr **exprp)
+{
+ struct expr *expr = *exprp;
+ unsigned int maxval;
+
+ expr_dtype_integer_compatible(ctx, expr);
+
+ expr_set_context(&ctx->ectx, NULL, 0);
+ if (expr->hash.expr &&
+ expr_evaluate(ctx, &expr->hash.expr) < 0)
+ return -1;
+
+ /* expr_evaluate_primary() sets the context to what to the input
+ * expression to be hashed. Since this input is transformed to a 4 bytes
+ * integer, restore context to the datatype that results from hashing.
+ */
+ maxval = expr->hash.mod + expr->hash.offset - 1;
+ __expr_set_context(&ctx->ectx, expr->dtype, expr->byteorder, expr->len,
+ maxval);
+
+ return 0;
+}
+
+/*
+ * Transfer the invertible binops to the constant side of an equality
+ * expression. A left shift is only invertible if the low n bits are
+ * zero.
+ */
+static int binop_can_transfer(struct eval_ctx *ctx,
+ struct expr *left, struct expr *right)
+{
+ int err;
+
+ switch (right->etype) {
+ case EXPR_VALUE:
+ break;
+ case EXPR_SET_ELEM:
+ return binop_can_transfer(ctx, left, right->key);
+ case EXPR_RANGE:
+ err = binop_can_transfer(ctx, left, right->left);
+ if (err <= 0)
+ return err;
+ return binop_can_transfer(ctx, left, right->right);
+ case EXPR_MAPPING:
+ return binop_can_transfer(ctx, left, right->left);
+ default:
+ return 0;
+ }
+
+ switch (left->op) {
+ case OP_LSHIFT:
+ if (mpz_scan1(right->value, 0) < mpz_get_uint32(left->right->value))
+ return expr_binary_error(ctx->msgs, right, left,
+ "Comparison is always false");
+ return 1;
+ case OP_RSHIFT:
+ if (ctx->ectx.len < right->len + mpz_get_uint32(left->right->value))
+ ctx->ectx.len += mpz_get_uint32(left->right->value);
+ return 1;
+ case OP_XOR:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+static int binop_transfer_one(struct eval_ctx *ctx,
+ const struct expr *left, struct expr **right)
+{
+ int err;
+
+ switch ((*right)->etype) {
+ case EXPR_MAPPING:
+ return binop_transfer_one(ctx, left, &(*right)->left);
+ case EXPR_VALUE:
+ break;
+ case EXPR_SET_ELEM:
+ return binop_transfer_one(ctx, left, &(*right)->key);
+ case EXPR_RANGE:
+ err = binop_transfer_one(ctx, left, &(*right)->left);
+ if (err < 0)
+ return err;
+ return binop_transfer_one(ctx, left, &(*right)->right);
+ default:
+ return 0;
+ }
+
+ switch (left->op) {
+ case OP_LSHIFT:
+ (*right) = binop_expr_alloc(&(*right)->location, OP_RSHIFT,
+ *right, expr_get(left->right));
+ break;
+ case OP_RSHIFT:
+ (*right) = binop_expr_alloc(&(*right)->location, OP_LSHIFT,
+ *right, expr_get(left->right));
+ break;
+ case OP_XOR:
+ (*right) = binop_expr_alloc(&(*right)->location, OP_XOR,
+ *right, expr_get(left->right));
+ break;
+ default:
+ BUG("invalid binary operation %u\n", left->op);
+ }
+
+ return expr_evaluate(ctx, right);
+}
+
+static void binop_transfer_handle_lhs(struct expr **expr)
+{
+ struct expr *tmp, *left = *expr;
+ unsigned int shift;
+
+ assert(left->etype == EXPR_BINOP);
+
+ switch (left->op) {
+ case OP_RSHIFT:
+ /* Mask out the bits the shift would have masked out */
+ shift = mpz_get_uint8(left->right->value);
+ mpz_bitmask(left->right->value, left->left->len);
+ mpz_lshift_ui(left->right->value, shift);
+ left->op = OP_AND;
+ break;
+ case OP_LSHIFT:
+ case OP_XOR:
+ tmp = expr_get(left->left);
+ datatype_set(tmp, left->dtype);
+ expr_free(left);
+ *expr = tmp;
+ break;
+ default:
+ BUG("invalid binop operation %u", left->op);
+ }
+}
+
+static int __binop_transfer(struct eval_ctx *ctx,
+ struct expr *left, struct expr **right)
+{
+ struct expr *i, *next;
+ int err;
+
+ assert(left->etype == EXPR_BINOP);
+
+ switch ((*right)->etype) {
+ case EXPR_VALUE:
+ err = binop_can_transfer(ctx, left, *right);
+ if (err <= 0)
+ return err;
+ if (binop_transfer_one(ctx, left, right) < 0)
+ return -1;
+ break;
+ case EXPR_RANGE:
+ err = binop_can_transfer(ctx, left, *right);
+ if (err <= 0)
+ return err;
+ if (binop_transfer_one(ctx, left, right) < 0)
+ return -1;
+ break;
+ case EXPR_SET:
+ list_for_each_entry(i, &(*right)->expressions, list) {
+ err = binop_can_transfer(ctx, left, i);
+ if (err <= 0)
+ return err;
+ }
+ list_for_each_entry_safe(i, next, &(*right)->expressions, list) {
+ list_del(&i->list);
+ err = binop_transfer_one(ctx, left, &i);
+ list_add_tail(&i->list, &next->list);
+ if (err < 0)
+ return err;
+ }
+ break;
+ case EXPR_SET_REF:
+ if (!set_is_anonymous((*right)->set->flags))
+ return 0;
+
+ return __binop_transfer(ctx, left, &(*right)->set->init);
+ default:
+ return 0;
+ }
+
+ return 1;
+}
+
+static int binop_transfer(struct eval_ctx *ctx, struct expr **expr)
+{
+ struct expr *left = (*expr)->left;
+ int ret;
+
+ if (left->etype != EXPR_BINOP)
+ return 0;
+
+ ret = __binop_transfer(ctx, left, &(*expr)->right);
+ if (ret <= 0)
+ return ret;
+
+ binop_transfer_handle_lhs(&(*expr)->left);
+ return 0;
+}
+
+static bool lhs_is_meta_hour(const struct expr *meta)
+{
+ if (meta->etype != EXPR_META)
+ return false;
+
+ return meta->meta.key == NFT_META_TIME_HOUR ||
+ meta->meta.key == NFT_META_TIME_DAY;
+}
+
+static void swap_values(struct expr *range)
+{
+ struct expr *left_tmp;
+
+ left_tmp = range->left;
+ range->left = range->right;
+ range->right = left_tmp;
+}
+
+static bool range_needs_swap(const struct expr *range)
+{
+ const struct expr *right = range->right;
+ const struct expr *left = range->left;
+
+ return mpz_cmp(left->value, right->value) > 0;
+}
+
+static void optimize_singleton_set(struct expr *rel, struct expr **expr)
+{
+ struct expr *set = rel->right, *i;
+
+ i = list_first_entry(&set->expressions, struct expr, list);
+ if (i->etype == EXPR_SET_ELEM &&
+ list_empty(&i->stmt_list)) {
+
+ switch (i->key->etype) {
+ case EXPR_PREFIX:
+ case EXPR_RANGE:
+ case EXPR_VALUE:
+ rel->right = *expr = i->key;
+ i->key = NULL;
+ expr_free(set);
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (rel->op == OP_IMPLICIT &&
+ rel->right->dtype->basetype &&
+ rel->right->dtype->basetype->type == TYPE_BITMASK &&
+ rel->right->dtype->type != TYPE_CT_STATE) {
+ rel->op = OP_EQ;
+ }
+}
+
+static int expr_evaluate_relational(struct eval_ctx *ctx, struct expr **expr)
+{
+ struct expr *rel = *expr, *left, *right;
+ struct proto_ctx *pctx;
+ struct expr *range;
+ int ret;
+
+ right = rel->right;
+ if (right->etype == EXPR_SYMBOL &&
+ right->symtype == SYMBOL_SET &&
+ expr_evaluate(ctx, &rel->right) < 0)
+ return -1;
+
+ if (expr_evaluate(ctx, &rel->left) < 0)
+ return -1;
+ left = rel->left;
+
+ pctx = eval_proto_ctx(ctx);
+
+ if (rel->right->etype == EXPR_RANGE && lhs_is_meta_hour(rel->left)) {
+ ret = __expr_evaluate_range(ctx, &rel->right);
+ if (ret)
+ return ret;
+
+ range = rel->right;
+
+ /*
+ * We may need to do this for proper cross-day ranges,
+ * e.g. meta hour 23:15-03:22
+ */
+ if (range_needs_swap(range)) {
+ if (ctx->nft->debug_mask & NFT_DEBUG_EVALUATION)
+ nft_print(&ctx->nft->output,
+ "Inverting range values for cross-day hour matching\n\n");
+
+ if (rel->op == OP_EQ || rel->op == OP_IMPLICIT) {
+ swap_values(range);
+ rel->op = OP_NEQ;
+ } else if (rel->op == OP_NEQ) {
+ swap_values(range);
+ rel->op = OP_EQ;
+ }
+ }
+ }
+
+ if (expr_evaluate(ctx, &rel->right) < 0)
+ return -1;
+ right = rel->right;
+
+ if (!expr_is_constant(right))
+ return expr_binary_error(ctx->msgs, right, rel,
+ "Right hand side of relational "
+ "expression (%s) must be constant",
+ expr_op_symbols[rel->op]);
+ if (expr_is_constant(left))
+ return expr_binary_error(ctx->msgs, left, right,
+ "Relational expression (%s) has "
+ "constant value",
+ expr_op_symbols[rel->op]);
+
+ if (!datatype_equal(left->dtype, right->dtype))
+ return expr_binary_error(ctx->msgs, right, left,
+ "datatype mismatch, expected %s, "
+ "expression has type %s",
+ left->dtype->desc,
+ right->dtype->desc);
+
+ /*
+ * Statements like 'ct secmark 12' are parsed as relational,
+ * disallow constant value on the right hand side.
+ */
+ if (((left->etype == EXPR_META &&
+ left->meta.key == NFT_META_SECMARK) ||
+ (left->etype == EXPR_CT &&
+ left->ct.key == NFT_CT_SECMARK)) &&
+ right->flags & EXPR_F_CONSTANT)
+ return expr_binary_error(ctx->msgs, right, left,
+ "Cannot be used with right hand side constant value");
+
+ switch (rel->op) {
+ case OP_EQ:
+ case OP_IMPLICIT:
+ case OP_NEQ:
+ if (right->etype == EXPR_SET && right->size == 1)
+ optimize_singleton_set(rel, &right);
+ break;
+ default:
+ break;
+ }
+
+ switch (rel->op) {
+ case OP_EQ:
+ case OP_IMPLICIT:
+ /*
+ * Update protocol context for payload and meta iiftype
+ * equality expressions.
+ */
+ relational_expr_pctx_update(pctx, rel);
+
+ /* fall through */
+ case OP_NEQ:
+ case OP_NEG:
+ if (rel->op == OP_NEG) {
+ if (left->etype == EXPR_BINOP)
+ return expr_binary_error(ctx->msgs, left, right,
+ "cannot combine negation with binary expression");
+ if (right->etype != EXPR_VALUE ||
+ right->dtype->basetype == NULL ||
+ right->dtype->basetype->type != TYPE_BITMASK)
+ return expr_binary_error(ctx->msgs, left, right,
+ "negation can only be used with singleton bitmask values. Did you mean \"!=\"?");
+ }
+
+ switch (right->etype) {
+ case EXPR_RANGE:
+ if (byteorder_conversion(ctx, &rel->left, BYTEORDER_BIG_ENDIAN) < 0)
+ return -1;
+ if (byteorder_conversion(ctx, &right->left, BYTEORDER_BIG_ENDIAN) < 0)
+ return -1;
+ if (byteorder_conversion(ctx, &right->right, BYTEORDER_BIG_ENDIAN) < 0)
+ return -1;
+ break;
+ case EXPR_PREFIX:
+ if (byteorder_conversion(ctx, &right->prefix, left->byteorder) < 0)
+ return -1;
+ break;
+ case EXPR_VALUE:
+ if (byteorder_conversion(ctx, &rel->right, left->byteorder) < 0)
+ return -1;
+ break;
+ case EXPR_SET:
+ if (right->size == 0)
+ return expr_error(ctx->msgs, right, "Set is empty");
+
+ right = rel->right =
+ implicit_set_declaration(ctx, "__set%d",
+ expr_get(left), NULL,
+ right);
+ /* fall through */
+ case EXPR_SET_REF:
+ if (rel->left->etype == EXPR_CT &&
+ (rel->left->ct.key == NFT_CT_SRC ||
+ rel->left->ct.key == NFT_CT_DST))
+ return expr_error(ctx->msgs, left,
+ "specify either ip or ip6 for address matching");
+
+ /* Data for range lookups needs to be in big endian order */
+ if (right->set->flags & NFT_SET_INTERVAL &&
+ byteorder_conversion(ctx, &rel->left, BYTEORDER_BIG_ENDIAN) < 0)
+ return -1;
+ break;
+ case EXPR_CONCAT:
+ return expr_binary_error(ctx->msgs, left, right,
+ "Use concatenations with sets and maps, not singleton values");
+ break;
+ default:
+ BUG("invalid expression type %s\n", expr_name(right));
+ }
+ break;
+ case OP_LT:
+ case OP_GT:
+ case OP_LTE:
+ case OP_GTE:
+ switch (left->etype) {
+ case EXPR_CONCAT:
+ return expr_binary_error(ctx->msgs, left, rel,
+ "Relational expression (%s) is undefined "
+ "for %s expressions",
+ expr_op_symbols[rel->op],
+ expr_name(left));
+ default:
+ break;
+ }
+
+ if (!expr_is_singleton(right))
+ return expr_binary_error(ctx->msgs, right, rel,
+ "Relational expression (%s) is undefined "
+ "for %s expressions",
+ expr_op_symbols[rel->op],
+ expr_name(right));
+
+ if (byteorder_conversion(ctx, &rel->left, BYTEORDER_BIG_ENDIAN) < 0)
+ return -1;
+ if (byteorder_conversion(ctx, &rel->right, BYTEORDER_BIG_ENDIAN) < 0)
+ return -1;
+ break;
+ default:
+ BUG("invalid relational operation %u\n", rel->op);
+ }
+
+ if (binop_transfer(ctx, expr) < 0)
+ return -1;
+
+ return 0;
+}
+
+static int expr_evaluate_fib(struct eval_ctx *ctx, struct expr **exprp)
+{
+ struct expr *expr = *exprp;
+
+ if (expr->flags & EXPR_F_BOOLEAN) {
+ expr->fib.flags |= NFTA_FIB_F_PRESENT;
+ datatype_set(expr, &boolean_type);
+ }
+ return expr_evaluate_primary(ctx, exprp);
+}
+
+static int expr_evaluate_meta(struct eval_ctx *ctx, struct expr **exprp)
+{
+ struct proto_ctx *pctx = eval_proto_ctx(ctx);
+ struct expr *meta = *exprp;
+
+ switch (meta->meta.key) {
+ case NFT_META_NFPROTO:
+ if (pctx->family != NFPROTO_INET &&
+ meta->flags & EXPR_F_PROTOCOL)
+ return expr_error(ctx->msgs, meta,
+ "meta nfproto is only useful in the inet family");
+ break;
+ case NFT_META_TIME_DAY:
+ __expr_set_context(&ctx->ectx, meta->dtype, meta->byteorder,
+ meta->len, 6);
+ return 0;
+ default:
+ break;
+ }
+
+ return expr_evaluate_primary(ctx, exprp);
+}
+
+static int expr_evaluate_socket(struct eval_ctx *ctx, struct expr **expr)
+{
+ enum nft_socket_keys key = (*expr)->socket.key;
+ int maxval = 0;
+
+ if (key == NFT_SOCKET_TRANSPARENT ||
+ key == NFT_SOCKET_WILDCARD)
+ maxval = 1;
+ __expr_set_context(&ctx->ectx, (*expr)->dtype, (*expr)->byteorder,
+ (*expr)->len, maxval);
+ return 0;
+}
+
+static int expr_evaluate_osf(struct eval_ctx *ctx, struct expr **expr)
+{
+ struct netlink_ctx nl_ctx = {
+ .nft = ctx->nft,
+ .seqnum = time(NULL),
+ };
+
+ nfnl_osf_load_fingerprints(&nl_ctx, 0);
+
+ return expr_evaluate_primary(ctx, expr);
+}
+
+static int expr_evaluate_variable(struct eval_ctx *ctx, struct expr **exprp)
+{
+ struct symbol *sym = (*exprp)->sym;
+ struct expr *new;
+
+ /* If variable is reused from different locations in the ruleset, then
+ * clone expression.
+ */
+ if (sym->refcnt > 2)
+ new = expr_clone(sym->expr);
+ else
+ new = expr_get(sym->expr);
+
+ if (expr_evaluate(ctx, &new) < 0) {
+ expr_free(new);
+ return -1;
+ }
+
+ expr_free(*exprp);
+ *exprp = new;
+
+ return 0;
+}
+
+static int expr_evaluate_xfrm(struct eval_ctx *ctx, struct expr **exprp)
+{
+ struct proto_ctx *pctx = eval_proto_ctx(ctx);
+ struct expr *expr = *exprp;
+
+ switch (pctx->family) {
+ case NFPROTO_IPV4:
+ case NFPROTO_IPV6:
+ case NFPROTO_INET:
+ break;
+ default:
+ return expr_error(ctx->msgs, expr, "ipsec expression is only"
+ " valid in ip/ip6/inet tables");
+ }
+
+ return expr_evaluate_primary(ctx, exprp);
+}
+
+static int expr_evaluate_flagcmp(struct eval_ctx *ctx, struct expr **exprp)
+{
+ struct expr *expr = *exprp, *binop, *rel;
+
+ if (expr->op != OP_EQ &&
+ expr->op != OP_NEQ)
+ return expr_error(ctx->msgs, expr, "either == or != is allowed");
+
+ binop = binop_expr_alloc(&expr->location, OP_AND,
+ expr_get(expr->flagcmp.expr),
+ expr_get(expr->flagcmp.mask));
+ rel = relational_expr_alloc(&expr->location, expr->op, binop,
+ expr_get(expr->flagcmp.value));
+ expr_free(expr);
+ *exprp = rel;
+
+ return expr_evaluate(ctx, exprp);
+}
+
+static int expr_evaluate(struct eval_ctx *ctx, struct expr **expr)
+{
+ if (ctx->nft->debug_mask & NFT_DEBUG_EVALUATION) {
+ struct error_record *erec;
+ erec = erec_create(EREC_INFORMATIONAL, &(*expr)->location,
+ "Evaluate %s", expr_name(*expr));
+ erec_print(&ctx->nft->output, erec, ctx->nft->debug_mask);
+ expr_print(*expr, &ctx->nft->output);
+ nft_print(&ctx->nft->output, "\n\n");
+ erec_destroy(erec);
+ }
+
+ switch ((*expr)->etype) {
+ case EXPR_SYMBOL:
+ return expr_evaluate_symbol(ctx, expr);
+ case EXPR_VARIABLE:
+ return expr_evaluate_variable(ctx, expr);
+ case EXPR_SET_REF:
+ expr_evaluate_set_ref(ctx, *expr);
+ return 0;
+ case EXPR_VALUE:
+ return expr_evaluate_value(ctx, expr);
+ case EXPR_EXTHDR:
+ return expr_evaluate_exthdr(ctx, expr);
+ case EXPR_VERDICT:
+ return expr_evaluate_primary(ctx, expr);
+ case EXPR_META:
+ return expr_evaluate_meta(ctx, expr);
+ case EXPR_SOCKET:
+ return expr_evaluate_socket(ctx, expr);
+ case EXPR_OSF:
+ return expr_evaluate_osf(ctx, expr);
+ case EXPR_FIB:
+ return expr_evaluate_fib(ctx, expr);
+ case EXPR_PAYLOAD:
+ return expr_evaluate_payload_inner(ctx, expr);
+ case EXPR_RT:
+ return expr_evaluate_rt(ctx, expr);
+ case EXPR_CT:
+ return expr_evaluate_ct(ctx, expr);
+ case EXPR_PREFIX:
+ return expr_evaluate_prefix(ctx, expr);
+ case EXPR_RANGE:
+ return expr_evaluate_range(ctx, expr);
+ case EXPR_UNARY:
+ return expr_evaluate_unary(ctx, expr);
+ case EXPR_BINOP:
+ return expr_evaluate_binop(ctx, expr);
+ case EXPR_CONCAT:
+ return expr_evaluate_concat(ctx, expr);
+ case EXPR_LIST:
+ return expr_evaluate_list(ctx, expr);
+ case EXPR_SET:
+ return expr_evaluate_set(ctx, expr);
+ case EXPR_SET_ELEM:
+ return expr_evaluate_set_elem(ctx, expr);
+ case EXPR_MAP:
+ return expr_evaluate_map(ctx, expr);
+ case EXPR_MAPPING:
+ return expr_evaluate_mapping(ctx, expr);
+ case EXPR_RELATIONAL:
+ return expr_evaluate_relational(ctx, expr);
+ case EXPR_NUMGEN:
+ return expr_evaluate_numgen(ctx, expr);
+ case EXPR_HASH:
+ return expr_evaluate_hash(ctx, expr);
+ case EXPR_XFRM:
+ return expr_evaluate_xfrm(ctx, expr);
+ case EXPR_SET_ELEM_CATCHALL:
+ return 0;
+ case EXPR_FLAGCMP:
+ return expr_evaluate_flagcmp(ctx, expr);
+ default:
+ BUG("unknown expression type %s\n", expr_name(*expr));
+ }
+}
+
+static int stmt_evaluate_expr(struct eval_ctx *ctx, struct stmt *stmt)
+{
+ memset(&ctx->ectx, 0, sizeof(ctx->ectx));
+ return expr_evaluate(ctx, &stmt->expr);
+}
+
+static int stmt_prefix_conversion(struct eval_ctx *ctx, struct expr **expr,
+ enum byteorder byteorder)
+{
+ struct expr *mask, *and, *or, *prefix, *base, *range;
+ int ret;
+
+ prefix = *expr;
+ base = prefix->prefix;
+
+ if (base->etype != EXPR_VALUE)
+ return expr_error(ctx->msgs, prefix,
+ "you cannot specify a prefix here, "
+ "unknown type %s", base->dtype->name);
+
+ if (!expr_is_constant(base))
+ return expr_error(ctx->msgs, prefix,
+ "Prefix expression is undefined for "
+ "non-constant expressions");
+
+ if (expr_basetype(base)->type != TYPE_INTEGER)
+ return expr_error(ctx->msgs, prefix,
+ "Prefix expression expected integer value");
+
+ mask = constant_expr_alloc(&prefix->location, expr_basetype(base),
+ BYTEORDER_HOST_ENDIAN, base->len, NULL);
+
+ mpz_prefixmask(mask->value, base->len, prefix->prefix_len);
+ and = binop_expr_alloc(&prefix->location, OP_AND, expr_get(base), mask);
+
+ mask = constant_expr_alloc(&prefix->location, expr_basetype(base),
+ BYTEORDER_HOST_ENDIAN, base->len, NULL);
+ mpz_bitmask(mask->value, prefix->len - prefix->prefix_len);
+ or = binop_expr_alloc(&prefix->location, OP_OR, expr_get(base), mask);
+
+ range = range_expr_alloc(&prefix->location, and, or);
+ ret = expr_evaluate(ctx, &range);
+ if (ret < 0) {
+ expr_free(range);
+ return ret;
+ }
+
+ expr_free(*expr);
+ *expr = range;
+ return 0;
+}
+
+static int __stmt_evaluate_arg(struct eval_ctx *ctx, struct stmt *stmt,
+ const struct datatype *dtype, unsigned int len,
+ enum byteorder byteorder, struct expr **expr)
+{
+ if ((*expr)->etype == EXPR_PAYLOAD &&
+ (*expr)->dtype->type == TYPE_INTEGER &&
+ ((*expr)->dtype->type != datatype_basetype(dtype)->type ||
+ (*expr)->len != len))
+ return stmt_binary_error(ctx, *expr, stmt,
+ "datatype mismatch: expected %s, "
+ "expression has type %s with length %d",
+ dtype->desc, (*expr)->dtype->desc,
+ (*expr)->len);
+
+ if (!datatype_compatible(dtype, (*expr)->dtype))
+ return stmt_binary_error(ctx, *expr, stmt, /* verdict vs invalid? */
+ "datatype mismatch: expected %s, "
+ "expression has type %s",
+ dtype->desc, (*expr)->dtype->desc);
+
+ if (dtype->type == TYPE_MARK &&
+ datatype_equal(datatype_basetype(dtype), datatype_basetype((*expr)->dtype)) &&
+ !expr_is_constant(*expr))
+ return byteorder_conversion(ctx, expr, byteorder);
+
+ /* we are setting a value, we can't use a set */
+ switch ((*expr)->etype) {
+ case EXPR_SET:
+ return stmt_binary_error(ctx, *expr, stmt,
+ "you cannot use a set here, unknown "
+ "value to use");
+ case EXPR_SET_REF:
+ return stmt_binary_error(ctx, *expr, stmt,
+ "you cannot reference a set here, "
+ "unknown value to use");
+ case EXPR_RT:
+ return byteorder_conversion(ctx, expr, byteorder);
+ case EXPR_PREFIX:
+ return stmt_prefix_conversion(ctx, expr, byteorder);
+ case EXPR_NUMGEN:
+ if (dtype->type == TYPE_IPADDR)
+ return byteorder_conversion(ctx, expr, byteorder);
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int stmt_evaluate_arg(struct eval_ctx *ctx, struct stmt *stmt,
+ const struct datatype *dtype, unsigned int len,
+ enum byteorder byteorder, struct expr **expr)
+{
+ __expr_set_context(&ctx->ectx, dtype, byteorder, len, 0);
+ if (expr_evaluate(ctx, expr) < 0)
+ return -1;
+
+ return __stmt_evaluate_arg(ctx, stmt, dtype, len, byteorder, expr);
+}
+
+/* like stmt_evaluate_arg, but keep existing context created
+ * by previous expr_evaluate().
+ *
+ * This is needed for add/update statements:
+ * ctx->ectx.key has the set key, which may be needed for 'typeof'
+ * sets: the 'add/update' expression might contain integer data types.
+ *
+ * Without the key we cannot derive the element size.
+ */
+static int stmt_evaluate_key(struct eval_ctx *ctx, struct stmt *stmt,
+ const struct datatype *dtype, unsigned int len,
+ enum byteorder byteorder, struct expr **expr)
+{
+ if (expr_evaluate(ctx, expr) < 0)
+ return -1;
+
+ return __stmt_evaluate_arg(ctx, stmt, dtype, len, byteorder, expr);
+}
+
+static int stmt_evaluate_verdict(struct eval_ctx *ctx, struct stmt *stmt)
+{
+ if (stmt_evaluate_arg(ctx, stmt, &verdict_type, 0, 0, &stmt->expr) < 0)
+ return -1;
+
+ switch (stmt->expr->etype) {
+ case EXPR_VERDICT:
+ if (stmt->expr->verdict != NFT_CONTINUE)
+ stmt->flags |= STMT_F_TERMINAL;
+ if (stmt->expr->chain != NULL) {
+ if (stmt_evaluate_arg(ctx, stmt, &string_type, 0, 0,
+ &stmt->expr->chain) < 0)
+ return -1;
+ if (stmt->expr->chain->etype != EXPR_VALUE) {
+ return expr_error(ctx->msgs, stmt->expr->chain,
+ "not a value expression");
+ }
+ }
+ break;
+ case EXPR_MAP:
+ break;
+ default:
+ BUG("invalid verdict expression %s\n", expr_name(stmt->expr));
+ }
+ return 0;
+}
+
+static bool stmt_evaluate_payload_need_csum(const struct expr *payload)
+{
+ const struct proto_desc *desc;
+
+ if (payload->payload.base == PROTO_BASE_INNER_HDR)
+ return true;
+
+ desc = payload->payload.desc;
+
+ return desc && desc->checksum_key;
+}
+
+static int stmt_evaluate_exthdr(struct eval_ctx *ctx, struct stmt *stmt)
+{
+ struct expr *exthdr;
+
+ if (__expr_evaluate_exthdr(ctx, &stmt->exthdr.expr) < 0)
+ return -1;
+
+ exthdr = stmt->exthdr.expr;
+ return stmt_evaluate_arg(ctx, stmt, exthdr->dtype, exthdr->len,
+ BYTEORDER_BIG_ENDIAN,
+ &stmt->exthdr.val);
+}
+
+static int stmt_evaluate_payload(struct eval_ctx *ctx, struct stmt *stmt)
+{
+ struct expr *mask, *and, *xor, *payload_bytes;
+ unsigned int masklen, extra_len = 0;
+ unsigned int payload_byte_size, payload_byte_offset;
+ uint8_t shift_imm, data[NFT_REG_SIZE];
+ struct expr *payload;
+ mpz_t bitmask, ff;
+ bool need_csum;
+
+ if (stmt->payload.expr->payload.inner_desc) {
+ return expr_error(ctx->msgs, stmt->payload.expr,
+ "payload statement for this expression is not supported");
+ }
+
+ if (__expr_evaluate_payload(ctx, stmt->payload.expr) < 0)
+ return -1;
+
+ payload = stmt->payload.expr;
+ if (stmt_evaluate_arg(ctx, stmt, payload->dtype, payload->len,
+ payload->byteorder, &stmt->payload.val) < 0)
+ return -1;
+
+ if (!expr_is_constant(stmt->payload.val) &&
+ byteorder_conversion(ctx, &stmt->payload.val,
+ payload->byteorder) < 0)
+ return -1;
+
+ need_csum = stmt_evaluate_payload_need_csum(payload);
+
+ if (!payload_needs_adjustment(payload)) {
+
+ /* We still need to munge the payload in case we have to
+ * update checksum and the length is not even because
+ * kernel checksum functions cannot deal with odd lengths.
+ */
+ if (!need_csum || ((payload->len / BITS_PER_BYTE) & 1) == 0)
+ return 0;
+ }
+
+ payload_byte_offset = payload->payload.offset / BITS_PER_BYTE;
+
+ shift_imm = expr_offset_shift(payload, payload->payload.offset,
+ &extra_len);
+ payload_byte_size = div_round_up(payload->len + extra_len,
+ BITS_PER_BYTE);
+
+ if (need_csum && payload_byte_size & 1) {
+ payload_byte_size++;
+
+ if (payload_byte_offset & 1) { /* prefer 16bit aligned fetch */
+ payload_byte_offset--;
+ assert(payload->payload.offset >= BITS_PER_BYTE);
+ } else {
+ shift_imm += BITS_PER_BYTE;
+ }
+ }
+
+ if (shift_imm) {
+ struct expr *off, *lshift;
+
+ off = constant_expr_alloc(&payload->location,
+ expr_basetype(payload),
+ BYTEORDER_HOST_ENDIAN,
+ sizeof(shift_imm), &shift_imm);
+
+ lshift = binop_expr_alloc(&payload->location, OP_LSHIFT,
+ stmt->payload.val, off);
+ lshift->dtype = payload->dtype;
+ lshift->byteorder = payload->byteorder;
+
+ stmt->payload.val = lshift;
+ }
+
+ masklen = payload_byte_size * BITS_PER_BYTE;
+ mpz_init_bitmask(ff, masklen);
+
+ mpz_init2(bitmask, masklen);
+ mpz_bitmask(bitmask, payload->len);
+ mpz_lshift_ui(bitmask, shift_imm);
+
+ mpz_xor(bitmask, ff, bitmask);
+ mpz_clear(ff);
+
+ assert(sizeof(data) * BITS_PER_BYTE >= masklen);
+ mpz_export_data(data, bitmask, payload->byteorder, payload_byte_size);
+ mask = constant_expr_alloc(&payload->location, expr_basetype(payload),
+ payload->byteorder, masklen, data);
+ mpz_clear(bitmask);
+
+ payload_bytes = payload_expr_alloc(&payload->location, NULL, 0);
+ payload_init_raw(payload_bytes, payload->payload.base,
+ payload_byte_offset * BITS_PER_BYTE,
+ payload_byte_size * BITS_PER_BYTE);
+
+ payload_bytes->payload.is_raw = 1;
+ payload_bytes->payload.desc = payload->payload.desc;
+ payload_bytes->byteorder = payload->byteorder;
+
+ payload->len = payload_bytes->len;
+ payload->payload.offset = payload_bytes->payload.offset;
+
+ and = binop_expr_alloc(&payload->location, OP_AND, payload_bytes, mask);
+
+ and->dtype = payload_bytes->dtype;
+ and->byteorder = payload_bytes->byteorder;
+ and->len = payload_bytes->len;
+
+ xor = binop_expr_alloc(&payload->location, OP_XOR, and,
+ stmt->payload.val);
+ xor->dtype = payload->dtype;
+ xor->byteorder = payload->byteorder;
+ xor->len = mask->len;
+
+ stmt->payload.val = xor;
+
+ return expr_evaluate(ctx, &stmt->payload.val);
+}
+
+static int stmt_evaluate_meter(struct eval_ctx *ctx, struct stmt *stmt)
+{
+ struct expr *key, *set, *setref;
+ struct set *existing_set;
+ struct table *table;
+
+ table = table_cache_find(&ctx->nft->cache.table_cache,
+ ctx->cmd->handle.table.name,
+ ctx->cmd->handle.family);
+ if (table == NULL)
+ return table_not_found(ctx);
+
+ existing_set = set_cache_find(table, stmt->meter.name);
+ if (existing_set)
+ return cmd_error(ctx, &stmt->location,
+ "%s; meter ‘%s’ overlaps an existing %s ‘%s’ in family %s",
+ strerror(EEXIST),
+ stmt->meter.name,
+ set_is_map(existing_set->flags) ? "map" : "set",
+ existing_set->handle.set.name,
+ family2str(existing_set->handle.family));
+
+ expr_set_context(&ctx->ectx, NULL, 0);
+ if (expr_evaluate(ctx, &stmt->meter.key) < 0)
+ return -1;
+ if (expr_is_constant(stmt->meter.key))
+ return expr_error(ctx->msgs, stmt->meter.key,
+ "Meter key expression can not be constant");
+ if (stmt->meter.key->comment)
+ return expr_error(ctx->msgs, stmt->meter.key,
+ "Meter key expression can not contain comments");
+
+ /* Declare an empty set */
+ key = stmt->meter.key;
+ set = set_expr_alloc(&key->location, NULL);
+ set->set_flags |= NFT_SET_EVAL;
+ if (key->timeout)
+ set->set_flags |= NFT_SET_TIMEOUT;
+
+ setref = implicit_set_declaration(ctx, stmt->meter.name,
+ expr_get(key), NULL, set);
+
+ setref->set->desc.size = stmt->meter.size;
+ stmt->meter.set = setref;
+
+ if (stmt_evaluate(ctx, stmt->meter.stmt) < 0)
+ return -1;
+ if (!(stmt->meter.stmt->flags & STMT_F_STATEFUL))
+ return stmt_binary_error(ctx, stmt->meter.stmt, stmt,
+ "meter statement must be stateful");
+
+ return 0;
+}
+
+static int stmt_evaluate_meta(struct eval_ctx *ctx, struct stmt *stmt)
+{
+ int ret;
+
+ ctx->stmt_len = stmt->meta.tmpl->len;
+
+ ret = stmt_evaluate_arg(ctx, stmt,
+ stmt->meta.tmpl->dtype,
+ stmt->meta.tmpl->len,
+ stmt->meta.tmpl->byteorder,
+ &stmt->meta.expr);
+ ctx->stmt_len = 0;
+
+ return ret;
+}
+
+static int stmt_evaluate_ct(struct eval_ctx *ctx, struct stmt *stmt)
+{
+ int ret;
+
+ ctx->stmt_len = stmt->ct.tmpl->len;
+
+ ret = stmt_evaluate_arg(ctx, stmt,
+ stmt->ct.tmpl->dtype,
+ stmt->ct.tmpl->len,
+ stmt->ct.tmpl->byteorder,
+ &stmt->ct.expr);
+ ctx->stmt_len = 0;
+
+ if (ret < 0)
+ return -1;
+
+ if (stmt->ct.key == NFT_CT_SECMARK && expr_is_constant(stmt->ct.expr))
+ return stmt_error(ctx, stmt,
+ "ct secmark must not be set to constant value");
+
+ return 0;
+}
+
+static int reject_payload_gen_dependency_tcp(struct eval_ctx *ctx,
+ struct stmt *stmt,
+ struct expr **payload)
+{
+ struct proto_ctx *pctx = eval_proto_ctx(ctx);
+ const struct proto_desc *desc;
+
+ desc = pctx->protocol[PROTO_BASE_TRANSPORT_HDR].desc;
+ if (desc != NULL)
+ return 0;
+ *payload = payload_expr_alloc(&stmt->location, &proto_tcp,
+ TCPHDR_UNSPEC);
+ return 1;
+}
+
+static int reject_payload_gen_dependency_family(struct eval_ctx *ctx,
+ struct stmt *stmt,
+ struct expr **payload)
+{
+ struct proto_ctx *pctx = eval_proto_ctx(ctx);
+ const struct proto_desc *base;
+
+ base = pctx->protocol[PROTO_BASE_NETWORK_HDR].desc;
+ if (base != NULL)
+ return 0;
+
+ if (stmt->reject.icmp_code < 0)
+ return stmt_error(ctx, stmt, "missing icmp error type");
+
+ /* Generate a network dependency */
+ switch (stmt->reject.family) {
+ case NFPROTO_IPV4:
+ *payload = payload_expr_alloc(&stmt->location, &proto_ip,
+ IPHDR_PROTOCOL);
+ break;
+ case NFPROTO_IPV6:
+ *payload = payload_expr_alloc(&stmt->location, &proto_ip6,
+ IP6HDR_NEXTHDR);
+ break;
+ default:
+ BUG("unknown reject family");
+ }
+ return 1;
+}
+
+static int stmt_reject_gen_dependency(struct eval_ctx *ctx, struct stmt *stmt,
+ struct expr *expr)
+{
+ struct expr *payload = NULL;
+ struct stmt *nstmt;
+ int ret;
+
+ switch (stmt->reject.type) {
+ case NFT_REJECT_TCP_RST:
+ ret = reject_payload_gen_dependency_tcp(ctx, stmt, &payload);
+ break;
+ case NFT_REJECT_ICMP_UNREACH:
+ ret = reject_payload_gen_dependency_family(ctx, stmt, &payload);
+ break;
+ default:
+ BUG("cannot generate reject dependency for type %d",
+ stmt->reject.type);
+ }
+ if (ret <= 0)
+ return ret;
+
+ if (payload_gen_dependency(ctx, payload, &nstmt) < 0) {
+ ret = -1;
+ goto out;
+ }
+
+ /*
+ * Unlike payload deps this adds the dependency at the beginning, i.e.
+ * log ... reject with tcp-reset
+ * turns into
+ * meta l4proto tcp log ... reject with tcp-reset
+ *
+ * Otherwise we'd log things that won't be rejected.
+ */
+ list_add(&nstmt->list, &ctx->rule->stmts);
+out:
+ xfree(payload);
+ return ret;
+}
+
+static int stmt_evaluate_reject_inet_family(struct eval_ctx *ctx,
+ struct stmt *stmt,
+ const struct proto_desc *desc)
+{
+ struct proto_ctx *pctx = eval_proto_ctx(ctx);
+ const struct proto_desc *base;
+ int protocol;
+
+ switch (stmt->reject.type) {
+ case NFT_REJECT_TCP_RST:
+ break;
+ case NFT_REJECT_ICMPX_UNREACH:
+ break;
+ case NFT_REJECT_ICMP_UNREACH:
+ base = pctx->protocol[PROTO_BASE_LL_HDR].desc;
+ protocol = proto_find_num(base, desc);
+ switch (protocol) {
+ case NFPROTO_IPV4:
+ case __constant_htons(ETH_P_IP):
+ if (stmt->reject.family == NFPROTO_IPV4)
+ break;
+ return stmt_binary_error(ctx, stmt->reject.expr,
+ &pctx->protocol[PROTO_BASE_NETWORK_HDR],
+ "conflicting protocols specified: ip vs ip6");
+ case NFPROTO_IPV6:
+ case __constant_htons(ETH_P_IPV6):
+ if (stmt->reject.family == NFPROTO_IPV6)
+ break;
+ return stmt_binary_error(ctx, stmt->reject.expr,
+ &pctx->protocol[PROTO_BASE_NETWORK_HDR],
+ "conflicting protocols specified: ip vs ip6");
+ default:
+ return stmt_error(ctx, stmt,
+ "cannot infer ICMP reject variant to use: explicit value required.\n");
+ }
+ break;
+ }
+
+ return 0;
+}
+
+static int stmt_evaluate_reject_inet(struct eval_ctx *ctx, struct stmt *stmt,
+ struct expr *expr)
+{
+ struct proto_ctx *pctx = eval_proto_ctx(ctx);
+ const struct proto_desc *desc;
+
+ desc = pctx->protocol[PROTO_BASE_NETWORK_HDR].desc;
+ if (desc != NULL &&
+ stmt_evaluate_reject_inet_family(ctx, stmt, desc) < 0)
+ return -1;
+ if (stmt->reject.type == NFT_REJECT_ICMPX_UNREACH)
+ return 0;
+ if (stmt_reject_gen_dependency(ctx, stmt, expr) < 0)
+ return -1;
+ return 0;
+}
+
+static int stmt_evaluate_reject_bridge_family(struct eval_ctx *ctx,
+ struct stmt *stmt,
+ const struct proto_desc *desc)
+{
+ struct proto_ctx *pctx = eval_proto_ctx(ctx);
+ const struct proto_desc *base;
+ int protocol;
+
+ switch (stmt->reject.type) {
+ case NFT_REJECT_ICMPX_UNREACH:
+ case NFT_REJECT_TCP_RST:
+ base = pctx->protocol[PROTO_BASE_LL_HDR].desc;
+ protocol = proto_find_num(base, desc);
+ switch (protocol) {
+ case __constant_htons(ETH_P_IP):
+ case __constant_htons(ETH_P_IPV6):
+ break;
+ default:
+ return stmt_binary_error(ctx, stmt,
+ &pctx->protocol[PROTO_BASE_NETWORK_HDR],
+ "cannot reject this network family");
+ }
+ break;
+ case NFT_REJECT_ICMP_UNREACH:
+ base = pctx->protocol[PROTO_BASE_LL_HDR].desc;
+ protocol = proto_find_num(base, desc);
+ switch (protocol) {
+ case __constant_htons(ETH_P_IP):
+ if (NFPROTO_IPV4 == stmt->reject.family)
+ break;
+ return stmt_binary_error(ctx, stmt->reject.expr,
+ &pctx->protocol[PROTO_BASE_NETWORK_HDR],
+ "conflicting protocols specified: ip vs ip6");
+ case __constant_htons(ETH_P_IPV6):
+ if (NFPROTO_IPV6 == stmt->reject.family)
+ break;
+ return stmt_binary_error(ctx, stmt->reject.expr,
+ &pctx->protocol[PROTO_BASE_NETWORK_HDR],
+ "conflicting protocols specified: ip vs ip6");
+ default:
+ return stmt_binary_error(ctx, stmt,
+ &pctx->protocol[PROTO_BASE_NETWORK_HDR],
+ "cannot reject this network family");
+ }
+ break;
+ }
+
+ return 0;
+}
+
+static int stmt_evaluate_reject_bridge(struct eval_ctx *ctx, struct stmt *stmt,
+ struct expr *expr)
+{
+ struct proto_ctx *pctx = eval_proto_ctx(ctx);
+ const struct proto_desc *desc;
+
+ desc = pctx->protocol[PROTO_BASE_LL_HDR].desc;
+ if (desc != &proto_eth && desc != &proto_vlan && desc != &proto_netdev)
+ return __stmt_binary_error(ctx, &stmt->location, NULL,
+ "cannot reject from this link layer protocol");
+
+ desc = pctx->protocol[PROTO_BASE_NETWORK_HDR].desc;
+ if (desc != NULL &&
+ stmt_evaluate_reject_bridge_family(ctx, stmt, desc) < 0)
+ return -1;
+ if (stmt->reject.type == NFT_REJECT_ICMPX_UNREACH)
+ return 0;
+ if (stmt_reject_gen_dependency(ctx, stmt, expr) < 0)
+ return -1;
+ return 0;
+}
+
+static int stmt_evaluate_reject_family(struct eval_ctx *ctx, struct stmt *stmt,
+ struct expr *expr)
+{
+ struct proto_ctx *pctx = eval_proto_ctx(ctx);
+
+ switch (pctx->family) {
+ case NFPROTO_ARP:
+ return stmt_error(ctx, stmt, "cannot use reject with arp");
+ case NFPROTO_IPV4:
+ case NFPROTO_IPV6:
+ switch (stmt->reject.type) {
+ case NFT_REJECT_TCP_RST:
+ if (stmt_reject_gen_dependency(ctx, stmt, expr) < 0)
+ return -1;
+ break;
+ case NFT_REJECT_ICMPX_UNREACH:
+ return stmt_binary_error(ctx, stmt->reject.expr, stmt,
+ "abstracted ICMP unreachable not supported");
+ case NFT_REJECT_ICMP_UNREACH:
+ if (stmt->reject.family == pctx->family)
+ break;
+ return stmt_binary_error(ctx, stmt->reject.expr, stmt,
+ "conflicting protocols specified: ip vs ip6");
+ }
+ break;
+ case NFPROTO_BRIDGE:
+ case NFPROTO_NETDEV:
+ if (stmt_evaluate_reject_bridge(ctx, stmt, expr) < 0)
+ return -1;
+ break;
+ case NFPROTO_INET:
+ if (stmt_evaluate_reject_inet(ctx, stmt, expr) < 0)
+ return -1;
+ break;
+ }
+
+ stmt->flags |= STMT_F_TERMINAL;
+ return 0;
+}
+
+static int stmt_evaluate_reject_default(struct eval_ctx *ctx,
+ struct stmt *stmt)
+{
+ struct proto_ctx *pctx = eval_proto_ctx(ctx);
+ const struct proto_desc *desc, *base;
+ int protocol;
+
+ switch (pctx->family) {
+ case NFPROTO_IPV4:
+ case NFPROTO_IPV6:
+ stmt->reject.type = NFT_REJECT_ICMP_UNREACH;
+ stmt->reject.family = pctx->family;
+ if (pctx->family == NFPROTO_IPV4)
+ stmt->reject.icmp_code = ICMP_PORT_UNREACH;
+ else
+ stmt->reject.icmp_code = ICMP6_DST_UNREACH_NOPORT;
+ break;
+ case NFPROTO_INET:
+ desc = pctx->protocol[PROTO_BASE_NETWORK_HDR].desc;
+ if (desc == NULL) {
+ stmt->reject.type = NFT_REJECT_ICMPX_UNREACH;
+ stmt->reject.icmp_code = NFT_REJECT_ICMPX_PORT_UNREACH;
+ break;
+ }
+ stmt->reject.type = NFT_REJECT_ICMP_UNREACH;
+ base = pctx->protocol[PROTO_BASE_LL_HDR].desc;
+ protocol = proto_find_num(base, desc);
+ switch (protocol) {
+ case NFPROTO_IPV4:
+ case __constant_htons(ETH_P_IP):
+ stmt->reject.family = NFPROTO_IPV4;
+ stmt->reject.icmp_code = ICMP_PORT_UNREACH;
+ break;
+ case NFPROTO_IPV6:
+ case __constant_htons(ETH_P_IPV6):
+ stmt->reject.family = NFPROTO_IPV6;
+ stmt->reject.icmp_code = ICMP6_DST_UNREACH_NOPORT;
+ break;
+ }
+ break;
+ case NFPROTO_BRIDGE:
+ case NFPROTO_NETDEV:
+ desc = pctx->protocol[PROTO_BASE_NETWORK_HDR].desc;
+ if (desc == NULL) {
+ stmt->reject.type = NFT_REJECT_ICMPX_UNREACH;
+ stmt->reject.icmp_code = NFT_REJECT_ICMPX_PORT_UNREACH;
+ break;
+ }
+ stmt->reject.type = NFT_REJECT_ICMP_UNREACH;
+ base = pctx->protocol[PROTO_BASE_LL_HDR].desc;
+ protocol = proto_find_num(base, desc);
+ switch (protocol) {
+ case __constant_htons(ETH_P_IP):
+ stmt->reject.family = NFPROTO_IPV4;
+ stmt->reject.icmp_code = ICMP_PORT_UNREACH;
+ break;
+ case __constant_htons(ETH_P_IPV6):
+ stmt->reject.family = NFPROTO_IPV6;
+ stmt->reject.icmp_code = ICMP6_DST_UNREACH_NOPORT;
+ break;
+ }
+ break;
+ }
+ return 0;
+}
+
+static int stmt_evaluate_reject_icmp(struct eval_ctx *ctx, struct stmt *stmt)
+{
+ struct parse_ctx parse_ctx = {
+ .tbl = &ctx->nft->output.tbl,
+ .input = &ctx->nft->input,
+ };
+ struct error_record *erec;
+ struct expr *code;
+
+ erec = symbol_parse(&parse_ctx, stmt->reject.expr, &code);
+ if (erec != NULL) {
+ erec_queue(erec, ctx->msgs);
+ return -1;
+ }
+ stmt->reject.icmp_code = mpz_get_uint8(code->value);
+ expr_free(code);
+
+ return 0;
+}
+
+static int stmt_evaluate_reset(struct eval_ctx *ctx, struct stmt *stmt)
+{
+ struct proto_ctx *pctx = eval_proto_ctx(ctx);
+ const struct proto_desc *desc, *base;
+ int protonum;
+
+ desc = pctx->protocol[PROTO_BASE_TRANSPORT_HDR].desc;
+ if (desc == NULL)
+ return 0;
+
+ base = pctx->protocol[PROTO_BASE_NETWORK_HDR].desc;
+ if (base == NULL)
+ base = &proto_inet_service;
+
+ protonum = proto_find_num(base, desc);
+ switch (protonum) {
+ case IPPROTO_TCP:
+ break;
+ default:
+ if (stmt->reject.type == NFT_REJECT_TCP_RST) {
+ return stmt_binary_error(ctx, stmt,
+ &pctx->protocol[PROTO_BASE_TRANSPORT_HDR],
+ "you cannot use tcp reset with this protocol");
+ }
+ break;
+ }
+ return 0;
+}
+
+static int stmt_evaluate_reject(struct eval_ctx *ctx, struct stmt *stmt)
+{
+ struct expr *expr = ctx->cmd->expr;
+
+ if (stmt->reject.icmp_code < 0) {
+ if (stmt_evaluate_reject_default(ctx, stmt) < 0)
+ return -1;
+ } else if (stmt->reject.expr != NULL) {
+ if (stmt_evaluate_reject_icmp(ctx, stmt) < 0)
+ return -1;
+ } else {
+ if (stmt_evaluate_reset(ctx, stmt) < 0)
+ return -1;
+ }
+
+ return stmt_evaluate_reject_family(ctx, stmt, expr);
+}
+
+static int nat_evaluate_family(struct eval_ctx *ctx, struct stmt *stmt)
+{
+ struct proto_ctx *pctx = eval_proto_ctx(ctx);
+ const struct proto_desc *nproto;
+
+ switch (pctx->family) {
+ case NFPROTO_IPV4:
+ case NFPROTO_IPV6:
+ if (stmt->nat.family == NFPROTO_UNSPEC)
+ stmt->nat.family = pctx->family;
+ return 0;
+ case NFPROTO_INET:
+ if (!stmt->nat.addr) {
+ stmt->nat.family = NFPROTO_INET;
+ return 0;
+ }
+ if (stmt->nat.family != NFPROTO_UNSPEC)
+ return 0;
+
+ nproto = pctx->protocol[PROTO_BASE_NETWORK_HDR].desc;
+
+ if (nproto == &proto_ip)
+ stmt->nat.family = NFPROTO_IPV4;
+ else if (nproto == &proto_ip6)
+ stmt->nat.family = NFPROTO_IPV6;
+
+ return 0;
+ default:
+ return stmt_error(ctx, stmt,
+ "NAT is only supported for IPv4/IPv6");
+ }
+}
+
+static const struct datatype *get_addr_dtype(uint8_t family)
+{
+ switch (family) {
+ case NFPROTO_IPV4:
+ return &ipaddr_type;
+ case NFPROTO_IPV6:
+ return &ip6addr_type;
+ }
+
+ return &invalid_type;
+}
+
+static int evaluate_addr(struct eval_ctx *ctx, struct stmt *stmt,
+ struct expr **expr)
+{
+ struct proto_ctx *pctx = eval_proto_ctx(ctx);
+ const struct datatype *dtype;
+
+ dtype = get_addr_dtype(pctx->family);
+
+ return stmt_evaluate_arg(ctx, stmt, dtype, dtype->size,
+ BYTEORDER_BIG_ENDIAN,
+ expr);
+}
+
+static bool nat_evaluate_addr_has_th_expr(const struct expr *map)
+{
+ const struct expr *i, *concat;
+
+ if (!map || map->etype != EXPR_MAP)
+ return false;
+
+ concat = map->map;
+ if (concat ->etype != EXPR_CONCAT)
+ return false;
+
+ list_for_each_entry(i, &concat->expressions, list) {
+ enum proto_bases base;
+
+ if (i->etype == EXPR_PAYLOAD &&
+ i->payload.base == PROTO_BASE_TRANSPORT_HDR &&
+ i->payload.desc != &proto_th)
+ return true;
+
+ if ((i->flags & EXPR_F_PROTOCOL) == 0)
+ continue;
+
+ switch (i->etype) {
+ case EXPR_META:
+ base = i->meta.base;
+ break;
+ case EXPR_PAYLOAD:
+ base = i->payload.base;
+ break;
+ default:
+ return false;
+ }
+
+ if (base == PROTO_BASE_NETWORK_HDR)
+ return true;
+ }
+
+ return false;
+}
+
+static int nat_evaluate_transport(struct eval_ctx *ctx, struct stmt *stmt,
+ struct expr **expr)
+{
+ struct proto_ctx *pctx = eval_proto_ctx(ctx);
+ int err;
+
+ err = stmt_evaluate_arg(ctx, stmt,
+ &inet_service_type, 2 * BITS_PER_BYTE,
+ BYTEORDER_BIG_ENDIAN, expr);
+ if (err < 0)
+ return err;
+
+ if (pctx->protocol[PROTO_BASE_TRANSPORT_HDR].desc == NULL &&
+ !nat_evaluate_addr_has_th_expr(stmt->nat.addr))
+ return stmt_binary_error(ctx, *expr, stmt,
+ "transport protocol mapping is only "
+ "valid after transport protocol match");
+
+ return 0;
+}
+
+static const char *stmt_name(const struct stmt *stmt)
+{
+ switch (stmt->ops->type) {
+ case STMT_NAT:
+ switch (stmt->nat.type) {
+ case NFT_NAT_SNAT:
+ return "snat";
+ case NFT_NAT_DNAT:
+ return "dnat";
+ case NFT_NAT_REDIR:
+ return "redirect";
+ case NFT_NAT_MASQ:
+ return "masquerade";
+ }
+ break;
+ default:
+ break;
+ }
+
+ return stmt->ops->name;
+}
+
+static int stmt_evaluate_l3proto(struct eval_ctx *ctx,
+ struct stmt *stmt, uint8_t family)
+{
+ struct proto_ctx *pctx = eval_proto_ctx(ctx);
+ const struct proto_desc *nproto;
+
+ nproto = pctx->protocol[PROTO_BASE_NETWORK_HDR].desc;
+
+ if ((nproto == &proto_ip && family != NFPROTO_IPV4) ||
+ (nproto == &proto_ip6 && family != NFPROTO_IPV6))
+ return stmt_binary_error(ctx, stmt,
+ &pctx->protocol[PROTO_BASE_NETWORK_HDR],
+ "conflicting protocols specified: %s vs. %s. You must specify ip or ip6 family in %s statement",
+ pctx->protocol[PROTO_BASE_NETWORK_HDR].desc->name,
+ family2str(family),
+ stmt->ops->name);
+ return 0;
+}
+
+static void expr_family_infer(struct proto_ctx *pctx, const struct expr *expr,
+ uint8_t *family)
+{
+ struct expr *i;
+
+ if (expr->etype == EXPR_MAP) {
+ switch (expr->map->etype) {
+ case EXPR_CONCAT:
+ list_for_each_entry(i, &expr->map->expressions, list) {
+ if (i->etype == EXPR_PAYLOAD) {
+ if (i->payload.desc == &proto_ip)
+ *family = NFPROTO_IPV4;
+ else if (i->payload.desc == &proto_ip6)
+ *family = NFPROTO_IPV6;
+ }
+ }
+ break;
+ case EXPR_PAYLOAD:
+ if (expr->map->payload.desc == &proto_ip)
+ *family = NFPROTO_IPV4;
+ else if (expr->map->payload.desc == &proto_ip6)
+ *family = NFPROTO_IPV6;
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+static int stmt_evaluate_addr(struct eval_ctx *ctx, struct stmt *stmt,
+ uint8_t *family, struct expr **addr)
+{
+ struct proto_ctx *pctx = eval_proto_ctx(ctx);
+ const struct datatype *dtype;
+ int err;
+
+ if (pctx->family == NFPROTO_INET) {
+ if (*family == NFPROTO_INET ||
+ *family == NFPROTO_UNSPEC)
+ expr_family_infer(pctx, *addr, family);
+
+ dtype = get_addr_dtype(*family);
+ if (dtype->size == 0) {
+ return stmt_error(ctx, stmt,
+ "specify `%s ip' or '%s ip6' in %s table to disambiguate",
+ stmt_name(stmt), stmt_name(stmt), family2str(pctx->family));
+ }
+
+ err = stmt_evaluate_arg(ctx, stmt, dtype, dtype->size,
+ BYTEORDER_BIG_ENDIAN, addr);
+ } else {
+ err = evaluate_addr(ctx, stmt, addr);
+ }
+
+ return err;
+}
+
+static int stmt_evaluate_nat_map(struct eval_ctx *ctx, struct stmt *stmt)
+{
+ struct proto_ctx *pctx = eval_proto_ctx(ctx);
+ struct expr *one, *two, *data, *tmp;
+ const struct datatype *dtype = NULL;
+ const struct datatype *dtype2;
+ int addr_type;
+ int err;
+
+ if (stmt->nat.family == NFPROTO_INET)
+ expr_family_infer(pctx, stmt->nat.addr, &stmt->nat.family);
+
+ switch (stmt->nat.family) {
+ case NFPROTO_IPV4:
+ addr_type = TYPE_IPADDR;
+ break;
+ case NFPROTO_IPV6:
+ addr_type = TYPE_IP6ADDR;
+ break;
+ default:
+ return stmt_error(ctx, stmt,
+ "specify `%s ip' or '%s ip6' in %s table to disambiguate",
+ stmt_name(stmt), stmt_name(stmt), family2str(pctx->family));
+ }
+ dtype = concat_type_alloc((addr_type << TYPE_BITS) | TYPE_INET_SERVICE);
+
+ expr_set_context(&ctx->ectx, dtype, dtype->size);
+ if (expr_evaluate(ctx, &stmt->nat.addr)) {
+ err = -1;
+ goto out;
+ }
+
+ if (pctx->protocol[PROTO_BASE_TRANSPORT_HDR].desc == NULL &&
+ !nat_evaluate_addr_has_th_expr(stmt->nat.addr)) {
+ err = stmt_binary_error(ctx, stmt->nat.addr, stmt,
+ "transport protocol mapping is only "
+ "valid after transport protocol match");
+ goto out;
+ }
+
+ if (stmt->nat.addr->etype != EXPR_MAP) {
+ err = 0;
+ goto out;
+ }
+
+ data = stmt->nat.addr->mappings->set->data;
+ if (data->flags & EXPR_F_INTERVAL)
+ stmt->nat.type_flags |= STMT_NAT_F_INTERVAL;
+
+ datatype_set(data, dtype);
+
+ if (expr_ops(data)->type != EXPR_CONCAT) {
+ err = __stmt_evaluate_arg(ctx, stmt, dtype, dtype->size,
+ BYTEORDER_BIG_ENDIAN,
+ &stmt->nat.addr);
+ goto out;
+ }
+
+ one = list_first_entry(&data->expressions, struct expr, list);
+ two = list_entry(one->list.next, struct expr, list);
+
+ if (one == two || !list_is_last(&two->list, &data->expressions)) {
+ err = __stmt_evaluate_arg(ctx, stmt, dtype, dtype->size,
+ BYTEORDER_BIG_ENDIAN,
+ &stmt->nat.addr);
+ goto out;
+ }
+
+ dtype2 = get_addr_dtype(stmt->nat.family);
+ tmp = one;
+ err = __stmt_evaluate_arg(ctx, stmt, dtype2, dtype2->size,
+ BYTEORDER_BIG_ENDIAN,
+ &tmp);
+ if (err < 0)
+ goto out;
+ if (tmp != one)
+ BUG("Internal error: Unexpected alteration of l3 expression");
+
+ tmp = two;
+ err = nat_evaluate_transport(ctx, stmt, &tmp);
+ if (err < 0)
+ goto out;
+ if (tmp != two)
+ BUG("Internal error: Unexpected alteration of l4 expression");
+
+out:
+ datatype_free(dtype);
+ return err;
+}
+
+static bool nat_concat_map(struct eval_ctx *ctx, struct stmt *stmt)
+{
+ struct expr *i;
+
+ if (stmt->nat.addr->etype != EXPR_MAP)
+ return false;
+
+ switch (stmt->nat.addr->mappings->etype) {
+ case EXPR_SET:
+ list_for_each_entry(i, &stmt->nat.addr->mappings->expressions, list) {
+ if (i->etype == EXPR_MAPPING &&
+ i->right->etype == EXPR_CONCAT) {
+ stmt->nat.type_flags |= STMT_NAT_F_CONCAT;
+ return true;
+ }
+ }
+ break;
+ case EXPR_SYMBOL:
+ /* expr_evaluate_map() see EXPR_SET_REF after this evaluation. */
+ if (expr_evaluate(ctx, &stmt->nat.addr->mappings))
+ return false;
+
+ if (stmt->nat.addr->mappings->set->data->etype == EXPR_CONCAT ||
+ stmt->nat.addr->mappings->set->data->dtype->subtypes) {
+ stmt->nat.type_flags |= STMT_NAT_F_CONCAT;
+ return true;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return false;
+}
+
+static int stmt_evaluate_nat(struct eval_ctx *ctx, struct stmt *stmt)
+{
+ int err;
+
+ err = nat_evaluate_family(ctx, stmt);
+ if (err < 0)
+ return err;
+
+ if (stmt->nat.addr != NULL) {
+ err = stmt_evaluate_l3proto(ctx, stmt, stmt->nat.family);
+ if (err < 0)
+ return err;
+
+ if (nat_concat_map(ctx, stmt) ||
+ stmt->nat.type_flags & STMT_NAT_F_CONCAT) {
+
+ err = stmt_evaluate_nat_map(ctx, stmt);
+ if (err < 0)
+ return err;
+
+ stmt->flags |= STMT_F_TERMINAL;
+ return 0;
+ }
+
+ err = stmt_evaluate_addr(ctx, stmt, &stmt->nat.family,
+ &stmt->nat.addr);
+ if (err < 0)
+ return err;
+ }
+
+ if (stmt->nat.proto != NULL) {
+ err = nat_evaluate_transport(ctx, stmt, &stmt->nat.proto);
+ if (err < 0)
+ return err;
+
+ stmt->nat.flags |= NF_NAT_RANGE_PROTO_SPECIFIED;
+ }
+
+ stmt->flags |= STMT_F_TERMINAL;
+ return 0;
+}
+
+static int stmt_evaluate_tproxy(struct eval_ctx *ctx, struct stmt *stmt)
+{
+ struct proto_ctx *pctx = eval_proto_ctx(ctx);
+ int err;
+
+ switch (pctx->family) {
+ case NFPROTO_IPV4:
+ case NFPROTO_IPV6: /* fallthrough */
+ if (stmt->tproxy.family == NFPROTO_UNSPEC)
+ stmt->tproxy.family = pctx->family;
+ break;
+ case NFPROTO_INET:
+ break;
+ default:
+ return stmt_error(ctx, stmt,
+ "tproxy is only supported for IPv4/IPv6/INET");
+ }
+
+ if (pctx->protocol[PROTO_BASE_TRANSPORT_HDR].desc == NULL)
+ return stmt_error(ctx, stmt, "Transparent proxy support requires"
+ " transport protocol match");
+
+ if (!stmt->tproxy.addr && !stmt->tproxy.port)
+ return stmt_error(ctx, stmt, "Either address or port must be specified!");
+
+ err = stmt_evaluate_l3proto(ctx, stmt, stmt->tproxy.family);
+ if (err < 0)
+ return err;
+
+ if (stmt->tproxy.addr != NULL) {
+ if (stmt->tproxy.addr->etype == EXPR_RANGE)
+ return stmt_error(ctx, stmt, "Address ranges are not supported for tproxy.");
+
+ err = stmt_evaluate_addr(ctx, stmt, &stmt->tproxy.family,
+ &stmt->tproxy.addr);
+
+ if (err < 0)
+ return err;
+ }
+
+ if (stmt->tproxy.port != NULL) {
+ if (stmt->tproxy.port->etype == EXPR_RANGE)
+ return stmt_error(ctx, stmt, "Port ranges are not supported for tproxy.");
+ err = nat_evaluate_transport(ctx, stmt, &stmt->tproxy.port);
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+static int stmt_evaluate_synproxy(struct eval_ctx *ctx, struct stmt *stmt)
+{
+ if (stmt->synproxy.flags != 0 &&
+ !(stmt->synproxy.flags & (NF_SYNPROXY_OPT_MSS |
+ NF_SYNPROXY_OPT_WSCALE |
+ NF_SYNPROXY_OPT_TIMESTAMP |
+ NF_SYNPROXY_OPT_SACK_PERM)))
+ return stmt_error(ctx, stmt, "This flags are not supported for SYNPROXY");
+
+ return 0;
+}
+
+static int rule_evaluate(struct eval_ctx *ctx, struct rule *rule,
+ enum cmd_ops op);
+
+static int stmt_evaluate_chain(struct eval_ctx *ctx, struct stmt *stmt)
+{
+ struct chain *chain = stmt->chain.chain;
+ struct cmd *cmd;
+
+ chain->flags |= CHAIN_F_BINDING;
+
+ if (ctx->table != NULL) {
+ list_add_tail(&chain->list, &ctx->table->chains);
+ } else {
+ struct rule *rule, *next;
+ struct handle h;
+
+ memset(&h, 0, sizeof(h));
+ handle_merge(&h, &chain->handle);
+ h.family = ctx->rule->handle.family;
+ xfree(h.table.name);
+ h.table.name = xstrdup(ctx->rule->handle.table.name);
+ h.chain.location = stmt->location;
+ h.chain_id = chain->handle.chain_id;
+
+ cmd = cmd_alloc(CMD_ADD, CMD_OBJ_CHAIN, &h, &stmt->location,
+ chain);
+ cmd->location = stmt->location;
+ list_add_tail(&cmd->list, &ctx->cmd->list);
+ h.chain_id = chain->handle.chain_id;
+
+ list_for_each_entry_safe(rule, next, &chain->rules, list) {
+ struct eval_ctx rule_ctx = {
+ .nft = ctx->nft,
+ .msgs = ctx->msgs,
+ .cmd = ctx->cmd,
+ };
+ struct handle h2 = {};
+
+ handle_merge(&rule->handle, &ctx->rule->handle);
+ xfree(rule->handle.table.name);
+ rule->handle.table.name = xstrdup(ctx->rule->handle.table.name);
+ xfree(rule->handle.chain.name);
+ rule->handle.chain.name = NULL;
+ rule->handle.chain_id = chain->handle.chain_id;
+ if (rule_evaluate(&rule_ctx, rule, CMD_INVALID) < 0)
+ return -1;
+
+ handle_merge(&h2, &rule->handle);
+ cmd = cmd_alloc(CMD_ADD, CMD_OBJ_RULE, &h2,
+ &rule->location, rule);
+ list_add_tail(&cmd->list, &ctx->cmd->list);
+ list_del(&rule->list);
+ }
+ }
+
+ return 0;
+}
+
+static int stmt_evaluate_optstrip(struct eval_ctx *ctx, struct stmt *stmt)
+{
+ return expr_evaluate(ctx, &stmt->optstrip.expr);
+}
+
+static int stmt_evaluate_dup(struct eval_ctx *ctx, struct stmt *stmt)
+{
+ struct proto_ctx *pctx = eval_proto_ctx(ctx);
+ int err;
+
+ switch (pctx->family) {
+ case NFPROTO_IPV4:
+ case NFPROTO_IPV6:
+ if (stmt->dup.to == NULL)
+ return stmt_error(ctx, stmt,
+ "missing destination address");
+ err = evaluate_addr(ctx, stmt, &stmt->dup.to);
+ if (err < 0)
+ return err;
+
+ if (stmt->dup.dev != NULL) {
+ err = stmt_evaluate_arg(ctx, stmt, &ifindex_type,
+ sizeof(uint32_t) * BITS_PER_BYTE,
+ BYTEORDER_HOST_ENDIAN,
+ &stmt->dup.dev);
+ if (err < 0)
+ return err;
+ }
+ break;
+ case NFPROTO_NETDEV:
+ if (stmt->dup.to == NULL)
+ return stmt_error(ctx, stmt,
+ "missing destination interface");
+ if (stmt->dup.dev != NULL)
+ return stmt_error(ctx, stmt, "cannot specify device");
+
+ err = stmt_evaluate_arg(ctx, stmt, &ifindex_type,
+ sizeof(uint32_t) * BITS_PER_BYTE,
+ BYTEORDER_HOST_ENDIAN, &stmt->dup.to);
+ if (err < 0)
+ return err;
+ break;
+ default:
+ return stmt_error(ctx, stmt, "unsupported family");
+ }
+ return 0;
+}
+
+static int stmt_evaluate_fwd(struct eval_ctx *ctx, struct stmt *stmt)
+{
+ struct proto_ctx *pctx = eval_proto_ctx(ctx);
+ const struct datatype *dtype;
+ int err, len;
+
+ switch (pctx->family) {
+ case NFPROTO_NETDEV:
+ if (stmt->fwd.dev == NULL)
+ return stmt_error(ctx, stmt,
+ "missing destination interface");
+
+ err = stmt_evaluate_arg(ctx, stmt, &ifindex_type,
+ sizeof(uint32_t) * BITS_PER_BYTE,
+ BYTEORDER_HOST_ENDIAN, &stmt->fwd.dev);
+ if (err < 0)
+ return err;
+
+ if (stmt->fwd.addr != NULL) {
+ switch (stmt->fwd.family) {
+ case NFPROTO_IPV4:
+ dtype = &ipaddr_type;
+ len = 4 * BITS_PER_BYTE;
+ break;
+ case NFPROTO_IPV6:
+ dtype = &ip6addr_type;
+ len = 16 * BITS_PER_BYTE;
+ break;
+ default:
+ return stmt_error(ctx, stmt, "missing family");
+ }
+ err = stmt_evaluate_arg(ctx, stmt, dtype, len,
+ BYTEORDER_BIG_ENDIAN,
+ &stmt->fwd.addr);
+ if (err < 0)
+ return err;
+ }
+ break;
+ default:
+ return stmt_error(ctx, stmt, "unsupported family");
+ }
+ stmt->flags |= STMT_F_TERMINAL;
+ return 0;
+}
+
+static int stmt_evaluate_queue(struct eval_ctx *ctx, struct stmt *stmt)
+{
+ if (stmt->queue.queue != NULL) {
+ if (stmt_evaluate_arg(ctx, stmt, &integer_type, 16,
+ BYTEORDER_HOST_ENDIAN,
+ &stmt->queue.queue) < 0)
+ return -1;
+
+ if ((stmt->queue.flags & NFT_QUEUE_FLAG_CPU_FANOUT) &&
+ stmt->queue.queue->etype != EXPR_RANGE)
+ return expr_error(ctx->msgs, stmt->queue.queue,
+ "fanout requires a range to be "
+ "specified");
+
+ if (ctx->ectx.maxval > USHRT_MAX)
+ return expr_error(ctx->msgs, stmt->queue.queue,
+ "queue expression max value exceeds %u", USHRT_MAX);
+ }
+ stmt->flags |= STMT_F_TERMINAL;
+ return 0;
+}
+
+static int stmt_evaluate_log_prefix(struct eval_ctx *ctx, struct stmt *stmt)
+{
+ char tmp[NF_LOG_PREFIXLEN] = {};
+ char prefix[NF_LOG_PREFIXLEN];
+ size_t len = sizeof(prefix);
+ size_t offset = 0;
+ struct expr *expr;
+
+ if (stmt->log.prefix->etype != EXPR_LIST) {
+ if (stmt->log.prefix &&
+ div_round_up(stmt->log.prefix->len, BITS_PER_BYTE) >= NF_LOG_PREFIXLEN)
+ return expr_error(ctx->msgs, stmt->log.prefix, "log prefix is too long");
+
+ return 0;
+ }
+
+ prefix[0] = '\0';
+
+ list_for_each_entry(expr, &stmt->log.prefix->expressions, list) {
+ int ret;
+
+ switch (expr->etype) {
+ case EXPR_VALUE:
+ expr_to_string(expr, tmp);
+ ret = snprintf(prefix + offset, len, "%s", tmp);
+ break;
+ case EXPR_VARIABLE:
+ ret = snprintf(prefix + offset, len, "%s",
+ expr->sym->expr->identifier);
+ break;
+ default:
+ BUG("unknown expression type %s\n", expr_name(expr));
+ break;
+ }
+ SNPRINTF_BUFFER_SIZE(ret, &len, &offset);
+ }
+
+ if (len == 0)
+ return stmt_error(ctx, stmt, "log prefix is too long");
+
+ expr = constant_expr_alloc(&stmt->log.prefix->location, &string_type,
+ BYTEORDER_HOST_ENDIAN,
+ strlen(prefix) * BITS_PER_BYTE, prefix);
+ expr_free(stmt->log.prefix);
+ stmt->log.prefix = expr;
+
+ return 0;
+}
+
+static int stmt_evaluate_log(struct eval_ctx *ctx, struct stmt *stmt)
+{
+ int ret = 0;
+
+ if (stmt->log.flags & (STMT_LOG_GROUP | STMT_LOG_SNAPLEN |
+ STMT_LOG_QTHRESHOLD)) {
+ if (stmt->log.flags & STMT_LOG_LEVEL)
+ return stmt_error(ctx, stmt,
+ "level and group are mutually exclusive");
+ if (stmt->log.logflags)
+ return stmt_error(ctx, stmt,
+ "flags and group are mutually exclusive");
+ }
+ if (stmt->log.level == NFT_LOGLEVEL_AUDIT &&
+ (stmt->log.flags & ~STMT_LOG_LEVEL || stmt->log.logflags))
+ return stmt_error(ctx, stmt,
+ "log level audit doesn't support any further options");
+
+ if (stmt->log.prefix)
+ ret = stmt_evaluate_log_prefix(ctx, stmt);
+
+ return ret;
+}
+
+static int stmt_evaluate_set(struct eval_ctx *ctx, struct stmt *stmt)
+{
+ struct set *this_set;
+ struct stmt *this;
+
+ expr_set_context(&ctx->ectx, NULL, 0);
+ if (expr_evaluate(ctx, &stmt->set.set) < 0)
+ return -1;
+ if (stmt->set.set->etype != EXPR_SET_REF)
+ return expr_error(ctx->msgs, stmt->set.set,
+ "Expression does not refer to a set");
+
+ if (stmt_evaluate_key(ctx, stmt,
+ stmt->set.set->set->key->dtype,
+ stmt->set.set->set->key->len,
+ stmt->set.set->set->key->byteorder,
+ &stmt->set.key->key) < 0)
+ return -1;
+ if (expr_is_constant(stmt->set.key))
+ return expr_error(ctx->msgs, stmt->set.key,
+ "Key expression can not be constant");
+ if (stmt->set.key->comment != NULL)
+ return expr_error(ctx->msgs, stmt->set.key,
+ "Key expression comments are not supported");
+ list_for_each_entry(this, &stmt->set.stmt_list, list) {
+ if (stmt_evaluate(ctx, this) < 0)
+ return -1;
+ if (!(this->flags & STMT_F_STATEFUL))
+ return stmt_error(ctx, this,
+ "statement must be stateful");
+ }
+
+ this_set = stmt->set.set->set;
+
+ /* Make sure EVAL flag is set on set definition so that kernel
+ * picks a set that allows updates from the packet path.
+ *
+ * Alternatively we could error out in case 'flags dynamic' was
+ * not given, but we can repair this here.
+ */
+ this_set->flags |= NFT_SET_EVAL;
+ return 0;
+}
+
+static int stmt_evaluate_map(struct eval_ctx *ctx, struct stmt *stmt)
+{
+ struct stmt *this;
+
+ expr_set_context(&ctx->ectx, NULL, 0);
+ if (expr_evaluate(ctx, &stmt->map.set) < 0)
+ return -1;
+ if (stmt->map.set->etype != EXPR_SET_REF)
+ return expr_error(ctx->msgs, stmt->map.set,
+ "Expression does not refer to a set");
+
+ if (stmt_evaluate_key(ctx, stmt,
+ stmt->map.set->set->key->dtype,
+ stmt->map.set->set->key->len,
+ stmt->map.set->set->key->byteorder,
+ &stmt->map.key->key) < 0)
+ return -1;
+ if (expr_is_constant(stmt->map.key))
+ return expr_error(ctx->msgs, stmt->map.key,
+ "Key expression can not be constant");
+ if (stmt->map.key->comment != NULL)
+ return expr_error(ctx->msgs, stmt->map.key,
+ "Key expression comments are not supported");
+
+ if (stmt_evaluate_arg(ctx, stmt,
+ stmt->map.set->set->data->dtype,
+ stmt->map.set->set->data->len,
+ stmt->map.set->set->data->byteorder,
+ &stmt->map.data->key) < 0)
+ return -1;
+ if (expr_is_constant(stmt->map.data))
+ return expr_error(ctx->msgs, stmt->map.data,
+ "Data expression can not be constant");
+ if (stmt->map.data->comment != NULL)
+ return expr_error(ctx->msgs, stmt->map.data,
+ "Data expression comments are not supported");
+ if (stmt->map.data->timeout > 0)
+ return expr_error(ctx->msgs, stmt->map.data,
+ "Data expression timeouts are not supported");
+
+ list_for_each_entry(this, &stmt->map.stmt_list, list) {
+ if (stmt_evaluate(ctx, this) < 0)
+ return -1;
+ if (!(this->flags & STMT_F_STATEFUL))
+ return stmt_error(ctx, this,
+ "statement must be stateful");
+ }
+
+ return 0;
+}
+
+static int stmt_evaluate_objref_map(struct eval_ctx *ctx, struct stmt *stmt)
+{
+ struct expr *map = stmt->objref.expr;
+ struct expr *mappings;
+ struct expr *key;
+
+ expr_set_context(&ctx->ectx, NULL, 0);
+ if (expr_evaluate(ctx, &map->map) < 0)
+ return -1;
+ if (expr_is_constant(map->map))
+ return expr_error(ctx->msgs, map->map,
+ "Map expression can not be constant");
+
+ mappings = map->mappings;
+ mappings->set_flags |= NFT_SET_OBJECT;
+
+ switch (map->mappings->etype) {
+ case EXPR_SET:
+ key = constant_expr_alloc(&stmt->location,
+ ctx->ectx.dtype,
+ ctx->ectx.byteorder,
+ ctx->ectx.len, NULL);
+
+ mappings = implicit_set_declaration(ctx, "__objmap%d",
+ key, NULL, mappings);
+ mappings->set->objtype = stmt->objref.type;
+
+ map->mappings = mappings;
+
+ ctx->set = mappings->set;
+ if (expr_evaluate(ctx, &map->mappings->set->init) < 0)
+ return -1;
+
+ if (set_is_interval(map->mappings->set->init->set_flags) &&
+ !(map->mappings->set->init->set_flags & NFT_SET_CONCAT) &&
+ interval_set_eval(ctx, ctx->set, map->mappings->set->init) < 0)
+ return -1;
+
+ ctx->set = NULL;
+
+ map_set_concat_info(map);
+ /* fall through */
+ case EXPR_SYMBOL:
+ if (expr_evaluate(ctx, &map->mappings) < 0)
+ return -1;
+ if (map->mappings->etype != EXPR_SET_REF)
+ return expr_error(ctx->msgs, map->mappings,
+ "Expression is not a map");
+ if (!set_is_objmap(map->mappings->set->flags))
+ return expr_error(ctx->msgs, map->mappings,
+ "Expression is not a map with objects");
+ break;
+ default:
+ BUG("invalid mapping expression %s\n",
+ expr_name(map->mappings));
+ }
+
+ if (!datatype_compatible(map->mappings->set->key->dtype, map->map->dtype))
+ return expr_binary_error(ctx->msgs, map->mappings, map->map,
+ "datatype mismatch, map expects %s, "
+ "mapping expression has type %s",
+ map->mappings->set->key->dtype->desc,
+ map->map->dtype->desc);
+
+ datatype_set(map, map->mappings->set->data->dtype);
+ map->flags |= EXPR_F_CONSTANT;
+
+ /* Data for range lookups needs to be in big endian order */
+ if (map->mappings->set->flags & NFT_SET_INTERVAL &&
+ byteorder_conversion(ctx, &map->map, BYTEORDER_BIG_ENDIAN) < 0)
+ return -1;
+
+ return 0;
+}
+
+static int stmt_evaluate_objref(struct eval_ctx *ctx, struct stmt *stmt)
+{
+ /* We need specific map evaluation for stateful objects. */
+ if (stmt->objref.expr->etype == EXPR_MAP)
+ return stmt_evaluate_objref_map(ctx, stmt);
+
+ if (stmt_evaluate_arg(ctx, stmt,
+ &string_type, NFT_OBJ_MAXNAMELEN * BITS_PER_BYTE,
+ BYTEORDER_HOST_ENDIAN, &stmt->objref.expr) < 0)
+ return -1;
+
+ if (!expr_is_constant(stmt->objref.expr))
+ return expr_error(ctx->msgs, stmt->objref.expr,
+ "Counter expression must be constant");
+
+ return 0;
+}
+
+int stmt_evaluate(struct eval_ctx *ctx, struct stmt *stmt)
+{
+ if (ctx->nft->debug_mask & NFT_DEBUG_EVALUATION) {
+ struct error_record *erec;
+ erec = erec_create(EREC_INFORMATIONAL, &stmt->location,
+ "Evaluate %s", stmt->ops->name);
+ erec_print(&ctx->nft->output, erec, ctx->nft->debug_mask);
+ stmt_print(stmt, &ctx->nft->output);
+ nft_print(&ctx->nft->output, "\n\n");
+ erec_destroy(erec);
+ }
+
+ switch (stmt->ops->type) {
+ case STMT_CONNLIMIT:
+ case STMT_COUNTER:
+ case STMT_LAST:
+ case STMT_LIMIT:
+ case STMT_QUOTA:
+ case STMT_NOTRACK:
+ case STMT_FLOW_OFFLOAD:
+ return 0;
+ case STMT_EXPRESSION:
+ return stmt_evaluate_expr(ctx, stmt);
+ case STMT_VERDICT:
+ return stmt_evaluate_verdict(ctx, stmt);
+ case STMT_PAYLOAD:
+ return stmt_evaluate_payload(ctx, stmt);
+ case STMT_EXTHDR:
+ return stmt_evaluate_exthdr(ctx, stmt);
+ case STMT_METER:
+ return stmt_evaluate_meter(ctx, stmt);
+ case STMT_META:
+ return stmt_evaluate_meta(ctx, stmt);
+ case STMT_CT:
+ return stmt_evaluate_ct(ctx, stmt);
+ case STMT_LOG:
+ return stmt_evaluate_log(ctx, stmt);
+ case STMT_REJECT:
+ return stmt_evaluate_reject(ctx, stmt);
+ case STMT_NAT:
+ return stmt_evaluate_nat(ctx, stmt);
+ case STMT_TPROXY:
+ return stmt_evaluate_tproxy(ctx, stmt);
+ case STMT_QUEUE:
+ return stmt_evaluate_queue(ctx, stmt);
+ case STMT_DUP:
+ return stmt_evaluate_dup(ctx, stmt);
+ case STMT_FWD:
+ return stmt_evaluate_fwd(ctx, stmt);
+ case STMT_SET:
+ return stmt_evaluate_set(ctx, stmt);
+ case STMT_OBJREF:
+ return stmt_evaluate_objref(ctx, stmt);
+ case STMT_MAP:
+ return stmt_evaluate_map(ctx, stmt);
+ case STMT_SYNPROXY:
+ return stmt_evaluate_synproxy(ctx, stmt);
+ case STMT_CHAIN:
+ return stmt_evaluate_chain(ctx, stmt);
+ case STMT_OPTSTRIP:
+ return stmt_evaluate_optstrip(ctx, stmt);
+ default:
+ BUG("unknown statement type %s\n", stmt->ops->name);
+ }
+}
+
+static int setelem_evaluate(struct eval_ctx *ctx, struct cmd *cmd)
+{
+ struct table *table;
+ struct set *set;
+
+ table = table_cache_find(&ctx->nft->cache.table_cache,
+ ctx->cmd->handle.table.name,
+ ctx->cmd->handle.family);
+ if (table == NULL)
+ return table_not_found(ctx);
+
+ set = set_cache_find(table, ctx->cmd->handle.set.name);
+ if (set == NULL)
+ return set_not_found(ctx, &ctx->cmd->handle.set.location,
+ ctx->cmd->handle.set.name);
+
+ if (set->key == NULL)
+ return -1;
+
+ set->existing_set = set;
+ ctx->set = set;
+ expr_set_context(&ctx->ectx, set->key->dtype, set->key->len);
+ if (expr_evaluate(ctx, &cmd->expr) < 0)
+ return -1;
+
+ cmd->elem.set = set_get(set);
+ if (set_is_interval(ctx->set->flags)) {
+ if (!(set->flags & NFT_SET_CONCAT) &&
+ interval_set_eval(ctx, ctx->set, cmd->expr) < 0)
+ return -1;
+
+ assert(cmd->expr->etype == EXPR_SET);
+ cmd->expr->set_flags |= NFT_SET_INTERVAL;
+ }
+
+ ctx->set = NULL;
+
+ return 0;
+}
+
+static int set_key_data_error(struct eval_ctx *ctx, const struct set *set,
+ const struct datatype *dtype,
+ const char *name)
+{
+ const char *hint = "";
+
+ if (dtype->size == 0)
+ hint = ". Try \"typeof expression\" instead of \"type datatype\".";
+
+ return set_error(ctx, set, "unqualified type %s "
+ "specified in %s definition%s",
+ dtype->name, name, hint);
+}
+
+static int set_expr_evaluate_concat(struct eval_ctx *ctx, struct expr **expr)
+{
+ unsigned int flags = EXPR_F_CONSTANT | EXPR_F_SINGLETON;
+ uint32_t ntype = 0, size = 0;
+ struct expr *i, *next;
+
+ list_for_each_entry_safe(i, next, &(*expr)->expressions, list) {
+ unsigned dsize_bytes;
+
+ if (i->etype == EXPR_CT &&
+ (i->ct.key == NFT_CT_SRC ||
+ i->ct.key == NFT_CT_DST))
+ return expr_error(ctx->msgs, i,
+ "specify either ip or ip6 for address matching");
+
+ if (i->etype == EXPR_PAYLOAD &&
+ i->dtype->type == TYPE_INTEGER) {
+ struct datatype *dtype;
+
+ dtype = datatype_clone(i->dtype);
+ dtype->size = i->len;
+ dtype->byteorder = i->byteorder;
+ __datatype_set(i, dtype);
+ }
+
+ if (i->dtype->size == 0 && i->len == 0)
+ return expr_binary_error(ctx->msgs, i, *expr,
+ "can not use variable sized "
+ "data types (%s) in concat "
+ "expressions",
+ i->dtype->name);
+
+ if (i->dtype->size)
+ assert(i->len == i->dtype->size);
+
+ flags &= i->flags;
+
+ ntype = concat_subtype_add(ntype, i->dtype->type);
+
+ dsize_bytes = div_round_up(i->len, BITS_PER_BYTE);
+ (*expr)->field_len[(*expr)->field_count++] = dsize_bytes;
+ size += netlink_padded_len(i->len);
+ }
+
+ (*expr)->flags |= flags;
+ __datatype_set(*expr, concat_type_alloc(ntype));
+ (*expr)->len = size;
+
+ expr_set_context(&ctx->ectx, (*expr)->dtype, (*expr)->len);
+ ctx->ectx.key = *expr;
+
+ return 0;
+}
+
+static int elems_evaluate(struct eval_ctx *ctx, struct set *set)
+{
+ ctx->set = set;
+ if (set->init != NULL) {
+ __expr_set_context(&ctx->ectx, set->key->dtype,
+ set->key->byteorder, set->key->len, 0);
+ if (expr_evaluate(ctx, &set->init) < 0)
+ return -1;
+ if (set->init->etype != EXPR_SET)
+ return expr_error(ctx->msgs, set->init, "Set %s: Unexpected initial type %s, missing { }?",
+ set->handle.set.name, expr_name(set->init));
+ }
+
+ if (set_is_interval(ctx->set->flags) &&
+ !(ctx->set->flags & NFT_SET_CONCAT) &&
+ interval_set_eval(ctx, ctx->set, set->init) < 0)
+ return -1;
+
+ ctx->set = NULL;
+
+ return 0;
+}
+
+static int set_evaluate(struct eval_ctx *ctx, struct set *set)
+{
+ struct set *existing_set = NULL;
+ unsigned int num_stmts = 0;
+ struct table *table;
+ struct stmt *stmt;
+ const char *type;
+
+ if (!set_is_anonymous(set->flags)) {
+ table = table_cache_find(&ctx->nft->cache.table_cache,
+ set->handle.table.name,
+ set->handle.family);
+ if (table == NULL)
+ return table_not_found(ctx);
+
+ existing_set = set_cache_find(table, set->handle.set.name);
+ if (!existing_set)
+ set_cache_add(set_get(set), table);
+
+ if (existing_set && existing_set->flags & NFT_SET_EVAL) {
+ uint32_t existing_flags = existing_set->flags & ~NFT_SET_EVAL;
+ uint32_t new_flags = set->flags & ~NFT_SET_EVAL;
+
+ if (existing_flags == new_flags)
+ set->flags |= NFT_SET_EVAL;
+ }
+ }
+
+ if (!(set->flags & NFT_SET_INTERVAL) && set->automerge)
+ return set_error(ctx, set, "auto-merge only works with interval sets");
+
+ type = set_is_map(set->flags) ? "map" : "set";
+
+ if (set->key == NULL)
+ return set_error(ctx, set, "%s definition does not specify key",
+ type);
+
+ if (set->key->len == 0) {
+ if (set->key->etype == EXPR_CONCAT &&
+ set_expr_evaluate_concat(ctx, &set->key) < 0)
+ return -1;
+
+ if (set->key->len == 0)
+ return set_key_data_error(ctx, set,
+ set->key->dtype, type);
+ }
+
+ if (set->flags & NFT_SET_INTERVAL && set->key->etype == EXPR_CONCAT) {
+ memcpy(&set->desc.field_len, &set->key->field_len,
+ sizeof(set->desc.field_len));
+ set->desc.field_count = set->key->field_count;
+ set->flags |= NFT_SET_CONCAT;
+ }
+
+ if (set_is_datamap(set->flags)) {
+ if (set->data == NULL)
+ return set_error(ctx, set, "map definition does not "
+ "specify mapping data type");
+
+ if (set->data->etype == EXPR_CONCAT &&
+ set_expr_evaluate_concat(ctx, &set->data) < 0)
+ return -1;
+
+ if (set->data->flags & EXPR_F_INTERVAL)
+ set->data->len *= 2;
+
+ if (set->data->len == 0 && set->data->dtype->type != TYPE_VERDICT)
+ return set_key_data_error(ctx, set,
+ set->data->dtype, type);
+ } else if (set_is_objmap(set->flags)) {
+ assert(set->data == NULL);
+ set->data = constant_expr_alloc(&netlink_location, &string_type,
+ BYTEORDER_HOST_ENDIAN,
+ NFT_OBJ_MAXNAMELEN * BITS_PER_BYTE,
+ NULL);
+
+ }
+
+ /* Default timeout value implies timeout support */
+ if (set->timeout)
+ set->flags |= NFT_SET_TIMEOUT;
+
+ list_for_each_entry(stmt, &set->stmt_list, list)
+ num_stmts++;
+
+ if (num_stmts > 1)
+ set->flags |= NFT_SET_EXPR;
+
+ if (set_is_anonymous(set->flags)) {
+ if (set_is_interval(set->init->set_flags) &&
+ !(set->init->set_flags & NFT_SET_CONCAT) &&
+ interval_set_eval(ctx, set, set->init) < 0)
+ return -1;
+
+ return 0;
+ }
+
+ set->existing_set = existing_set;
+
+ return 0;
+}
+
+static bool evaluate_priority(struct eval_ctx *ctx, struct prio_spec *prio,
+ int family, int hook)
+{
+ char prio_str[NFT_NAME_MAXLEN];
+ char prio_fst[NFT_NAME_MAXLEN];
+ struct location loc;
+ int priority;
+ int prio_snd;
+ char op;
+
+ expr_set_context(&ctx->ectx, &priority_type, NFT_NAME_MAXLEN * BITS_PER_BYTE);
+
+ if (expr_evaluate(ctx, &prio->expr) < 0)
+ return false;
+ if (prio->expr->etype != EXPR_VALUE) {
+ expr_error(ctx->msgs, prio->expr, "%s is not a valid "
+ "priority expression", expr_name(prio->expr));
+ return false;
+ }
+ if (prio->expr->dtype->type == TYPE_INTEGER)
+ return true;
+
+ mpz_export_data(prio_str, prio->expr->value, BYTEORDER_HOST_ENDIAN,
+ NFT_NAME_MAXLEN);
+ loc = prio->expr->location;
+
+ if (sscanf(prio_str, "%s %c %d", prio_fst, &op, &prio_snd) < 3) {
+ priority = std_prio_lookup(prio_str, family, hook);
+ if (priority == NF_IP_PRI_LAST)
+ return false;
+ } else {
+ priority = std_prio_lookup(prio_fst, family, hook);
+ if (priority == NF_IP_PRI_LAST)
+ return false;
+ if (op == '+')
+ priority += prio_snd;
+ else if (op == '-')
+ priority -= prio_snd;
+ else
+ return false;
+ }
+ expr_free(prio->expr);
+ prio->expr = constant_expr_alloc(&loc, &integer_type,
+ BYTEORDER_HOST_ENDIAN,
+ sizeof(int) * BITS_PER_BYTE,
+ &priority);
+ return true;
+}
+
+static bool evaluate_expr_variable(struct eval_ctx *ctx, struct expr **exprp)
+{
+ struct expr *expr;
+
+ if (expr_evaluate(ctx, exprp) < 0)
+ return false;
+
+ expr = *exprp;
+ if (expr->etype != EXPR_VALUE &&
+ expr->etype != EXPR_SET) {
+ expr_error(ctx->msgs, expr, "%s is not a valid "
+ "variable expression", expr_name(expr));
+ return false;
+ }
+
+ return true;
+}
+
+static bool evaluate_device_expr(struct eval_ctx *ctx, struct expr **dev_expr)
+{
+ struct expr *expr, *next, *key;
+ LIST_HEAD(tmp);
+
+ if ((*dev_expr)->etype == EXPR_VARIABLE) {
+ expr_set_context(&ctx->ectx, &ifname_type,
+ IFNAMSIZ * BITS_PER_BYTE);
+ if (!evaluate_expr_variable(ctx, dev_expr))
+ return false;
+ }
+
+ if ((*dev_expr)->etype != EXPR_SET &&
+ (*dev_expr)->etype != EXPR_LIST)
+ return true;
+
+ list_for_each_entry_safe(expr, next, &(*dev_expr)->expressions, list) {
+ list_del(&expr->list);
+
+ switch (expr->etype) {
+ case EXPR_VARIABLE:
+ expr_set_context(&ctx->ectx, &ifname_type,
+ IFNAMSIZ * BITS_PER_BYTE);
+ if (!evaluate_expr_variable(ctx, &expr))
+ return false;
+ break;
+ case EXPR_SET_ELEM:
+ key = expr_clone(expr->key);
+ expr_free(expr);
+ expr = key;
+ break;
+ case EXPR_VALUE:
+ break;
+ default:
+ BUG("invalid expression type %s\n", expr_name(expr));
+ break;
+ }
+
+ list_add(&expr->list, &tmp);
+ }
+ list_splice_init(&tmp, &(*dev_expr)->expressions);
+
+ return true;
+}
+
+static uint32_t str2hooknum(uint32_t family, const char *hook);
+
+static int flowtable_evaluate(struct eval_ctx *ctx, struct flowtable *ft)
+{
+ struct table *table;
+
+ table = table_cache_find(&ctx->nft->cache.table_cache,
+ ctx->cmd->handle.table.name,
+ ctx->cmd->handle.family);
+ if (table == NULL)
+ return table_not_found(ctx);
+
+ if (!ft_cache_find(table, ft->handle.flowtable.name)) {
+ if (!ft->hook.name)
+ return chain_error(ctx, ft, "missing hook and priority in flowtable declaration");
+
+ ft_cache_add(flowtable_get(ft), table);
+ }
+
+ if (ft->hook.name) {
+ ft->hook.num = str2hooknum(NFPROTO_NETDEV, ft->hook.name);
+ if (ft->hook.num == NF_INET_NUMHOOKS)
+ return chain_error(ctx, ft, "invalid hook %s",
+ ft->hook.name);
+ if (!evaluate_priority(ctx, &ft->priority, NFPROTO_NETDEV, ft->hook.num))
+ return __stmt_binary_error(ctx, &ft->priority.loc, NULL,
+ "invalid priority expression %s.",
+ expr_name(ft->priority.expr));
+ }
+
+ if (ft->dev_expr && !evaluate_device_expr(ctx, &ft->dev_expr))
+ return -1;
+
+ return 0;
+}
+
+/* make src point at dst, either via handle.position or handle.position_id */
+static void link_rules(struct rule *src, struct rule *dst)
+{
+ static uint32_t ref_id = 0;
+
+ if (dst->handle.handle.id) {
+ /* dst is in kernel, make src reference it by handle */
+ src->handle.position.id = dst->handle.handle.id;
+ src->handle.position.location = src->handle.index.location;
+ return;
+ }
+
+ /* dst is not in kernel, make src reference it by per-transaction ID */
+ if (!dst->handle.rule_id)
+ dst->handle.rule_id = ++ref_id;
+ src->handle.position_id = dst->handle.rule_id;
+}
+
+static int rule_cache_update(struct eval_ctx *ctx, enum cmd_ops op)
+{
+ struct rule *rule = ctx->rule, *ref = NULL;
+ struct table *table;
+ struct chain *chain;
+
+ table = table_cache_find(&ctx->nft->cache.table_cache,
+ rule->handle.table.name,
+ rule->handle.family);
+ if (!table)
+ return table_not_found(ctx);
+
+ chain = chain_cache_find(table, rule->handle.chain.name);
+ if (!chain)
+ return chain_not_found(ctx);
+
+ if (rule->handle.index.id) {
+ ref = rule_lookup_by_index(chain, rule->handle.index.id);
+ if (!ref)
+ return cmd_error(ctx, &rule->handle.index.location,
+ "Could not process rule: %s",
+ strerror(ENOENT));
+
+ link_rules(rule, ref);
+ } else if (rule->handle.handle.id) {
+ ref = rule_lookup(chain, rule->handle.handle.id);
+ if (!ref)
+ return cmd_error(ctx, &rule->handle.handle.location,
+ "Could not process rule: %s",
+ strerror(ENOENT));
+ } else if (rule->handle.position.id) {
+ ref = rule_lookup(chain, rule->handle.position.id);
+ if (!ref)
+ return cmd_error(ctx, &rule->handle.position.location,
+ "Could not process rule: %s",
+ strerror(ENOENT));
+ }
+
+ switch (op) {
+ case CMD_INSERT:
+ rule_get(rule);
+ if (ref)
+ list_add_tail(&rule->list, &ref->list);
+ else
+ list_add(&rule->list, &chain->rules);
+ break;
+ case CMD_ADD:
+ rule_get(rule);
+ if (ref)
+ list_add(&rule->list, &ref->list);
+ else
+ list_add_tail(&rule->list, &chain->rules);
+ break;
+ case CMD_REPLACE:
+ rule_get(rule);
+ list_add(&rule->list, &ref->list);
+ /* fall through */
+ case CMD_DELETE:
+ list_del(&ref->list);
+ rule_free(ref);
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+static int rule_evaluate(struct eval_ctx *ctx, struct rule *rule,
+ enum cmd_ops op)
+{
+ struct stmt *stmt, *tstmt = NULL;
+ struct error_record *erec;
+
+ proto_ctx_init(&ctx->_pctx[0], rule->handle.family, ctx->nft->debug_mask, false);
+ /* use NFPROTO_BRIDGE to set up proto_eth as base protocol. */
+ proto_ctx_init(&ctx->_pctx[1], NFPROTO_BRIDGE, ctx->nft->debug_mask, true);
+ memset(&ctx->ectx, 0, sizeof(ctx->ectx));
+
+ ctx->rule = rule;
+ list_for_each_entry(stmt, &rule->stmts, list) {
+ if (tstmt != NULL)
+ return stmt_binary_error(ctx, stmt, tstmt,
+ "Statement after terminal "
+ "statement has no effect");
+
+ ctx->stmt = stmt;
+ if (stmt_evaluate(ctx, stmt) < 0)
+ return -1;
+ if (stmt->flags & STMT_F_TERMINAL)
+ tstmt = stmt;
+
+ ctx->inner_desc = NULL;
+ }
+
+ erec = rule_postprocess(rule);
+ if (erec != NULL) {
+ erec_queue(erec, ctx->msgs);
+ return -1;
+ }
+
+ if (nft_cache_needs_update(&ctx->nft->cache))
+ return rule_cache_update(ctx, op);
+
+ return 0;
+}
+
+static uint32_t str2hooknum(uint32_t family, const char *hook)
+{
+ if (!hook)
+ return NF_INET_NUMHOOKS;
+
+ switch (family) {
+ case NFPROTO_INET:
+ if (!strcmp(hook, "ingress"))
+ return NF_INET_INGRESS;
+ /* fall through */
+ case NFPROTO_IPV4:
+ case NFPROTO_BRIDGE:
+ case NFPROTO_IPV6:
+ /* These families have overlapping values for each hook */
+ if (!strcmp(hook, "prerouting"))
+ return NF_INET_PRE_ROUTING;
+ else if (!strcmp(hook, "input"))
+ return NF_INET_LOCAL_IN;
+ else if (!strcmp(hook, "forward"))
+ return NF_INET_FORWARD;
+ else if (!strcmp(hook, "postrouting"))
+ return NF_INET_POST_ROUTING;
+ else if (!strcmp(hook, "output"))
+ return NF_INET_LOCAL_OUT;
+ break;
+ case NFPROTO_ARP:
+ if (!strcmp(hook, "input"))
+ return NF_ARP_IN;
+ else if (!strcmp(hook, "forward"))
+ return NF_ARP_FORWARD;
+ else if (!strcmp(hook, "output"))
+ return NF_ARP_OUT;
+ break;
+ case NFPROTO_NETDEV:
+ if (!strcmp(hook, "ingress"))
+ return NF_NETDEV_INGRESS;
+ else if (!strcmp(hook, "egress"))
+ return NF_NETDEV_EGRESS;
+ break;
+ default:
+ break;
+ }
+
+ return NF_INET_NUMHOOKS;
+}
+
+static int chain_evaluate(struct eval_ctx *ctx, struct chain *chain)
+{
+ struct table *table;
+
+ table = table_cache_find(&ctx->nft->cache.table_cache,
+ ctx->cmd->handle.table.name,
+ ctx->cmd->handle.family);
+ if (table == NULL)
+ return table_not_found(ctx);
+
+ if (chain == NULL) {
+ if (!chain_cache_find(table, ctx->cmd->handle.chain.name)) {
+ chain = chain_alloc();
+ handle_merge(&chain->handle, &ctx->cmd->handle);
+ chain_cache_add(chain, table);
+ }
+ return 0;
+ } else if (!(chain->flags & CHAIN_F_BINDING)) {
+ if (!chain_cache_find(table, chain->handle.chain.name))
+ chain_cache_add(chain_get(chain), table);
+ }
+
+ if (chain->flags & CHAIN_F_BASECHAIN) {
+ chain->hook.num = str2hooknum(chain->handle.family,
+ chain->hook.name);
+ if (chain->hook.num == NF_INET_NUMHOOKS)
+ return __stmt_binary_error(ctx, &chain->hook.loc, NULL,
+ "The %s family does not support this hook",
+ family2str(chain->handle.family));
+
+ if (!evaluate_priority(ctx, &chain->priority,
+ chain->handle.family, chain->hook.num))
+ return __stmt_binary_error(ctx, &chain->priority.loc, NULL,
+ "invalid priority expression %s in this context.",
+ expr_name(chain->priority.expr));
+ if (chain->policy) {
+ expr_set_context(&ctx->ectx, &policy_type,
+ NFT_NAME_MAXLEN * BITS_PER_BYTE);
+ if (!evaluate_expr_variable(ctx, &chain->policy))
+ return chain_error(ctx, chain, "invalid policy expression %s",
+ expr_name(chain->policy));
+ }
+ }
+
+ if (chain->dev_expr) {
+ if (!(chain->flags & CHAIN_F_BASECHAIN))
+ chain->flags |= CHAIN_F_BASECHAIN;
+
+ if (chain->handle.family == NFPROTO_NETDEV ||
+ (chain->handle.family == NFPROTO_INET &&
+ chain->hook.num == NF_INET_INGRESS)) {
+ if (chain->dev_expr &&
+ !evaluate_device_expr(ctx, &chain->dev_expr))
+ return -1;
+ } else if (chain->dev_expr) {
+ return __stmt_binary_error(ctx, &chain->dev_expr->location, NULL,
+ "This chain type cannot be bound to device");
+ }
+ }
+
+ return 0;
+}
+
+static int ct_expect_evaluate(struct eval_ctx *ctx, struct obj *obj)
+{
+ struct ct_expect *ct = &obj->ct_expect;
+
+ if (!ct->l4proto ||
+ !ct->dport ||
+ !ct->timeout ||
+ !ct->size)
+ return __stmt_binary_error(ctx, &obj->location, NULL,
+ "missing options");
+
+ return 0;
+}
+
+static int ct_timeout_evaluate(struct eval_ctx *ctx, struct obj *obj)
+{
+ struct ct_timeout *ct = &obj->ct_timeout;
+ struct timeout_state *ts, *next;
+ unsigned int i;
+
+ for (i = 0; i < timeout_protocol[ct->l4proto].array_size; i++)
+ ct->timeout[i] = timeout_protocol[ct->l4proto].dflt_timeout[i];
+
+ list_for_each_entry_safe(ts, next, &ct->timeout_list, head) {
+ if (timeout_str2num(ct->l4proto, ts) < 0)
+ return __stmt_binary_error(ctx, &ts->location, NULL,
+ "invalid state for this protocol");
+
+ ct->timeout[ts->timeout_index] = ts->timeout_value;
+ list_del(&ts->head);
+ xfree(ts->timeout_str);
+ xfree(ts);
+ }
+
+ return 0;
+}
+
+static int obj_evaluate(struct eval_ctx *ctx, struct obj *obj)
+{
+ struct table *table;
+
+ table = table_cache_find(&ctx->nft->cache.table_cache,
+ ctx->cmd->handle.table.name,
+ ctx->cmd->handle.family);
+ if (!table)
+ return table_not_found(ctx);
+
+ if (!obj_cache_find(table, obj->handle.obj.name, obj->type))
+ obj_cache_add(obj_get(obj), table);
+
+ switch (obj->type) {
+ case NFT_OBJECT_CT_TIMEOUT:
+ return ct_timeout_evaluate(ctx, obj);
+ case NFT_OBJECT_CT_EXPECT:
+ return ct_expect_evaluate(ctx, obj);
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int table_evaluate(struct eval_ctx *ctx, struct table *table)
+{
+ if (!table_cache_find(&ctx->nft->cache.table_cache,
+ ctx->cmd->handle.table.name,
+ ctx->cmd->handle.family)) {
+ if (!table) {
+ table = table_alloc();
+ handle_merge(&table->handle, &ctx->cmd->handle);
+ table_cache_add(table, &ctx->nft->cache);
+ } else {
+ table_cache_add(table_get(table), &ctx->nft->cache);
+ }
+ }
+
+ return 0;
+}
+
+static int cmd_evaluate_add(struct eval_ctx *ctx, struct cmd *cmd)
+{
+ switch (cmd->obj) {
+ case CMD_OBJ_ELEMENTS:
+ return setelem_evaluate(ctx, cmd);
+ case CMD_OBJ_SET:
+ handle_merge(&cmd->set->handle, &cmd->handle);
+ return set_evaluate(ctx, cmd->set);
+ case CMD_OBJ_SETELEMS:
+ return elems_evaluate(ctx, cmd->set);
+ case CMD_OBJ_RULE:
+ handle_merge(&cmd->rule->handle, &cmd->handle);
+ return rule_evaluate(ctx, cmd->rule, cmd->op);
+ case CMD_OBJ_CHAIN:
+ return chain_evaluate(ctx, cmd->chain);
+ case CMD_OBJ_TABLE:
+ return table_evaluate(ctx, cmd->table);
+ case CMD_OBJ_FLOWTABLE:
+ handle_merge(&cmd->flowtable->handle, &cmd->handle);
+ return flowtable_evaluate(ctx, cmd->flowtable);
+ case CMD_OBJ_COUNTER:
+ case CMD_OBJ_QUOTA:
+ case CMD_OBJ_CT_HELPER:
+ case CMD_OBJ_LIMIT:
+ case CMD_OBJ_CT_TIMEOUT:
+ case CMD_OBJ_SECMARK:
+ case CMD_OBJ_CT_EXPECT:
+ case CMD_OBJ_SYNPROXY:
+ handle_merge(&cmd->object->handle, &cmd->handle);
+ return obj_evaluate(ctx, cmd->object);
+ default:
+ BUG("invalid command object type %u\n", cmd->obj);
+ }
+}
+
+static void table_del_cache(struct eval_ctx *ctx, struct cmd *cmd)
+{
+ struct table *table;
+
+ if (!cmd->handle.table.name)
+ return;
+
+ table = table_cache_find(&ctx->nft->cache.table_cache,
+ cmd->handle.table.name,
+ cmd->handle.family);
+ if (!table)
+ return;
+
+ table_cache_del(table);
+ table_free(table);
+}
+
+static void chain_del_cache(struct eval_ctx *ctx, struct cmd *cmd)
+{
+ struct table *table;
+ struct chain *chain;
+
+ if (!cmd->handle.chain.name)
+ return;
+
+ table = table_cache_find(&ctx->nft->cache.table_cache,
+ cmd->handle.table.name,
+ cmd->handle.family);
+ if (!table)
+ return;
+
+ chain = chain_cache_find(table, cmd->handle.chain.name);
+ if (!chain)
+ return;
+
+ chain_cache_del(chain);
+ chain_free(chain);
+}
+
+static void set_del_cache(struct eval_ctx *ctx, struct cmd *cmd)
+{
+ struct table *table;
+ struct set *set;
+
+ if (!cmd->handle.set.name)
+ return;
+
+ table = table_cache_find(&ctx->nft->cache.table_cache,
+ cmd->handle.table.name,
+ cmd->handle.family);
+ if (!table)
+ return;
+
+ set = set_cache_find(table, cmd->handle.set.name);
+ if (!set)
+ return;
+
+ set_cache_del(set);
+ set_free(set);
+}
+
+static void ft_del_cache(struct eval_ctx *ctx, struct cmd *cmd)
+{
+ struct flowtable *ft;
+ struct table *table;
+
+ if (!cmd->handle.flowtable.name)
+ return;
+
+ table = table_cache_find(&ctx->nft->cache.table_cache,
+ cmd->handle.table.name,
+ cmd->handle.family);
+ if (!table)
+ return;
+
+ ft = ft_cache_find(table, cmd->handle.flowtable.name);
+ if (!ft)
+ return;
+
+ ft_cache_del(ft);
+ flowtable_free(ft);
+}
+
+static void obj_del_cache(struct eval_ctx *ctx, struct cmd *cmd, int type)
+{
+ struct table *table;
+ struct obj *obj;
+
+ if (!cmd->handle.obj.name)
+ return;
+
+ table = table_cache_find(&ctx->nft->cache.table_cache,
+ cmd->handle.table.name,
+ cmd->handle.family);
+ if (!table)
+ return;
+
+ obj = obj_cache_find(table, cmd->handle.obj.name, type);
+ if (!obj)
+ return;
+
+ obj_cache_del(obj);
+ obj_free(obj);
+}
+
+static int cmd_evaluate_delete(struct eval_ctx *ctx, struct cmd *cmd)
+{
+ switch (cmd->obj) {
+ case CMD_OBJ_ELEMENTS:
+ return setelem_evaluate(ctx, cmd);
+ case CMD_OBJ_SET:
+ set_del_cache(ctx, cmd);
+ return 0;
+ case CMD_OBJ_RULE:
+ return 0;
+ case CMD_OBJ_CHAIN:
+ chain_del_cache(ctx, cmd);
+ return 0;
+ case CMD_OBJ_TABLE:
+ table_del_cache(ctx, cmd);
+ return 0;
+ case CMD_OBJ_FLOWTABLE:
+ ft_del_cache(ctx, cmd);
+ return 0;
+ case CMD_OBJ_COUNTER:
+ obj_del_cache(ctx, cmd, NFT_OBJECT_COUNTER);
+ return 0;
+ case CMD_OBJ_QUOTA:
+ obj_del_cache(ctx, cmd, NFT_OBJECT_QUOTA);
+ return 0;
+ case CMD_OBJ_CT_HELPER:
+ obj_del_cache(ctx, cmd, NFT_OBJECT_CT_HELPER);
+ return 0;
+ case CMD_OBJ_CT_TIMEOUT:
+ obj_del_cache(ctx, cmd, NFT_OBJECT_CT_TIMEOUT);
+ return 0;
+ case CMD_OBJ_LIMIT:
+ obj_del_cache(ctx, cmd, NFT_OBJECT_LIMIT);
+ return 0;
+ case CMD_OBJ_SECMARK:
+ obj_del_cache(ctx, cmd, NFT_OBJECT_SECMARK);
+ return 0;
+ case CMD_OBJ_CT_EXPECT:
+ obj_del_cache(ctx, cmd, NFT_OBJECT_CT_EXPECT);
+ return 0;
+ case CMD_OBJ_SYNPROXY:
+ obj_del_cache(ctx, cmd, NFT_OBJECT_SYNPROXY);
+ return 0;
+ default:
+ BUG("invalid command object type %u\n", cmd->obj);
+ }
+}
+
+static int cmd_evaluate_get(struct eval_ctx *ctx, struct cmd *cmd)
+{
+ switch (cmd->obj) {
+ case CMD_OBJ_ELEMENTS:
+ return setelem_evaluate(ctx, cmd);
+ default:
+ BUG("invalid command object type %u\n", cmd->obj);
+ }
+}
+
+static int obj_not_found(struct eval_ctx *ctx, const struct location *loc,
+ const char *obj_name)
+{
+ const struct table *table;
+ struct obj *obj;
+
+ obj = obj_lookup_fuzzy(obj_name, &ctx->nft->cache, &table);
+ if (obj == NULL)
+ return cmd_error(ctx, loc, "%s", strerror(ENOENT));
+
+ return cmd_error(ctx, loc,
+ "%s; did you mean obj ‘%s’ in table %s ‘%s’?",
+ strerror(ENOENT), obj->handle.obj.name,
+ family2str(obj->handle.family),
+ table->handle.table.name);
+}
+
+static int cmd_evaluate_list_obj(struct eval_ctx *ctx, const struct cmd *cmd,
+ uint32_t obj_type)
+{
+ const struct table *table;
+
+ if (obj_type == NFT_OBJECT_UNSPEC)
+ obj_type = NFT_OBJECT_COUNTER;
+
+ table = table_cache_find(&ctx->nft->cache.table_cache,
+ cmd->handle.table.name,
+ cmd->handle.family);
+ if (table == NULL)
+ return table_not_found(ctx);
+
+ if (!obj_cache_find(table, cmd->handle.obj.name, obj_type))
+ return obj_not_found(ctx, &cmd->handle.obj.location,
+ cmd->handle.obj.name);
+
+ return 0;
+}
+
+static int cmd_evaluate_list(struct eval_ctx *ctx, struct cmd *cmd)
+{
+ struct flowtable *ft;
+ struct table *table;
+ struct set *set;
+
+ switch (cmd->obj) {
+ case CMD_OBJ_TABLE:
+ if (cmd->handle.table.name == NULL)
+ return 0;
+
+ table = table_cache_find(&ctx->nft->cache.table_cache,
+ cmd->handle.table.name,
+ cmd->handle.family);
+ if (!table)
+ return table_not_found(ctx);
+
+ return 0;
+ case CMD_OBJ_SET:
+ case CMD_OBJ_MAP:
+ case CMD_OBJ_METER:
+ table = table_cache_find(&ctx->nft->cache.table_cache,
+ cmd->handle.table.name,
+ cmd->handle.family);
+ if (!table)
+ return table_not_found(ctx);
+
+ set = set_cache_find(table, cmd->handle.set.name);
+ if (set == NULL)
+ return set_not_found(ctx, &ctx->cmd->handle.set.location,
+ ctx->cmd->handle.set.name);
+ if ((cmd->obj == CMD_OBJ_SET && !set_is_literal(set->flags)) ||
+ (cmd->obj == CMD_OBJ_MAP && !map_is_literal(set->flags)) ||
+ (cmd->obj == CMD_OBJ_METER && !set_is_meter(set->flags)))
+ return cmd_error(ctx, &ctx->cmd->handle.set.location,
+ "%s", strerror(ENOENT));
+
+ cmd->set = set_get(set);
+ return 0;
+ case CMD_OBJ_CHAIN:
+ table = table_cache_find(&ctx->nft->cache.table_cache,
+ cmd->handle.table.name,
+ cmd->handle.family);
+ if (!table)
+ return table_not_found(ctx);
+
+ if (!chain_cache_find(table, cmd->handle.chain.name))
+ return chain_not_found(ctx);
+
+ return 0;
+ case CMD_OBJ_FLOWTABLE:
+ table = table_cache_find(&ctx->nft->cache.table_cache,
+ cmd->handle.table.name,
+ cmd->handle.family);
+ if (!table)
+ return table_not_found(ctx);
+
+ ft = ft_cache_find(table, cmd->handle.flowtable.name);
+ if (!ft)
+ return flowtable_not_found(ctx, &ctx->cmd->handle.flowtable.location,
+ ctx->cmd->handle.flowtable.name);
+
+ return 0;
+ case CMD_OBJ_QUOTA:
+ return cmd_evaluate_list_obj(ctx, cmd, NFT_OBJECT_QUOTA);
+ case CMD_OBJ_COUNTER:
+ return cmd_evaluate_list_obj(ctx, cmd, NFT_OBJECT_COUNTER);
+ case CMD_OBJ_CT_HELPER:
+ return cmd_evaluate_list_obj(ctx, cmd, NFT_OBJECT_CT_HELPER);
+ case CMD_OBJ_CT_TIMEOUT:
+ return cmd_evaluate_list_obj(ctx, cmd, NFT_OBJECT_CT_TIMEOUT);
+ case CMD_OBJ_LIMIT:
+ return cmd_evaluate_list_obj(ctx, cmd, NFT_OBJECT_LIMIT);
+ case CMD_OBJ_SECMARK:
+ return cmd_evaluate_list_obj(ctx, cmd, NFT_OBJECT_SECMARK);
+ case CMD_OBJ_CT_EXPECT:
+ return cmd_evaluate_list_obj(ctx, cmd, NFT_OBJECT_CT_EXPECT);
+ case CMD_OBJ_SYNPROXY:
+ return cmd_evaluate_list_obj(ctx, cmd, NFT_OBJECT_SYNPROXY);
+ case CMD_OBJ_COUNTERS:
+ case CMD_OBJ_QUOTAS:
+ case CMD_OBJ_CT_HELPERS:
+ case CMD_OBJ_LIMITS:
+ case CMD_OBJ_SETS:
+ case CMD_OBJ_FLOWTABLES:
+ case CMD_OBJ_SECMARKS:
+ case CMD_OBJ_SYNPROXYS:
+ case CMD_OBJ_CT_TIMEOUTS:
+ case CMD_OBJ_CT_EXPECTATIONS:
+ if (cmd->handle.table.name == NULL)
+ return 0;
+ if (!table_cache_find(&ctx->nft->cache.table_cache,
+ cmd->handle.table.name,
+ cmd->handle.family))
+ return table_not_found(ctx);
+
+ return 0;
+ case CMD_OBJ_CHAINS:
+ case CMD_OBJ_RULESET:
+ case CMD_OBJ_METERS:
+ case CMD_OBJ_MAPS:
+ return 0;
+ case CMD_OBJ_HOOKS:
+ if (cmd->handle.chain.name) {
+ int hooknum = str2hooknum(cmd->handle.family, cmd->handle.chain.name);
+
+ if (hooknum == NF_INET_NUMHOOKS)
+ return chain_not_found(ctx);
+
+ cmd->handle.chain_id = hooknum;
+ }
+ return 0;
+ default:
+ BUG("invalid command object type %u\n", cmd->obj);
+ }
+}
+
+static int cmd_evaluate_reset(struct eval_ctx *ctx, struct cmd *cmd)
+{
+ switch (cmd->obj) {
+ case CMD_OBJ_COUNTER:
+ case CMD_OBJ_QUOTA:
+ case CMD_OBJ_COUNTERS:
+ case CMD_OBJ_QUOTAS:
+ case CMD_OBJ_RULES:
+ case CMD_OBJ_RULE:
+ if (cmd->handle.table.name == NULL)
+ return 0;
+ if (!table_cache_find(&ctx->nft->cache.table_cache,
+ cmd->handle.table.name,
+ cmd->handle.family))
+ return table_not_found(ctx);
+
+ return 0;
+ case CMD_OBJ_ELEMENTS:
+ return setelem_evaluate(ctx, cmd);
+ case CMD_OBJ_SET:
+ case CMD_OBJ_MAP:
+ return cmd_evaluate_list(ctx, cmd);
+ default:
+ BUG("invalid command object type %u\n", cmd->obj);
+ }
+}
+
+static void __flush_set_cache(struct set *set)
+{
+ if (set->init != NULL) {
+ expr_free(set->init);
+ set->init = NULL;
+ }
+}
+
+static int cmd_evaluate_flush(struct eval_ctx *ctx, struct cmd *cmd)
+{
+ struct cache *table_cache = &ctx->nft->cache.table_cache;
+ struct table *table;
+ struct set *set;
+
+ switch (cmd->obj) {
+ case CMD_OBJ_RULESET:
+ break;
+ case CMD_OBJ_TABLE:
+ /* Flushing a table does not empty the sets in the table nor remove
+ * any chains.
+ */
+ case CMD_OBJ_CHAIN:
+ /* Chains don't hold sets */
+ break;
+ case CMD_OBJ_SET:
+ table = table_cache_find(table_cache, cmd->handle.table.name,
+ cmd->handle.family);
+ if (!table)
+ return table_not_found(ctx);
+
+ set = set_cache_find(table, cmd->handle.set.name);
+ if (set == NULL)
+ return set_not_found(ctx, &ctx->cmd->handle.set.location,
+ ctx->cmd->handle.set.name);
+ else if (!set_is_literal(set->flags))
+ return cmd_error(ctx, &ctx->cmd->handle.set.location,
+ "%s", strerror(ENOENT));
+
+ __flush_set_cache(set);
+
+ return 0;
+ case CMD_OBJ_MAP:
+ table = table_cache_find(table_cache, cmd->handle.table.name,
+ cmd->handle.family);
+ if (!table)
+ return table_not_found(ctx);
+
+ set = set_cache_find(table, cmd->handle.set.name);
+ if (set == NULL)
+ return set_not_found(ctx, &ctx->cmd->handle.set.location,
+ ctx->cmd->handle.set.name);
+ else if (!map_is_literal(set->flags))
+ return cmd_error(ctx, &ctx->cmd->handle.set.location,
+ "%s", strerror(ENOENT));
+
+ __flush_set_cache(set);
+
+ return 0;
+ case CMD_OBJ_METER:
+ table = table_cache_find(table_cache, cmd->handle.table.name,
+ cmd->handle.family);
+ if (!table)
+ return table_not_found(ctx);
+
+ set = set_cache_find(table, cmd->handle.set.name);
+ if (set == NULL)
+ return set_not_found(ctx, &ctx->cmd->handle.set.location,
+ ctx->cmd->handle.set.name);
+ else if (!set_is_meter(set->flags))
+ return cmd_error(ctx, &ctx->cmd->handle.set.location,
+ "%s", strerror(ENOENT));
+
+ __flush_set_cache(set);
+
+ return 0;
+ default:
+ BUG("invalid command object type %u\n", cmd->obj);
+ }
+ return 0;
+}
+
+static int cmd_evaluate_rename(struct eval_ctx *ctx, struct cmd *cmd)
+{
+ struct table *table;
+
+ switch (cmd->obj) {
+ case CMD_OBJ_CHAIN:
+ table = table_cache_find(&ctx->nft->cache.table_cache,
+ cmd->handle.table.name,
+ cmd->handle.family);
+ if (!table)
+ return table_not_found(ctx);
+
+ if (!chain_cache_find(table, ctx->cmd->handle.chain.name))
+ return chain_not_found(ctx);
+
+ break;
+ default:
+ BUG("invalid command object type %u\n", cmd->obj);
+ }
+ return 0;
+}
+
+enum {
+ CMD_MONITOR_EVENT_ANY,
+ CMD_MONITOR_EVENT_NEW,
+ CMD_MONITOR_EVENT_DEL,
+ CMD_MONITOR_EVENT_MAX
+};
+
+static uint32_t monitor_flags[CMD_MONITOR_EVENT_MAX][CMD_MONITOR_OBJ_MAX] = {
+ [CMD_MONITOR_EVENT_ANY] = {
+ [CMD_MONITOR_OBJ_ANY] = 0xffffffff,
+ [CMD_MONITOR_OBJ_TABLES] = (1 << NFT_MSG_NEWTABLE) |
+ (1 << NFT_MSG_DELTABLE),
+ [CMD_MONITOR_OBJ_CHAINS] = (1 << NFT_MSG_NEWCHAIN) |
+ (1 << NFT_MSG_DELCHAIN),
+ [CMD_MONITOR_OBJ_RULES] = (1 << NFT_MSG_NEWRULE) |
+ (1 << NFT_MSG_DELRULE),
+ [CMD_MONITOR_OBJ_SETS] = (1 << NFT_MSG_NEWSET) |
+ (1 << NFT_MSG_DELSET),
+ [CMD_MONITOR_OBJ_ELEMS] = (1 << NFT_MSG_NEWSETELEM) |
+ (1 << NFT_MSG_DELSETELEM),
+ [CMD_MONITOR_OBJ_RULESET] = (1 << NFT_MSG_NEWTABLE) |
+ (1 << NFT_MSG_DELTABLE) |
+ (1 << NFT_MSG_NEWCHAIN) |
+ (1 << NFT_MSG_DELCHAIN) |
+ (1 << NFT_MSG_NEWRULE) |
+ (1 << NFT_MSG_DELRULE) |
+ (1 << NFT_MSG_NEWSET) |
+ (1 << NFT_MSG_DELSET) |
+ (1 << NFT_MSG_NEWSETELEM) |
+ (1 << NFT_MSG_DELSETELEM) |
+ (1 << NFT_MSG_NEWOBJ) |
+ (1 << NFT_MSG_DELOBJ),
+ [CMD_MONITOR_OBJ_TRACE] = (1 << NFT_MSG_TRACE),
+ },
+ [CMD_MONITOR_EVENT_NEW] = {
+ [CMD_MONITOR_OBJ_ANY] = (1 << NFT_MSG_NEWTABLE) |
+ (1 << NFT_MSG_NEWCHAIN) |
+ (1 << NFT_MSG_NEWRULE) |
+ (1 << NFT_MSG_NEWSET) |
+ (1 << NFT_MSG_NEWSETELEM),
+ [CMD_MONITOR_OBJ_TABLES] = (1 << NFT_MSG_NEWTABLE),
+ [CMD_MONITOR_OBJ_CHAINS] = (1 << NFT_MSG_NEWCHAIN),
+ [CMD_MONITOR_OBJ_RULES] = (1 << NFT_MSG_NEWRULE),
+ [CMD_MONITOR_OBJ_SETS] = (1 << NFT_MSG_NEWSET),
+ [CMD_MONITOR_OBJ_ELEMS] = (1 << NFT_MSG_NEWSETELEM),
+ [CMD_MONITOR_OBJ_RULESET] = (1 << NFT_MSG_NEWTABLE) |
+ (1 << NFT_MSG_NEWCHAIN) |
+ (1 << NFT_MSG_NEWRULE) |
+ (1 << NFT_MSG_NEWSET) |
+ (1 << NFT_MSG_NEWSETELEM) |
+ (1 << NFT_MSG_NEWOBJ),
+ [CMD_MONITOR_OBJ_TRACE] = 0,
+ },
+ [CMD_MONITOR_EVENT_DEL] = {
+ [CMD_MONITOR_OBJ_ANY] = (1 << NFT_MSG_DELTABLE) |
+ (1 << NFT_MSG_DELCHAIN) |
+ (1 << NFT_MSG_DELRULE) |
+ (1 << NFT_MSG_DELSET) |
+ (1 << NFT_MSG_DELSETELEM),
+ [CMD_MONITOR_OBJ_TABLES] = (1 << NFT_MSG_DELTABLE),
+ [CMD_MONITOR_OBJ_CHAINS] = (1 << NFT_MSG_DELCHAIN),
+ [CMD_MONITOR_OBJ_RULES] = (1 << NFT_MSG_DELRULE),
+ [CMD_MONITOR_OBJ_SETS] = (1 << NFT_MSG_DELSET),
+ [CMD_MONITOR_OBJ_ELEMS] = (1 << NFT_MSG_DELSETELEM),
+ [CMD_MONITOR_OBJ_RULESET] = (1 << NFT_MSG_DELTABLE) |
+ (1 << NFT_MSG_DELCHAIN) |
+ (1 << NFT_MSG_DELRULE) |
+ (1 << NFT_MSG_DELSET) |
+ (1 << NFT_MSG_DELSETELEM) |
+ (1 << NFT_MSG_DELOBJ),
+ [CMD_MONITOR_OBJ_TRACE] = 0,
+ },
+};
+
+static int cmd_evaluate_monitor(struct eval_ctx *ctx, struct cmd *cmd)
+{
+ uint32_t event;
+
+ if (cmd->monitor->event == NULL)
+ event = CMD_MONITOR_EVENT_ANY;
+ else if (strcmp(cmd->monitor->event, "new") == 0)
+ event = CMD_MONITOR_EVENT_NEW;
+ else if (strcmp(cmd->monitor->event, "destroy") == 0)
+ event = CMD_MONITOR_EVENT_DEL;
+ else {
+ return monitor_error(ctx, cmd->monitor, "invalid event %s",
+ cmd->monitor->event);
+ }
+
+ cmd->monitor->flags = monitor_flags[event][cmd->monitor->type];
+ return 0;
+}
+
+static int cmd_evaluate_export(struct eval_ctx *ctx, struct cmd *cmd)
+{
+ if (cmd->markup->format == __NFT_OUTPUT_NOTSUPP)
+ return cmd_error(ctx, &cmd->location,
+ "this output type is not supported, use nft -j list ruleset for JSON support instead");
+ else if (cmd->markup->format == NFTNL_OUTPUT_JSON)
+ return cmd_error(ctx, &cmd->location,
+ "JSON export is no longer supported, use 'nft -j list ruleset' instead");
+
+ return 0;
+}
+
+static int cmd_evaluate_import(struct eval_ctx *ctx, struct cmd *cmd)
+{
+ if (cmd->markup->format == __NFT_OUTPUT_NOTSUPP)
+ return cmd_error(ctx, &cmd->location,
+ "this output type not supported");
+
+ return 0;
+}
+
+static const char * const cmd_op_name[] = {
+ [CMD_INVALID] = "invalid",
+ [CMD_ADD] = "add",
+ [CMD_REPLACE] = "replace",
+ [CMD_CREATE] = "create",
+ [CMD_INSERT] = "insert",
+ [CMD_DELETE] = "delete",
+ [CMD_GET] = "get",
+ [CMD_LIST] = "list",
+ [CMD_FLUSH] = "flush",
+ [CMD_RENAME] = "rename",
+ [CMD_EXPORT] = "export",
+ [CMD_MONITOR] = "monitor",
+ [CMD_DESCRIBE] = "describe",
+ [CMD_DESTROY] = "destroy",
+};
+
+static const char *cmd_op_to_name(enum cmd_ops op)
+{
+ if (op > CMD_DESCRIBE)
+ return "unknown";
+
+ return cmd_op_name[op];
+}
+
+int cmd_evaluate(struct eval_ctx *ctx, struct cmd *cmd)
+{
+ if (ctx->nft->debug_mask & NFT_DEBUG_EVALUATION) {
+ struct error_record *erec;
+
+ erec = erec_create(EREC_INFORMATIONAL, &cmd->location,
+ "Evaluate %s", cmd_op_to_name(cmd->op));
+ erec_print(&ctx->nft->output, erec, ctx->nft->debug_mask);
+ nft_print(&ctx->nft->output, "\n\n");
+ erec_destroy(erec);
+ }
+
+ memset(&ctx->ectx, 0, sizeof(ctx->ectx));
+
+ ctx->cmd = cmd;
+ switch (cmd->op) {
+ case CMD_ADD:
+ case CMD_REPLACE:
+ case CMD_CREATE:
+ case CMD_INSERT:
+ return cmd_evaluate_add(ctx, cmd);
+ case CMD_DELETE:
+ case CMD_DESTROY:
+ return cmd_evaluate_delete(ctx, cmd);
+ case CMD_GET:
+ return cmd_evaluate_get(ctx, cmd);
+ case CMD_LIST:
+ return cmd_evaluate_list(ctx, cmd);
+ case CMD_RESET:
+ return cmd_evaluate_reset(ctx, cmd);
+ case CMD_FLUSH:
+ return cmd_evaluate_flush(ctx, cmd);
+ case CMD_RENAME:
+ return cmd_evaluate_rename(ctx, cmd);
+ case CMD_EXPORT:
+ return cmd_evaluate_export(ctx, cmd);
+ case CMD_DESCRIBE:
+ return 0;
+ case CMD_MONITOR:
+ return cmd_evaluate_monitor(ctx, cmd);
+ case CMD_IMPORT:
+ return cmd_evaluate_import(ctx, cmd);
+ default:
+ BUG("invalid command operation %u\n", cmd->op);
+ };
+}