summaryrefslogtreecommitdiffstats
path: root/src/netlink.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/netlink.c2240
1 files changed, 2240 insertions, 0 deletions
diff --git a/src/netlink.c b/src/netlink.c
new file mode 100644
index 0000000..120a8ba
--- /dev/null
+++ b/src/netlink.c
@@ -0,0 +1,2240 @@
+/*
+ * Copyright (c) 2008-2012 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 <errno.h>
+#include <libmnl/libmnl.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <inttypes.h>
+
+#include <libnftnl/table.h>
+#include <libnftnl/trace.h>
+#include <libnftnl/chain.h>
+#include <libnftnl/expr.h>
+#include <libnftnl/object.h>
+#include <libnftnl/set.h>
+#include <libnftnl/flowtable.h>
+#include <libnftnl/udata.h>
+#include <libnftnl/ruleset.h>
+#include <libnftnl/common.h>
+#include <libnftnl/udata.h>
+#include <linux/netfilter/nfnetlink.h>
+#include <linux/netfilter/nf_tables.h>
+#include <linux/netfilter.h>
+
+#include <nftables.h>
+#include <parser.h>
+#include <netlink.h>
+#include <mnl.h>
+#include <expression.h>
+#include <statement.h>
+#include <gmputil.h>
+#include <utils.h>
+#include <erec.h>
+#include <iface.h>
+
+#define nft_mon_print(monh, ...) nft_print(&monh->ctx->nft->output, __VA_ARGS__)
+
+const struct input_descriptor indesc_netlink = {
+ .name = "netlink",
+ .type = INDESC_NETLINK,
+};
+
+const struct location netlink_location = {
+ .indesc = &indesc_netlink,
+};
+
+void __noreturn __netlink_abi_error(const char *file, int line,
+ const char *reason)
+{
+ fprintf(stderr, "E: Contact urgently your Linux kernel vendor. "
+ "Netlink ABI is broken: %s:%d %s\n", file, line, reason);
+ abort();
+}
+
+int netlink_io_error(struct netlink_ctx *ctx, const struct location *loc,
+ const char *fmt, ...)
+{
+ struct error_record *erec;
+ va_list ap;
+
+ if (loc == NULL)
+ loc = &netlink_location;
+
+ va_start(ap, fmt);
+ erec = erec_vcreate(EREC_ERROR, loc, fmt, ap);
+ va_end(ap);
+ erec_queue(erec, ctx->msgs);
+ return -1;
+}
+
+void __noreturn __netlink_init_error(const char *filename, int line,
+ const char *reason)
+{
+ fprintf(stderr, "%s:%d: Unable to initialize Netlink socket: %s\n",
+ filename, line, reason);
+ exit(NFT_EXIT_NONL);
+}
+
+struct nftnl_expr *alloc_nft_expr(const char *name)
+{
+ struct nftnl_expr *nle;
+
+ nle = nftnl_expr_alloc(name);
+ if (nle == NULL)
+ memory_allocation_error();
+
+ return nle;
+}
+static void netlink_gen_key(const struct expr *expr,
+ struct nft_data_linearize *data);
+static void __netlink_gen_data(const struct expr *expr,
+ struct nft_data_linearize *data, bool expand);
+
+struct nftnl_set_elem *alloc_nftnl_setelem(const struct expr *set,
+ const struct expr *expr)
+{
+ const struct expr *elem, *data;
+ struct nftnl_set_elem *nlse;
+ struct nft_data_linearize nld;
+ struct nftnl_udata_buf *udbuf = NULL;
+ uint32_t flags = 0;
+ int num_exprs = 0;
+ struct stmt *stmt;
+ struct expr *key;
+
+ nlse = nftnl_set_elem_alloc();
+ if (nlse == NULL)
+ memory_allocation_error();
+
+ data = NULL;
+ if (expr->etype == EXPR_MAPPING) {
+ elem = expr->left;
+ if (!(expr->flags & EXPR_F_INTERVAL_END))
+ data = expr->right;
+ } else {
+ elem = expr;
+ }
+ if (elem->etype != EXPR_SET_ELEM)
+ BUG("Unexpected expression type: got %d\n", elem->etype);
+
+ key = elem->key;
+
+ switch (key->etype) {
+ case EXPR_SET_ELEM_CATCHALL:
+ break;
+ default:
+ if (set->set_flags & NFT_SET_INTERVAL &&
+ key->etype == EXPR_CONCAT && key->field_count > 1) {
+ key->flags |= EXPR_F_INTERVAL;
+ netlink_gen_key(key, &nld);
+ key->flags &= ~EXPR_F_INTERVAL;
+
+ nftnl_set_elem_set(nlse, NFTNL_SET_ELEM_KEY, &nld.value, nld.len);
+
+ key->flags |= EXPR_F_INTERVAL_END;
+ netlink_gen_key(key, &nld);
+ key->flags &= ~EXPR_F_INTERVAL_END;
+
+ nftnl_set_elem_set(nlse, NFTNL_SET_ELEM_KEY_END,
+ &nld.value, nld.len);
+ } else {
+ netlink_gen_key(key, &nld);
+ nftnl_set_elem_set(nlse, NFTNL_SET_ELEM_KEY, &nld.value, nld.len);
+ }
+ break;
+ }
+
+ if (elem->timeout)
+ nftnl_set_elem_set_u64(nlse, NFTNL_SET_ELEM_TIMEOUT,
+ elem->timeout);
+ if (elem->expiration)
+ nftnl_set_elem_set_u64(nlse, NFTNL_SET_ELEM_EXPIRATION,
+ elem->expiration);
+ list_for_each_entry(stmt, &elem->stmt_list, list)
+ num_exprs++;
+
+ if (num_exprs == 1) {
+ list_for_each_entry(stmt, &elem->stmt_list, list) {
+ nftnl_set_elem_set(nlse, NFTNL_SET_ELEM_EXPR,
+ netlink_gen_stmt_stateful(stmt), 0);
+ }
+ } else if (num_exprs > 1) {
+ list_for_each_entry(stmt, &elem->stmt_list, list) {
+ nftnl_set_elem_add_expr(nlse,
+ netlink_gen_stmt_stateful(stmt));
+ }
+ }
+ if (elem->comment || expr->elem_flags) {
+ udbuf = nftnl_udata_buf_alloc(NFT_USERDATA_MAXLEN);
+ if (!udbuf)
+ memory_allocation_error();
+ }
+ if (elem->comment) {
+ if (!nftnl_udata_put_strz(udbuf, NFTNL_UDATA_SET_ELEM_COMMENT,
+ elem->comment))
+ memory_allocation_error();
+ }
+ if (expr->elem_flags) {
+ if (!nftnl_udata_put_u32(udbuf, NFTNL_UDATA_SET_ELEM_FLAGS,
+ expr->elem_flags))
+ memory_allocation_error();
+ }
+ if (udbuf) {
+ nftnl_set_elem_set(nlse, NFTNL_SET_ELEM_USERDATA,
+ nftnl_udata_buf_data(udbuf),
+ nftnl_udata_buf_len(udbuf));
+ nftnl_udata_buf_free(udbuf);
+ }
+ if (set_is_datamap(set->set_flags) && data != NULL) {
+ __netlink_gen_data(data, &nld, !(data->flags & EXPR_F_SINGLETON));
+ switch (data->etype) {
+ case EXPR_VERDICT:
+ nftnl_set_elem_set_u32(nlse, NFTNL_SET_ELEM_VERDICT,
+ data->verdict);
+ if (data->chain != NULL)
+ nftnl_set_elem_set(nlse, NFTNL_SET_ELEM_CHAIN,
+ nld.chain, strlen(nld.chain));
+ break;
+ case EXPR_CONCAT:
+ assert(nld.len > 0);
+ /* fallthrough */
+ case EXPR_VALUE:
+ case EXPR_RANGE:
+ case EXPR_PREFIX:
+ nftnl_set_elem_set(nlse, NFTNL_SET_ELEM_DATA,
+ nld.value, nld.len);
+ break;
+ default:
+ BUG("unexpected set element expression\n");
+ break;
+ }
+ }
+ if (set_is_objmap(set->set_flags) && data != NULL) {
+ netlink_gen_data(data, &nld);
+ nftnl_set_elem_set(nlse, NFTNL_SET_ELEM_OBJREF,
+ nld.value, nld.len);
+ }
+
+ if (expr->flags & EXPR_F_INTERVAL_END)
+ flags |= NFT_SET_ELEM_INTERVAL_END;
+ if (key->etype == EXPR_SET_ELEM_CATCHALL)
+ flags |= NFT_SET_ELEM_CATCHALL;
+
+ if (flags)
+ nftnl_set_elem_set_u32(nlse, NFTNL_SET_ELEM_FLAGS, flags);
+
+ return nlse;
+}
+
+void netlink_gen_raw_data(const mpz_t value, enum byteorder byteorder,
+ unsigned int len, struct nft_data_linearize *data)
+{
+ assert(len > 0);
+ mpz_export_data(data->value, value, byteorder, len);
+ data->len = len;
+}
+
+static int netlink_export_pad(unsigned char *data, const mpz_t v,
+ const struct expr *i)
+{
+ mpz_export_data(data, v, i->byteorder,
+ div_round_up(i->len, BITS_PER_BYTE));
+
+ return netlink_padded_len(i->len) / BITS_PER_BYTE;
+}
+
+static int __netlink_gen_concat_key(uint32_t flags, const struct expr *i,
+ unsigned char *data)
+{
+ struct expr *expr;
+
+ switch (i->etype) {
+ case EXPR_RANGE:
+ if (flags & EXPR_F_INTERVAL_END)
+ expr = i->right;
+ else
+ expr = i->left;
+
+ if (expr_basetype(expr)->type == TYPE_INTEGER &&
+ expr->byteorder == BYTEORDER_HOST_ENDIAN)
+ mpz_switch_byteorder(expr->value, expr->len / BITS_PER_BYTE);
+
+ i = expr;
+ break;
+ case EXPR_PREFIX:
+ if (flags & EXPR_F_INTERVAL_END) {
+ int count;
+ mpz_t v;
+
+ mpz_init_bitmask(v, i->len - i->prefix_len);
+
+ if (i->byteorder == BYTEORDER_HOST_ENDIAN)
+ mpz_switch_byteorder(v, i->len / BITS_PER_BYTE);
+
+ mpz_add(v, i->prefix->value, v);
+ count = netlink_export_pad(data, v, i);
+ mpz_clear(v);
+ return count;
+ }
+ return netlink_export_pad(data, i->prefix->value, i);
+ case EXPR_VALUE:
+ /* Switch byteorder only once for singleton values when the set
+ * contains concatenation of intervals.
+ */
+ if (!(flags & EXPR_F_INTERVAL))
+ break;
+
+ expr = (struct expr *)i;
+ if (expr_basetype(expr)->type == TYPE_INTEGER &&
+ expr->byteorder == BYTEORDER_HOST_ENDIAN)
+ mpz_switch_byteorder(expr->value, expr->len / BITS_PER_BYTE);
+ break;
+ default:
+ BUG("invalid expression type '%s' in set", expr_ops(i)->name);
+ }
+
+ return netlink_export_pad(data, i->value, i);
+}
+
+static void netlink_gen_concat_key(const struct expr *expr,
+ struct nft_data_linearize *nld)
+{
+ unsigned int len = expr->len / BITS_PER_BYTE, offset = 0;
+ unsigned char data[len];
+ const struct expr *i;
+
+ memset(data, 0, len);
+
+ list_for_each_entry(i, &expr->expressions, list)
+ offset += __netlink_gen_concat_key(expr->flags, i, data + offset);
+
+ memcpy(nld->value, data, len);
+ nld->len = len;
+}
+
+static int __netlink_gen_concat_data(int end, const struct expr *i,
+ unsigned char *data)
+{
+ switch (i->etype) {
+ case EXPR_RANGE:
+ i = end ? i->right : i->left;
+ break;
+ case EXPR_PREFIX:
+ if (end) {
+ int count;
+ mpz_t v;
+
+ mpz_init_bitmask(v, i->len - i->prefix_len);
+ mpz_add(v, i->prefix->value, v);
+ count = netlink_export_pad(data, v, i);
+ mpz_clear(v);
+ return count;
+ }
+ return netlink_export_pad(data, i->prefix->value, i);
+ case EXPR_VALUE:
+ break;
+ default:
+ BUG("invalid expression type '%s' in set", expr_ops(i)->name);
+ }
+
+ return netlink_export_pad(data, i->value, i);
+}
+
+static void __netlink_gen_concat_expand(const struct expr *expr,
+ struct nft_data_linearize *nld)
+{
+ unsigned int len = div_round_up(expr->len, BITS_PER_BYTE) * 2, offset = 0;
+ unsigned char data[len];
+ const struct expr *i;
+
+ memset(data, 0, len);
+
+ list_for_each_entry(i, &expr->expressions, list)
+ offset += __netlink_gen_concat_data(false, i, data + offset);
+
+ list_for_each_entry(i, &expr->expressions, list)
+ offset += __netlink_gen_concat_data(true, i, data + offset);
+
+ memcpy(nld->value, data, len);
+ nld->len = len;
+}
+
+static void __netlink_gen_concat(const struct expr *expr,
+ struct nft_data_linearize *nld)
+{
+ unsigned int len = expr->len / BITS_PER_BYTE, offset = 0;
+ unsigned char data[len];
+ const struct expr *i;
+
+ memset(data, 0, len);
+
+ list_for_each_entry(i, &expr->expressions, list)
+ offset += __netlink_gen_concat_data(expr->flags, i, data + offset);
+
+ memcpy(nld->value, data, len);
+ nld->len = len;
+}
+
+static void netlink_gen_concat_data(const struct expr *expr,
+ struct nft_data_linearize *nld, bool expand)
+{
+ if (expand)
+ __netlink_gen_concat_expand(expr, nld);
+ else
+ __netlink_gen_concat(expr, nld);
+}
+
+static void netlink_gen_constant_data(const struct expr *expr,
+ struct nft_data_linearize *data)
+{
+ assert(expr->etype == EXPR_VALUE);
+ netlink_gen_raw_data(expr->value, expr->byteorder,
+ div_round_up(expr->len, BITS_PER_BYTE), data);
+}
+
+static void netlink_gen_chain(const struct expr *expr,
+ struct nft_data_linearize *data)
+{
+ char chain[NFT_CHAIN_MAXNAMELEN];
+ unsigned int len;
+
+ len = expr->chain->len / BITS_PER_BYTE;
+
+ if (!len)
+ BUG("chain length is 0");
+
+ if (len > sizeof(chain))
+ BUG("chain is too large (%u, %u max)",
+ len, (unsigned int)sizeof(chain));
+
+ memset(chain, 0, sizeof(chain));
+
+ mpz_export_data(chain, expr->chain->value,
+ BYTEORDER_HOST_ENDIAN, len);
+ snprintf(data->chain, NFT_CHAIN_MAXNAMELEN, "%s", chain);
+}
+
+static void netlink_gen_verdict(const struct expr *expr,
+ struct nft_data_linearize *data)
+{
+
+ data->verdict = expr->verdict;
+
+ switch (expr->verdict) {
+ case NFT_JUMP:
+ case NFT_GOTO:
+ if (expr->chain)
+ netlink_gen_chain(expr, data);
+ else
+ data->chain_id = expr->chain_id;
+ break;
+ }
+}
+
+static void netlink_gen_range(const struct expr *expr,
+ struct nft_data_linearize *nld)
+{
+ unsigned int len = div_round_up(expr->left->len, BITS_PER_BYTE) * 2;
+ unsigned char data[len];
+ unsigned int offset = 0;
+
+ memset(data, 0, len);
+ offset = netlink_export_pad(data, expr->left->value, expr->left);
+ netlink_export_pad(data + offset, expr->right->value, expr->right);
+ memcpy(nld->value, data, len);
+ nld->len = len;
+}
+
+static void netlink_gen_prefix(const struct expr *expr,
+ struct nft_data_linearize *nld)
+{
+ unsigned int len = div_round_up(expr->len, BITS_PER_BYTE) * 2;
+ unsigned char data[len];
+ int offset;
+ mpz_t v;
+
+ offset = netlink_export_pad(data, expr->prefix->value, expr);
+ mpz_init_bitmask(v, expr->len - expr->prefix_len);
+ mpz_add(v, expr->prefix->value, v);
+ netlink_export_pad(data + offset, v, expr->prefix);
+ mpz_clear(v);
+
+ memcpy(nld->value, data, len);
+ nld->len = len;
+}
+
+static void netlink_gen_key(const struct expr *expr,
+ struct nft_data_linearize *data)
+{
+ switch (expr->etype) {
+ case EXPR_VALUE:
+ return netlink_gen_constant_data(expr, data);
+ case EXPR_CONCAT:
+ return netlink_gen_concat_key(expr, data);
+ case EXPR_RANGE:
+ return netlink_gen_range(expr, data);
+ case EXPR_PREFIX:
+ return netlink_gen_prefix(expr, data);
+ default:
+ BUG("invalid data expression type %s\n", expr_name(expr));
+ }
+}
+
+static void __netlink_gen_data(const struct expr *expr,
+ struct nft_data_linearize *data, bool expand)
+{
+ switch (expr->etype) {
+ case EXPR_VALUE:
+ return netlink_gen_constant_data(expr, data);
+ case EXPR_CONCAT:
+ return netlink_gen_concat_data(expr, data, expand);
+ case EXPR_VERDICT:
+ return netlink_gen_verdict(expr, data);
+ case EXPR_RANGE:
+ return netlink_gen_range(expr, data);
+ case EXPR_PREFIX:
+ return netlink_gen_prefix(expr, data);
+ default:
+ BUG("invalid data expression type %s\n", expr_name(expr));
+ }
+}
+
+void netlink_gen_data(const struct expr *expr, struct nft_data_linearize *data)
+{
+ __netlink_gen_data(expr, data, false);
+}
+
+struct expr *netlink_alloc_value(const struct location *loc,
+ const struct nft_data_delinearize *nld)
+{
+ return constant_expr_alloc(loc, &invalid_type, BYTEORDER_INVALID,
+ nld->len * BITS_PER_BYTE, nld->value);
+}
+
+static struct expr *netlink_alloc_verdict(const struct location *loc,
+ const struct nft_data_delinearize *nld)
+{
+ struct expr *chain;
+
+ switch (nld->verdict) {
+ case NFT_JUMP:
+ case NFT_GOTO:
+ chain = constant_expr_alloc(loc, &string_type,
+ BYTEORDER_HOST_ENDIAN,
+ strlen(nld->chain) * BITS_PER_BYTE,
+ nld->chain);
+ break;
+ default:
+ chain = NULL;
+ break;
+ }
+
+ return verdict_expr_alloc(loc, nld->verdict, chain);
+}
+
+struct expr *netlink_alloc_data(const struct location *loc,
+ const struct nft_data_delinearize *nld,
+ enum nft_registers dreg)
+{
+ switch (dreg) {
+ case NFT_REG_VERDICT:
+ return netlink_alloc_verdict(loc, nld);
+ default:
+ return netlink_alloc_value(loc, nld);
+ }
+}
+
+void netlink_dump_rule(const struct nftnl_rule *nlr, struct netlink_ctx *ctx)
+{
+ FILE *fp = ctx->nft->output.output_fp;
+
+ if (!(ctx->nft->debug_mask & NFT_DEBUG_NETLINK) || !fp)
+ return;
+
+ nftnl_rule_fprintf(fp, nlr, 0, 0);
+ fprintf(fp, "\n");
+}
+
+void netlink_dump_expr(const struct nftnl_expr *nle,
+ FILE *fp, unsigned int debug_mask)
+{
+ if (!(debug_mask & NFT_DEBUG_NETLINK))
+ return;
+
+ nftnl_expr_fprintf(fp, nle, 0, 0);
+ fprintf(fp, "\n");
+}
+
+void netlink_dump_chain(const struct nftnl_chain *nlc, struct netlink_ctx *ctx)
+{
+ FILE *fp = ctx->nft->output.output_fp;
+
+ if (!(ctx->nft->debug_mask & NFT_DEBUG_NETLINK) || !fp)
+ return;
+
+ nftnl_chain_fprintf(fp, nlc, 0, 0);
+ fprintf(fp, "\n");
+}
+
+static int chain_parse_udata_cb(const struct nftnl_udata *attr, void *data)
+{
+ unsigned char *value = nftnl_udata_get(attr);
+ uint8_t type = nftnl_udata_type(attr);
+ const struct nftnl_udata **tb = data;
+ uint8_t len = nftnl_udata_len(attr);
+
+ switch (type) {
+ case NFTNL_UDATA_CHAIN_COMMENT:
+ if (value[len - 1] != '\0')
+ return -1;
+ break;
+ default:
+ return 0;
+ }
+ tb[type] = attr;
+ return 0;
+}
+
+static int qsort_device_cmp(const void *a, const void *b)
+{
+ const char **x = (const char **)a;
+ const char **y = (const char **)b;
+
+ return strcmp(*x, *y);
+}
+
+struct chain *netlink_delinearize_chain(struct netlink_ctx *ctx,
+ const struct nftnl_chain *nlc)
+{
+ const struct nftnl_udata *ud[NFTNL_UDATA_OBJ_MAX + 1] = {};
+ int priority, policy, len = 0, i;
+ const char * const *dev_array;
+ struct chain *chain;
+ const char *udata;
+ uint32_t ulen;
+
+ chain = chain_alloc();
+ chain->handle.family =
+ nftnl_chain_get_u32(nlc, NFTNL_CHAIN_FAMILY);
+ chain->handle.table.name =
+ xstrdup(nftnl_chain_get_str(nlc, NFTNL_CHAIN_TABLE));
+ chain->handle.chain.name =
+ xstrdup(nftnl_chain_get_str(nlc, NFTNL_CHAIN_NAME));
+ chain->handle.handle.id =
+ nftnl_chain_get_u64(nlc, NFTNL_CHAIN_HANDLE);
+ if (nftnl_chain_is_set(nlc, NFTNL_CHAIN_FLAGS))
+ chain->flags = nftnl_chain_get_u32(nlc, NFTNL_CHAIN_FLAGS);
+
+ if (nftnl_chain_is_set(nlc, NFTNL_CHAIN_HOOKNUM) &&
+ nftnl_chain_is_set(nlc, NFTNL_CHAIN_PRIO) &&
+ nftnl_chain_is_set(nlc, NFTNL_CHAIN_TYPE) &&
+ nftnl_chain_is_set(nlc, NFTNL_CHAIN_POLICY)) {
+ chain->hook.num =
+ nftnl_chain_get_u32(nlc, NFTNL_CHAIN_HOOKNUM);
+ chain->hook.name =
+ hooknum2str(chain->handle.family, chain->hook.num);
+ priority = nftnl_chain_get_s32(nlc, NFTNL_CHAIN_PRIO);
+ chain->priority.expr =
+ constant_expr_alloc(&netlink_location,
+ &integer_type,
+ BYTEORDER_HOST_ENDIAN,
+ sizeof(int) * BITS_PER_BYTE,
+ &priority);
+ chain->type.str =
+ xstrdup(nftnl_chain_get_str(nlc, NFTNL_CHAIN_TYPE));
+ policy = nftnl_chain_get_u32(nlc, NFTNL_CHAIN_POLICY);
+ chain->policy = constant_expr_alloc(&netlink_location,
+ &integer_type,
+ BYTEORDER_HOST_ENDIAN,
+ sizeof(int) * BITS_PER_BYTE,
+ &policy);
+ nftnl_chain_get_u32(nlc, NFTNL_CHAIN_POLICY);
+ if (nftnl_chain_is_set(nlc, NFTNL_CHAIN_DEV)) {
+ chain->dev_array = xmalloc(sizeof(char *) * 2);
+ chain->dev_array_len = 1;
+ chain->dev_array[0] =
+ xstrdup(nftnl_chain_get_str(nlc, NFTNL_CHAIN_DEV));
+ chain->dev_array[1] = NULL;
+ } else if (nftnl_chain_is_set(nlc, NFTNL_CHAIN_DEVICES)) {
+ dev_array = nftnl_chain_get(nlc, NFTNL_CHAIN_DEVICES);
+ while (dev_array[len])
+ len++;
+
+ chain->dev_array = xmalloc((len + 1)* sizeof(char *));
+ for (i = 0; i < len; i++)
+ chain->dev_array[i] = xstrdup(dev_array[i]);
+
+ chain->dev_array[i] = NULL;
+ chain->dev_array_len = len;
+ }
+ chain->flags |= CHAIN_F_BASECHAIN;
+
+ if (chain->dev_array_len) {
+ qsort(chain->dev_array, chain->dev_array_len,
+ sizeof(char *), qsort_device_cmp);
+ }
+ }
+
+ if (nftnl_chain_is_set(nlc, NFTNL_CHAIN_USERDATA)) {
+ udata = nftnl_chain_get_data(nlc, NFTNL_CHAIN_USERDATA, &ulen);
+ if (nftnl_udata_parse(udata, ulen, chain_parse_udata_cb, ud) < 0) {
+ netlink_io_error(ctx, NULL, "Cannot parse userdata");
+ chain_free(chain);
+ return NULL;
+ }
+ if (ud[NFTNL_UDATA_CHAIN_COMMENT])
+ chain->comment = xstrdup(nftnl_udata_get(ud[NFTNL_UDATA_CHAIN_COMMENT]));
+ }
+
+ return chain;
+}
+
+static int table_parse_udata_cb(const struct nftnl_udata *attr, void *data)
+{
+ unsigned char *value = nftnl_udata_get(attr);
+ const struct nftnl_udata **tb = data;
+ uint8_t type = nftnl_udata_type(attr);
+ uint8_t len = nftnl_udata_len(attr);
+
+ switch (type) {
+ case NFTNL_UDATA_TABLE_COMMENT:
+ if (value[len - 1] != '\0')
+ return -1;
+ break;
+ default:
+ return 0;
+ }
+ tb[type] = attr;
+ return 0;
+}
+
+struct table *netlink_delinearize_table(struct netlink_ctx *ctx,
+ const struct nftnl_table *nlt)
+{
+ const struct nftnl_udata *ud[NFTNL_UDATA_TABLE_MAX + 1] = {};
+ struct table *table;
+ const char *udata;
+ uint32_t ulen;
+
+ table = table_alloc();
+ table->handle.family = nftnl_table_get_u32(nlt, NFTNL_TABLE_FAMILY);
+ table->handle.table.name = xstrdup(nftnl_table_get_str(nlt, NFTNL_TABLE_NAME));
+ table->flags = nftnl_table_get_u32(nlt, NFTNL_TABLE_FLAGS);
+ table->handle.handle.id = nftnl_table_get_u64(nlt, NFTNL_TABLE_HANDLE);
+ table->owner = nftnl_table_get_u32(nlt, NFTNL_TABLE_OWNER);
+
+ if (nftnl_table_is_set(nlt, NFTNL_TABLE_USERDATA)) {
+ udata = nftnl_table_get_data(nlt, NFTNL_TABLE_USERDATA, &ulen);
+ if (nftnl_udata_parse(udata, ulen, table_parse_udata_cb, ud) < 0) {
+ netlink_io_error(ctx, NULL, "Cannot parse userdata");
+ table_free(table);
+ return NULL;
+ }
+ if (ud[NFTNL_UDATA_TABLE_COMMENT])
+ table->comment = xstrdup(nftnl_udata_get(ud[NFTNL_UDATA_TABLE_COMMENT]));
+ }
+
+ return table;
+}
+
+static int list_table_cb(struct nftnl_table *nlt, void *arg)
+{
+ struct netlink_ctx *ctx = arg;
+ struct table *table;
+
+ table = netlink_delinearize_table(ctx, nlt);
+ list_add_tail(&table->list, &ctx->list);
+
+ return 0;
+}
+
+int netlink_list_tables(struct netlink_ctx *ctx, const struct handle *h,
+ const struct nft_cache_filter *filter)
+{
+ struct nftnl_table_list *table_cache;
+ uint32_t family = h->family;
+ const char *table = NULL;
+
+ if (filter) {
+ family = filter->list.family;
+ table = filter->list.table;
+ }
+
+ table_cache = mnl_nft_table_dump(ctx, family, table);
+ if (table_cache == NULL) {
+ if (errno == EINTR)
+ return -1;
+
+ return -1;
+ }
+
+ ctx->data = h;
+ nftnl_table_list_foreach(table_cache, list_table_cb, ctx);
+ nftnl_table_list_free(table_cache);
+ return 0;
+}
+
+enum nft_data_types dtype_map_to_kernel(const struct datatype *dtype)
+{
+ switch (dtype->type) {
+ case TYPE_VERDICT:
+ return NFT_DATA_VERDICT;
+ default:
+ return dtype->type;
+ }
+}
+
+static const struct datatype *dtype_map_from_kernel(enum nft_data_types type)
+{
+ /* The function always returns ownership of a reference. But for
+ * &verdict_Type and datatype_lookup(), those are static instances,
+ * we can omit the datatype_get() call.
+ */
+ switch (type) {
+ case NFT_DATA_VERDICT:
+ return &verdict_type;
+ default:
+ if (type & ~TYPE_MASK)
+ return concat_type_alloc(type);
+ return datatype_lookup((enum datatypes) type);
+ }
+}
+
+void netlink_dump_set(const struct nftnl_set *nls, struct netlink_ctx *ctx)
+{
+ FILE *fp = ctx->nft->output.output_fp;
+
+ if (!(ctx->nft->debug_mask & NFT_DEBUG_NETLINK) || !fp)
+ return;
+
+ nftnl_set_fprintf(fp, nls, 0, 0);
+ fprintf(fp, "\n");
+}
+
+static int set_parse_udata_cb(const struct nftnl_udata *attr, void *data)
+{
+ unsigned char *value = nftnl_udata_get(attr);
+ const struct nftnl_udata **tb = data;
+ uint8_t type = nftnl_udata_type(attr);
+ uint8_t len = nftnl_udata_len(attr);
+
+ switch (type) {
+ case NFTNL_UDATA_SET_KEYBYTEORDER:
+ case NFTNL_UDATA_SET_DATABYTEORDER:
+ case NFTNL_UDATA_SET_MERGE_ELEMENTS:
+ case NFTNL_UDATA_SET_DATA_INTERVAL:
+ if (len != sizeof(uint32_t))
+ return -1;
+ break;
+ case NFTNL_UDATA_SET_KEY_TYPEOF:
+ case NFTNL_UDATA_SET_DATA_TYPEOF:
+ if (len < 3)
+ return -1;
+ break;
+ case NFTNL_UDATA_SET_COMMENT:
+ if (value[len - 1] != '\0')
+ return -1;
+ break;
+ default:
+ return 0;
+ }
+ tb[type] = attr;
+ return 0;
+}
+
+static int set_key_parse_udata(const struct nftnl_udata *attr, void *data)
+{
+ const struct nftnl_udata **tb = data;
+ uint8_t type = nftnl_udata_type(attr);
+ uint8_t len = nftnl_udata_len(attr);
+
+ switch (type) {
+ case NFTNL_UDATA_SET_TYPEOF_EXPR:
+ if (len != sizeof(uint32_t))
+ return -1;
+ break;
+ case NFTNL_UDATA_SET_TYPEOF_DATA:
+ break;
+ default:
+ return 0;
+ }
+ tb[type] = attr;
+ return 0;
+}
+
+static struct expr *set_make_key(const struct nftnl_udata *attr)
+{
+ const struct nftnl_udata *ud[NFTNL_UDATA_SET_TYPEOF_MAX + 1] = {};
+ const struct expr_ops *ops;
+ struct expr *expr;
+ uint32_t etype;
+ int err;
+
+ if (!attr)
+ return NULL;
+
+ err = nftnl_udata_parse(nftnl_udata_get(attr), nftnl_udata_len(attr),
+ set_key_parse_udata, ud);
+ if (err < 0)
+ return NULL;
+
+ if (!ud[NFTNL_UDATA_SET_TYPEOF_EXPR] ||
+ !ud[NFTNL_UDATA_SET_TYPEOF_DATA])
+ return NULL;
+
+ etype = nftnl_udata_get_u32(ud[NFTNL_UDATA_SET_TYPEOF_EXPR]);
+ ops = expr_ops_by_type_u32(etype);
+ if (!ops)
+ return NULL;
+
+ expr = ops->parse_udata(ud[NFTNL_UDATA_SET_TYPEOF_DATA]);
+ if (!expr)
+ return NULL;
+
+ return expr;
+}
+
+static bool set_udata_key_valid(const struct expr *e, uint32_t len)
+{
+ if (!e)
+ return false;
+
+ return div_round_up(e->len, BITS_PER_BYTE) == len / BITS_PER_BYTE;
+}
+
+struct setelem_parse_ctx {
+ struct set *set;
+ struct nft_cache *cache;
+ struct list_head stmt_list;
+};
+
+static int set_elem_parse_expressions(struct nftnl_expr *e, void *data)
+{
+ struct setelem_parse_ctx *setelem_parse_ctx = data;
+ struct nft_cache *cache = setelem_parse_ctx->cache;
+ struct set *set = setelem_parse_ctx->set;
+ struct stmt *stmt;
+
+ stmt = netlink_parse_set_expr(set, cache, e);
+ list_add_tail(&stmt->list, &setelem_parse_ctx->stmt_list);
+
+ return 0;
+}
+
+struct set *netlink_delinearize_set(struct netlink_ctx *ctx,
+ const struct nftnl_set *nls)
+{
+ const struct nftnl_udata *ud[NFTNL_UDATA_SET_MAX + 1] = {};
+ enum byteorder keybyteorder = BYTEORDER_INVALID;
+ enum byteorder databyteorder = BYTEORDER_INVALID;
+ struct setelem_parse_ctx set_parse_ctx;
+ const struct datatype *datatype = NULL;
+ const struct datatype *keytype = NULL;
+ const struct datatype *dtype2 = NULL;
+ const struct datatype *dtype = NULL;
+ struct expr *typeof_expr_data = NULL;
+ struct expr *typeof_expr_key = NULL;
+ const char *udata, *comment = NULL;
+ uint32_t flags, key, objtype = 0;
+ uint32_t data_interval = 0;
+ bool automerge = false;
+ struct set *set;
+ uint32_t ulen;
+ uint32_t klen;
+
+ if (nftnl_set_is_set(nls, NFTNL_SET_USERDATA)) {
+ udata = nftnl_set_get_data(nls, NFTNL_SET_USERDATA, &ulen);
+ if (nftnl_udata_parse(udata, ulen, set_parse_udata_cb, ud) < 0) {
+ netlink_io_error(ctx, NULL, "Cannot parse userdata");
+ return NULL;
+ }
+
+#define GET_U32_UDATA(var, attr) \
+ if (ud[attr]) \
+ var = nftnl_udata_get_u32(ud[attr])
+
+ GET_U32_UDATA(keybyteorder, NFTNL_UDATA_SET_KEYBYTEORDER);
+ GET_U32_UDATA(databyteorder, NFTNL_UDATA_SET_DATABYTEORDER);
+ GET_U32_UDATA(automerge, NFTNL_UDATA_SET_MERGE_ELEMENTS);
+ GET_U32_UDATA(data_interval, NFTNL_UDATA_SET_DATA_INTERVAL);
+
+#undef GET_U32_UDATA
+ typeof_expr_key = set_make_key(ud[NFTNL_UDATA_SET_KEY_TYPEOF]);
+ if (ud[NFTNL_UDATA_SET_DATA_TYPEOF])
+ typeof_expr_data = set_make_key(ud[NFTNL_UDATA_SET_DATA_TYPEOF]);
+ if (ud[NFTNL_UDATA_SET_COMMENT])
+ comment = nftnl_udata_get(ud[NFTNL_UDATA_SET_COMMENT]);
+ }
+
+ key = nftnl_set_get_u32(nls, NFTNL_SET_KEY_TYPE);
+ keytype = dtype_map_from_kernel(key);
+ if (keytype == NULL) {
+ netlink_io_error(ctx, NULL, "Unknown data type in set key %u",
+ key);
+ return NULL;
+ }
+
+ flags = nftnl_set_get_u32(nls, NFTNL_SET_FLAGS);
+ if (set_is_datamap(flags)) {
+ uint32_t data;
+
+ data = nftnl_set_get_u32(nls, NFTNL_SET_DATA_TYPE);
+ datatype = dtype_map_from_kernel(data);
+ if (datatype == NULL) {
+ netlink_io_error(ctx, NULL,
+ "Unknown data type in set key %u",
+ data);
+ set = NULL;
+ goto out;
+ }
+ }
+
+ if (set_is_objmap(flags)) {
+ objtype = nftnl_set_get_u32(nls, NFTNL_SET_OBJ_TYPE);
+ assert(!datatype);
+ datatype = &string_type;
+ }
+
+ set = set_alloc(&netlink_location);
+ set->handle.family = nftnl_set_get_u32(nls, NFTNL_SET_FAMILY);
+ set->handle.table.name = xstrdup(nftnl_set_get_str(nls, NFTNL_SET_TABLE));
+ set->handle.set.name = xstrdup(nftnl_set_get_str(nls, NFTNL_SET_NAME));
+ set->automerge = automerge;
+ if (comment)
+ set->comment = xstrdup(comment);
+
+ init_list_head(&set_parse_ctx.stmt_list);
+
+ if (nftnl_set_is_set(nls, NFTNL_SET_EXPR)) {
+ const struct nftnl_expr *nle;
+ struct stmt *stmt;
+
+ nle = nftnl_set_get(nls, NFTNL_SET_EXPR);
+ stmt = netlink_parse_set_expr(set, &ctx->nft->cache, nle);
+ list_add_tail(&stmt->list, &set_parse_ctx.stmt_list);
+ } else if (nftnl_set_is_set(nls, NFTNL_SET_EXPRESSIONS)) {
+ set_parse_ctx.cache = &ctx->nft->cache;
+ set_parse_ctx.set = set;
+ nftnl_set_expr_foreach(nls, set_elem_parse_expressions,
+ &set_parse_ctx);
+ }
+ list_splice_tail(&set_parse_ctx.stmt_list, &set->stmt_list);
+
+ if (datatype) {
+ uint32_t dlen;
+
+ dtype2 = set_datatype_alloc(datatype, databyteorder);
+ klen = nftnl_set_get_u32(nls, NFTNL_SET_DATA_LEN) * BITS_PER_BYTE;
+
+ dlen = data_interval ? klen / 2 : klen;
+
+ if (set_udata_key_valid(typeof_expr_data, dlen)) {
+ typeof_expr_data->len = klen;
+ set->data = typeof_expr_data;
+ typeof_expr_data = NULL;
+ } else {
+ set->data = constant_expr_alloc(&netlink_location,
+ dtype2,
+ databyteorder, klen,
+ NULL);
+
+ /* Can't use 'typeof' keyword, so discard key too */
+ expr_free(typeof_expr_key);
+ typeof_expr_key = NULL;
+ }
+
+ if (data_interval)
+ set->data->flags |= EXPR_F_INTERVAL;
+ }
+
+ dtype = set_datatype_alloc(keytype, keybyteorder);
+ klen = nftnl_set_get_u32(nls, NFTNL_SET_KEY_LEN) * BITS_PER_BYTE;
+
+ if (set_udata_key_valid(typeof_expr_key, klen)) {
+ set->key = typeof_expr_key;
+ typeof_expr_key = NULL;
+ set->key_typeof_valid = true;
+ } else {
+ set->key = constant_expr_alloc(&netlink_location, dtype,
+ keybyteorder, klen,
+ NULL);
+ }
+
+ set->flags = nftnl_set_get_u32(nls, NFTNL_SET_FLAGS);
+ set->handle.handle.id = nftnl_set_get_u64(nls, NFTNL_SET_HANDLE);
+
+ set->objtype = objtype;
+
+ if (nftnl_set_is_set(nls, NFTNL_SET_TIMEOUT))
+ set->timeout = nftnl_set_get_u64(nls, NFTNL_SET_TIMEOUT);
+ if (nftnl_set_is_set(nls, NFTNL_SET_GC_INTERVAL))
+ set->gc_int = nftnl_set_get_u32(nls, NFTNL_SET_GC_INTERVAL);
+
+ if (nftnl_set_is_set(nls, NFTNL_SET_POLICY))
+ set->policy = nftnl_set_get_u32(nls, NFTNL_SET_POLICY);
+
+ if (nftnl_set_is_set(nls, NFTNL_SET_DESC_SIZE))
+ set->desc.size = nftnl_set_get_u32(nls, NFTNL_SET_DESC_SIZE);
+
+ if (nftnl_set_is_set(nls, NFTNL_SET_DESC_CONCAT)) {
+ uint32_t len = NFT_REG32_COUNT;
+ const uint8_t *data;
+
+ data = nftnl_set_get_data(nls, NFTNL_SET_DESC_CONCAT, &len);
+ if (data) {
+ memcpy(set->desc.field_len, data, len);
+ set->desc.field_count = len;
+ }
+ }
+
+out:
+ expr_free(typeof_expr_data);
+ expr_free(typeof_expr_key);
+ datatype_free(datatype);
+ datatype_free(keytype);
+ datatype_free(dtype2);
+ datatype_free(dtype);
+ return set;
+}
+
+void alloc_setelem_cache(const struct expr *set, struct nftnl_set *nls)
+{
+ struct nftnl_set_elem *nlse;
+ const struct expr *expr;
+
+ list_for_each_entry(expr, &set->expressions, list) {
+ nlse = alloc_nftnl_setelem(set, expr);
+ nftnl_set_elem_add(nls, nlse);
+ }
+}
+
+static bool range_expr_is_prefix(const struct expr *range, uint32_t *prefix_len)
+{
+ const struct expr *right = range->right;
+ const struct expr *left = range->left;
+ uint32_t len = left->len;
+ unsigned long n1, n2;
+ uint32_t plen;
+ mpz_t bitmask;
+
+ mpz_init2(bitmask, left->len);
+ mpz_xor(bitmask, left->value, right->value);
+
+ n1 = mpz_scan0(bitmask, 0);
+ if (n1 == ULONG_MAX)
+ goto not_a_prefix;
+
+ n2 = mpz_scan1(bitmask, n1 + 1);
+ if (n2 < len)
+ goto not_a_prefix;
+
+ plen = len - n1;
+
+ if (mpz_scan1(left->value, 0) < len - plen)
+ goto not_a_prefix;
+
+ mpz_clear(bitmask);
+ *prefix_len = plen;
+
+ return true;
+
+not_a_prefix:
+ mpz_clear(bitmask);
+
+ return false;
+}
+
+struct expr *range_expr_to_prefix(struct expr *range)
+{
+ struct expr *prefix;
+ uint32_t prefix_len;
+
+ if (range_expr_is_prefix(range, &prefix_len)) {
+ prefix = prefix_expr_alloc(&range->location,
+ expr_get(range->left),
+ prefix_len);
+ expr_free(range);
+ return prefix;
+ }
+
+ return range;
+}
+
+static struct expr *range_expr_reduce(struct expr *range)
+{
+ struct expr *expr;
+
+ if (!mpz_cmp(range->left->value, range->right->value)) {
+ expr = expr_get(range->left);
+ expr_free(range);
+ return expr;
+ }
+
+ if (range->left->dtype->type != TYPE_IPADDR &&
+ range->left->dtype->type != TYPE_IP6ADDR)
+ return range;
+
+ return range_expr_to_prefix(range);
+}
+
+static struct expr *netlink_parse_interval_elem(const struct set *set,
+ struct expr *expr)
+{
+ unsigned int len = div_round_up(expr->len, BITS_PER_BYTE);
+ const struct datatype *dtype = set->data->dtype;
+ struct expr *range, *left, *right;
+ char data[len];
+
+ mpz_export_data(data, expr->value, dtype->byteorder, len);
+ left = constant_expr_alloc(&internal_location, dtype,
+ dtype->byteorder,
+ (len / 2) * BITS_PER_BYTE, &data[0]);
+ right = constant_expr_alloc(&internal_location, dtype,
+ dtype->byteorder,
+ (len / 2) * BITS_PER_BYTE, &data[len / 2]);
+ range = range_expr_alloc(&expr->location, left, right);
+ expr_free(expr);
+
+ return range_expr_to_prefix(range);
+}
+
+static struct expr *concat_elem_expr(const struct set *set, struct expr *key,
+ const struct datatype *dtype,
+ struct expr *data, int *off)
+{
+ const struct datatype *subtype;
+ unsigned int sub_length;
+ struct expr *expr;
+
+ if (key) {
+ (*off)--;
+ sub_length = round_up(key->len, BITS_PER_BYTE);
+
+ expr = constant_expr_splice(data, sub_length);
+ expr->dtype = datatype_get(key->dtype);
+ expr->byteorder = key->byteorder;
+ expr->len = key->len;
+ } else {
+ subtype = concat_subtype_lookup(dtype->type, --(*off));
+ sub_length = round_up(subtype->size, BITS_PER_BYTE);
+ expr = constant_expr_splice(data, sub_length);
+ expr->dtype = subtype;
+ expr->byteorder = subtype->byteorder;
+ }
+
+ if (expr_basetype(expr)->type == TYPE_STRING ||
+ (!(set->flags & NFT_SET_INTERVAL) &&
+ expr->byteorder == BYTEORDER_HOST_ENDIAN))
+ mpz_switch_byteorder(expr->value, expr->len / BITS_PER_BYTE);
+
+ if (expr->dtype->basetype != NULL &&
+ expr->dtype->basetype->type == TYPE_BITMASK)
+ expr = bitmask_expr_to_binops(expr);
+
+ data->len -= netlink_padding_len(sub_length);
+
+ return expr;
+}
+
+static struct expr *netlink_parse_concat_elem_key(const struct set *set,
+ struct expr *data)
+{
+ const struct datatype *dtype = set->key->dtype;
+ struct expr *concat, *expr, *n = NULL;
+ int off = dtype->subtypes;
+
+ if (set->key->etype == EXPR_CONCAT)
+ n = list_first_entry(&set->key->expressions, struct expr, list);
+
+ concat = concat_expr_alloc(&data->location);
+ while (off > 0) {
+ expr = concat_elem_expr(set, n, dtype, data, &off);
+ compound_expr_add(concat, expr);
+ if (set->key->etype == EXPR_CONCAT)
+ n = list_next_entry(n, list);
+ }
+
+ expr_free(data);
+
+ return concat;
+}
+
+static struct expr *netlink_parse_concat_elem(const struct set *set,
+ struct expr *data)
+{
+ const struct datatype *dtype = set->data->dtype;
+ struct expr *concat, *expr, *left, *range;
+ struct list_head expressions;
+ int off = dtype->subtypes;
+
+ init_list_head(&expressions);
+
+ concat = concat_expr_alloc(&data->location);
+ while (off > 0) {
+ expr = concat_elem_expr(set, NULL, dtype, data, &off);
+ list_add_tail(&expr->list, &expressions);
+ }
+
+ if (set->data->flags & EXPR_F_INTERVAL) {
+ assert(!list_empty(&expressions));
+
+ off = dtype->subtypes;
+
+ while (off > 0) {
+ left = list_first_entry(&expressions, struct expr, list);
+
+ expr = concat_elem_expr(set, NULL, dtype, data, &off);
+ list_del(&left->list);
+
+ range = range_expr_alloc(&data->location, left, expr);
+ range = range_expr_reduce(range);
+ compound_expr_add(concat, range);
+ }
+ assert(list_empty(&expressions));
+ } else {
+ list_splice_tail(&expressions, &concat->expressions);
+ }
+
+ expr_free(data);
+
+ return concat;
+}
+
+static int set_elem_parse_udata_cb(const struct nftnl_udata *attr, void *data)
+{
+ const struct nftnl_udata **tb = data;
+ unsigned char *value = nftnl_udata_get(attr);
+ uint8_t type = nftnl_udata_type(attr);
+ uint8_t len = nftnl_udata_len(attr);
+
+ switch (type) {
+ case NFTNL_UDATA_SET_ELEM_COMMENT:
+ if (value[len - 1] != '\0')
+ return -1;
+ break;
+ case NFTNL_UDATA_SET_ELEM_FLAGS:
+ if (len != sizeof(uint32_t))
+ return -1;
+ break;
+ default:
+ return 0;
+ }
+ tb[type] = attr;
+ return 0;
+}
+
+static void set_elem_parse_udata(struct nftnl_set_elem *nlse,
+ struct expr *expr)
+{
+ const struct nftnl_udata *ud[NFTNL_UDATA_SET_ELEM_MAX + 1] = {};
+ const void *data;
+ uint32_t len;
+
+ data = nftnl_set_elem_get(nlse, NFTNL_SET_ELEM_USERDATA, &len);
+ if (nftnl_udata_parse(data, len, set_elem_parse_udata_cb, ud))
+ return;
+
+ if (ud[NFTNL_UDATA_SET_ELEM_COMMENT])
+ expr->comment =
+ xstrdup(nftnl_udata_get(ud[NFTNL_UDATA_SET_ELEM_COMMENT]));
+ if (ud[NFTNL_UDATA_SET_ELEM_FLAGS])
+ expr->elem_flags =
+ nftnl_udata_get_u32(ud[NFTNL_UDATA_SET_ELEM_FLAGS]);
+}
+
+int netlink_delinearize_setelem(struct nftnl_set_elem *nlse,
+ struct set *set, struct nft_cache *cache)
+{
+ struct setelem_parse_ctx setelem_parse_ctx = {
+ .set = set,
+ .cache = cache,
+ };
+ struct nft_data_delinearize nld;
+ struct expr *expr, *key, *data;
+ uint32_t flags = 0;
+
+ init_list_head(&setelem_parse_ctx.stmt_list);
+
+ if (nftnl_set_elem_is_set(nlse, NFTNL_SET_ELEM_KEY))
+ nld.value = nftnl_set_elem_get(nlse, NFTNL_SET_ELEM_KEY, &nld.len);
+ if (nftnl_set_elem_is_set(nlse, NFTNL_SET_ELEM_FLAGS))
+ flags = nftnl_set_elem_get_u32(nlse, NFTNL_SET_ELEM_FLAGS);
+
+key_end:
+ if (nftnl_set_elem_is_set(nlse, NFTNL_SET_ELEM_KEY)) {
+ key = netlink_alloc_value(&netlink_location, &nld);
+ datatype_set(key, set->key->dtype);
+ key->byteorder = set->key->byteorder;
+ if (set->key->dtype->subtypes)
+ key = netlink_parse_concat_elem_key(set, key);
+
+ if (!(set->flags & NFT_SET_INTERVAL) &&
+ key->byteorder == BYTEORDER_HOST_ENDIAN)
+ mpz_switch_byteorder(key->value, key->len / BITS_PER_BYTE);
+
+ if (key->dtype->basetype != NULL &&
+ key->dtype->basetype->type == TYPE_BITMASK)
+ key = bitmask_expr_to_binops(key);
+ } else if (flags & NFT_SET_ELEM_CATCHALL) {
+ key = set_elem_catchall_expr_alloc(&netlink_location);
+ datatype_set(key, set->key->dtype);
+ key->byteorder = set->key->byteorder;
+ key->len = set->key->len;
+ } else {
+ BUG("Unexpected set element with no key\n");
+ }
+
+ expr = set_elem_expr_alloc(&netlink_location, key);
+ expr->flags |= EXPR_F_KERNEL;
+
+ if (nftnl_set_elem_is_set(nlse, NFTNL_SET_ELEM_TIMEOUT))
+ expr->timeout = nftnl_set_elem_get_u64(nlse, NFTNL_SET_ELEM_TIMEOUT);
+ if (nftnl_set_elem_is_set(nlse, NFTNL_SET_ELEM_EXPIRATION))
+ expr->expiration = nftnl_set_elem_get_u64(nlse, NFTNL_SET_ELEM_EXPIRATION);
+ if (nftnl_set_elem_is_set(nlse, NFTNL_SET_ELEM_USERDATA))
+ set_elem_parse_udata(nlse, expr);
+ if (nftnl_set_elem_is_set(nlse, NFTNL_SET_ELEM_EXPR)) {
+ const struct nftnl_expr *nle;
+ struct stmt *stmt;
+
+ nle = nftnl_set_elem_get(nlse, NFTNL_SET_ELEM_EXPR, NULL);
+ stmt = netlink_parse_set_expr(set, cache, nle);
+ list_add_tail(&stmt->list, &setelem_parse_ctx.stmt_list);
+ } else if (nftnl_set_elem_is_set(nlse, NFTNL_SET_ELEM_EXPRESSIONS)) {
+ nftnl_set_elem_expr_foreach(nlse, set_elem_parse_expressions,
+ &setelem_parse_ctx);
+ }
+ list_splice_tail_init(&setelem_parse_ctx.stmt_list, &expr->stmt_list);
+
+ if (flags & NFT_SET_ELEM_INTERVAL_END) {
+ expr->flags |= EXPR_F_INTERVAL_END;
+ if (mpz_cmp_ui(set->key->value, 0) == 0)
+ set->root = true;
+ }
+
+ if (set_is_datamap(set->flags)) {
+ if (nftnl_set_elem_is_set(nlse, NFTNL_SET_ELEM_DATA)) {
+ nld.value = nftnl_set_elem_get(nlse, NFTNL_SET_ELEM_DATA,
+ &nld.len);
+ } else if (nftnl_set_elem_is_set(nlse, NFTNL_SET_ELEM_CHAIN)) {
+ nld.chain = nftnl_set_elem_get_str(nlse, NFTNL_SET_ELEM_CHAIN);
+ nld.verdict = nftnl_set_elem_get_u32(nlse, NFTNL_SET_ELEM_VERDICT);
+ } else if (nftnl_set_elem_is_set(nlse, NFTNL_SET_ELEM_VERDICT)) {
+ nld.verdict = nftnl_set_elem_get_u32(nlse, NFTNL_SET_ELEM_VERDICT);
+ } else
+ goto out;
+
+ data = netlink_alloc_data(&netlink_location, &nld,
+ set->data->dtype->type == TYPE_VERDICT ?
+ NFT_REG_VERDICT : NFT_REG_1);
+ datatype_set(data, set->data->dtype);
+ data->byteorder = set->data->byteorder;
+
+ if (set->data->dtype->subtypes) {
+ data = netlink_parse_concat_elem(set, data);
+ } else if (set->data->flags & EXPR_F_INTERVAL)
+ data = netlink_parse_interval_elem(set, data);
+
+ if (data->byteorder == BYTEORDER_HOST_ENDIAN)
+ mpz_switch_byteorder(data->value, data->len / BITS_PER_BYTE);
+
+ expr = mapping_expr_alloc(&netlink_location, expr, data);
+ }
+ if (set_is_objmap(set->flags)) {
+ if (nftnl_set_elem_is_set(nlse, NFTNL_SET_ELEM_OBJREF)) {
+ nld.value = nftnl_set_elem_get(nlse,
+ NFTNL_SET_ELEM_OBJREF,
+ &nld.len);
+ } else
+ goto out;
+
+ data = netlink_alloc_value(&netlink_location, &nld);
+ data->dtype = &string_type;
+ data->byteorder = BYTEORDER_HOST_ENDIAN;
+ mpz_switch_byteorder(data->value, data->len / BITS_PER_BYTE);
+ expr = mapping_expr_alloc(&netlink_location, expr, data);
+ }
+out:
+ compound_expr_add(set->init, expr);
+
+ if (!(flags & NFT_SET_ELEM_INTERVAL_END) &&
+ nftnl_set_elem_is_set(nlse, NFTNL_SET_ELEM_KEY_END)) {
+ flags |= NFT_SET_ELEM_INTERVAL_END;
+ nld.value = nftnl_set_elem_get(nlse, NFTNL_SET_ELEM_KEY_END,
+ &nld.len);
+ goto key_end;
+ }
+
+ return 0;
+}
+
+static int list_setelem_cb(struct nftnl_set_elem *nlse, void *arg)
+{
+ struct netlink_ctx *ctx = arg;
+ return netlink_delinearize_setelem(nlse, ctx->set, &ctx->nft->cache);
+}
+
+static int list_setelem_debug_cb(struct nftnl_set_elem *nlse, void *arg)
+{
+ int r;
+
+ r = list_setelem_cb(nlse, arg);
+ if (r == 0) {
+ struct netlink_ctx *ctx = arg;
+ FILE *fp = ctx->nft->output.output_fp;
+
+ fprintf(fp, "\t");
+ nftnl_set_elem_fprintf(fp, nlse, 0, 0);
+ fprintf(fp, "\n");
+ }
+
+ return r;
+}
+
+static int list_setelements(struct nftnl_set *s, struct netlink_ctx *ctx)
+{
+ FILE *fp = ctx->nft->output.output_fp;
+
+ if (fp && (ctx->nft->debug_mask & NFT_DEBUG_NETLINK)) {
+ const char *table, *name;
+ uint32_t family = nftnl_set_get_u32(s, NFTNL_SET_FAMILY);
+
+ table = nftnl_set_get_str(s, NFTNL_SET_TABLE);
+ name = nftnl_set_get_str(s, NFTNL_SET_NAME);
+
+ fprintf(fp, "%s %s @%s\n", family2str(family), table, name);
+
+ return nftnl_set_elem_foreach(s, list_setelem_debug_cb, ctx);
+ }
+
+ return nftnl_set_elem_foreach(s, list_setelem_cb, ctx);
+}
+
+int netlink_list_setelems(struct netlink_ctx *ctx, const struct handle *h,
+ struct set *set, bool reset)
+{
+ struct nftnl_set *nls;
+ int err;
+
+ nls = nftnl_set_alloc();
+ if (nls == NULL)
+ memory_allocation_error();
+
+ nftnl_set_set_u32(nls, NFTNL_SET_FAMILY, h->family);
+ nftnl_set_set_str(nls, NFTNL_SET_TABLE, h->table.name);
+ nftnl_set_set_str(nls, NFTNL_SET_NAME, h->set.name);
+ if (h->handle.id)
+ nftnl_set_set_u64(nls, NFTNL_SET_HANDLE, h->handle.id);
+
+ err = mnl_nft_setelem_get(ctx, nls, reset);
+ if (err < 0) {
+ nftnl_set_free(nls);
+ if (errno == EINTR)
+ return -1;
+
+ return 0;
+ }
+
+ ctx->set = set;
+ set->init = set_expr_alloc(&internal_location, set);
+ list_setelements(nls, ctx);
+
+ if (set->flags & NFT_SET_INTERVAL && set->desc.field_count > 1)
+ concat_range_aggregate(set->init);
+ else if (set->flags & NFT_SET_INTERVAL)
+ interval_map_decompose(set->init);
+ else
+ list_expr_sort(&ctx->set->init->expressions);
+
+ nftnl_set_free(nls);
+ ctx->set = NULL;
+
+ return 0;
+}
+
+int netlink_get_setelem(struct netlink_ctx *ctx, const struct handle *h,
+ const struct location *loc, struct set *cache_set,
+ struct set *set, struct expr *init, bool reset)
+{
+ struct nftnl_set *nls, *nls_out = NULL;
+ int err = 0;
+
+ nls = nftnl_set_alloc();
+ if (nls == NULL)
+ memory_allocation_error();
+
+ nftnl_set_set_u32(nls, NFTNL_SET_FAMILY, h->family);
+ nftnl_set_set_str(nls, NFTNL_SET_TABLE, h->table.name);
+ nftnl_set_set_str(nls, NFTNL_SET_NAME, h->set.name);
+ if (h->handle.id)
+ nftnl_set_set_u64(nls, NFTNL_SET_HANDLE, h->handle.id);
+
+ alloc_setelem_cache(init, nls);
+
+ netlink_dump_set(nls, ctx);
+
+ nls_out = mnl_nft_setelem_get_one(ctx, nls, reset);
+ if (!nls_out) {
+ nftnl_set_free(nls);
+ return -1;
+ }
+
+ ctx->set = set;
+ set->init = set_expr_alloc(loc, set);
+ list_setelements(nls_out, ctx);
+
+ if (set->flags & NFT_SET_INTERVAL && set->desc.field_count > 1)
+ concat_range_aggregate(set->init);
+ else if (set->flags & NFT_SET_INTERVAL)
+ err = get_set_decompose(cache_set, set);
+ else
+ list_expr_sort(&ctx->set->init->expressions);
+
+ nftnl_set_free(nls);
+ nftnl_set_free(nls_out);
+ ctx->set = NULL;
+
+ return err;
+}
+
+void netlink_dump_obj(struct nftnl_obj *nln, struct netlink_ctx *ctx)
+{
+ FILE *fp = ctx->nft->output.output_fp;
+
+ if (!(ctx->nft->debug_mask & NFT_DEBUG_NETLINK) || !fp)
+ return;
+
+ nftnl_obj_fprintf(fp, nln, 0, 0);
+ fprintf(fp, "\n");
+}
+
+static int obj_parse_udata_cb(const struct nftnl_udata *attr, void *data)
+{
+ unsigned char *value = nftnl_udata_get(attr);
+ uint8_t type = nftnl_udata_type(attr);
+ const struct nftnl_udata **tb = data;
+ uint8_t len = nftnl_udata_len(attr);
+
+ switch (type) {
+ case NFTNL_UDATA_OBJ_COMMENT:
+ if (value[len - 1] != '\0')
+ return -1;
+ break;
+ default:
+ return 0;
+ }
+ tb[type] = attr;
+ return 0;
+}
+
+struct obj *netlink_delinearize_obj(struct netlink_ctx *ctx,
+ struct nftnl_obj *nlo)
+{
+ const struct nftnl_udata *ud[NFTNL_UDATA_OBJ_MAX + 1] = {};
+ const char *udata;
+ struct obj *obj;
+ uint32_t type;
+ uint32_t ulen;
+
+ obj = obj_alloc(&netlink_location);
+ obj->handle.family = nftnl_obj_get_u32(nlo, NFTNL_OBJ_FAMILY);
+ obj->handle.table.name =
+ xstrdup(nftnl_obj_get_str(nlo, NFTNL_OBJ_TABLE));
+ obj->handle.obj.name =
+ xstrdup(nftnl_obj_get_str(nlo, NFTNL_OBJ_NAME));
+ obj->handle.handle.id =
+ nftnl_obj_get_u64(nlo, NFTNL_OBJ_HANDLE);
+ if (nftnl_obj_is_set(nlo, NFTNL_OBJ_USERDATA)) {
+ udata = nftnl_obj_get_data(nlo, NFTNL_OBJ_USERDATA, &ulen);
+ if (nftnl_udata_parse(udata, ulen, obj_parse_udata_cb, ud) < 0) {
+ netlink_io_error(ctx, NULL, "Cannot parse userdata");
+ obj_free(obj);
+ return NULL;
+ }
+ if (ud[NFTNL_UDATA_OBJ_COMMENT])
+ obj->comment = xstrdup(nftnl_udata_get(ud[NFTNL_UDATA_OBJ_COMMENT]));
+ }
+
+ type = nftnl_obj_get_u32(nlo, NFTNL_OBJ_TYPE);
+ switch (type) {
+ case NFT_OBJECT_COUNTER:
+ obj->counter.packets =
+ nftnl_obj_get_u64(nlo, NFTNL_OBJ_CTR_PKTS);
+ obj->counter.bytes =
+ nftnl_obj_get_u64(nlo, NFTNL_OBJ_CTR_BYTES);
+ break;
+ case NFT_OBJECT_QUOTA:
+ obj->quota.bytes =
+ nftnl_obj_get_u64(nlo, NFTNL_OBJ_QUOTA_BYTES);
+ obj->quota.used =
+ nftnl_obj_get_u64(nlo, NFTNL_OBJ_QUOTA_CONSUMED);
+ obj->quota.flags =
+ nftnl_obj_get_u32(nlo, NFTNL_OBJ_QUOTA_FLAGS);
+ break;
+ case NFT_OBJECT_SECMARK:
+ snprintf(obj->secmark.ctx, sizeof(obj->secmark.ctx), "%s",
+ nftnl_obj_get_str(nlo, NFTNL_OBJ_SECMARK_CTX));
+ break;
+ case NFT_OBJECT_CT_HELPER:
+ snprintf(obj->ct_helper.name, sizeof(obj->ct_helper.name), "%s",
+ nftnl_obj_get_str(nlo, NFTNL_OBJ_CT_HELPER_NAME));
+ obj->ct_helper.l3proto = nftnl_obj_get_u16(nlo, NFTNL_OBJ_CT_HELPER_L3PROTO);
+ obj->ct_helper.l4proto = nftnl_obj_get_u8(nlo, NFTNL_OBJ_CT_HELPER_L4PROTO);
+ break;
+ case NFT_OBJECT_CT_TIMEOUT:
+ init_list_head(&obj->ct_timeout.timeout_list);
+ obj->ct_timeout.l3proto = nftnl_obj_get_u16(nlo, NFTNL_OBJ_CT_TIMEOUT_L3PROTO);
+ obj->ct_timeout.l4proto = nftnl_obj_get_u8(nlo, NFTNL_OBJ_CT_TIMEOUT_L4PROTO);
+ memcpy(obj->ct_timeout.timeout,
+ nftnl_obj_get(nlo, NFTNL_OBJ_CT_TIMEOUT_ARRAY),
+ NFTNL_CTTIMEOUT_ARRAY_MAX * sizeof(uint32_t));
+ break;
+ case NFT_OBJECT_LIMIT:
+ obj->limit.rate =
+ nftnl_obj_get_u64(nlo, NFTNL_OBJ_LIMIT_RATE);
+ obj->limit.unit =
+ nftnl_obj_get_u64(nlo, NFTNL_OBJ_LIMIT_UNIT);
+ obj->limit.burst =
+ nftnl_obj_get_u32(nlo, NFTNL_OBJ_LIMIT_BURST);
+ obj->limit.type =
+ nftnl_obj_get_u32(nlo, NFTNL_OBJ_LIMIT_TYPE);
+ obj->limit.flags =
+ nftnl_obj_get_u32(nlo, NFTNL_OBJ_LIMIT_FLAGS);
+ break;
+ case NFT_OBJECT_CT_EXPECT:
+ obj->ct_expect.l3proto =
+ nftnl_obj_get_u16(nlo, NFTNL_OBJ_CT_EXPECT_L3PROTO);
+ obj->ct_expect.l4proto =
+ nftnl_obj_get_u8(nlo, NFTNL_OBJ_CT_EXPECT_L4PROTO);
+ obj->ct_expect.dport =
+ nftnl_obj_get_u16(nlo, NFTNL_OBJ_CT_EXPECT_DPORT);
+ obj->ct_expect.timeout =
+ nftnl_obj_get_u32(nlo, NFTNL_OBJ_CT_EXPECT_TIMEOUT);
+ obj->ct_expect.size =
+ nftnl_obj_get_u8(nlo, NFTNL_OBJ_CT_EXPECT_SIZE);
+ break;
+ case NFT_OBJECT_SYNPROXY:
+ obj->synproxy.mss =
+ nftnl_obj_get_u16(nlo, NFTNL_OBJ_SYNPROXY_MSS);
+ obj->synproxy.wscale =
+ nftnl_obj_get_u8(nlo, NFTNL_OBJ_SYNPROXY_WSCALE);
+ obj->synproxy.flags =
+ nftnl_obj_get_u32(nlo, NFTNL_OBJ_SYNPROXY_FLAGS);
+ break;
+ }
+ obj->type = type;
+
+ return obj;
+}
+
+void netlink_dump_flowtable(struct nftnl_flowtable *flo,
+ struct netlink_ctx *ctx)
+{
+ FILE *fp = ctx->nft->output.output_fp;
+
+ if (!(ctx->nft->debug_mask & NFT_DEBUG_NETLINK) || !fp)
+ return;
+
+ nftnl_flowtable_fprintf(fp, flo, 0, 0);
+ fprintf(fp, "\n");
+}
+
+static int list_obj_cb(struct nftnl_obj *nls, void *arg)
+{
+ struct netlink_ctx *ctx = arg;
+ struct obj *obj;
+
+ obj = netlink_delinearize_obj(ctx, nls);
+ if (obj == NULL)
+ return -1;
+ list_add_tail(&obj->list, &ctx->list);
+ return 0;
+}
+
+int netlink_reset_objs(struct netlink_ctx *ctx, const struct cmd *cmd,
+ uint32_t type, bool dump)
+{
+ const struct handle *h = &cmd->handle;
+ struct nftnl_obj_list *obj_cache;
+ int err;
+
+ obj_cache = mnl_nft_obj_dump(ctx, h->family,
+ h->table.name, h->obj.name, type, dump, true);
+ if (obj_cache == NULL)
+ return -1;
+
+ err = nftnl_obj_list_foreach(obj_cache, list_obj_cb, ctx);
+ nftnl_obj_list_free(obj_cache);
+ return err;
+}
+
+int netlink_reset_rules(struct netlink_ctx *ctx, const struct cmd *cmd,
+ bool dump)
+{
+ const struct handle *h = &cmd->handle;
+ struct nft_cache_filter f = {
+ .list.table = h->table.name,
+ .list.chain = h->chain.name,
+ .list.rule_handle = h->handle.id,
+ };
+ struct rule *rule, *next, *crule, *cnext;
+ struct table *table;
+ struct chain *chain;
+ int ret;
+
+ ret = rule_cache_dump(ctx, h, &f, dump, true);
+
+ list_for_each_entry_safe(rule, next, &ctx->list, list) {
+ table = table_cache_find(&ctx->nft->cache.table_cache,
+ rule->handle.table.name,
+ rule->handle.family);
+ if (!table)
+ continue;
+
+ chain = chain_cache_find(table, rule->handle.chain.name);
+ if (!chain)
+ continue;
+
+ list_del(&rule->list);
+ list_for_each_entry_safe(crule, cnext, &chain->rules, list) {
+ if (crule->handle.handle.id != rule->handle.handle.id)
+ continue;
+
+ list_replace(&crule->list, &rule->list);
+ rule_free(crule);
+ rule = NULL;
+ break;
+ }
+ if (rule) {
+ list_add_tail(&rule->list, &chain->rules);
+ }
+ }
+ list_for_each_entry_safe(rule, next, &ctx->list, list) {
+ list_del(&rule->list);
+ rule_free(rule);
+ }
+
+ return ret;
+}
+
+struct flowtable *
+netlink_delinearize_flowtable(struct netlink_ctx *ctx,
+ struct nftnl_flowtable *nlo)
+{
+ struct flowtable *flowtable;
+ const char * const *dev_array;
+ int len = 0, i, priority;
+
+ flowtable = flowtable_alloc(&netlink_location);
+ flowtable->handle.family =
+ nftnl_flowtable_get_u32(nlo, NFTNL_FLOWTABLE_FAMILY);
+ flowtable->handle.table.name =
+ xstrdup(nftnl_flowtable_get_str(nlo, NFTNL_FLOWTABLE_TABLE));
+ flowtable->handle.flowtable.name =
+ xstrdup(nftnl_flowtable_get_str(nlo, NFTNL_FLOWTABLE_NAME));
+ flowtable->handle.handle.id =
+ nftnl_flowtable_get_u64(nlo, NFTNL_FLOWTABLE_HANDLE);
+ if (nftnl_flowtable_is_set(nlo, NFTNL_FLOWTABLE_FLAGS))
+ flowtable->flags = nftnl_flowtable_get_u32(nlo, NFTNL_FLOWTABLE_FLAGS);
+ dev_array = nftnl_flowtable_get(nlo, NFTNL_FLOWTABLE_DEVICES);
+ while (dev_array[len])
+ len++;
+
+ if (len)
+ flowtable->dev_array = xmalloc(len * sizeof(char *));
+ for (i = 0; i < len; i++)
+ flowtable->dev_array[i] = xstrdup(dev_array[i]);
+
+ flowtable->dev_array_len = len;
+
+ if (flowtable->dev_array_len) {
+ qsort(flowtable->dev_array, flowtable->dev_array_len,
+ sizeof(char *), qsort_device_cmp);
+ }
+
+ priority = nftnl_flowtable_get_u32(nlo, NFTNL_FLOWTABLE_PRIO);
+ flowtable->priority.expr =
+ constant_expr_alloc(&netlink_location,
+ &integer_type,
+ BYTEORDER_HOST_ENDIAN,
+ sizeof(int) *
+ BITS_PER_BYTE,
+ &priority);
+ flowtable->hook.num =
+ nftnl_flowtable_get_u32(nlo, NFTNL_FLOWTABLE_HOOKNUM);
+ flowtable->flags =
+ nftnl_flowtable_get_u32(nlo, NFTNL_FLOWTABLE_FLAGS);
+
+ return flowtable;
+}
+
+static int list_flowtable_cb(struct nftnl_flowtable *nls, void *arg)
+{
+ struct netlink_ctx *ctx = arg;
+ struct flowtable *flowtable;
+
+ flowtable = netlink_delinearize_flowtable(ctx, nls);
+ if (flowtable == NULL)
+ return -1;
+ list_add_tail(&flowtable->list, &ctx->list);
+ return 0;
+}
+
+int netlink_list_flowtables(struct netlink_ctx *ctx, const struct handle *h)
+{
+ struct nftnl_flowtable_list *flowtable_cache;
+ int err;
+
+ flowtable_cache = mnl_nft_flowtable_dump(ctx, h->family,
+ h->table.name, NULL);
+ if (flowtable_cache == NULL) {
+ if (errno == EINTR)
+ return -1;
+
+ return 0;
+ }
+
+ err = nftnl_flowtable_list_foreach(flowtable_cache, list_flowtable_cb, ctx);
+ nftnl_flowtable_list_free(flowtable_cache);
+ return err;
+}
+
+static void trace_print_hdr(const struct nftnl_trace *nlt,
+ struct output_ctx *octx)
+{
+ nft_print(octx, "trace id %08x %s ",
+ nftnl_trace_get_u32(nlt, NFTNL_TRACE_ID),
+ family2str(nftnl_trace_get_u32(nlt, NFTNL_TRACE_FAMILY)));
+ if (nftnl_trace_is_set(nlt, NFTNL_TRACE_TABLE))
+ nft_print(octx, "%s ",
+ nftnl_trace_get_str(nlt, NFTNL_TRACE_TABLE));
+ if (nftnl_trace_is_set(nlt, NFTNL_TRACE_CHAIN))
+ nft_print(octx, "%s ",
+ nftnl_trace_get_str(nlt, NFTNL_TRACE_CHAIN));
+}
+
+static void trace_print_expr(const struct nftnl_trace *nlt, unsigned int attr,
+ struct expr *lhs, struct output_ctx *octx)
+{
+ struct expr *rhs, *rel;
+ const void *data;
+ uint32_t len;
+
+ data = nftnl_trace_get_data(nlt, attr, &len);
+ rhs = constant_expr_alloc(&netlink_location,
+ lhs->dtype, lhs->byteorder,
+ len * BITS_PER_BYTE, data);
+ rel = relational_expr_alloc(&netlink_location, OP_EQ, lhs, rhs);
+
+ expr_print(rel, octx);
+ nft_print(octx, " ");
+ expr_free(rel);
+}
+
+static void trace_print_verdict(const struct nftnl_trace *nlt,
+ struct output_ctx *octx)
+{
+ struct expr *chain_expr = NULL;
+ const char *chain = NULL;
+ unsigned int verdict;
+ struct expr *expr;
+
+ verdict = nftnl_trace_get_u32(nlt, NFTNL_TRACE_VERDICT);
+ if (nftnl_trace_is_set(nlt, NFTNL_TRACE_JUMP_TARGET)) {
+ chain = xstrdup(nftnl_trace_get_str(nlt, NFTNL_TRACE_JUMP_TARGET));
+ chain_expr = constant_expr_alloc(&netlink_location,
+ &string_type,
+ BYTEORDER_HOST_ENDIAN,
+ strlen(chain) * BITS_PER_BYTE,
+ chain);
+ }
+ expr = verdict_expr_alloc(&netlink_location, verdict, chain_expr);
+
+ nft_print(octx, "verdict ");
+ expr_print(expr, octx);
+ expr_free(expr);
+}
+
+static void trace_print_policy(const struct nftnl_trace *nlt,
+ struct output_ctx *octx)
+{
+ unsigned int policy;
+ struct expr *expr;
+
+ policy = nftnl_trace_get_u32(nlt, NFTNL_TRACE_POLICY);
+
+ expr = verdict_expr_alloc(&netlink_location, policy, NULL);
+
+ nft_print(octx, "policy ");
+ expr_print(expr, octx);
+ expr_free(expr);
+}
+
+static struct rule *trace_lookup_rule(const struct nftnl_trace *nlt,
+ uint64_t rule_handle,
+ struct nft_cache *cache)
+{
+ struct chain *chain;
+ struct table *table;
+ struct handle h;
+
+ h.family = nftnl_trace_get_u32(nlt, NFTNL_TRACE_FAMILY);
+ h.table.name = nftnl_trace_get_str(nlt, NFTNL_TRACE_TABLE);
+ h.chain.name = nftnl_trace_get_str(nlt, NFTNL_TRACE_CHAIN);
+
+ if (!h.table.name)
+ return NULL;
+
+ table = table_cache_find(&cache->table_cache, h.table.name, h.family);
+ if (!table)
+ return NULL;
+
+ chain = chain_cache_find(table, h.chain.name);
+ if (!chain)
+ return NULL;
+
+ return rule_lookup(chain, rule_handle);
+}
+
+static void trace_print_rule(const struct nftnl_trace *nlt,
+ struct output_ctx *octx, struct nft_cache *cache)
+{
+ uint64_t rule_handle;
+ struct rule *rule;
+
+ rule_handle = nftnl_trace_get_u64(nlt, NFTNL_TRACE_RULE_HANDLE);
+ rule = trace_lookup_rule(nlt, rule_handle, cache);
+
+ trace_print_hdr(nlt, octx);
+
+ if (rule) {
+ nft_print(octx, "rule ");
+ rule_print(rule, octx);
+ } else {
+ nft_print(octx, "unknown rule handle %" PRIu64, rule_handle);
+ }
+
+ nft_print(octx, " (");
+ trace_print_verdict(nlt, octx);
+ nft_print(octx, ")\n");
+}
+
+static void trace_gen_stmts(struct list_head *stmts,
+ struct proto_ctx *ctx, struct payload_dep_ctx *pctx,
+ const struct nftnl_trace *nlt, unsigned int attr,
+ enum proto_bases base)
+{
+ struct list_head unordered = LIST_HEAD_INIT(unordered);
+ struct list_head list;
+ struct expr *rel, *lhs, *rhs, *tmp, *nexpr;
+ struct stmt *stmt;
+ const struct proto_desc *desc;
+ const void *hdr;
+ uint32_t hlen;
+ unsigned int n;
+
+ if (!nftnl_trace_is_set(nlt, attr))
+ return;
+ hdr = nftnl_trace_get_data(nlt, attr, &hlen);
+
+ lhs = payload_expr_alloc(&netlink_location, NULL, 0);
+ payload_init_raw(lhs, base, 0, hlen * BITS_PER_BYTE);
+ rhs = constant_expr_alloc(&netlink_location,
+ &invalid_type, BYTEORDER_INVALID,
+ hlen * BITS_PER_BYTE, hdr);
+
+restart:
+ init_list_head(&list);
+ payload_expr_expand(&list, lhs, ctx);
+ expr_free(lhs);
+
+ desc = NULL;
+ list_for_each_entry_safe(lhs, nexpr, &list, list) {
+ if (desc && desc != ctx->protocol[base].desc) {
+ /* Chained protocols */
+ lhs->payload.offset = 0;
+ if (ctx->protocol[base].desc == NULL)
+ break;
+ goto restart;
+ }
+
+ tmp = constant_expr_splice(rhs, lhs->len);
+ expr_set_type(tmp, lhs->dtype, lhs->byteorder);
+ if (tmp->byteorder == BYTEORDER_HOST_ENDIAN)
+ mpz_switch_byteorder(tmp->value, tmp->len / BITS_PER_BYTE);
+
+ /* Skip unknown and filtered expressions */
+ desc = lhs->payload.desc;
+ if (lhs->dtype == &invalid_type ||
+ desc->checksum_key == payload_hdr_field(lhs) ||
+ desc->format.filter & (1 << payload_hdr_field(lhs))) {
+ expr_free(lhs);
+ expr_free(tmp);
+ continue;
+ }
+
+ rel = relational_expr_alloc(&lhs->location, OP_EQ, lhs, tmp);
+ stmt = expr_stmt_alloc(&rel->location, rel);
+ list_add_tail(&stmt->list, &unordered);
+
+ desc = ctx->protocol[base].desc;
+ relational_expr_pctx_update(ctx, rel);
+ }
+
+ expr_free(rhs);
+
+ n = 0;
+next:
+ list_for_each_entry(stmt, &unordered, list) {
+ enum proto_bases b = base;
+
+ rel = stmt->expr;
+ lhs = rel->left;
+
+ /* Move statements to result list in defined order */
+ desc = lhs->payload.desc;
+ if (desc->format.order[n] &&
+ desc->format.order[n] != payload_hdr_field(lhs))
+ continue;
+
+ list_move_tail(&stmt->list, stmts);
+ n++;
+
+ if (payload_is_stacked(desc, rel))
+ b--;
+
+ /* Don't strip 'icmp type' from payload dump. */
+ if (pctx->icmp_type == 0)
+ payload_dependency_kill(pctx, lhs, ctx->family);
+ if (lhs->flags & EXPR_F_PROTOCOL)
+ payload_dependency_store(pctx, stmt, b);
+
+ goto next;
+ }
+}
+
+static void trace_print_packet(const struct nftnl_trace *nlt,
+ struct output_ctx *octx)
+{
+ struct list_head stmts = LIST_HEAD_INIT(stmts);
+ const struct proto_desc *ll_desc;
+ struct payload_dep_ctx pctx = {};
+ struct proto_ctx ctx;
+ uint16_t dev_type;
+ uint32_t nfproto;
+ struct stmt *stmt, *next;
+
+ trace_print_hdr(nlt, octx);
+
+ nft_print(octx, "packet: ");
+ if (nftnl_trace_is_set(nlt, NFTNL_TRACE_IIF))
+ trace_print_expr(nlt, NFTNL_TRACE_IIF,
+ meta_expr_alloc(&netlink_location,
+ NFT_META_IIF), octx);
+ if (nftnl_trace_is_set(nlt, NFTNL_TRACE_OIF))
+ trace_print_expr(nlt, NFTNL_TRACE_OIF,
+ meta_expr_alloc(&netlink_location,
+ NFT_META_OIF), octx);
+
+ proto_ctx_init(&ctx, nftnl_trace_get_u32(nlt, NFTNL_TRACE_FAMILY), 0, false);
+ ll_desc = ctx.protocol[PROTO_BASE_LL_HDR].desc;
+ if ((ll_desc == &proto_inet || ll_desc == &proto_netdev) &&
+ nftnl_trace_is_set(nlt, NFTNL_TRACE_NFPROTO)) {
+ nfproto = nftnl_trace_get_u32(nlt, NFTNL_TRACE_NFPROTO);
+
+ proto_ctx_update(&ctx, PROTO_BASE_LL_HDR, &netlink_location, NULL);
+ proto_ctx_update(&ctx, PROTO_BASE_NETWORK_HDR, &netlink_location,
+ proto_find_upper(ll_desc, nfproto));
+ }
+ if (ctx.protocol[PROTO_BASE_LL_HDR].desc == NULL &&
+ nftnl_trace_is_set(nlt, NFTNL_TRACE_IIFTYPE)) {
+ dev_type = nftnl_trace_get_u16(nlt, NFTNL_TRACE_IIFTYPE);
+ proto_ctx_update(&ctx, PROTO_BASE_LL_HDR, &netlink_location,
+ proto_dev_desc(dev_type));
+ }
+
+ trace_gen_stmts(&stmts, &ctx, &pctx, nlt, NFTNL_TRACE_LL_HEADER,
+ PROTO_BASE_LL_HDR);
+ trace_gen_stmts(&stmts, &ctx, &pctx, nlt, NFTNL_TRACE_NETWORK_HEADER,
+ PROTO_BASE_NETWORK_HDR);
+ trace_gen_stmts(&stmts, &ctx, &pctx, nlt, NFTNL_TRACE_TRANSPORT_HEADER,
+ PROTO_BASE_TRANSPORT_HDR);
+
+ list_for_each_entry_safe(stmt, next, &stmts, list) {
+ stmt_print(stmt, octx);
+ nft_print(octx, " ");
+ stmt_free(stmt);
+ }
+ nft_print(octx, "\n");
+}
+
+int netlink_events_trace_cb(const struct nlmsghdr *nlh, int type,
+ struct netlink_mon_handler *monh)
+{
+ struct nftnl_trace *nlt;
+
+ assert(type == NFT_MSG_TRACE);
+
+ nlt = nftnl_trace_alloc();
+ if (!nlt)
+ memory_allocation_error();
+
+ if (nftnl_trace_nlmsg_parse(nlh, nlt) < 0)
+ netlink_abi_error();
+
+ if (nftnl_trace_is_set(nlt, NFTNL_TRACE_LL_HEADER) ||
+ nftnl_trace_is_set(nlt, NFTNL_TRACE_NETWORK_HEADER))
+ trace_print_packet(nlt, &monh->ctx->nft->output);
+
+ switch (nftnl_trace_get_u32(nlt, NFTNL_TRACE_TYPE)) {
+ case NFT_TRACETYPE_RULE:
+ if (nftnl_trace_is_set(nlt, NFTNL_TRACE_RULE_HANDLE))
+ trace_print_rule(nlt, &monh->ctx->nft->output,
+ &monh->ctx->nft->cache);
+ break;
+ case NFT_TRACETYPE_POLICY:
+ trace_print_hdr(nlt, &monh->ctx->nft->output);
+
+ if (nftnl_trace_is_set(nlt, NFTNL_TRACE_POLICY)) {
+ trace_print_policy(nlt, &monh->ctx->nft->output);
+ nft_mon_print(monh, " ");
+ }
+
+ if (nftnl_trace_is_set(nlt, NFTNL_TRACE_MARK))
+ trace_print_expr(nlt, NFTNL_TRACE_MARK,
+ meta_expr_alloc(&netlink_location,
+ NFT_META_MARK),
+ &monh->ctx->nft->output);
+ nft_mon_print(monh, "\n");
+ break;
+ case NFT_TRACETYPE_RETURN:
+ trace_print_hdr(nlt, &monh->ctx->nft->output);
+
+ if (nftnl_trace_is_set(nlt, NFTNL_TRACE_VERDICT)) {
+ trace_print_verdict(nlt, &monh->ctx->nft->output);
+ nft_mon_print(monh, " ");
+ }
+
+ if (nftnl_trace_is_set(nlt, NFTNL_TRACE_MARK))
+ trace_print_expr(nlt, NFTNL_TRACE_MARK,
+ meta_expr_alloc(&netlink_location,
+ NFT_META_MARK),
+ &monh->ctx->nft->output);
+ nft_mon_print(monh, "\n");
+ break;
+ }
+
+ nftnl_trace_free(nlt);
+ return MNL_CB_OK;
+}