summaryrefslogtreecommitdiffstats
path: root/src/netlink_delinearize.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/netlink_delinearize.c')
-rw-r--r--src/netlink_delinearize.c3512
1 files changed, 3512 insertions, 0 deletions
diff --git a/src/netlink_delinearize.c b/src/netlink_delinearize.c
new file mode 100644
index 0000000..e214510
--- /dev/null
+++ b/src/netlink_delinearize.c
@@ -0,0 +1,3512 @@
+/*
+ * Copyright (c) 2008 Patrick McHardy <kaber@trash.net>
+ * Copyright (c) 2013 Pablo Neira Ayuso <pablo@netfilter.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Development of this code funded by Astaro AG (http://www.astaro.com/)
+ */
+
+#include <nft.h>
+
+#include <limits.h>
+#include <linux/netfilter/nf_tables.h>
+#include <arpa/inet.h>
+#include <linux/netfilter/nf_nat.h>
+#include <linux/netfilter.h>
+#include <net/ethernet.h>
+#include <netlink.h>
+#include <rule.h>
+#include <statement.h>
+#include <expression.h>
+#include <gmputil.h>
+#include <utils.h>
+#include <erec.h>
+#include <sys/socket.h>
+#include <libnftnl/udata.h>
+#include <cache.h>
+#include <xt.h>
+
+struct dl_proto_ctx *dl_proto_ctx(struct rule_pp_ctx *ctx)
+{
+ return ctx->dl;
+}
+
+static struct dl_proto_ctx *dl_proto_ctx_outer(struct rule_pp_ctx *ctx)
+{
+ return &ctx->_dl[0];
+}
+
+static int netlink_parse_expr(const struct nftnl_expr *nle,
+ struct netlink_parse_ctx *ctx);
+
+static void __fmtstring(3, 4) netlink_error(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const char *fmt, ...)
+{
+ struct error_record *erec;
+ va_list ap;
+
+ va_start(ap, fmt);
+ erec = erec_vcreate(EREC_ERROR, loc, fmt, ap);
+ va_end(ap);
+ erec_queue(erec, ctx->msgs);
+}
+
+static unsigned int netlink_parse_register(const struct nftnl_expr *nle,
+ unsigned int attr)
+{
+ unsigned int reg;
+
+ reg = nftnl_expr_get_u32(nle, attr);
+ /* Translate 128bit registers to corresponding 32bit registers */
+ if (reg >= NFT_REG_1 && reg <= NFT_REG_4)
+ reg = 1 + (reg - NFT_REG_1) * (NFT_REG_SIZE / NFT_REG32_SIZE);
+ else if (reg >= NFT_REG32_00)
+ reg = 1 + reg - NFT_REG32_00;
+
+ return reg;
+}
+
+static void netlink_set_register(struct netlink_parse_ctx *ctx,
+ enum nft_registers reg,
+ struct expr *expr)
+{
+ if (reg == NFT_REG_VERDICT || reg > MAX_REGS) {
+ netlink_error(ctx, &expr->location,
+ "Invalid destination register %u", reg);
+ expr_free(expr);
+ return;
+ }
+
+ expr_free(ctx->registers[reg]);
+
+ ctx->registers[reg] = expr;
+}
+
+static struct expr *netlink_get_register(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ enum nft_registers reg)
+{
+ struct expr *expr;
+
+ if (reg == NFT_REG_VERDICT || reg > MAX_REGS) {
+ netlink_error(ctx, loc, "Invalid source register %u", reg);
+ return NULL;
+ }
+
+ expr = ctx->registers[reg];
+ if (expr != NULL)
+ expr = expr_clone(expr);
+
+ return expr;
+}
+
+static void netlink_release_registers(struct netlink_parse_ctx *ctx)
+{
+ int i;
+
+ for (i = 0; i <= MAX_REGS; i++)
+ expr_free(ctx->registers[i]);
+}
+
+static struct expr *netlink_parse_concat_expr(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ unsigned int reg,
+ unsigned int len)
+{
+ struct expr *concat, *expr;
+ unsigned int consumed;
+
+ concat = concat_expr_alloc(loc);
+ while (len > 0) {
+ expr = netlink_get_register(ctx, loc, reg);
+ if (expr == NULL) {
+ netlink_error(ctx, loc,
+ "Relational expression size mismatch");
+ goto err;
+ }
+ compound_expr_add(concat, expr);
+
+ consumed = netlink_padded_len(expr->len);
+ assert(consumed > 0);
+ len -= consumed;
+ reg += netlink_register_space(expr->len);
+ }
+ return concat;
+
+err:
+ expr_free(concat);
+ return NULL;
+}
+
+static struct expr *netlink_parse_concat_key(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ unsigned int reg,
+ const struct expr *key)
+{
+ uint32_t type = key->dtype->type;
+ unsigned int n, len = key->len;
+ struct expr *concat, *expr;
+ unsigned int consumed;
+
+ concat = concat_expr_alloc(loc);
+ n = div_round_up(fls(type), TYPE_BITS);
+
+ while (len > 0) {
+ const struct datatype *i;
+
+ expr = netlink_get_register(ctx, loc, reg);
+ if (expr == NULL) {
+ netlink_error(ctx, loc,
+ "Concat expression size mismatch");
+ goto err;
+ }
+
+ if (n > 0 && concat_subtype_id(type, --n)) {
+ i = concat_subtype_lookup(type, n);
+
+ expr_set_type(expr, i, i->byteorder);
+ }
+
+ compound_expr_add(concat, expr);
+
+ consumed = netlink_padded_len(expr->len);
+ assert(consumed > 0);
+ len -= consumed;
+ reg += netlink_register_space(expr->len);
+ }
+
+ return concat;
+
+err:
+ expr_free(concat);
+ return NULL;
+}
+
+static struct expr *netlink_parse_concat_data(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ unsigned int reg,
+ unsigned int len,
+ struct expr *data)
+{
+ struct expr *concat, *expr, *i;
+
+ concat = concat_expr_alloc(loc);
+ while (len > 0) {
+ expr = netlink_get_register(ctx, loc, reg);
+ if (expr == NULL) {
+ netlink_error(ctx, loc,
+ "Relational expression size mismatch");
+ goto err;
+ }
+ i = constant_expr_splice(data, expr->len);
+ data->len -= netlink_padding_len(expr->len);
+ compound_expr_add(concat, i);
+
+ len -= netlink_padded_len(expr->len);
+ reg += netlink_register_space(expr->len);
+ expr_free(expr);
+ }
+ return concat;
+
+err:
+ expr_free(concat);
+ return NULL;
+}
+
+static void netlink_parse_chain_verdict(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ struct expr *expr,
+ enum nft_verdicts verdict)
+{
+ char chain_name[NFT_CHAIN_MAXNAMELEN] = {};
+ struct chain *chain;
+
+ expr_chain_export(expr->chain, chain_name);
+ chain = chain_binding_lookup(ctx->table, chain_name);
+
+ /* Special case: 'nft list chain x y' needs to pull in implicit chains */
+ if (!chain && !strncmp(chain_name, "__chain", strlen("__chain"))) {
+ nft_chain_cache_update(ctx->nlctx, ctx->table, chain_name);
+ chain = chain_binding_lookup(ctx->table, chain_name);
+ }
+
+ if (chain) {
+ ctx->stmt = chain_stmt_alloc(loc, chain, verdict);
+ expr_free(expr);
+ } else {
+ ctx->stmt = verdict_stmt_alloc(loc, expr);
+ }
+}
+
+static void netlink_parse_immediate(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const struct nftnl_expr *nle)
+{
+ struct nft_data_delinearize nld;
+ enum nft_registers dreg;
+ struct expr *expr;
+
+ if (nftnl_expr_is_set(nle, NFTNL_EXPR_IMM_VERDICT)) {
+ nld.verdict = nftnl_expr_get_u32(nle, NFTNL_EXPR_IMM_VERDICT);
+ if (nftnl_expr_is_set(nle, NFTNL_EXPR_IMM_CHAIN)) {
+ nld.chain = nftnl_expr_get(nle, NFTNL_EXPR_IMM_CHAIN,
+ &nld.len);
+ }
+ } else if (nftnl_expr_is_set(nle, NFTNL_EXPR_IMM_DATA)) {
+ nld.value = nftnl_expr_get(nle, NFTNL_EXPR_IMM_DATA, &nld.len);
+ }
+
+ dreg = netlink_parse_register(nle, NFTNL_EXPR_IMM_DREG);
+ expr = netlink_alloc_data(loc, &nld, dreg);
+
+ if (dreg == NFT_REG_VERDICT) {
+ switch (expr->verdict) {
+ case NFT_JUMP:
+ netlink_parse_chain_verdict(ctx, loc, expr, NFT_JUMP);
+ break;
+ case NFT_GOTO:
+ netlink_parse_chain_verdict(ctx, loc, expr, NFT_GOTO);
+ break;
+ default:
+ ctx->stmt = verdict_stmt_alloc(loc, expr);
+ break;
+ }
+ } else {
+ netlink_set_register(ctx, dreg, expr);
+ }
+}
+
+static void netlink_parse_xfrm(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const struct nftnl_expr *nle)
+{
+ enum nft_registers dreg;
+ enum nft_xfrm_keys key;
+ struct expr *expr;
+ uint32_t spnum;
+ uint8_t dir;
+
+ key = nftnl_expr_get_u32(nle, NFTNL_EXPR_XFRM_KEY);
+ dir = nftnl_expr_get_u8(nle, NFTNL_EXPR_XFRM_DIR);
+ spnum = nftnl_expr_get_u32(nle, NFTNL_EXPR_XFRM_SPNUM);
+ expr = xfrm_expr_alloc(loc, dir, spnum, key);
+
+ dreg = netlink_parse_register(nle, NFTNL_EXPR_XFRM_DREG);
+ netlink_set_register(ctx, dreg, expr);
+}
+
+static enum ops netlink_parse_range_op(const struct nftnl_expr *nle)
+{
+ switch (nftnl_expr_get_u32(nle, NFTNL_EXPR_RANGE_OP)) {
+ case NFT_RANGE_EQ:
+ return OP_EQ;
+ case NFT_RANGE_NEQ:
+ return OP_NEQ;
+ default:
+ return OP_INVALID;
+ }
+}
+
+static void netlink_parse_range(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const struct nftnl_expr *nle)
+{
+ struct expr *expr, *left, *right, *from, *to;
+ struct nft_data_delinearize nld;
+ enum nft_registers sreg;
+ enum ops op;
+
+ sreg = netlink_parse_register(nle, NFTNL_EXPR_RANGE_SREG);
+ left = netlink_get_register(ctx, loc, sreg);
+ if (left == NULL)
+ return netlink_error(ctx, loc,
+ "Relational expression has no left hand side");
+
+ op = netlink_parse_range_op(nle);
+
+ nld.value = nftnl_expr_get(nle, NFTNL_EXPR_RANGE_FROM_DATA, &nld.len);
+ from = netlink_alloc_value(loc, &nld);
+
+ nld.value = nftnl_expr_get(nle, NFTNL_EXPR_RANGE_TO_DATA, &nld.len);
+ to = netlink_alloc_value(loc, &nld);
+
+ right = range_expr_alloc(loc, from, to);
+ expr = relational_expr_alloc(loc, op, left, right);
+ ctx->stmt = expr_stmt_alloc(loc, expr);
+}
+
+static enum ops netlink_parse_cmp_op(const struct nftnl_expr *nle)
+{
+ switch (nftnl_expr_get_u32(nle, NFTNL_EXPR_CMP_OP)) {
+ case NFT_CMP_EQ:
+ return OP_EQ;
+ case NFT_CMP_NEQ:
+ return OP_NEQ;
+ case NFT_CMP_LT:
+ return OP_LT;
+ case NFT_CMP_LTE:
+ return OP_LTE;
+ case NFT_CMP_GT:
+ return OP_GT;
+ case NFT_CMP_GTE:
+ return OP_GTE;
+ default:
+ return OP_INVALID;
+ }
+}
+
+static void netlink_parse_cmp(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const struct nftnl_expr *nle)
+{
+ struct nft_data_delinearize nld;
+ enum nft_registers sreg;
+ struct expr *expr, *left, *right, *tmp;
+ enum ops op;
+
+ sreg = netlink_parse_register(nle, NFTNL_EXPR_CMP_SREG);
+ left = netlink_get_register(ctx, loc, sreg);
+ if (left == NULL)
+ return netlink_error(ctx, loc,
+ "Relational expression has no left "
+ "hand side");
+
+ op = netlink_parse_cmp_op(nle);
+
+ nld.value = nftnl_expr_get(nle, NFTNL_EXPR_CMP_DATA, &nld.len);
+ right = netlink_alloc_value(loc, &nld);
+
+ if (left->len > right->len &&
+ expr_basetype(left) != &string_type) {
+ mpz_lshift_ui(right->value, left->len - right->len);
+ right = prefix_expr_alloc(loc, right, right->len);
+ right->prefix->len = left->len;
+ } else if (left->len > 0 && left->len < right->len) {
+ expr_free(left);
+ left = netlink_parse_concat_expr(ctx, loc, sreg, right->len);
+ if (left == NULL)
+ goto err_free;
+ tmp = netlink_parse_concat_data(ctx, loc, sreg, right->len, right);
+ if (tmp == NULL)
+ goto err_free;
+ expr_free(right);
+ right = tmp;
+ }
+
+ expr = relational_expr_alloc(loc, op, left, right);
+ ctx->stmt = expr_stmt_alloc(loc, expr);
+ return;
+err_free:
+ expr_free(left);
+ expr_free(right);
+}
+
+static void netlink_parse_lookup(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const struct nftnl_expr *nle)
+{
+ enum nft_registers sreg, dreg;
+ const char *name;
+ struct expr *expr, *left, *right;
+ struct set *set;
+ uint32_t flag;
+
+ name = nftnl_expr_get_str(nle, NFTNL_EXPR_LOOKUP_SET);
+ set = set_cache_find(ctx->table, name);
+ if (set == NULL)
+ return netlink_error(ctx, loc,
+ "Unknown set '%s' in lookup expression",
+ name);
+
+ sreg = netlink_parse_register(nle, NFTNL_EXPR_LOOKUP_SREG);
+ left = netlink_get_register(ctx, loc, sreg);
+ if (left == NULL)
+ return netlink_error(ctx, loc,
+ "Lookup expression has no left hand side");
+
+ if (left->len < set->key->len) {
+ expr_free(left);
+ left = netlink_parse_concat_expr(ctx, loc, sreg, set->key->len);
+ if (left == NULL)
+ return;
+ }
+
+ right = set_ref_expr_alloc(loc, set);
+
+ if (nftnl_expr_is_set(nle, NFTNL_EXPR_LOOKUP_DREG)) {
+ dreg = netlink_parse_register(nle, NFTNL_EXPR_LOOKUP_DREG);
+ expr = map_expr_alloc(loc, left, right);
+ if (dreg != NFT_REG_VERDICT)
+ return netlink_set_register(ctx, dreg, expr);
+ } else {
+ expr = relational_expr_alloc(loc, OP_EQ, left, right);
+ }
+
+ if (nftnl_expr_is_set(nle, NFTNL_EXPR_LOOKUP_FLAGS)) {
+ flag = nftnl_expr_get_u32(nle, NFTNL_EXPR_LOOKUP_FLAGS);
+ if (flag & NFT_LOOKUP_F_INV)
+ expr->op = OP_NEQ;
+ }
+
+ ctx->stmt = expr_stmt_alloc(loc, expr);
+}
+
+static struct expr *netlink_parse_bitwise_bool(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const struct nftnl_expr *nle,
+ enum nft_registers sreg,
+ struct expr *left)
+
+{
+ struct nft_data_delinearize nld;
+ struct expr *expr, *mask, *xor, *or;
+ mpz_t m, x, o;
+
+ expr = left;
+
+ nld.value = nftnl_expr_get(nle, NFTNL_EXPR_BITWISE_MASK, &nld.len);
+ mask = netlink_alloc_value(loc, &nld);
+ mpz_init_set(m, mask->value);
+
+ nld.value = nftnl_expr_get(nle, NFTNL_EXPR_BITWISE_XOR, &nld.len);
+ xor = netlink_alloc_value(loc, &nld);
+ mpz_init_set(x, xor->value);
+
+ mpz_init_set_ui(o, 0);
+ if (mpz_scan0(m, 0) != mask->len || mpz_cmp_ui(x, 0)) {
+ /* o = (m & x) ^ x */
+ mpz_and(o, m, x);
+ mpz_xor(o, o, x);
+ /* x &= m */
+ mpz_and(x, x, m);
+ /* m |= o */
+ mpz_ior(m, m, o);
+ }
+
+ if (left->len > 0 && mpz_scan0(m, 0) >= left->len) {
+ /* mask encompasses the entire value */
+ expr_free(mask);
+ } else {
+ mpz_set(mask->value, m);
+ expr = binop_expr_alloc(loc, OP_AND, expr, mask);
+ expr->len = left->len;
+ }
+
+ if (mpz_cmp_ui(x, 0)) {
+ mpz_set(xor->value, x);
+ expr = binop_expr_alloc(loc, OP_XOR, expr, xor);
+ expr->len = left->len;
+ } else
+ expr_free(xor);
+
+ if (mpz_cmp_ui(o, 0)) {
+ nld.value = nftnl_expr_get(nle, NFTNL_EXPR_BITWISE_XOR,
+ &nld.len);
+
+ or = netlink_alloc_value(loc, &nld);
+ mpz_set(or->value, o);
+ expr = binop_expr_alloc(loc, OP_OR, expr, or);
+ expr->len = left->len;
+ }
+
+ mpz_clear(m);
+ mpz_clear(x);
+ mpz_clear(o);
+
+ return expr;
+}
+
+static struct expr *netlink_parse_bitwise_shift(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const struct nftnl_expr *nle,
+ enum ops op,
+ enum nft_registers sreg,
+ struct expr *left)
+{
+ struct nft_data_delinearize nld;
+ struct expr *expr, *right;
+
+ nld.value = nftnl_expr_get(nle, NFTNL_EXPR_BITWISE_DATA, &nld.len);
+ right = netlink_alloc_value(loc, &nld);
+ right->byteorder = BYTEORDER_HOST_ENDIAN;
+
+ expr = binop_expr_alloc(loc, op, left, right);
+ expr->len = nftnl_expr_get_u32(nle, NFTNL_EXPR_BITWISE_LEN) * BITS_PER_BYTE;
+
+ return expr;
+}
+
+static void netlink_parse_bitwise(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const struct nftnl_expr *nle)
+{
+ enum nft_registers sreg, dreg;
+ struct expr *expr, *left;
+ enum nft_bitwise_ops op;
+
+ sreg = netlink_parse_register(nle, NFTNL_EXPR_BITWISE_SREG);
+ left = netlink_get_register(ctx, loc, sreg);
+ if (left == NULL)
+ return netlink_error(ctx, loc,
+ "Bitwise expression has no left "
+ "hand side");
+
+ op = nftnl_expr_get_u32(nle, NFTNL_EXPR_BITWISE_OP);
+
+ switch (op) {
+ case NFT_BITWISE_BOOL:
+ expr = netlink_parse_bitwise_bool(ctx, loc, nle, sreg,
+ left);
+ break;
+ case NFT_BITWISE_LSHIFT:
+ expr = netlink_parse_bitwise_shift(ctx, loc, nle, OP_LSHIFT,
+ sreg, left);
+ break;
+ case NFT_BITWISE_RSHIFT:
+ expr = netlink_parse_bitwise_shift(ctx, loc, nle, OP_RSHIFT,
+ sreg, left);
+ break;
+ default:
+ BUG("invalid bitwise operation %u\n", op);
+ }
+
+ dreg = netlink_parse_register(nle, NFTNL_EXPR_BITWISE_DREG);
+ netlink_set_register(ctx, dreg, expr);
+}
+
+static void netlink_parse_byteorder(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const struct nftnl_expr *nle)
+{
+ enum nft_registers sreg, dreg;
+ struct expr *expr, *arg;
+ enum ops op;
+
+ sreg = netlink_parse_register(nle, NFTNL_EXPR_BYTEORDER_SREG);
+ arg = netlink_get_register(ctx, loc, sreg);
+ if (arg == NULL)
+ return netlink_error(ctx, loc,
+ "Byteorder expression has no left "
+ "hand side");
+
+ switch (nftnl_expr_get_u32(nle, NFTNL_EXPR_BYTEORDER_OP)) {
+ case NFT_BYTEORDER_NTOH:
+ op = OP_NTOH;
+ break;
+ case NFT_BYTEORDER_HTON:
+ op = OP_HTON;
+ break;
+ default:
+ BUG("invalid byteorder operation %u\n",
+ nftnl_expr_get_u32(nle, NFTNL_EXPR_BYTEORDER_OP));
+ }
+
+ expr = unary_expr_alloc(loc, op, arg);
+ expr->len = arg->len;
+
+ dreg = netlink_parse_register(nle, NFTNL_EXPR_BYTEORDER_DREG);
+ netlink_set_register(ctx, dreg, expr);
+}
+
+static void netlink_parse_payload_expr(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const struct nftnl_expr *nle)
+{
+ enum nft_registers dreg;
+ uint32_t base, offset, len;
+ struct expr *expr;
+
+ base = nftnl_expr_get_u32(nle, NFTNL_EXPR_PAYLOAD_BASE) + 1;
+
+ if (base == NFT_PAYLOAD_TUN_HEADER + 1)
+ base = NFT_PAYLOAD_INNER_HEADER + 1;
+
+ offset = nftnl_expr_get_u32(nle, NFTNL_EXPR_PAYLOAD_OFFSET) * BITS_PER_BYTE;
+ len = nftnl_expr_get_u32(nle, NFTNL_EXPR_PAYLOAD_LEN) * BITS_PER_BYTE;
+
+ expr = payload_expr_alloc(loc, NULL, 0);
+ payload_init_raw(expr, base, offset, len);
+
+ dreg = netlink_parse_register(nle, NFTNL_EXPR_PAYLOAD_DREG);
+
+ if (ctx->inner)
+ ctx->inner_reg = dreg;
+
+ netlink_set_register(ctx, dreg, expr);
+}
+
+static void netlink_parse_inner(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const struct nftnl_expr *nle)
+{
+ const struct proto_desc *inner_desc;
+ const struct nftnl_expr *inner_nle;
+ uint32_t hdrsize, flags, type;
+ struct expr *expr;
+
+ hdrsize = nftnl_expr_get_u32(nle, NFTNL_EXPR_INNER_HDRSIZE);
+ type = nftnl_expr_get_u32(nle, NFTNL_EXPR_INNER_TYPE);
+ flags = nftnl_expr_get_u32(nle, NFTNL_EXPR_INNER_FLAGS);
+
+ inner_nle = nftnl_expr_get(nle, NFTNL_EXPR_INNER_EXPR, NULL);
+ if (!inner_nle) {
+ netlink_error(ctx, loc, "Could not parse inner expression");
+ return;
+ }
+
+ inner_desc = proto_find_inner(type, hdrsize, flags);
+
+ ctx->inner = true;
+ if (netlink_parse_expr(inner_nle, ctx) < 0) {
+ ctx->inner = false;
+ return;
+ }
+ ctx->inner = false;
+
+ expr = netlink_get_register(ctx, loc, ctx->inner_reg);
+ assert(expr);
+
+ if (expr->etype == EXPR_PAYLOAD &&
+ expr->payload.base == PROTO_BASE_INNER_HDR) {
+ const struct proto_hdr_template *tmpl;
+ unsigned int i;
+
+ for (i = 1; i < array_size(inner_desc->templates); i++) {
+ tmpl = &inner_desc->templates[i];
+
+ if (tmpl->len == 0)
+ return;
+
+ if (tmpl->offset != expr->payload.offset ||
+ tmpl->len != expr->len)
+ continue;
+
+ expr->payload.desc = inner_desc;
+ expr->payload.tmpl = tmpl;
+ break;
+ }
+ }
+
+ switch (expr->etype) {
+ case EXPR_PAYLOAD:
+ expr->payload.inner_desc = inner_desc;
+ break;
+ case EXPR_META:
+ expr->meta.inner_desc = inner_desc;
+ break;
+ default:
+ assert(0);
+ break;
+ }
+
+ netlink_set_register(ctx, ctx->inner_reg, expr);
+}
+
+static void netlink_parse_payload_stmt(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const struct nftnl_expr *nle)
+{
+ enum nft_registers sreg;
+ uint32_t base, offset, len;
+ struct expr *expr, *val;
+ struct stmt *stmt;
+
+ base = nftnl_expr_get_u32(nle, NFTNL_EXPR_PAYLOAD_BASE) + 1;
+ offset = nftnl_expr_get_u32(nle, NFTNL_EXPR_PAYLOAD_OFFSET) * BITS_PER_BYTE;
+ len = nftnl_expr_get_u32(nle, NFTNL_EXPR_PAYLOAD_LEN) * BITS_PER_BYTE;
+
+ sreg = netlink_parse_register(nle, NFTNL_EXPR_PAYLOAD_SREG);
+ val = netlink_get_register(ctx, loc, sreg);
+ if (val == NULL)
+ return netlink_error(ctx, loc,
+ "payload statement has no expression");
+
+ expr = payload_expr_alloc(loc, NULL, 0);
+ payload_init_raw(expr, base, offset, len);
+
+ stmt = payload_stmt_alloc(loc, expr, val);
+ rule_stmt_append(ctx->rule, stmt);
+}
+
+static void netlink_parse_payload(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const struct nftnl_expr *nle)
+{
+ if (nftnl_expr_is_set(nle, NFTNL_EXPR_PAYLOAD_DREG))
+ netlink_parse_payload_expr(ctx, loc, nle);
+ else
+ netlink_parse_payload_stmt(ctx, loc, nle);
+}
+
+static void netlink_parse_exthdr(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const struct nftnl_expr *nle)
+{
+ uint32_t offset, len, flags;
+ enum nft_registers dreg;
+ enum nft_exthdr_op op;
+ uint8_t type;
+ struct expr *expr;
+
+ type = nftnl_expr_get_u8(nle, NFTNL_EXPR_EXTHDR_TYPE);
+ offset = nftnl_expr_get_u32(nle, NFTNL_EXPR_EXTHDR_OFFSET) * BITS_PER_BYTE;
+ len = nftnl_expr_get_u32(nle, NFTNL_EXPR_EXTHDR_LEN) * BITS_PER_BYTE;
+ op = nftnl_expr_get_u32(nle, NFTNL_EXPR_EXTHDR_OP);
+ flags = nftnl_expr_get_u32(nle, NFTNL_EXPR_EXTHDR_FLAGS);
+
+ expr = exthdr_expr_alloc(loc, NULL, 0);
+ exthdr_init_raw(expr, type, offset, len, op, flags);
+
+ if (nftnl_expr_is_set(nle, NFTNL_EXPR_EXTHDR_DREG)) {
+ dreg = netlink_parse_register(nle, NFTNL_EXPR_EXTHDR_DREG);
+ netlink_set_register(ctx, dreg, expr);
+ } else if (nftnl_expr_is_set(nle, NFTNL_EXPR_EXTHDR_SREG)) {
+ enum nft_registers sreg;
+ struct stmt *stmt;
+ struct expr *val;
+
+ sreg = netlink_parse_register(nle, NFTNL_EXPR_EXTHDR_SREG);
+ val = netlink_get_register(ctx, loc, sreg);
+ if (val == NULL) {
+ expr_free(expr);
+ return netlink_error(ctx, loc,
+ "exthdr statement has no expression");
+ }
+
+ expr_set_type(val, expr->dtype, expr->byteorder);
+
+ stmt = exthdr_stmt_alloc(loc, expr, val);
+ rule_stmt_append(ctx->rule, stmt);
+ } else {
+ struct stmt *stmt = optstrip_stmt_alloc(loc, expr);
+
+ rule_stmt_append(ctx->rule, stmt);
+ }
+}
+
+static void netlink_parse_hash(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const struct nftnl_expr *nle)
+{
+ enum nft_registers sreg, dreg;
+ struct expr *expr, *hexpr;
+ uint32_t mod, seed, len, offset;
+ enum nft_hash_types type;
+ bool seed_set;
+
+ type = nftnl_expr_get_u32(nle, NFTNL_EXPR_HASH_TYPE);
+ offset = nftnl_expr_get_u32(nle, NFTNL_EXPR_HASH_OFFSET);
+ seed_set = nftnl_expr_is_set(nle, NFTNL_EXPR_HASH_SEED);
+ seed = nftnl_expr_get_u32(nle, NFTNL_EXPR_HASH_SEED);
+ mod = nftnl_expr_get_u32(nle, NFTNL_EXPR_HASH_MODULUS);
+
+ expr = hash_expr_alloc(loc, mod, seed_set, seed, offset, type);
+
+ if (type != NFT_HASH_SYM) {
+ sreg = netlink_parse_register(nle, NFTNL_EXPR_HASH_SREG);
+ hexpr = netlink_get_register(ctx, loc, sreg);
+
+ if (hexpr == NULL) {
+ netlink_error(ctx, loc,
+ "hash statement has no expression");
+ goto out_err;
+ }
+ len = nftnl_expr_get_u32(nle,
+ NFTNL_EXPR_HASH_LEN) * BITS_PER_BYTE;
+ if (hexpr->len < len) {
+ expr_free(hexpr);
+ hexpr = netlink_parse_concat_expr(ctx, loc, sreg, len);
+ if (hexpr == NULL)
+ goto out_err;
+ }
+ expr->hash.expr = hexpr;
+ }
+
+ dreg = netlink_parse_register(nle, NFTNL_EXPR_HASH_DREG);
+ netlink_set_register(ctx, dreg, expr);
+ return;
+out_err:
+ expr_free(expr);
+}
+
+static void netlink_parse_fib(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const struct nftnl_expr *nle)
+{
+ enum nft_registers dreg;
+ struct expr *expr;
+ uint32_t flags, result;
+
+ flags = nftnl_expr_get_u32(nle, NFTNL_EXPR_FIB_FLAGS);
+ result = nftnl_expr_get_u32(nle, NFTNL_EXPR_FIB_RESULT);
+
+ expr = fib_expr_alloc(loc, flags, result);
+
+ dreg = netlink_parse_register(nle, NFTNL_EXPR_FIB_DREG);
+ netlink_set_register(ctx, dreg, expr);
+}
+
+static void netlink_parse_meta_expr(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const struct nftnl_expr *nle)
+{
+ enum nft_registers dreg;
+ uint32_t key;
+ struct expr *expr;
+
+ key = nftnl_expr_get_u32(nle, NFTNL_EXPR_META_KEY);
+ expr = meta_expr_alloc(loc, key);
+
+ dreg = netlink_parse_register(nle, NFTNL_EXPR_META_DREG);
+ if (ctx->inner)
+ ctx->inner_reg = dreg;
+
+ netlink_set_register(ctx, dreg, expr);
+}
+
+static void netlink_parse_socket(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const struct nftnl_expr *nle)
+{
+ enum nft_registers dreg;
+ uint32_t key, level;
+ struct expr * expr;
+
+ key = nftnl_expr_get_u32(nle, NFTNL_EXPR_SOCKET_KEY);
+ level = nftnl_expr_get_u32(nle, NFTNL_EXPR_SOCKET_LEVEL);
+ expr = socket_expr_alloc(loc, key, level);
+
+ dreg = netlink_parse_register(nle, NFTNL_EXPR_SOCKET_DREG);
+ netlink_set_register(ctx, dreg, expr);
+}
+
+static void netlink_parse_osf(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const struct nftnl_expr *nle)
+{
+ enum nft_registers dreg;
+ struct expr *expr;
+ uint32_t flags;
+ uint8_t ttl;
+
+ ttl = nftnl_expr_get_u8(nle, NFTNL_EXPR_OSF_TTL);
+ flags = nftnl_expr_get_u32(nle, NFTNL_EXPR_OSF_FLAGS);
+ expr = osf_expr_alloc(loc, ttl, flags);
+
+ dreg = netlink_parse_register(nle, NFTNL_EXPR_OSF_DREG);
+ netlink_set_register(ctx, dreg, expr);
+}
+
+static void netlink_parse_meta_stmt(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const struct nftnl_expr *nle)
+{
+ enum nft_registers sreg;
+ uint32_t key;
+ struct stmt *stmt;
+ struct expr *expr;
+
+ sreg = netlink_parse_register(nle, NFTNL_EXPR_META_SREG);
+ expr = netlink_get_register(ctx, loc, sreg);
+ if (expr == NULL)
+ return netlink_error(ctx, loc,
+ "meta statement has no expression");
+
+ key = nftnl_expr_get_u32(nle, NFTNL_EXPR_META_KEY);
+ stmt = meta_stmt_alloc(loc, key, expr);
+
+ if (stmt->meta.tmpl)
+ expr_set_type(expr, stmt->meta.tmpl->dtype, stmt->meta.tmpl->byteorder);
+
+ ctx->stmt = stmt;
+}
+
+static void netlink_parse_meta(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const struct nftnl_expr *nle)
+{
+ if (nftnl_expr_is_set(nle, NFTNL_EXPR_META_DREG))
+ netlink_parse_meta_expr(ctx, loc, nle);
+ else
+ netlink_parse_meta_stmt(ctx, loc, nle);
+}
+
+static void netlink_parse_rt(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const struct nftnl_expr *nle)
+{
+ enum nft_registers dreg;
+ uint32_t key;
+ struct expr *expr;
+
+ key = nftnl_expr_get_u32(nle, NFTNL_EXPR_RT_KEY);
+ expr = rt_expr_alloc(loc, key, false);
+
+ dreg = netlink_parse_register(nle, NFTNL_EXPR_RT_DREG);
+ netlink_set_register(ctx, dreg, expr);
+}
+
+static void netlink_parse_numgen(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const struct nftnl_expr *nle)
+{
+ uint32_t type, until, offset;
+ enum nft_registers dreg;
+ struct expr *expr;
+
+ type = nftnl_expr_get_u32(nle, NFTNL_EXPR_NG_TYPE);
+ until = nftnl_expr_get_u32(nle, NFTNL_EXPR_NG_MODULUS);
+ offset = nftnl_expr_get_u32(nle, NFTNL_EXPR_NG_OFFSET);
+
+ expr = numgen_expr_alloc(loc, type, until, offset);
+ dreg = netlink_parse_register(nle, NFTNL_EXPR_NG_DREG);
+ netlink_set_register(ctx, dreg, expr);
+}
+
+static void netlink_parse_notrack(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const struct nftnl_expr *nle)
+{
+ ctx->stmt = notrack_stmt_alloc(loc);
+}
+
+static void netlink_parse_flow_offload(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const struct nftnl_expr *nle)
+{
+ const char *table_name;
+
+ table_name = xstrdup(nftnl_expr_get_str(nle, NFTNL_EXPR_FLOW_TABLE_NAME));
+ ctx->stmt = flow_offload_stmt_alloc(loc, table_name);
+}
+
+static void netlink_parse_ct_stmt(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const struct nftnl_expr *nle)
+{
+ enum nft_registers sreg;
+ uint32_t key;
+ struct stmt *stmt;
+ struct expr *expr;
+ int8_t dir = -1;
+
+ sreg = netlink_parse_register(nle, NFTNL_EXPR_CT_SREG);
+ expr = netlink_get_register(ctx, loc, sreg);
+ if (expr == NULL)
+ return netlink_error(ctx, loc,
+ "ct statement has no expression");
+
+ if (nftnl_expr_is_set(nle, NFTNL_EXPR_CT_DIR))
+ dir = nftnl_expr_get_u8(nle, NFTNL_EXPR_CT_DIR);
+
+ key = nftnl_expr_get_u32(nle, NFTNL_EXPR_CT_KEY);
+ stmt = ct_stmt_alloc(loc, key, dir, expr);
+ expr_set_type(expr, stmt->ct.tmpl->dtype, stmt->ct.tmpl->byteorder);
+
+ ctx->stmt = stmt;
+}
+
+static void netlink_parse_ct_expr(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const struct nftnl_expr *nle)
+{
+ struct expr *expr = NULL;
+ enum nft_registers dreg;
+ int8_t dir = -1;
+ uint32_t key;
+
+ if (nftnl_expr_is_set(nle, NFTNL_EXPR_CT_DIR))
+ dir = nftnl_expr_get_u8(nle, NFTNL_EXPR_CT_DIR);
+
+ key = nftnl_expr_get_u32(nle, NFTNL_EXPR_CT_KEY);
+ expr = ct_expr_alloc(loc, key, dir);
+
+ dreg = netlink_parse_register(nle, NFTNL_EXPR_CT_DREG);
+ netlink_set_register(ctx, dreg, expr);
+}
+
+static void netlink_parse_ct(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const struct nftnl_expr *nle)
+{
+ if (nftnl_expr_is_set(nle, NFTNL_EXPR_CT_DREG))
+ netlink_parse_ct_expr(ctx, loc, nle);
+ else
+ netlink_parse_ct_stmt(ctx, loc, nle);
+}
+
+static void netlink_parse_connlimit(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const struct nftnl_expr *nle)
+{
+ struct stmt *stmt;
+
+ stmt = connlimit_stmt_alloc(loc);
+ stmt->connlimit.count =
+ nftnl_expr_get_u32(nle, NFTNL_EXPR_CONNLIMIT_COUNT);
+ stmt->connlimit.flags =
+ nftnl_expr_get_u32(nle, NFTNL_EXPR_CONNLIMIT_FLAGS);
+
+ ctx->stmt = stmt;
+}
+
+static void netlink_parse_counter(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const struct nftnl_expr *nle)
+{
+ struct stmt *stmt;
+
+ stmt = counter_stmt_alloc(loc);
+ stmt->counter.packets = nftnl_expr_get_u64(nle, NFTNL_EXPR_CTR_PACKETS);
+ stmt->counter.bytes = nftnl_expr_get_u64(nle, NFTNL_EXPR_CTR_BYTES);
+
+ ctx->stmt = stmt;
+}
+
+static void netlink_parse_last(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const struct nftnl_expr *nle)
+{
+ struct stmt *stmt;
+
+ stmt = last_stmt_alloc(loc);
+ stmt->last.used = nftnl_expr_get_u64(nle, NFTNL_EXPR_LAST_MSECS);
+ stmt->last.set = nftnl_expr_get_u32(nle, NFTNL_EXPR_LAST_SET);
+
+ ctx->stmt = stmt;
+}
+
+static void netlink_parse_log(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const struct nftnl_expr *nle)
+{
+ struct stmt *stmt;
+ const char *prefix;
+
+ stmt = log_stmt_alloc(loc);
+ prefix = nftnl_expr_get_str(nle, NFTNL_EXPR_LOG_PREFIX);
+ if (nftnl_expr_is_set(nle, NFTNL_EXPR_LOG_PREFIX)) {
+ stmt->log.prefix = constant_expr_alloc(&internal_location,
+ &string_type,
+ BYTEORDER_HOST_ENDIAN,
+ (strlen(prefix) + 1) * BITS_PER_BYTE,
+ prefix);
+ stmt->log.flags |= STMT_LOG_PREFIX;
+ }
+ if (nftnl_expr_is_set(nle, NFTNL_EXPR_LOG_GROUP)) {
+ stmt->log.group = nftnl_expr_get_u16(nle, NFTNL_EXPR_LOG_GROUP);
+ stmt->log.flags |= STMT_LOG_GROUP;
+ }
+ if (nftnl_expr_is_set(nle, NFTNL_EXPR_LOG_SNAPLEN)) {
+ stmt->log.snaplen =
+ nftnl_expr_get_u32(nle, NFTNL_EXPR_LOG_SNAPLEN);
+ stmt->log.flags |= STMT_LOG_SNAPLEN;
+ }
+ if (nftnl_expr_is_set(nle, NFTNL_EXPR_LOG_QTHRESHOLD)) {
+ stmt->log.qthreshold =
+ nftnl_expr_get_u16(nle, NFTNL_EXPR_LOG_QTHRESHOLD);
+ stmt->log.flags |= STMT_LOG_QTHRESHOLD;
+ }
+ if (nftnl_expr_is_set(nle, NFTNL_EXPR_LOG_LEVEL)) {
+ stmt->log.level =
+ nftnl_expr_get_u32(nle, NFTNL_EXPR_LOG_LEVEL);
+ stmt->log.flags |= STMT_LOG_LEVEL;
+ }
+ if (nftnl_expr_is_set(nle, NFTNL_EXPR_LOG_FLAGS)) {
+ stmt->log.logflags =
+ nftnl_expr_get_u32(nle, NFTNL_EXPR_LOG_FLAGS);
+ }
+
+ ctx->stmt = stmt;
+}
+
+static void netlink_parse_limit(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const struct nftnl_expr *nle)
+{
+ struct stmt *stmt;
+
+ stmt = limit_stmt_alloc(loc);
+ stmt->limit.rate = nftnl_expr_get_u64(nle, NFTNL_EXPR_LIMIT_RATE);
+ stmt->limit.unit = nftnl_expr_get_u64(nle, NFTNL_EXPR_LIMIT_UNIT);
+ stmt->limit.type = nftnl_expr_get_u32(nle, NFTNL_EXPR_LIMIT_TYPE);
+ stmt->limit.burst = nftnl_expr_get_u32(nle, NFTNL_EXPR_LIMIT_BURST);
+ stmt->limit.flags = nftnl_expr_get_u32(nle, NFTNL_EXPR_LIMIT_FLAGS);
+
+ ctx->stmt = stmt;
+}
+
+static void netlink_parse_quota(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const struct nftnl_expr *nle)
+{
+ struct stmt *stmt;
+
+ stmt = quota_stmt_alloc(loc);
+ stmt->quota.bytes = nftnl_expr_get_u64(nle, NFTNL_EXPR_QUOTA_BYTES);
+ stmt->quota.used =
+ nftnl_expr_get_u64(nle, NFTNL_EXPR_QUOTA_CONSUMED);
+ stmt->quota.flags = nftnl_expr_get_u32(nle, NFTNL_EXPR_QUOTA_FLAGS);
+
+ ctx->stmt = stmt;
+}
+
+static void netlink_parse_reject(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const struct nftnl_expr *expr)
+{
+ struct stmt *stmt;
+ uint8_t icmp_code;
+
+ stmt = reject_stmt_alloc(loc);
+ stmt->reject.type = nftnl_expr_get_u32(expr, NFTNL_EXPR_REJECT_TYPE);
+ icmp_code = nftnl_expr_get_u8(expr, NFTNL_EXPR_REJECT_CODE);
+ stmt->reject.icmp_code = icmp_code;
+ stmt->reject.expr = constant_expr_alloc(loc, &integer_type,
+ BYTEORDER_HOST_ENDIAN, 8,
+ &icmp_code);
+ ctx->stmt = stmt;
+}
+
+static bool is_nat_addr_map(const struct expr *addr, uint8_t family,
+ struct stmt *stmt)
+{
+ const struct expr *mappings, *data;
+ const struct set *set;
+
+ if (!addr ||
+ expr_ops(addr)->type != EXPR_MAP)
+ return false;
+
+ mappings = addr->right;
+ if (expr_ops(mappings)->type != EXPR_SET_REF)
+ return false;
+
+ set = mappings->set;
+ data = set->data;
+
+ if (!(data->flags & EXPR_F_INTERVAL))
+ return false;
+
+ stmt->nat.family = family;
+
+ /* if we're dealing with an address:address map,
+ * the length will be bit_sizeof(addr) + 32 (one register).
+ */
+ switch (family) {
+ case NFPROTO_IPV4:
+ if (data->len == 32 + 32) {
+ stmt->nat.type_flags |= STMT_NAT_F_INTERVAL;
+ return true;
+ } else if (data->len == 32 + 32 + 32 + 32) {
+ stmt->nat.type_flags |= STMT_NAT_F_INTERVAL |
+ STMT_NAT_F_CONCAT;
+ return true;
+ }
+ break;
+ case NFPROTO_IPV6:
+ if (data->len == 128 + 128) {
+ stmt->nat.type_flags |= STMT_NAT_F_INTERVAL;
+ return true;
+ } else if (data->len == 128 + 32 + 128 + 32) {
+ stmt->nat.type_flags |= STMT_NAT_F_INTERVAL |
+ STMT_NAT_F_CONCAT;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static bool is_nat_proto_map(const struct expr *addr, uint8_t family)
+{
+ const struct expr *mappings, *data;
+ const struct set *set;
+
+ if (!addr ||
+ expr_ops(addr)->type != EXPR_MAP)
+ return false;
+
+ mappings = addr->right;
+ if (expr_ops(mappings)->type != EXPR_SET_REF)
+ return false;
+
+ set = mappings->set;
+ data = set->data;
+
+ /* if we're dealing with an address:inet_service map,
+ * the length will be bit_sizeof(addr) + 32 (one register).
+ */
+ switch (family) {
+ case NFPROTO_IPV4:
+ return data->len == 32 + 32;
+ case NFPROTO_IPV6:
+ return data->len == 128 + 32;
+ }
+
+ return false;
+}
+
+static void netlink_parse_nat(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const struct nftnl_expr *nle)
+{
+ struct stmt *stmt;
+ struct expr *addr, *proto;
+ enum nft_registers reg1, reg2;
+ int family;
+
+ stmt = nat_stmt_alloc(loc,
+ nftnl_expr_get_u32(nle, NFTNL_EXPR_NAT_TYPE));
+
+ family = nftnl_expr_get_u32(nle, NFTNL_EXPR_NAT_FAMILY);
+
+ if (ctx->table->handle.family == NFPROTO_INET)
+ stmt->nat.family = family;
+
+ if (nftnl_expr_is_set(nle, NFTNL_EXPR_NAT_FLAGS))
+ stmt->nat.flags = nftnl_expr_get_u32(nle, NFTNL_EXPR_NAT_FLAGS);
+
+ if (stmt->nat.flags & NF_NAT_RANGE_NETMAP)
+ stmt->nat.type_flags |= STMT_NAT_F_PREFIX;
+
+ addr = NULL;
+ reg1 = netlink_parse_register(nle, NFTNL_EXPR_NAT_REG_ADDR_MIN);
+ if (reg1) {
+ addr = netlink_get_register(ctx, loc, reg1);
+ if (addr == NULL) {
+ netlink_error(ctx, loc,
+ "NAT statement has no address expression");
+ goto out_err;
+ }
+
+ if (family == NFPROTO_IPV4)
+ expr_set_type(addr, &ipaddr_type, BYTEORDER_BIG_ENDIAN);
+ else
+ expr_set_type(addr, &ip6addr_type,
+ BYTEORDER_BIG_ENDIAN);
+ stmt->nat.addr = addr;
+ }
+
+ if (is_nat_addr_map(addr, family, stmt)) {
+ stmt->nat.family = family;
+ ctx->stmt = stmt;
+ return;
+ }
+
+ reg2 = netlink_parse_register(nle, NFTNL_EXPR_NAT_REG_ADDR_MAX);
+ if (reg2 && reg2 != reg1) {
+ addr = netlink_get_register(ctx, loc, reg2);
+ if (addr == NULL) {
+ netlink_error(ctx, loc,
+ "NAT statement has no address expression");
+ goto out_err;
+ }
+
+ if (family == NFPROTO_IPV4)
+ expr_set_type(addr, &ipaddr_type, BYTEORDER_BIG_ENDIAN);
+ else
+ expr_set_type(addr, &ip6addr_type,
+ BYTEORDER_BIG_ENDIAN);
+ if (stmt->nat.addr != NULL) {
+ addr = range_expr_alloc(loc, stmt->nat.addr, addr);
+ addr = range_expr_to_prefix(addr);
+ }
+ stmt->nat.addr = addr;
+ }
+
+ if (is_nat_proto_map(addr, family)) {
+ stmt->nat.family = family;
+ stmt->nat.type_flags |= STMT_NAT_F_CONCAT;
+ ctx->stmt = stmt;
+ return;
+ }
+
+ reg1 = netlink_parse_register(nle, NFTNL_EXPR_NAT_REG_PROTO_MIN);
+ if (reg1) {
+ proto = netlink_get_register(ctx, loc, reg1);
+ if (proto == NULL) {
+ netlink_error(ctx, loc,
+ "NAT statement has no proto expression");
+ goto out_err;
+ }
+
+ expr_set_type(proto, &inet_service_type, BYTEORDER_BIG_ENDIAN);
+ stmt->nat.proto = proto;
+ }
+
+ reg2 = netlink_parse_register(nle, NFTNL_EXPR_NAT_REG_PROTO_MAX);
+ if (reg2 && reg2 != reg1) {
+ proto = netlink_get_register(ctx, loc, reg2);
+ if (proto == NULL) {
+ netlink_error(ctx, loc,
+ "NAT statement has no proto expression");
+ goto out_err;
+ }
+
+ expr_set_type(proto, &inet_service_type, BYTEORDER_BIG_ENDIAN);
+ if (stmt->nat.proto != NULL)
+ proto = range_expr_alloc(loc, stmt->nat.proto, proto);
+ stmt->nat.proto = proto;
+ }
+
+ ctx->stmt = stmt;
+ return;
+out_err:
+ stmt_free(stmt);
+}
+
+static void netlink_parse_synproxy(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const struct nftnl_expr *nle)
+{
+ struct stmt *stmt;
+
+ stmt = synproxy_stmt_alloc(loc);
+ stmt->synproxy.mss = nftnl_expr_get_u16(nle, NFTNL_EXPR_SYNPROXY_MSS);
+ stmt->synproxy.wscale = nftnl_expr_get_u8(nle,
+ NFTNL_EXPR_SYNPROXY_WSCALE);
+ stmt->synproxy.flags = nftnl_expr_get_u32(nle,
+ NFTNL_EXPR_SYNPROXY_FLAGS);
+
+ ctx->stmt = stmt;
+}
+
+static void netlink_parse_tproxy(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const struct nftnl_expr *nle)
+{
+ struct stmt *stmt;
+ struct expr *addr, *port;
+ enum nft_registers reg;
+
+ stmt = tproxy_stmt_alloc(loc);
+ stmt->tproxy.family = nftnl_expr_get_u32(nle, NFTNL_EXPR_TPROXY_FAMILY);
+ stmt->tproxy.table_family = ctx->table->handle.family;
+
+ reg = netlink_parse_register(nle, NFTNL_EXPR_TPROXY_REG_ADDR);
+ if (reg) {
+ addr = netlink_get_register(ctx, loc, reg);
+ if (addr == NULL)
+ goto err;
+
+ switch (stmt->tproxy.family) {
+ case NFPROTO_IPV4:
+ expr_set_type(addr, &ipaddr_type, BYTEORDER_BIG_ENDIAN);
+ break;
+ case NFPROTO_IPV6:
+ expr_set_type(addr, &ip6addr_type, BYTEORDER_BIG_ENDIAN);
+ break;
+ default:
+ netlink_error(ctx, loc,
+ "tproxy address must be IPv4 or IPv6");
+ goto err;
+ }
+ stmt->tproxy.addr = addr;
+ }
+
+ reg = netlink_parse_register(nle, NFTNL_EXPR_TPROXY_REG_PORT);
+ if (reg) {
+ port = netlink_get_register(ctx, loc, reg);
+ if (port == NULL)
+ goto err;
+ expr_set_type(port, &inet_service_type, BYTEORDER_BIG_ENDIAN);
+ stmt->tproxy.port = port;
+ }
+
+ ctx->stmt = stmt;
+ return;
+err:
+ stmt_free(stmt);
+}
+
+static void netlink_parse_masq(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const struct nftnl_expr *nle)
+{
+ enum nft_registers reg1, reg2;
+ struct expr *proto;
+ struct stmt *stmt;
+ uint32_t flags = 0;
+
+ if (nftnl_expr_is_set(nle, NFTNL_EXPR_MASQ_FLAGS))
+ flags = nftnl_expr_get_u32(nle, NFTNL_EXPR_MASQ_FLAGS);
+
+ stmt = nat_stmt_alloc(loc, NFT_NAT_MASQ);
+ stmt->nat.flags = flags;
+
+ reg1 = netlink_parse_register(nle, NFTNL_EXPR_MASQ_REG_PROTO_MIN);
+ if (reg1) {
+ proto = netlink_get_register(ctx, loc, reg1);
+ if (proto == NULL) {
+ netlink_error(ctx, loc,
+ "MASQUERADE statement has no proto expression");
+ goto out_err;
+ }
+ expr_set_type(proto, &inet_service_type, BYTEORDER_BIG_ENDIAN);
+ stmt->nat.proto = proto;
+ }
+
+ reg2 = netlink_parse_register(nle, NFTNL_EXPR_MASQ_REG_PROTO_MAX);
+ if (reg2 && reg2 != reg1) {
+ proto = netlink_get_register(ctx, loc, reg2);
+ if (proto == NULL) {
+ netlink_error(ctx, loc,
+ "MASQUERADE statement has no proto expression");
+ goto out_err;
+ }
+ expr_set_type(proto, &inet_service_type, BYTEORDER_BIG_ENDIAN);
+ if (stmt->nat.proto != NULL)
+ proto = range_expr_alloc(loc, stmt->nat.proto, proto);
+ stmt->nat.proto = proto;
+ }
+
+ ctx->stmt = stmt;
+ return;
+out_err:
+ stmt_free(stmt);
+}
+
+static void netlink_parse_redir(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const struct nftnl_expr *nle)
+{
+ struct stmt *stmt;
+ struct expr *proto;
+ enum nft_registers reg1, reg2;
+ uint32_t flags;
+
+ stmt = nat_stmt_alloc(loc, NFT_NAT_REDIR);
+
+ if (nftnl_expr_is_set(nle, NFTNL_EXPR_REDIR_FLAGS)) {
+ flags = nftnl_expr_get_u32(nle, NFTNL_EXPR_REDIR_FLAGS);
+ stmt->nat.flags = flags;
+ }
+
+ reg1 = netlink_parse_register(nle, NFTNL_EXPR_REDIR_REG_PROTO_MIN);
+ if (reg1) {
+ proto = netlink_get_register(ctx, loc, reg1);
+ if (proto == NULL) {
+ netlink_error(ctx, loc,
+ "redirect statement has no proto expression");
+ goto out_err;
+ }
+
+ expr_set_type(proto, &inet_service_type, BYTEORDER_BIG_ENDIAN);
+ stmt->nat.proto = proto;
+ }
+
+ reg2 = netlink_parse_register(nle, NFTNL_EXPR_REDIR_REG_PROTO_MAX);
+ if (reg2 && reg2 != reg1) {
+ proto = netlink_get_register(ctx, loc, reg2);
+ if (proto == NULL) {
+ netlink_error(ctx, loc,
+ "redirect statement has no proto expression");
+ goto out_err;
+ }
+
+ expr_set_type(proto, &inet_service_type, BYTEORDER_BIG_ENDIAN);
+ if (stmt->nat.proto != NULL)
+ proto = range_expr_alloc(loc, stmt->nat.proto,
+ proto);
+ stmt->nat.proto = proto;
+ }
+
+ ctx->stmt = stmt;
+ return;
+out_err:
+ stmt_free(stmt);
+}
+
+static void netlink_parse_dup(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const struct nftnl_expr *nle)
+{
+ enum nft_registers reg1, reg2;
+ struct expr *addr, *dev;
+ struct stmt *stmt;
+
+ stmt = dup_stmt_alloc(loc);
+
+ reg1 = netlink_parse_register(nle, NFTNL_EXPR_DUP_SREG_ADDR);
+ if (reg1) {
+ addr = netlink_get_register(ctx, loc, reg1);
+ if (addr == NULL) {
+ netlink_error(ctx, loc,
+ "DUP statement has no destination expression");
+ goto out_err;
+ }
+
+ switch (ctx->table->handle.family) {
+ case NFPROTO_IPV4:
+ expr_set_type(addr, &ipaddr_type, BYTEORDER_BIG_ENDIAN);
+ break;
+ case NFPROTO_IPV6:
+ expr_set_type(addr, &ip6addr_type,
+ BYTEORDER_BIG_ENDIAN);
+ break;
+ }
+ stmt->dup.to = addr;
+ }
+
+ reg2 = netlink_parse_register(nle, NFTNL_EXPR_DUP_SREG_DEV);
+ if (reg2) {
+ dev = netlink_get_register(ctx, loc, reg2);
+ if (dev == NULL) {
+ netlink_error(ctx, loc,
+ "DUP statement has no output expression");
+ goto out_err;
+ }
+
+ expr_set_type(dev, &ifindex_type, BYTEORDER_HOST_ENDIAN);
+ if (stmt->dup.to == NULL)
+ stmt->dup.to = dev;
+ else
+ stmt->dup.dev = dev;
+ }
+
+ ctx->stmt = stmt;
+ return;
+out_err:
+ stmt_free(stmt);
+}
+
+static void netlink_parse_fwd(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const struct nftnl_expr *nle)
+{
+ enum nft_registers reg1, reg2;
+ struct expr *dev, *addr;
+ struct stmt *stmt;
+
+ stmt = fwd_stmt_alloc(loc);
+
+ reg1 = netlink_parse_register(nle, NFTNL_EXPR_FWD_SREG_DEV);
+ if (reg1) {
+ dev = netlink_get_register(ctx, loc, reg1);
+ if (dev == NULL) {
+ netlink_error(ctx, loc,
+ "fwd statement has no output expression");
+ goto out_err;
+ }
+
+ expr_set_type(dev, &ifindex_type, BYTEORDER_HOST_ENDIAN);
+ stmt->fwd.dev = dev;
+ }
+
+ if (nftnl_expr_is_set(nle, NFTNL_EXPR_FWD_NFPROTO)) {
+ stmt->fwd.family =
+ nftnl_expr_get_u32(nle, NFTNL_EXPR_FWD_NFPROTO);
+ }
+
+ if (nftnl_expr_is_set(nle, NFTNL_EXPR_FWD_SREG_ADDR)) {
+ reg2 = netlink_parse_register(nle, NFTNL_EXPR_FWD_SREG_ADDR);
+ if (reg2) {
+ addr = netlink_get_register(ctx, loc, reg2);
+ if (addr == NULL) {
+ netlink_error(ctx, loc,
+ "fwd statement has no output expression");
+ goto out_err;
+ }
+
+ switch (stmt->fwd.family) {
+ case AF_INET:
+ expr_set_type(addr, &ipaddr_type,
+ BYTEORDER_BIG_ENDIAN);
+ break;
+ case AF_INET6:
+ expr_set_type(addr, &ip6addr_type,
+ BYTEORDER_BIG_ENDIAN);
+ break;
+ default:
+ netlink_error(ctx, loc,
+ "fwd statement has no family");
+ goto out_err;
+ }
+ stmt->fwd.addr = addr;
+ }
+ }
+
+ ctx->stmt = stmt;
+ return;
+out_err:
+ stmt_free(stmt);
+}
+
+static void netlink_parse_queue(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const struct nftnl_expr *nle)
+{
+ struct expr *expr;
+ uint16_t flags;
+
+ if (nftnl_expr_is_set(nle, NFTNL_EXPR_QUEUE_SREG_QNUM)) {
+ enum nft_registers reg = netlink_parse_register(nle, NFTNL_EXPR_QUEUE_SREG_QNUM);
+
+ expr = netlink_get_register(ctx, loc, reg);
+ if (!expr) {
+ netlink_error(ctx, loc, "queue statement has no sreg expression");
+ return;
+ }
+ } else {
+ uint16_t total = nftnl_expr_get_u16(nle, NFTNL_EXPR_QUEUE_TOTAL);
+ uint16_t num = nftnl_expr_get_u16(nle, NFTNL_EXPR_QUEUE_NUM);
+
+ expr = constant_expr_alloc(loc, &integer_type,
+ BYTEORDER_HOST_ENDIAN, 16, &num);
+
+ if (total > 1) {
+ struct expr *high;
+
+ total += num - 1;
+ high = constant_expr_alloc(loc, &integer_type,
+ BYTEORDER_HOST_ENDIAN, 16, &total);
+ expr = range_expr_alloc(loc, expr, high);
+ }
+ }
+
+ flags = nftnl_expr_get_u16(nle, NFTNL_EXPR_QUEUE_FLAGS);
+ ctx->stmt = queue_stmt_alloc(loc, expr, flags);
+}
+
+struct dynset_parse_ctx {
+ struct netlink_parse_ctx *nlctx;
+ const struct location *loc;
+ struct list_head stmt_list;
+};
+
+static int dynset_parse_expressions(struct nftnl_expr *e, void *data)
+{
+ struct dynset_parse_ctx *dynset_parse_ctx = data;
+ struct netlink_parse_ctx *ctx = dynset_parse_ctx->nlctx;
+ const struct location *loc = dynset_parse_ctx->loc;
+ struct stmt *stmt;
+
+ if (netlink_parse_expr(e, ctx) < 0 || !ctx->stmt) {
+ netlink_error(ctx, loc, "Could not parse dynset stmt");
+ return -1;
+ }
+ stmt = ctx->stmt;
+
+ list_add_tail(&stmt->list, &dynset_parse_ctx->stmt_list);
+
+ return 0;
+}
+
+static void netlink_parse_dynset(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const struct nftnl_expr *nle)
+{
+ struct dynset_parse_ctx dynset_parse_ctx = {
+ .nlctx = ctx,
+ .loc = loc,
+ };
+ struct expr *expr, *expr_data = NULL;
+ enum nft_registers sreg, sreg_data;
+ struct stmt *stmt, *dstmt, *next;
+ const struct nftnl_expr *dnle;
+ struct set *set;
+ const char *name;
+
+ init_list_head(&dynset_parse_ctx.stmt_list);
+
+ name = nftnl_expr_get_str(nle, NFTNL_EXPR_DYNSET_SET_NAME);
+ set = set_cache_find(ctx->table, name);
+ if (set == NULL)
+ return netlink_error(ctx, loc,
+ "Unknown set '%s' in dynset statement",
+ name);
+
+ sreg = netlink_parse_register(nle, NFTNL_EXPR_DYNSET_SREG_KEY);
+ expr = netlink_get_register(ctx, loc, sreg);
+ if (expr == NULL)
+ return netlink_error(ctx, loc,
+ "Dynset statement has no key expression");
+
+ if (expr->len < set->key->len) {
+ expr_free(expr);
+ expr = netlink_parse_concat_key(ctx, loc, sreg, set->key);
+ if (expr == NULL)
+ return;
+ } else if (expr->dtype == &invalid_type) {
+ expr_set_type(expr, datatype_get(set->key->dtype), set->key->byteorder);
+ }
+
+ expr = set_elem_expr_alloc(&expr->location, expr);
+ expr->timeout = nftnl_expr_get_u64(nle, NFTNL_EXPR_DYNSET_TIMEOUT);
+
+ if (nftnl_expr_is_set(nle, NFTNL_EXPR_DYNSET_EXPR)) {
+ dstmt = NULL;
+ dnle = nftnl_expr_get(nle, NFTNL_EXPR_DYNSET_EXPR, NULL);
+ if (dnle != NULL) {
+ if (netlink_parse_expr(dnle, ctx) < 0)
+ goto out_err;
+ if (ctx->stmt == NULL) {
+ netlink_error(ctx, loc,
+ "Could not parse dynset stmt");
+ goto out_err;
+ }
+ dstmt = ctx->stmt;
+ list_add_tail(&dstmt->list,
+ &dynset_parse_ctx.stmt_list);
+ }
+ } else if (nftnl_expr_is_set(nle, NFTNL_EXPR_DYNSET_EXPRESSIONS)) {
+ if (nftnl_expr_expr_foreach(nle, dynset_parse_expressions,
+ &dynset_parse_ctx) < 0)
+ goto out_err;
+ }
+
+ if (nftnl_expr_is_set(nle, NFTNL_EXPR_DYNSET_SREG_DATA)) {
+ sreg_data = netlink_parse_register(nle, NFTNL_EXPR_DYNSET_SREG_DATA);
+ expr_data = netlink_get_register(ctx, loc, sreg_data);
+
+ if (expr_data && expr_data->len < set->data->len) {
+ expr_free(expr_data);
+ expr_data = netlink_parse_concat_expr(ctx, loc, sreg_data, set->data->len);
+ if (expr_data == NULL)
+ netlink_error(ctx, loc,
+ "Could not parse dynset map data expressions");
+ }
+ }
+
+ if (expr_data != NULL) {
+ expr_set_type(expr_data, set->data->dtype, set->data->byteorder);
+ stmt = map_stmt_alloc(loc);
+ stmt->map.set = set_ref_expr_alloc(loc, set);
+ stmt->map.key = expr;
+ stmt->map.data = expr_data;
+ stmt->map.op = nftnl_expr_get_u32(nle, NFTNL_EXPR_DYNSET_OP);
+ list_splice_tail(&dynset_parse_ctx.stmt_list,
+ &stmt->map.stmt_list);
+ } else {
+ if (!list_empty(&dynset_parse_ctx.stmt_list) &&
+ set_is_anonymous(set->flags)) {
+ stmt = meter_stmt_alloc(loc);
+ stmt->meter.set = set_ref_expr_alloc(loc, set);
+ stmt->meter.key = expr;
+ stmt->meter.stmt = list_first_entry(&dynset_parse_ctx.stmt_list,
+ struct stmt, list);
+ stmt->meter.size = set->desc.size;
+ } else {
+ stmt = set_stmt_alloc(loc);
+ stmt->set.set = set_ref_expr_alloc(loc, set);
+ stmt->set.op = nftnl_expr_get_u32(nle, NFTNL_EXPR_DYNSET_OP);
+ stmt->set.key = expr;
+ list_splice_tail(&dynset_parse_ctx.stmt_list,
+ &stmt->set.stmt_list);
+ }
+ }
+
+ ctx->stmt = stmt;
+ return;
+out_err:
+ list_for_each_entry_safe(dstmt, next, &dynset_parse_ctx.stmt_list, list)
+ stmt_free(dstmt);
+
+ expr_free(expr);
+}
+
+static void netlink_parse_objref(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const struct nftnl_expr *nle)
+{
+ uint32_t type = nftnl_expr_get_u32(nle, NFTNL_EXPR_OBJREF_IMM_TYPE);
+ struct expr *expr;
+ struct stmt *stmt;
+
+ if (nftnl_expr_is_set(nle, NFTNL_EXPR_OBJREF_IMM_NAME)) {
+ struct nft_data_delinearize nld;
+
+ type = nftnl_expr_get_u32(nle, NFTNL_EXPR_OBJREF_IMM_TYPE);
+ nld.value = nftnl_expr_get(nle, NFTNL_EXPR_OBJREF_IMM_NAME,
+ &nld.len);
+ expr = netlink_alloc_value(&netlink_location, &nld);
+ datatype_set(expr, &string_type);
+ expr->byteorder = BYTEORDER_HOST_ENDIAN;
+ } else if (nftnl_expr_is_set(nle, NFTNL_EXPR_OBJREF_SET_SREG)) {
+ struct expr *left, *right;
+ enum nft_registers sreg;
+ const char *name;
+ struct set *set;
+
+ name = nftnl_expr_get_str(nle, NFTNL_EXPR_OBJREF_SET_NAME);
+ set = set_cache_find(ctx->table, name);
+ if (set == NULL)
+ return netlink_error(ctx, loc,
+ "Unknown set '%s' in objref expression",
+ name);
+
+ sreg = netlink_parse_register(nle, NFTNL_EXPR_OBJREF_SET_SREG);
+ left = netlink_get_register(ctx, loc, sreg);
+ if (left == NULL)
+ return netlink_error(ctx, loc,
+ "objref expression has no left hand side");
+
+ if (left->len < set->key->len) {
+ expr_free(left);
+ left = netlink_parse_concat_expr(ctx, loc, sreg, set->key->len);
+ if (left == NULL)
+ return;
+ }
+
+ right = set_ref_expr_alloc(loc, set);
+ expr = map_expr_alloc(loc, left, right);
+ expr_set_type(expr, &string_type, BYTEORDER_HOST_ENDIAN);
+ type = set->objtype;
+ } else {
+ netlink_error(ctx, loc, "unknown objref expression type %u",
+ type);
+ return;
+ }
+
+ stmt = objref_stmt_alloc(loc);
+ stmt->objref.type = type;
+ stmt->objref.expr = expr;
+ ctx->stmt = stmt;
+}
+
+struct expr_handler {
+ const char *name;
+ void (*parse)(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const struct nftnl_expr *nle);
+};
+
+static const struct expr_handler netlink_parsers[] = {
+ { .name = "immediate", .parse = netlink_parse_immediate },
+ { .name = "cmp", .parse = netlink_parse_cmp },
+ { .name = "lookup", .parse = netlink_parse_lookup },
+ { .name = "bitwise", .parse = netlink_parse_bitwise },
+ { .name = "byteorder", .parse = netlink_parse_byteorder },
+ { .name = "payload", .parse = netlink_parse_payload },
+ { .name = "inner", .parse = netlink_parse_inner },
+ { .name = "exthdr", .parse = netlink_parse_exthdr },
+ { .name = "meta", .parse = netlink_parse_meta },
+ { .name = "socket", .parse = netlink_parse_socket },
+ { .name = "osf", .parse = netlink_parse_osf },
+ { .name = "rt", .parse = netlink_parse_rt },
+ { .name = "ct", .parse = netlink_parse_ct },
+ { .name = "connlimit", .parse = netlink_parse_connlimit },
+ { .name = "counter", .parse = netlink_parse_counter },
+ { .name = "last", .parse = netlink_parse_last },
+ { .name = "log", .parse = netlink_parse_log },
+ { .name = "limit", .parse = netlink_parse_limit },
+ { .name = "range", .parse = netlink_parse_range },
+ { .name = "reject", .parse = netlink_parse_reject },
+ { .name = "nat", .parse = netlink_parse_nat },
+ { .name = "tproxy", .parse = netlink_parse_tproxy },
+ { .name = "notrack", .parse = netlink_parse_notrack },
+ { .name = "masq", .parse = netlink_parse_masq },
+ { .name = "redir", .parse = netlink_parse_redir },
+ { .name = "dup", .parse = netlink_parse_dup },
+ { .name = "queue", .parse = netlink_parse_queue },
+ { .name = "dynset", .parse = netlink_parse_dynset },
+ { .name = "fwd", .parse = netlink_parse_fwd },
+ { .name = "target", .parse = netlink_parse_target },
+ { .name = "match", .parse = netlink_parse_match },
+ { .name = "objref", .parse = netlink_parse_objref },
+ { .name = "quota", .parse = netlink_parse_quota },
+ { .name = "numgen", .parse = netlink_parse_numgen },
+ { .name = "hash", .parse = netlink_parse_hash },
+ { .name = "fib", .parse = netlink_parse_fib },
+ { .name = "tcpopt", .parse = netlink_parse_exthdr },
+ { .name = "flow_offload", .parse = netlink_parse_flow_offload },
+ { .name = "xfrm", .parse = netlink_parse_xfrm },
+ { .name = "synproxy", .parse = netlink_parse_synproxy },
+};
+
+static int netlink_parse_expr(const struct nftnl_expr *nle,
+ struct netlink_parse_ctx *ctx)
+{
+ const char *type = nftnl_expr_get_str(nle, NFTNL_EXPR_NAME);
+ struct location loc;
+ unsigned int i;
+
+ memset(&loc, 0, sizeof(loc));
+ loc.indesc = &indesc_netlink;
+ loc.nle = nle;
+
+ for (i = 0; i < array_size(netlink_parsers); i++) {
+ if (strcmp(type, netlink_parsers[i].name))
+ continue;
+
+ netlink_parsers[i].parse(ctx, &loc, nle);
+
+ return 0;
+ }
+ netlink_error(ctx, &loc, "unknown expression type '%s'", type);
+
+ return 0;
+}
+
+static int netlink_parse_rule_expr(struct nftnl_expr *nle, void *arg)
+{
+ struct netlink_parse_ctx *ctx = arg;
+ int err;
+
+ err = netlink_parse_expr(nle, ctx);
+ if (err < 0)
+ return err;
+ if (ctx->stmt != NULL) {
+ rule_stmt_append(ctx->rule, ctx->stmt);
+ ctx->stmt = NULL;
+ }
+ return 0;
+}
+
+struct stmt *netlink_parse_set_expr(const struct set *set,
+ const struct nft_cache *cache,
+ const struct nftnl_expr *nle)
+{
+ struct netlink_parse_ctx ctx, *pctx = &ctx;
+ struct handle h = {};
+
+ handle_merge(&h, &set->handle);
+ pctx->rule = rule_alloc(&netlink_location, &h);
+ pctx->table = table_cache_find(&cache->table_cache,
+ set->handle.table.name,
+ set->handle.family);
+ assert(pctx->table != NULL);
+
+ if (netlink_parse_expr(nle, pctx) < 0)
+ return NULL;
+
+ init_list_head(&pctx->rule->stmts);
+ rule_free(pctx->rule);
+
+ return pctx->stmt;
+}
+
+static bool meta_outer_may_dependency_kill(struct rule_pp_ctx *ctx,
+ const struct expr *expr)
+{
+ struct dl_proto_ctx *dl_outer = dl_proto_ctx_outer(ctx);
+ struct stmt *stmt = dl_outer->pdctx.pdeps[expr->payload.inner_desc->base];
+ struct expr *dep;
+ uint8_t l4proto;
+
+ if (!stmt)
+ return false;
+
+ dep = stmt->expr;
+
+ if (dep->left->meta.key != NFT_META_L4PROTO)
+ return false;
+
+ l4proto = mpz_get_uint8(dep->right->value);
+
+ switch (l4proto) {
+ case IPPROTO_GRE:
+ if (expr->payload.inner_desc == &proto_gre ||
+ expr->payload.inner_desc == &proto_gretap)
+ return true;
+ break;
+ default:
+ break;
+ }
+
+ return false;
+}
+
+static void expr_postprocess(struct rule_pp_ctx *ctx, struct expr **exprp);
+
+static void payload_match_expand(struct rule_pp_ctx *ctx,
+ struct expr *expr,
+ struct expr *payload)
+{
+ struct expr *left = payload, *right = expr->right, *tmp;
+ struct list_head list = LIST_HEAD_INIT(list);
+ struct dl_proto_ctx *dl = dl_proto_ctx(ctx);
+ enum proto_bases base = left->payload.base;
+ struct expr *nexpr = NULL;
+ struct stmt *nstmt;
+
+ payload_expr_expand(&list, left, &dl->pctx);
+
+ list_for_each_entry(left, &list, list) {
+ tmp = constant_expr_splice(right, left->len);
+ expr_set_type(tmp, left->dtype, left->byteorder);
+
+ if (left->payload.tmpl && (left->len < left->payload.tmpl->len)) {
+ mpz_lshift_ui(tmp->value, left->payload.tmpl->len - left->len);
+ tmp->len = left->payload.tmpl->len;
+ tmp = prefix_expr_alloc(&tmp->location, tmp, left->len);
+ }
+
+ nexpr = relational_expr_alloc(&expr->location, expr->op,
+ left, tmp);
+ if (expr->op == OP_EQ)
+ relational_expr_pctx_update(&dl->pctx, nexpr);
+
+ nstmt = expr_stmt_alloc(&ctx->stmt->location, nexpr);
+ list_add_tail(&nstmt->list, &ctx->stmt->list);
+
+ assert(left->etype == EXPR_PAYLOAD);
+ assert(left->payload.base);
+ assert(base == left->payload.base);
+
+ if (expr->left->payload.inner_desc) {
+ if (expr->left->payload.inner_desc == expr->left->payload.desc) {
+ nexpr->left->payload.desc = expr->left->payload.desc;
+ nexpr->left->payload.tmpl = expr->left->payload.tmpl;
+ }
+ nexpr->left->payload.inner_desc = expr->left->payload.inner_desc;
+
+ if (meta_outer_may_dependency_kill(ctx, expr->left)) {
+ struct dl_proto_ctx *dl_outer = dl_proto_ctx_outer(ctx);
+
+ payload_dependency_release(&dl_outer->pdctx, expr->left->payload.inner_desc->base);
+ }
+ }
+
+ if (payload_is_stacked(dl->pctx.protocol[base].desc, nexpr))
+ base--;
+
+ /* Remember the first payload protocol expression to
+ * kill it later on if made redundant by a higher layer
+ * payload expression.
+ */
+ payload_dependency_kill(&dl->pdctx, nexpr->left,
+ dl->pctx.family);
+ if (expr->op == OP_EQ && left->flags & EXPR_F_PROTOCOL)
+ payload_dependency_store(&dl->pdctx, nstmt, base);
+ }
+ list_del(&ctx->stmt->list);
+ stmt_free(ctx->stmt);
+ ctx->stmt = NULL;
+}
+
+static void payload_icmp_check(struct rule_pp_ctx *rctx, struct expr *expr, const struct expr *value)
+{
+ struct dl_proto_ctx *dl = dl_proto_ctx(rctx);
+ const struct proto_hdr_template *tmpl;
+ const struct proto_desc *desc;
+ uint8_t icmp_type;
+ unsigned int i;
+
+ assert(expr->etype == EXPR_PAYLOAD);
+ assert(value->etype == EXPR_VALUE);
+
+ if (expr->payload.base != PROTO_BASE_TRANSPORT_HDR)
+ return;
+
+ /* icmp(v6) type is 8 bit, if value is smaller or larger, this is not
+ * a protocol dependency.
+ */
+ if (expr->len != 8 || value->len != 8 || dl->pctx.th_dep.icmp.type)
+ return;
+
+ desc = dl->pctx.protocol[expr->payload.base].desc;
+ if (desc == NULL)
+ return;
+
+ /* not icmp? ignore. */
+ if (desc != &proto_icmp && desc != &proto_icmp6)
+ return;
+
+ assert(desc->base == expr->payload.base);
+
+ icmp_type = mpz_get_uint8(value->value);
+
+ for (i = 1; i < array_size(desc->templates); i++) {
+ tmpl = &desc->templates[i];
+
+ if (tmpl->len == 0)
+ return;
+
+ if (tmpl->offset != expr->payload.offset ||
+ tmpl->len != expr->len)
+ continue;
+
+ /* Matches but doesn't load a protocol key -> ignore. */
+ if (desc->protocol_key != i)
+ return;
+
+ expr->payload.desc = desc;
+ expr->payload.tmpl = tmpl;
+ dl->pctx.th_dep.icmp.type = icmp_type;
+ return;
+ }
+}
+
+static void payload_match_postprocess(struct rule_pp_ctx *ctx,
+ struct expr *expr,
+ struct expr *payload)
+{
+ struct dl_proto_ctx *dl = dl_proto_ctx(ctx);
+
+ switch (expr->op) {
+ case OP_EQ:
+ case OP_NEQ:
+ if (expr->right->etype == EXPR_VALUE) {
+ payload_match_expand(ctx, expr, payload);
+ break;
+ } else if (expr->right->etype == EXPR_SET_REF) {
+ struct set *set = expr->right->set;
+
+ if (set_is_anonymous(set->flags) &&
+ set->init &&
+ !list_empty(&set->init->expressions)) {
+ struct expr *elem;
+
+ elem = list_first_entry(&set->init->expressions, struct expr, list);
+
+ if (elem->etype == EXPR_SET_ELEM &&
+ elem->key->etype == EXPR_VALUE)
+ payload_icmp_check(ctx, payload, elem->key);
+ }
+ }
+ /* Fall through */
+ default:
+ payload_expr_complete(payload, &dl->pctx);
+ expr_set_type(expr->right, payload->dtype,
+ payload->byteorder);
+ payload_dependency_kill(&dl->pdctx, payload, dl->pctx.family);
+ break;
+ }
+}
+
+static uint8_t ether_type_to_nfproto(uint16_t l3proto)
+{
+ switch(l3proto) {
+ case ETH_P_IP:
+ return NFPROTO_IPV4;
+ case ETH_P_IPV6:
+ return NFPROTO_IPV6;
+ default:
+ break;
+ }
+
+ return NFPROTO_UNSPEC;
+}
+
+static bool __meta_dependency_may_kill(const struct expr *dep, uint8_t *nfproto)
+{
+ uint16_t l3proto;
+
+ switch (dep->left->etype) {
+ case EXPR_META:
+ switch (dep->left->meta.key) {
+ case NFT_META_NFPROTO:
+ *nfproto = mpz_get_uint8(dep->right->value);
+ break;
+ case NFT_META_PROTOCOL:
+ l3proto = mpz_get_uint16(dep->right->value);
+ *nfproto = ether_type_to_nfproto(l3proto);
+ break;
+ default:
+ return true;
+ }
+ break;
+ case EXPR_PAYLOAD:
+ if (dep->left->payload.base != PROTO_BASE_LL_HDR)
+ return true;
+
+ if (dep->left->dtype != &ethertype_type)
+ return true;
+
+ l3proto = mpz_get_uint16(dep->right->value);
+ *nfproto = ether_type_to_nfproto(l3proto);
+ break;
+ default:
+ return true;
+ }
+
+ return false;
+}
+
+/* We have seen a protocol key expression that restricts matching at the network
+ * base, leave it in place since this is meaningful in bridge, inet and netdev
+ * families. Exceptions are ICMP and ICMPv6 where this code assumes that can
+ * only happen with IPv4 and IPv6.
+ */
+static bool meta_may_dependency_kill(struct payload_dep_ctx *ctx,
+ unsigned int family,
+ const struct expr *expr)
+{
+ uint8_t l4proto, nfproto = NFPROTO_UNSPEC;
+ struct expr *dep = payload_dependency_get(ctx, PROTO_BASE_NETWORK_HDR);
+
+ if (!dep)
+ return true;
+
+ if (__meta_dependency_may_kill(dep, &nfproto))
+ return true;
+
+ switch (family) {
+ case NFPROTO_INET:
+ case NFPROTO_NETDEV:
+ case NFPROTO_BRIDGE:
+ break;
+ case NFPROTO_IPV4:
+ case NFPROTO_IPV6:
+ return family == nfproto;
+ default:
+ return true;
+ }
+
+ if (expr->left->meta.key != NFT_META_L4PROTO)
+ return true;
+
+ l4proto = mpz_get_uint8(expr->right->value);
+
+ switch (l4proto) {
+ case IPPROTO_ICMP:
+ case IPPROTO_ICMPV6:
+ break;
+ default:
+ return false;
+ }
+
+ if ((nfproto == NFPROTO_IPV4 && l4proto == IPPROTO_ICMPV6) ||
+ (nfproto == NFPROTO_IPV6 && l4proto == IPPROTO_ICMP))
+ return false;
+
+ return true;
+}
+
+static void ct_meta_common_postprocess(struct rule_pp_ctx *ctx,
+ const struct expr *expr,
+ enum proto_bases base)
+{
+ struct dl_proto_ctx *dl = dl_proto_ctx(ctx);
+ const struct expr *left = expr->left;
+ struct expr *right = expr->right;
+
+ if (right->etype == EXPR_SET || right->etype == EXPR_SET_REF)
+ expr_set_type(right, left->dtype, left->byteorder);
+
+ switch (expr->op) {
+ case OP_EQ:
+ if (expr->right->etype == EXPR_RANGE ||
+ expr->right->etype == EXPR_SET ||
+ expr->right->etype == EXPR_SET_REF)
+ break;
+
+ relational_expr_pctx_update(&dl->pctx, expr);
+
+ if (base < PROTO_BASE_TRANSPORT_HDR) {
+ if (payload_dependency_exists(&dl->pdctx, base) &&
+ meta_may_dependency_kill(&dl->pdctx,
+ dl->pctx.family, expr))
+ payload_dependency_release(&dl->pdctx, base);
+
+ if (left->flags & EXPR_F_PROTOCOL)
+ payload_dependency_store(&dl->pdctx, ctx->stmt, base);
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+static void meta_match_postprocess(struct rule_pp_ctx *ctx,
+ const struct expr *expr)
+{
+ const struct expr *left = expr->left;
+
+ ct_meta_common_postprocess(ctx, expr, left->meta.base);
+}
+
+static void ct_match_postprocess(struct rule_pp_ctx *ctx,
+ const struct expr *expr)
+{
+ const struct expr *left = expr->left;
+
+ ct_meta_common_postprocess(ctx, expr, left->ct.base);
+}
+
+/* Convert a bitmask to a prefix length */
+static unsigned int expr_mask_to_prefix(const struct expr *expr)
+{
+ unsigned long n;
+
+ n = mpz_scan1(expr->value, 0);
+ if (n == ULONG_MAX)
+ return 0;
+ return mpz_scan0(expr->value, n + 1) - n;
+}
+
+/* Return true if a bitmask can be expressed as a prefix length */
+static bool expr_mask_is_prefix(const struct expr *expr)
+{
+ unsigned long n1, n2;
+
+ n1 = mpz_scan1(expr->value, 0);
+ if (n1 == ULONG_MAX)
+ return true;
+ n2 = mpz_scan0(expr->value, n1 + 1);
+ if (n2 < expr->len || n2 == ULONG_MAX)
+ return false;
+ return true;
+}
+
+/* Convert a series of inclusive OR expressions into a list */
+static struct expr *binop_tree_to_list(struct expr *list, struct expr *expr)
+{
+ if (expr->etype == EXPR_BINOP && expr->op == OP_OR) {
+ if (list == NULL)
+ list = list_expr_alloc(&expr->location);
+ list = binop_tree_to_list(list, expr->left);
+ list = binop_tree_to_list(list, expr->right);
+ } else {
+ if (list == NULL)
+ return expr_get(expr);
+ compound_expr_add(list, expr_get(expr));
+ }
+
+ return list;
+}
+
+static void binop_adjust_one(const struct expr *binop, struct expr *value,
+ unsigned int shift)
+{
+ struct expr *left = binop->left;
+
+ assert(value->len >= binop->right->len);
+
+ mpz_rshift_ui(value->value, shift);
+ switch (left->etype) {
+ case EXPR_PAYLOAD:
+ case EXPR_EXTHDR:
+ value->len = left->len;
+ break;
+ default:
+ BUG("unknown expression type %s\n", expr_name(left));
+ break;
+ }
+}
+
+static void binop_adjust(const struct expr *binop, struct expr *right,
+ unsigned int shift)
+{
+ struct expr *i;
+
+ switch (right->etype) {
+ case EXPR_VALUE:
+ binop_adjust_one(binop, right, shift);
+ break;
+ case EXPR_SET_REF:
+ if (!set_is_anonymous(right->set->flags))
+ break;
+
+ list_for_each_entry(i, &right->set->init->expressions, list) {
+ switch (i->key->etype) {
+ case EXPR_VALUE:
+ binop_adjust_one(binop, i->key, shift);
+ break;
+ case EXPR_RANGE:
+ binop_adjust_one(binop, i->key->left, shift);
+ binop_adjust_one(binop, i->key->right, shift);
+ break;
+ case EXPR_SET_ELEM:
+ binop_adjust(binop, i->key->key, shift);
+ break;
+ default:
+ BUG("unknown expression type %s\n", expr_name(i->key));
+ }
+ }
+ break;
+ case EXPR_RANGE:
+ binop_adjust_one(binop, right->left, shift);
+ binop_adjust_one(binop, right->right, shift);
+ break;
+ default:
+ BUG("unknown expression type %s\n", expr_name(right));
+ break;
+ }
+}
+
+static void __binop_postprocess(struct rule_pp_ctx *ctx,
+ struct expr *expr,
+ struct expr *left,
+ struct expr *mask,
+ struct expr **expr_binop)
+{
+ struct dl_proto_ctx *dl = dl_proto_ctx(ctx);
+ struct expr *binop = *expr_binop;
+ unsigned int shift;
+
+ assert(binop->etype == EXPR_BINOP);
+
+ if ((left->etype == EXPR_PAYLOAD &&
+ payload_expr_trim(left, mask, &dl->pctx, &shift)) ||
+ (left->etype == EXPR_EXTHDR &&
+ exthdr_find_template(left, mask, &shift))) {
+ struct expr *right = NULL;
+
+ /* mask is implicit, binop needs to be removed.
+ *
+ * Fix all values of the expression according to the mask
+ * and then process the payload instruction using the real
+ * sizes and offsets we're interested in.
+ *
+ * Finally, convert the expression to 1) by replacing
+ * the binop with the binop payload/exthdr expression.
+ */
+ switch (expr->etype) {
+ case EXPR_BINOP:
+ case EXPR_RELATIONAL:
+ right = expr->right;
+ binop_adjust(binop, right, shift);
+ break;
+ case EXPR_MAP:
+ right = expr->mappings;
+ binop_adjust(binop, right, shift);
+ break;
+ default:
+ break;
+ }
+
+ assert(binop->left == left);
+ *expr_binop = expr_get(left);
+
+ if (left->etype == EXPR_PAYLOAD)
+ payload_match_postprocess(ctx, expr, left);
+ else if (left->etype == EXPR_EXTHDR && right)
+ expr_set_type(right, left->dtype, left->byteorder);
+
+ expr_free(binop);
+ }
+}
+
+static void binop_postprocess(struct rule_pp_ctx *ctx, struct expr *expr,
+ struct expr **expr_binop)
+{
+ struct expr *binop = *expr_binop;
+ struct expr *left = binop->left;
+ struct expr *mask = binop->right;
+
+ __binop_postprocess(ctx, expr, left, mask, expr_binop);
+}
+
+static void map_binop_postprocess(struct rule_pp_ctx *ctx, struct expr *expr)
+{
+ struct expr *binop = expr->map;
+
+ if (binop->op != OP_AND)
+ return;
+
+ if (binop->left->etype == EXPR_PAYLOAD &&
+ binop->right->etype == EXPR_VALUE)
+ binop_postprocess(ctx, expr, &expr->map);
+}
+
+static bool is_shift_by_zero(const struct expr *binop)
+{
+ struct expr *rhs;
+
+ if (binop->op != OP_RSHIFT && binop->op != OP_LSHIFT)
+ return false;
+
+ rhs = binop->right;
+ if (rhs->etype != EXPR_VALUE || rhs->len > 64)
+ return false;
+
+ return mpz_get_uint64(rhs->value) == 0;
+}
+
+static void relational_binop_postprocess(struct rule_pp_ctx *ctx,
+ struct expr **exprp)
+{
+ struct expr *expr = *exprp, *binop = expr->left, *right = expr->right;
+
+ if (binop->op == OP_AND && (expr->op == OP_NEQ || expr->op == OP_EQ) &&
+ right->dtype->basetype &&
+ right->dtype->basetype->type == TYPE_BITMASK) {
+ switch (right->etype) {
+ case EXPR_VALUE:
+ if (!mpz_cmp_ui(right->value, 0)) {
+ /* Flag comparison: data & flags != 0
+ *
+ * Split the flags into a list of flag values and convert the
+ * op to OP_EQ.
+ */
+ expr_free(right);
+
+ expr->left = expr_get(binop->left);
+ expr->right = binop_tree_to_list(NULL, binop->right);
+ switch (expr->op) {
+ case OP_NEQ:
+ expr->op = OP_IMPLICIT;
+ break;
+ case OP_EQ:
+ expr->op = OP_NEG;
+ break;
+ default:
+ BUG("unknown operation type %d\n", expr->op);
+ }
+ expr_free(binop);
+ } else if (binop->right->etype == EXPR_VALUE &&
+ right->etype == EXPR_VALUE &&
+ !mpz_cmp(right->value, binop->right->value)) {
+ /* Skip flag / flag representation for:
+ * data & flag == flag
+ * data & flag != flag
+ */
+ ;
+ } else {
+ *exprp = flagcmp_expr_alloc(&expr->location, expr->op,
+ expr_get(binop->left),
+ binop_tree_to_list(NULL, binop->right),
+ expr_get(right));
+ expr_free(expr);
+ }
+ break;
+ case EXPR_BINOP:
+ *exprp = flagcmp_expr_alloc(&expr->location, expr->op,
+ expr_get(binop->left),
+ binop_tree_to_list(NULL, binop->right),
+ binop_tree_to_list(NULL, right));
+ expr_free(expr);
+ break;
+ default:
+ break;
+ }
+ } else if (binop->left->dtype->flags & DTYPE_F_PREFIX &&
+ binop->op == OP_AND && expr->right->etype == EXPR_VALUE &&
+ expr_mask_is_prefix(binop->right)) {
+ expr->left = expr_get(binop->left);
+ expr->right = prefix_expr_alloc(&expr->location,
+ expr_get(right),
+ expr_mask_to_prefix(binop->right));
+ expr_free(right);
+ expr_free(binop);
+ } else if (binop->op == OP_AND &&
+ binop->right->etype == EXPR_VALUE) {
+ /*
+ * This *might* be a payload match testing header fields that
+ * have non byte divisible offsets and/or bit lengths.
+ *
+ * Thus we need to deal with two different cases.
+ *
+ * 1 the simple version:
+ * relation
+ * payload value|setlookup
+ *
+ * expr: relation, left: payload, right: value, e.g. tcp dport == 22.
+ *
+ * 2. The '&' version (this is what we're looking at now).
+ * relation
+ * binop value1|setlookup
+ * payload value2
+ *
+ * expr: relation, left: binop, right: value, e.g.
+ * ip saddr 10.0.0.0/8
+ *
+ * payload_expr_trim will figure out if the mask is needed to match
+ * templates.
+ */
+ binop_postprocess(ctx, expr, &expr->left);
+ } else if (binop->op == OP_RSHIFT && binop->left->op == OP_AND &&
+ binop->right->etype == EXPR_VALUE && binop->left->right->etype == EXPR_VALUE) {
+ /* Handle 'ip version @s4' and similar, i.e. set lookups where the lhs needs
+ * fixups to mask out unwanted bits AND a shift.
+ */
+
+ binop_postprocess(ctx, binop, &binop->left);
+ if (is_shift_by_zero(binop)) {
+ struct expr *lhs = binop->left;
+
+ expr_get(lhs);
+ expr_free(binop);
+ expr->left = lhs;
+ }
+ }
+}
+
+static bool payload_binop_postprocess(struct rule_pp_ctx *ctx,
+ struct expr **exprp)
+{
+ struct expr *expr = *exprp;
+
+ if (expr->op != OP_RSHIFT)
+ return false;
+
+ if (expr->left->etype == EXPR_UNARY) {
+ /*
+ * If the payload value was originally in a different byte-order
+ * from the payload expression, there will be a byte-order
+ * conversion to remove.
+ */
+ struct expr *left = expr_get(expr->left->arg);
+ expr_free(expr->left);
+ expr->left = left;
+ }
+
+ if (expr->left->etype != EXPR_BINOP || expr->left->op != OP_AND)
+ return false;
+
+ if (expr->left->left->etype != EXPR_PAYLOAD)
+ return false;
+
+ expr_set_type(expr->right, &integer_type,
+ BYTEORDER_HOST_ENDIAN);
+ expr_postprocess(ctx, &expr->right);
+
+ binop_postprocess(ctx, expr, &expr->left);
+ *exprp = expr_get(expr->left);
+ expr_free(expr);
+
+ return true;
+}
+
+static struct expr *string_wildcard_expr_alloc(struct location *loc,
+ const struct expr *mask,
+ const struct expr *expr)
+{
+ unsigned int len = div_round_up(expr->len, BITS_PER_BYTE);
+ char data[len + 2];
+ int pos;
+
+ mpz_export_data(data, expr->value, BYTEORDER_HOST_ENDIAN, len);
+ pos = div_round_up(expr_mask_to_prefix(mask), BITS_PER_BYTE);
+ data[pos] = '*';
+ data[pos + 1] = '\0';
+
+ return constant_expr_alloc(loc, expr->dtype, BYTEORDER_HOST_ENDIAN,
+ expr->len + BITS_PER_BYTE, data);
+}
+
+/* This calculates the string length and checks if it is nul-terminated, this
+ * function is quite a hack :)
+ */
+static bool __expr_postprocess_string(struct expr **exprp)
+{
+ struct expr *expr = *exprp;
+ unsigned int len = div_round_up(expr->len, BITS_PER_BYTE);
+ char data[len + 1];
+
+ mpz_export_data(data, expr->value, BYTEORDER_HOST_ENDIAN, len);
+
+ if (data[len - 1] != '\0')
+ return false;
+
+ len = strlen(data);
+ if (len && data[len - 1] == '*') {
+ data[len - 1] = '\\';
+ data[len] = '*';
+ data[len + 1] = '\0';
+ expr = constant_expr_alloc(&expr->location, expr->dtype,
+ BYTEORDER_HOST_ENDIAN,
+ (len + 2) * BITS_PER_BYTE, data);
+ expr_free(*exprp);
+ *exprp = expr;
+ }
+
+ return true;
+}
+
+static struct expr *expr_postprocess_string(struct expr *expr)
+{
+ struct expr *mask, *out;
+
+ assert(expr_basetype(expr)->type == TYPE_STRING);
+ if (__expr_postprocess_string(&expr))
+ return expr;
+
+ mask = constant_expr_alloc(&expr->location, &integer_type,
+ BYTEORDER_HOST_ENDIAN,
+ expr->len + BITS_PER_BYTE, NULL);
+ mpz_clear(mask->value);
+ mpz_init_bitmask(mask->value, expr->len);
+ out = string_wildcard_expr_alloc(&expr->location, mask, expr);
+ expr_free(expr);
+ expr_free(mask);
+ return out;
+}
+
+static void expr_postprocess(struct rule_pp_ctx *ctx, struct expr **exprp)
+{
+ struct dl_proto_ctx *dl = dl_proto_ctx(ctx);
+ struct expr *expr = *exprp, *i;
+
+ switch (expr->etype) {
+ case EXPR_MAP:
+ switch (expr->map->etype) {
+ case EXPR_BINOP:
+ map_binop_postprocess(ctx, expr);
+ break;
+ default:
+ break;
+ }
+
+ expr_postprocess(ctx, &expr->map);
+ expr_postprocess(ctx, &expr->mappings);
+ break;
+ case EXPR_MAPPING:
+ expr_postprocess(ctx, &expr->left);
+ expr_postprocess(ctx, &expr->right);
+ break;
+ case EXPR_SET:
+ list_for_each_entry(i, &expr->expressions, list)
+ expr_postprocess(ctx, &i);
+ break;
+ case EXPR_CONCAT: {
+ unsigned int type = expr->dtype->type, ntype = 0;
+ int off = expr->dtype->subtypes;
+ const struct datatype *dtype;
+ LIST_HEAD(tmp);
+ struct expr *n;
+
+ ctx->flags |= RULE_PP_IN_CONCATENATION;
+ list_for_each_entry_safe(i, n, &expr->expressions, list) {
+ if (type) {
+ dtype = concat_subtype_lookup(type, --off);
+ expr_set_type(i, dtype, dtype->byteorder);
+ }
+ list_del(&i->list);
+ expr_postprocess(ctx, &i);
+ list_add_tail(&i->list, &tmp);
+
+ ntype = concat_subtype_add(ntype, i->dtype->type);
+ }
+ ctx->flags &= ~RULE_PP_IN_CONCATENATION;
+ list_splice(&tmp, &expr->expressions);
+ __datatype_set(expr, concat_type_alloc(ntype));
+ break;
+ }
+ case EXPR_UNARY:
+ expr_postprocess(ctx, &expr->arg);
+ expr_set_type(expr, expr->arg->dtype, !expr->arg->byteorder);
+ break;
+ case EXPR_BINOP:
+ if (payload_binop_postprocess(ctx, exprp))
+ break;
+
+ expr_postprocess(ctx, &expr->left);
+ switch (expr->op) {
+ case OP_LSHIFT:
+ case OP_RSHIFT:
+ expr_set_type(expr->right, &integer_type,
+ BYTEORDER_HOST_ENDIAN);
+ break;
+ case OP_AND:
+ if (expr->right->len > expr->left->len) {
+ expr_set_type(expr->right, expr->left->dtype,
+ BYTEORDER_HOST_ENDIAN);
+ } else {
+ expr_set_type(expr->right, expr->left->dtype,
+ expr->left->byteorder);
+ }
+
+ /* Do not process OP_AND in ordinary rule context.
+ *
+ * Removal needs to be performed as part of the relational
+ * operation because the RHS constant might need to be adjusted
+ * (shifted).
+ *
+ * This is different in set element context or concatenations:
+ * There is no relational operation (eq, neq and so on), thus
+ * it needs to be processed right away.
+ */
+ if ((ctx->flags & RULE_PP_REMOVE_OP_AND) &&
+ expr->left->etype == EXPR_PAYLOAD &&
+ expr->right->etype == EXPR_VALUE) {
+ __binop_postprocess(ctx, expr, expr->left, expr->right, exprp);
+ return;
+ }
+ break;
+ default:
+ if (expr->right->len > expr->left->len) {
+ expr_set_type(expr->right, expr->left->dtype,
+ BYTEORDER_HOST_ENDIAN);
+ } else {
+ expr_set_type(expr->right, expr->left->dtype,
+ expr->left->byteorder);
+ }
+ }
+ expr_postprocess(ctx, &expr->right);
+
+ switch (expr->op) {
+ case OP_LSHIFT:
+ case OP_RSHIFT:
+ expr_set_type(expr, &xinteger_type,
+ BYTEORDER_HOST_ENDIAN);
+ break;
+ default:
+ expr_set_type(expr, expr->left->dtype,
+ expr->left->byteorder);
+ }
+
+ break;
+ case EXPR_RELATIONAL:
+ switch (expr->left->etype) {
+ case EXPR_PAYLOAD:
+ payload_match_postprocess(ctx, expr, expr->left);
+ return;
+ case EXPR_CONCAT:
+ if (expr->right->etype == EXPR_SET_REF) {
+ assert(expr->left->dtype == &invalid_type);
+ assert(expr->right->dtype != &invalid_type);
+
+ datatype_set(expr->left, expr->right->dtype);
+ }
+ expr_postprocess(ctx, &expr->left);
+ break;
+ default:
+ expr_postprocess(ctx, &expr->left);
+ break;
+ }
+
+ expr_set_type(expr->right, expr->left->dtype, expr->left->byteorder);
+ expr_postprocess(ctx, &expr->right);
+
+ switch (expr->left->etype) {
+ case EXPR_CT:
+ ct_match_postprocess(ctx, expr);
+ break;
+ case EXPR_META:
+ meta_match_postprocess(ctx, expr);
+ break;
+ case EXPR_BINOP:
+ relational_binop_postprocess(ctx, exprp);
+ break;
+ default:
+ break;
+ }
+ break;
+ case EXPR_PAYLOAD:
+ payload_expr_complete(expr, &dl->pctx);
+ if (expr->payload.inner_desc) {
+ if (meta_outer_may_dependency_kill(ctx, expr)) {
+ struct dl_proto_ctx *dl_outer = dl_proto_ctx_outer(ctx);
+
+ payload_dependency_release(&dl_outer->pdctx, expr->payload.inner_desc->base);
+ }
+ }
+ payload_dependency_kill(&dl->pdctx, expr, dl->pctx.family);
+ break;
+ case EXPR_VALUE:
+ // FIXME
+ if (expr->byteorder == BYTEORDER_HOST_ENDIAN)
+ mpz_switch_byteorder(expr->value, expr->len / BITS_PER_BYTE);
+
+ if (expr_basetype(expr)->type == TYPE_STRING)
+ *exprp = expr_postprocess_string(expr);
+
+ expr = *exprp;
+ if (expr->dtype->basetype != NULL &&
+ expr->dtype->basetype->type == TYPE_BITMASK)
+ *exprp = bitmask_expr_to_binops(expr);
+
+ break;
+ case EXPR_RANGE:
+ expr_postprocess(ctx, &expr->left);
+ expr_postprocess(ctx, &expr->right);
+ break;
+ case EXPR_PREFIX:
+ expr_postprocess(ctx, &expr->prefix);
+ break;
+ case EXPR_SET_ELEM:
+ ctx->flags |= RULE_PP_IN_SET_ELEM;
+ expr_postprocess(ctx, &expr->key);
+ ctx->flags &= ~RULE_PP_IN_SET_ELEM;
+ break;
+ case EXPR_EXTHDR:
+ exthdr_dependency_kill(&dl->pdctx, expr, dl->pctx.family);
+ break;
+ case EXPR_SET_REF:
+ case EXPR_META:
+ case EXPR_RT:
+ case EXPR_VERDICT:
+ case EXPR_NUMGEN:
+ case EXPR_FIB:
+ case EXPR_SOCKET:
+ case EXPR_OSF:
+ case EXPR_XFRM:
+ break;
+ case EXPR_HASH:
+ if (expr->hash.expr)
+ expr_postprocess(ctx, &expr->hash.expr);
+ break;
+ case EXPR_CT:
+ ct_expr_update_type(&dl->pctx, expr);
+ break;
+ default:
+ BUG("unknown expression type %s\n", expr_name(expr));
+ }
+}
+
+static void stmt_reject_postprocess(struct rule_pp_ctx *rctx)
+{
+ struct dl_proto_ctx *dl = dl_proto_ctx(rctx);
+ const struct proto_desc *desc, *base;
+ struct stmt *stmt = rctx->stmt;
+ int protocol;
+
+ switch (dl->pctx.family) {
+ case NFPROTO_IPV4:
+ stmt->reject.family = dl->pctx.family;
+ datatype_set(stmt->reject.expr, &icmp_code_type);
+ if (stmt->reject.type == NFT_REJECT_TCP_RST &&
+ payload_dependency_exists(&dl->pdctx,
+ PROTO_BASE_TRANSPORT_HDR))
+ payload_dependency_release(&dl->pdctx,
+ PROTO_BASE_TRANSPORT_HDR);
+ break;
+ case NFPROTO_IPV6:
+ stmt->reject.family = dl->pctx.family;
+ datatype_set(stmt->reject.expr, &icmpv6_code_type);
+ if (stmt->reject.type == NFT_REJECT_TCP_RST &&
+ payload_dependency_exists(&dl->pdctx,
+ PROTO_BASE_TRANSPORT_HDR))
+ payload_dependency_release(&dl->pdctx,
+ PROTO_BASE_TRANSPORT_HDR);
+ break;
+ case NFPROTO_INET:
+ case NFPROTO_BRIDGE:
+ case NFPROTO_NETDEV:
+ if (stmt->reject.type == NFT_REJECT_ICMPX_UNREACH) {
+ datatype_set(stmt->reject.expr, &icmpx_code_type);
+ break;
+ }
+
+ /* always print full icmp(6) name, simple 'reject' might be ambiguious
+ * because ipv4 vs. ipv6 info might be lost
+ */
+ stmt->reject.verbose_print = 1;
+
+ base = dl->pctx.protocol[PROTO_BASE_LL_HDR].desc;
+ desc = dl->pctx.protocol[PROTO_BASE_NETWORK_HDR].desc;
+ protocol = proto_find_num(base, desc);
+ switch (protocol) {
+ case NFPROTO_IPV4: /* INET */
+ case __constant_htons(ETH_P_IP): /* BRIDGE, NETDEV */
+ stmt->reject.family = NFPROTO_IPV4;
+ datatype_set(stmt->reject.expr, &icmp_code_type);
+ break;
+ case NFPROTO_IPV6: /* INET */
+ case __constant_htons(ETH_P_IPV6): /* BRIDGE, NETDEV */
+ stmt->reject.family = NFPROTO_IPV6;
+ datatype_set(stmt->reject.expr, &icmpv6_code_type);
+ break;
+ default:
+ break;
+ }
+
+ if (payload_dependency_exists(&dl->pdctx, PROTO_BASE_NETWORK_HDR))
+ payload_dependency_release(&dl->pdctx,
+ PROTO_BASE_NETWORK_HDR);
+ break;
+ default:
+ break;
+ }
+}
+
+static bool expr_may_merge_range(struct expr *expr, struct expr *prev,
+ enum ops *op)
+{
+ struct expr *left, *prev_left;
+
+ if (prev->etype == EXPR_RELATIONAL &&
+ expr->etype == EXPR_RELATIONAL) {
+ /* ct and meta needs an unary to swap byteorder, in this case
+ * we have to explore the inner branch in this tree.
+ */
+ if (expr->left->etype == EXPR_UNARY)
+ left = expr->left->arg;
+ else
+ left = expr->left;
+
+ if (prev->left->etype == EXPR_UNARY)
+ prev_left = prev->left->arg;
+ else
+ prev_left = prev->left;
+
+ if (left->etype == prev_left->etype) {
+ if (expr->op == OP_LTE && prev->op == OP_GTE) {
+ *op = OP_EQ;
+ return true;
+ } else if (expr->op == OP_GT && prev->op == OP_LT) {
+ *op = OP_NEQ;
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+static void expr_postprocess_range(struct rule_pp_ctx *ctx, enum ops op)
+{
+ struct dl_proto_ctx *dl = dl_proto_ctx(ctx);
+ struct stmt *nstmt, *stmt = ctx->stmt;
+ struct expr *nexpr, *rel;
+
+ nexpr = range_expr_alloc(&dl->pdctx.prev->location,
+ expr_clone(dl->pdctx.prev->expr->right),
+ expr_clone(stmt->expr->right));
+ expr_set_type(nexpr, stmt->expr->right->dtype,
+ stmt->expr->right->byteorder);
+
+ rel = relational_expr_alloc(&dl->pdctx.prev->location, op,
+ expr_clone(stmt->expr->left), nexpr);
+
+ nstmt = expr_stmt_alloc(&stmt->location, rel);
+ list_add_tail(&nstmt->list, &stmt->list);
+
+ list_del(&dl->pdctx.prev->list);
+ stmt_free(dl->pdctx.prev);
+
+ list_del(&stmt->list);
+ stmt_free(stmt);
+ ctx->stmt = nstmt;
+}
+
+static void stmt_expr_postprocess(struct rule_pp_ctx *ctx)
+{
+ struct dl_proto_ctx *dl = dl_proto_ctx(ctx);
+ enum ops op;
+
+ expr_postprocess(ctx, &ctx->stmt->expr);
+
+ if (dl->pdctx.prev && ctx->stmt &&
+ ctx->stmt->ops->type == dl->pdctx.prev->ops->type &&
+ expr_may_merge_range(ctx->stmt->expr, dl->pdctx.prev->expr, &op))
+ expr_postprocess_range(ctx, op);
+}
+
+static void stmt_payload_binop_pp(struct rule_pp_ctx *ctx, struct expr *binop)
+{
+ struct dl_proto_ctx *dl = dl_proto_ctx(ctx);
+ struct expr *payload = binop->left;
+ struct expr *mask = binop->right;
+ unsigned int shift;
+
+ assert(payload->etype == EXPR_PAYLOAD);
+ if (payload_expr_trim(payload, mask, &dl->pctx, &shift)) {
+ binop_adjust(binop, mask, shift);
+ payload_expr_complete(payload, &dl->pctx);
+ expr_set_type(mask, payload->dtype,
+ payload->byteorder);
+ }
+}
+
+/**
+ * stmt_payload_binop_postprocess - decode payload set binop
+ *
+ * @ctx: rule postprocessing context
+ *
+ * This helper has to be called if expr_postprocess() failed to
+ * decode the payload operation.
+ *
+ * Usually a failure to decode means that userspace had to munge
+ * the original payload expression because it has an odd size or
+ * a non-byte divisible offset/length.
+ *
+ * If that was the case, the 'value' expression is not a value but
+ * a binop expression with a munged payload expression on the left
+ * and a mask to clear the real payload offset/length.
+ *
+ * So chech if we have one of the following binops:
+ * I)
+ * binop (|)
+ * binop(&) value/set
+ * payload value(mask)
+ *
+ * This is the normal case, the | RHS is the value the user wants
+ * to set, the & RHS is the mask value that discards bits we need
+ * to clear but retains everything unrelated to the set operation.
+ *
+ * IIa)
+ * binop (&)
+ * payload mask
+ *
+ * User specified a zero set value -- netlink bitwise decoding
+ * discarded the redundant "| 0" part. This is identical to I),
+ * we can just set value to 0 after we inferred the real payload size.
+ *
+ * IIb)
+ * binop (|)
+ * payload value/set
+ *
+ * This happens when user wants to set all bits, netlink bitwise
+ * decoding changed '(payload & mask) ^ bits_to_set' into
+ * 'payload | bits_to_set', discarding the redundant "& 0xfff...".
+ */
+static void stmt_payload_binop_postprocess(struct rule_pp_ctx *ctx)
+{
+ struct expr *expr, *binop, *payload, *value, *mask;
+ struct stmt *stmt = ctx->stmt;
+ mpz_t bitmask;
+
+ expr = stmt->payload.val;
+
+ if (expr->etype != EXPR_BINOP)
+ return;
+
+ switch (expr->left->etype) {
+ case EXPR_BINOP: {/* I? */
+ mpz_t tmp;
+
+ if (expr->op != OP_OR)
+ return;
+
+ value = expr->right;
+ if (value->etype != EXPR_VALUE)
+ return;
+
+ binop = expr->left;
+ if (binop->op != OP_AND)
+ return;
+
+ payload = binop->left;
+ if (payload->etype != EXPR_PAYLOAD)
+ return;
+
+ if (!payload_expr_cmp(stmt->payload.expr, payload))
+ return;
+
+ mask = binop->right;
+ if (mask->etype != EXPR_VALUE)
+ return;
+
+ mpz_init(tmp);
+ mpz_set(tmp, mask->value);
+
+ mpz_init_bitmask(bitmask, payload->len);
+ mpz_xor(bitmask, bitmask, mask->value);
+ mpz_xor(bitmask, bitmask, value->value);
+ mpz_set(mask->value, bitmask);
+ mpz_clear(bitmask);
+
+ binop_postprocess(ctx, expr, &expr->left);
+ if (!payload_is_known(payload)) {
+ mpz_set(mask->value, tmp);
+ mpz_clear(tmp);
+ return;
+ }
+
+ mpz_clear(tmp);
+ expr_free(stmt->payload.expr);
+ stmt->payload.expr = expr_get(payload);
+ stmt->payload.val = expr_get(expr->right);
+ expr_free(expr);
+ break;
+ }
+ case EXPR_PAYLOAD: /* II? */
+ value = expr->right;
+ if (value->etype != EXPR_VALUE)
+ return;
+
+ switch (expr->op) {
+ case OP_AND: /* IIa */
+ payload = expr->left;
+ mpz_init_bitmask(bitmask, payload->len);
+ mpz_xor(bitmask, bitmask, value->value);
+ mpz_set(value->value, bitmask);
+ mpz_clear(bitmask);
+ break;
+ case OP_OR: /* IIb */
+ break;
+ default: /* No idea */
+ return;
+ }
+
+ stmt_payload_binop_pp(ctx, expr);
+ if (!payload_is_known(expr->left))
+ return;
+
+ expr_free(stmt->payload.expr);
+
+ switch (expr->op) {
+ case OP_AND:
+ /* Mask was used to match payload, i.e.
+ * user asked to set zero value.
+ */
+ mpz_set_ui(value->value, 0);
+ break;
+ default:
+ break;
+ }
+
+ stmt->payload.expr = expr_get(expr->left);
+ stmt->payload.val = expr_get(expr->right);
+ expr_free(expr);
+ break;
+ default: /* No idea */
+ break;
+ }
+}
+
+static void stmt_payload_postprocess(struct rule_pp_ctx *ctx)
+{
+ struct dl_proto_ctx *dl = dl_proto_ctx(ctx);
+ struct stmt *stmt = ctx->stmt;
+
+ payload_expr_complete(stmt->payload.expr, &dl->pctx);
+ if (!payload_is_known(stmt->payload.expr))
+ stmt_payload_binop_postprocess(ctx);
+
+ expr_postprocess(ctx, &stmt->payload.expr);
+
+ expr_set_type(stmt->payload.val,
+ stmt->payload.expr->dtype,
+ stmt->payload.expr->byteorder);
+
+ expr_postprocess(ctx, &stmt->payload.val);
+}
+
+static void stmt_queue_postprocess(struct rule_pp_ctx *ctx)
+{
+ struct stmt *stmt = ctx->stmt;
+ struct expr *e = stmt->queue.queue;
+
+ if (e == NULL || e->etype == EXPR_VALUE ||
+ e->etype == EXPR_RANGE)
+ return;
+
+ expr_postprocess(ctx, &stmt->queue.queue);
+}
+
+/*
+ * We can only remove payload dependencies if they occur without
+ * a statement with side effects in between.
+ *
+ * For instance:
+ * 'ip protocol tcp tcp dport 22 counter' is same as
+ * 'tcp dport 22 counter'.
+ *
+ * 'ip protocol tcp counter tcp dport 22' cannot be written as
+ * 'counter tcp dport 22' (that would be counter ip protocol tcp, but
+ * that counts every packet, not just ip/tcp).
+ */
+static void
+rule_maybe_reset_payload_deps(struct payload_dep_ctx *pdctx, enum stmt_types t)
+{
+ if (t == STMT_EXPRESSION)
+ return;
+
+ payload_dependency_reset(pdctx);
+}
+
+static bool has_inner_desc(const struct expr *expr)
+{
+ struct expr *i;
+
+ switch (expr->etype) {
+ case EXPR_BINOP:
+ return has_inner_desc(expr->left);
+ case EXPR_CONCAT:
+ list_for_each_entry(i, &expr->expressions, list) {
+ if (has_inner_desc(i))
+ return true;
+ }
+ break;
+ case EXPR_META:
+ return expr->meta.inner_desc;
+ case EXPR_PAYLOAD:
+ return expr->payload.inner_desc;
+ case EXPR_SET_ELEM:
+ return has_inner_desc(expr->key);
+ default:
+ break;
+ }
+
+ return false;
+}
+
+static struct dl_proto_ctx *rule_update_dl_proto_ctx(struct rule_pp_ctx *rctx)
+{
+ const struct stmt *stmt = rctx->stmt;
+ bool inner = false;
+
+ switch (stmt->ops->type) {
+ case STMT_EXPRESSION:
+ if (has_inner_desc(stmt->expr->left))
+ inner = true;
+ break;
+ case STMT_SET:
+ if (has_inner_desc(stmt->set.key))
+ inner = true;
+ break;
+ default:
+ break;
+ }
+
+ if (inner)
+ rctx->dl = &rctx->_dl[1];
+ else
+ rctx->dl = &rctx->_dl[0];
+
+ return rctx->dl;
+}
+
+static void rule_parse_postprocess(struct netlink_parse_ctx *ctx, struct rule *rule)
+{
+ struct stmt *stmt, *next;
+ struct dl_proto_ctx *dl;
+ struct rule_pp_ctx rctx;
+ struct expr *expr;
+
+ memset(&rctx, 0, sizeof(rctx));
+ proto_ctx_init(&rctx._dl[0].pctx, rule->handle.family, ctx->debug_mask, false);
+ /* use NFPROTO_BRIDGE to set up proto_eth as base protocol. */
+ proto_ctx_init(&rctx._dl[1].pctx, NFPROTO_BRIDGE, ctx->debug_mask, true);
+
+ list_for_each_entry_safe(stmt, next, &rule->stmts, list) {
+ enum stmt_types type = stmt->ops->type;
+
+ rctx.stmt = stmt;
+ dl = rule_update_dl_proto_ctx(&rctx);
+
+ switch (type) {
+ case STMT_EXPRESSION:
+ stmt_expr_postprocess(&rctx);
+ break;
+ case STMT_PAYLOAD:
+ stmt_payload_postprocess(&rctx);
+ break;
+ case STMT_METER:
+ expr_postprocess(&rctx, &stmt->meter.key);
+ break;
+ case STMT_META:
+ if (stmt->meta.expr != NULL)
+ expr_postprocess(&rctx, &stmt->meta.expr);
+ break;
+ case STMT_CT:
+ if (stmt->ct.expr != NULL) {
+ expr_postprocess(&rctx, &stmt->ct.expr);
+
+ if (stmt->ct.expr->etype == EXPR_BINOP &&
+ stmt->ct.key == NFT_CT_EVENTMASK) {
+ expr = binop_tree_to_list(NULL, stmt->ct.expr);
+ expr_free(stmt->ct.expr);
+ stmt->ct.expr = expr;
+ }
+ }
+ break;
+ case STMT_NAT:
+ if (stmt->nat.addr != NULL)
+ expr_postprocess(&rctx, &stmt->nat.addr);
+ if (stmt->nat.proto != NULL)
+ expr_postprocess(&rctx, &stmt->nat.proto);
+ break;
+ case STMT_TPROXY:
+ if (stmt->tproxy.addr)
+ expr_postprocess(&rctx, &stmt->tproxy.addr);
+ if (stmt->tproxy.port) {
+ payload_dependency_reset(&dl->pdctx);
+ expr_postprocess(&rctx, &stmt->tproxy.port);
+ }
+ break;
+ case STMT_REJECT:
+ stmt_reject_postprocess(&rctx);
+ break;
+ case STMT_SET:
+ expr_postprocess(&rctx, &stmt->set.key);
+ break;
+ case STMT_MAP:
+ expr_postprocess(&rctx, &stmt->map.key);
+ expr_postprocess(&rctx, &stmt->map.data);
+ break;
+ case STMT_DUP:
+ if (stmt->dup.to != NULL)
+ expr_postprocess(&rctx, &stmt->dup.to);
+ if (stmt->dup.dev != NULL)
+ expr_postprocess(&rctx, &stmt->dup.dev);
+ break;
+ case STMT_FWD:
+ expr_postprocess(&rctx, &stmt->fwd.dev);
+ if (stmt->fwd.addr != NULL)
+ expr_postprocess(&rctx, &stmt->fwd.addr);
+ break;
+ case STMT_XT:
+ stmt_xt_postprocess(&rctx, stmt, rule);
+ break;
+ case STMT_OBJREF:
+ expr_postprocess(&rctx, &stmt->objref.expr);
+ break;
+ case STMT_QUEUE:
+ stmt_queue_postprocess(&rctx);
+ break;
+ default:
+ break;
+ }
+
+ dl->pdctx.prev = rctx.stmt;
+
+ rule_maybe_reset_payload_deps(&dl->pdctx, type);
+ }
+}
+
+static int parse_rule_udata_cb(const struct nftnl_udata *attr, void *data)
+{
+ unsigned char *value = nftnl_udata_get(attr);
+ uint8_t type = nftnl_udata_type(attr);
+ uint8_t len = nftnl_udata_len(attr);
+ const struct nftnl_udata **tb = data;
+
+ switch (type) {
+ case NFTNL_UDATA_RULE_COMMENT:
+ if (value[len - 1] != '\0')
+ return -1;
+ break;
+ default:
+ return 0;
+ }
+ tb[type] = attr;
+ return 0;
+}
+
+static char *nftnl_rule_get_comment(const struct nftnl_rule *nlr)
+{
+ const struct nftnl_udata *tb[NFTNL_UDATA_RULE_MAX + 1] = {};
+ const void *data;
+ uint32_t len;
+
+ if (!nftnl_rule_is_set(nlr, NFTNL_RULE_USERDATA))
+ return NULL;
+
+ data = nftnl_rule_get_data(nlr, NFTNL_RULE_USERDATA, &len);
+
+ if (nftnl_udata_parse(data, len, parse_rule_udata_cb, tb) < 0)
+ return NULL;
+
+ if (!tb[NFTNL_UDATA_RULE_COMMENT])
+ return NULL;
+
+ return xstrdup(nftnl_udata_get(tb[NFTNL_UDATA_RULE_COMMENT]));
+}
+
+struct rule *netlink_delinearize_rule(struct netlink_ctx *ctx,
+ struct nftnl_rule *nlr)
+{
+ struct netlink_parse_ctx _ctx, *pctx = &_ctx;
+ struct handle h;
+
+ memset(&_ctx, 0, sizeof(_ctx));
+ _ctx.msgs = ctx->msgs;
+ _ctx.debug_mask = ctx->nft->debug_mask;
+ _ctx.nlctx = ctx;
+
+ memset(&h, 0, sizeof(h));
+ h.family = nftnl_rule_get_u32(nlr, NFTNL_RULE_FAMILY);
+ h.table.name = xstrdup(nftnl_rule_get_str(nlr, NFTNL_RULE_TABLE));
+ h.chain.name = xstrdup(nftnl_rule_get_str(nlr, NFTNL_RULE_CHAIN));
+ h.handle.id = nftnl_rule_get_u64(nlr, NFTNL_RULE_HANDLE);
+
+ if (nftnl_rule_is_set(nlr, NFTNL_RULE_POSITION))
+ h.position.id = nftnl_rule_get_u64(nlr, NFTNL_RULE_POSITION);
+
+ pctx->rule = rule_alloc(&netlink_location, &h);
+ pctx->table = table_cache_find(&ctx->nft->cache.table_cache,
+ h.table.name, h.family);
+ if (!pctx->table) {
+ errno = ENOENT;
+ return NULL;
+ }
+
+ pctx->rule->comment = nftnl_rule_get_comment(nlr);
+
+ nftnl_expr_foreach(nlr, netlink_parse_rule_expr, pctx);
+
+ rule_parse_postprocess(pctx, pctx->rule);
+ netlink_release_registers(pctx);
+ return pctx->rule;
+}