summaryrefslogtreecommitdiffstats
path: root/src/lib.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib.c')
-rw-r--r--src/lib.c2404
1 files changed, 2404 insertions, 0 deletions
diff --git a/src/lib.c b/src/lib.c
new file mode 100644
index 0000000..05bc188
--- /dev/null
+++ b/src/lib.c
@@ -0,0 +1,2404 @@
+/*
+ * (C) 2024-2025 by 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 as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+/* Funded through the NGI0 Entrust established by NLnet (https://nlnet.nl)
+ * with support from the European Commission's Next Generation Internet
+ * programme.
+ */
+
+#include <stdlib.h>
+#include <sys/select.h>
+#include <sys/time.h>
+#include <string.h>
+#include <time.h>
+#include <errno.h>
+#include <arpa/inet.h>
+#include <libmnl/libmnl.h>
+#include <libnftnl/common.h>
+#include <linux/netfilter.h>
+#include <linux/netfilter/nf_tables.h>
+#include <linux/netfilter/nf_tables_compat.h>
+#include <linux/netfilter/nfnetlink.h>
+#include <linux/netfilter/nfnetlink_hook.h>
+#include <limits.h>
+#include "lib.h"
+#include "test.h"
+
+static struct mnl_socket *__setup_socket(struct test_batch *batch)
+{
+ unsigned int newbuffsiz;
+ struct mnl_socket *nl;
+
+ nl = mnl_socket_open(NETLINK_NETFILTER);
+ if (nl == NULL) {
+ perror("mnl_socket_open");
+ exit(EXIT_FAILURE);
+ }
+
+ if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) {
+ perror("mnl_socket_bind");
+ exit(EXIT_FAILURE);
+ }
+
+ newbuffsiz = sizeof(batch->buf);
+
+ /* Rise sender buffer length to avoid hitting -EMSGSIZE */
+ setsockopt(mnl_socket_get_fd(nl), SOL_SOCKET, SO_SNDBUF,
+ &newbuffsiz, sizeof(socklen_t));
+
+ /* unpriviledged containers check for CAP_NET_ADMIN on the init_user_ns. */
+ setsockopt(mnl_socket_get_fd(nl), SOL_SOCKET, SO_SNDBUFFORCE,
+ &newbuffsiz, sizeof(socklen_t));
+
+ return nl;
+}
+
+static struct mnl_nlmsg_batch *__setup_batch(char *buf, uint32_t bufsiz)
+{
+ struct mnl_nlmsg_batch *batch;
+
+ batch = mnl_nlmsg_batch_start(buf, bufsiz);
+ nftnl_batch_begin(mnl_nlmsg_batch_current(batch), 0);
+ mnl_nlmsg_batch_next(batch);
+
+ return batch;
+}
+
+void setup_batch(struct test_batch *batch)
+{
+ INIT_LIST_HEAD(&batch->cmd);
+ batch->batch = __setup_batch(batch->buf, sizeof(batch->buf));
+ batch->nl = __setup_socket(batch);
+}
+
+static void add_cmd(struct test_batch *batch, enum test_batch_type type, void *obj)
+{
+ struct test_batch_cmd *cmd;
+
+ cmd = calloc(1, sizeof(struct test_batch_cmd));
+ if (!cmd)
+ return;
+
+ cmd->type = type;
+ cmd->obj = obj;
+ cmd->lineno = batch->lineno;
+ list_add_tail(&cmd->list, &batch->cmd);
+}
+
+static void setup_table_ctx(struct test_batch *batch,
+ const struct table *table)
+{
+ if (table) {
+ if (table->nfproto)
+ batch->ctx.family = *table->nfproto;
+ if (table->name)
+ batch->ctx.table = table->name;
+ } else {
+ batch->ctx.family = NFPROTO_UNSPEC;
+ batch->ctx.table = NULL;
+ }
+}
+
+static void setup_batch_ctx(struct test_batch *batch,
+ struct rule *rule,
+ struct set *set)
+{
+ batch->ctx.rule = rule;
+ batch->ctx.set = set;
+}
+
+void free_table(struct table *table)
+{
+ free((void *)table->userdata);
+ free((void *)table->name);
+ free(table);
+}
+
+void add_table(struct test_batch *batch, struct table *table)
+{
+ add_cmd(batch, ADD_TABLE, table);
+ setup_table_ctx(batch, table);
+}
+
+void set_table(struct test_batch *batch, struct table *table)
+{
+ add_cmd(batch, SET_TABLE, table);
+ setup_table_ctx(batch, table);
+}
+
+void del_table(struct test_batch *batch, struct table *table)
+{
+ add_cmd(batch, DEL_TABLE, table);
+}
+
+void free_chain(struct chain *chain)
+{
+ free((void *)chain->table);
+ free((void *)chain->name);
+ free((void *)chain->type);
+ free((void *)chain->dev);
+ array_free(chain->dev_array);
+ free((void *)chain->userdata);
+ free(chain);
+}
+
+void add_chain(struct test_batch *batch, struct chain *chain)
+{
+ if (batch->ctx.table) {
+ chain->table = strdup(batch->ctx.table);
+ *chain->family = batch->ctx.family;
+ }
+
+ add_cmd(batch, ADD_CHAIN, chain);
+ setup_batch_ctx(batch, NULL, NULL);
+}
+
+struct array *array_alloc(void)
+{
+ return calloc(1, sizeof(struct array));
+}
+
+bool array_add(struct array *array, void *data)
+{
+ if (array->num >= ARRAY_MAX)
+ return false;
+
+ array->data[array->num++] = data;
+
+ return true;
+}
+
+void array_free(struct array *array)
+{
+ int i;
+
+ if (!array)
+ return;
+
+ for (i = 0; i < array->num; i++)
+ free(array->data[i]);
+
+ free(array);
+}
+
+void add_basechain(struct test_batch *batch, struct chain *chain)
+{
+ if (batch->ctx.table) {
+ chain->table = strdup(batch->ctx.table);
+ *chain->family = batch->ctx.family;
+ }
+
+ add_cmd(batch, ADD_BASECHAIN, chain);
+ setup_batch_ctx(batch, NULL, NULL);
+}
+
+void del_basechain(struct test_batch *batch, struct chain *chain)
+{
+ if (batch->ctx.table) {
+ chain->table = strdup(batch->ctx.table);
+ *chain->family = batch->ctx.family;
+ }
+
+ add_cmd(batch, DEL_BASECHAIN, chain);
+}
+
+void free_flowtable(struct flowtable *flowtable)
+{
+ free((void *)flowtable->table);
+ free((void *)flowtable->name);
+ array_free(flowtable->dev_array);
+ free(flowtable);
+}
+
+void add_flowtable(struct test_batch *batch, struct flowtable *flowtable)
+{
+ if (batch->ctx.table) {
+ flowtable->table = strdup(batch->ctx.table);
+ *flowtable->family = batch->ctx.family;
+ }
+
+ add_cmd(batch, ADD_FLOWTABLE, flowtable);
+ setup_batch_ctx(batch, NULL, NULL);
+}
+
+void del_flowtable(struct test_batch *batch, struct flowtable *flowtable)
+{
+ if (batch->ctx.table) {
+ flowtable->table = strdup(batch->ctx.table);
+ *flowtable->family = batch->ctx.family;
+ }
+
+ add_cmd(batch, DEL_FLOWTABLE, flowtable);
+}
+
+void free_expr_list(struct list_head *expr_list)
+{
+ struct expr *expr, *next;
+
+ list_for_each_entry_safe(expr, next, expr_list, list) {
+ switch (expr->type) {
+ case BITWISE:
+ free_bitwise((struct bitwise *)expr);
+ break;
+ case BYTEORDER:
+ free_byteorder((struct byteorder *)expr);
+ break;
+ case CMP:
+ free_cmp((struct cmp *)expr);
+ break;
+ case COUNTER:
+ free_counter((struct counter *)expr);
+ break;
+ case CONNLIMIT:
+ free_connlimit((struct connlimit *)expr);
+ break;
+ case CT:
+ free_ct((struct ct *)expr);
+ break;
+ case DUP:
+ free_dup((struct dup *)expr);
+ break;
+ case DYNSET:
+ free_dynset((struct dynset *)expr);
+ break;
+ case EXTHDR:
+ free_exthdr((struct exthdr *)expr);
+ break;
+ case FIB:
+ free_fib((struct fib *)expr);
+ break;
+ case FWD:
+ free_fwd((struct fwd *)expr);
+ break;
+ case HASH:
+ free_hash((struct hash *)expr);
+ break;
+ case INNER:
+ free_inner((struct inner *)expr);
+ break;
+ case IMMEDIATE:
+ free_immediate((struct immediate *)expr);
+ break;
+ case LAST:
+ free_last((struct last *)expr);
+ break;
+ case LIMIT:
+ free_limit((struct limit *)expr);
+ break;
+ case LOG:
+ free_log((struct log *)expr);
+ break;
+ case LOOKUP:
+ free_lookup((struct lookup *)expr);
+ break;
+ case MASQ:
+ free_masq((struct masq *)expr);
+ break;
+ case MATCH:
+ free_match((struct match *)expr);
+ break;
+ case META:
+ free_meta((struct meta *)expr);
+ break;
+ case NAT:
+ free_nat((struct nat *)expr);
+ break;
+ case NUMGEN:
+ free_numgen((struct numgen *)expr);
+ break;
+ case OBJREF:
+ free_objref((struct objref *)expr);
+ break;
+ case OSF:
+ free_osf((struct osf *)expr);
+ break;
+ case PAYLOAD:
+ free_payload((struct payload *)expr);
+ break;
+ case QUEUE:
+ free_queue((struct queue *)expr);
+ break;
+ case QUOTA:
+ free_quota((struct quota *)expr);
+ break;
+ case RANGE:
+ free_range((struct range *)expr);
+ break;
+ case REDIR:
+ free_redir((struct redir *)expr);
+ break;
+ case REJECT:
+ free_reject((struct reject *)expr);
+ break;
+ case RT:
+ free_rt((struct rt *)expr);
+ break;
+ case SOCKET:
+ free_socket((struct socket *)expr);
+ break;
+ case SYNPROXY:
+ free_synproxy((struct synproxy *)expr);
+ break;
+ case TARGET:
+ free_target((struct target *)expr);
+ break;
+ case TPROXY:
+ free_tproxy((struct tproxy *)expr);
+ break;
+ case TUNNEL:
+ free_tunnel((struct tunnel *)expr);
+ break;
+ case XFRM:
+ free_xfrm((struct xfrm *)expr);
+ break;
+ }
+ }
+}
+
+void free_rule(struct rule *rule)
+{
+ free_expr_list(&rule->expr_list);
+ free((void *)rule->table);
+ free((void *)rule->chain);
+ free((void *)rule->userdata);
+ free(rule);
+}
+
+void add_rule(struct test_batch *batch, struct rule *rule)
+{
+ if (batch->ctx.table) {
+ rule->table = strdup(batch->ctx.table);
+ *rule->family = batch->ctx.family;
+ }
+
+ add_cmd(batch, ADD_RULE, rule);
+ setup_batch_ctx(batch, rule, NULL);
+}
+
+int add_expr(struct test_batch *batch, struct expr *expr)
+{
+ if (batch->ctx.expr_list) {
+ list_add_tail(&expr->list, batch->ctx.expr_list);
+ batch->ctx.expr_list = NULL;
+ } else if (batch->ctx.rule) {
+ list_add_tail(&expr->list, &batch->ctx.rule->expr_list);
+ } else if (batch->ctx.set) {
+ list_add_tail(&expr->list, &batch->ctx.set->expr_list);
+ } else {
+ return -1;
+ }
+
+ return 0;
+}
+
+void free_obj(struct obj *obj)
+{
+ free((void *)obj->table);
+ free((void *)obj->name);
+ free((void *)obj->userdata);
+
+ if (obj->type) {
+ switch (*obj->type) {
+ case NFT_OBJECT_COUNTER:
+ free(obj->u.counter);
+ break;
+ case NFT_OBJECT_QUOTA:
+ free(obj->u.quota);
+ break;
+ case NFT_OBJECT_LIMIT:
+ free(obj->u.limit);
+ break;
+ case NFT_OBJECT_CONNLIMIT:
+ free(obj->u.connlimit);
+ break;
+ case NFT_OBJECT_TUNNEL:
+ free(obj->u.tun);
+ break;
+ case NFT_OBJECT_CT_EXPECT:
+ free(obj->u.ct_expect);
+ break;
+ case NFT_OBJECT_SYNPROXY:
+ free(obj->u.synproxy);
+ break;
+ case NFT_OBJECT_CT_HELPER:
+ free((void *)obj->u.ct_helper->name);
+ free(obj->u.ct_helper);
+ break;
+ case NFT_OBJECT_CT_TIMEOUT:
+ array_u32_free(obj->u.ct_timeout->timeout_array);
+ free(obj->u.ct_timeout);
+ break;
+ case NFT_OBJECT_SECMARK:
+ free((void *)obj->u.secmark->ctx);
+ free(obj->u.secmark);
+ break;
+ }
+ }
+
+ free(obj);
+}
+
+void add_obj(struct test_batch *batch, struct obj *obj)
+{
+ if (batch->ctx.table) {
+ obj->table = strdup(batch->ctx.table);
+ *obj->family = batch->ctx.family;
+ }
+
+ add_cmd(batch, ADD_OBJECT, obj);
+ setup_batch_ctx(batch, NULL, NULL);
+}
+
+void del_obj(struct test_batch *batch, struct obj *obj)
+{
+ if (batch->ctx.table) {
+ obj->table = strdup(batch->ctx.table);
+ *obj->family = batch->ctx.family;
+ }
+
+ add_cmd(batch, DEL_OBJECT, obj);
+}
+
+struct array_u8 *array_u8_alloc(void)
+{
+ return calloc(1, sizeof(struct array_u8));
+}
+
+bool array_u8_add(struct array_u8 *array, const char *value)
+{
+ uint32_t val;
+ char *endptr;
+
+ if (array->num >= ARRAY_MAX)
+ return false;
+
+ val = strtoul(value, &endptr, 0);
+ if (!endptr)
+ return false;
+
+ if (val > UINT16_MAX)
+ return false;
+
+ array->data[array->num++] = val;
+ free((void *)value);
+
+ return true;
+}
+
+void array_u8_free(struct array_u8 *array)
+{
+ free(array);
+}
+
+struct array_u32 *array_u32_alloc(void)
+{
+ return calloc(1, sizeof(struct array_u32));
+}
+
+bool array_u32_add(struct array_u32 *array, const char *value)
+{
+ uint32_t val;
+ char *endptr;
+
+ if (array->num >= ARRAY_MAX)
+ return false;
+
+ val = strtoul(value, &endptr, 0);
+ if (!endptr)
+ return false;
+
+ if (val > UINT16_MAX)
+ return false;
+
+ array->data[array->num++] = val;
+ free((void *)value);
+
+ return true;
+}
+
+void array_u32_free(struct array_u32 *array)
+{
+ free(array);
+}
+
+void free_set(struct set *set)
+{
+ free_expr_list(&set->expr_list);
+ free((void *)set->userdata);
+ free((void *)set->table);
+ free((void *)set->name);
+ array_u8_free(set->field_array);
+ free(set);
+}
+
+void add_set(struct test_batch *batch, struct set *set)
+{
+ if (batch->ctx.table) {
+ set->table = strdup(batch->ctx.table);
+ *set->family = batch->ctx.family;
+ }
+
+ add_cmd(batch, ADD_SET, set);
+ setup_batch_ctx(batch, NULL, set);
+}
+
+void set_set(struct test_batch *batch, struct set *set)
+{
+ if (batch->ctx.table) {
+ set->table = strdup(batch->ctx.table);
+ *set->family = batch->ctx.family;
+ }
+
+ add_cmd(batch, SET_SET, set);
+ setup_batch_ctx(batch, NULL, set);
+}
+
+void free_elem(struct elem *elem)
+{
+ free((void *)elem->table);
+ free((void *)elem->set);
+ free((void *)elem->chain);
+ free((void *)elem->objname);
+ free((void *)elem->userdata);
+ free(elem);
+}
+
+void add_elem(struct test_batch *batch, struct elem *elem)
+{
+ if (batch->ctx.table) {
+ elem->table = strdup(batch->ctx.table);
+ *elem->family = batch->ctx.family;
+ }
+ if (batch->ctx.set) {
+ if (batch->ctx.set->name)
+ elem->set = strdup(batch->ctx.set->name);
+ if (batch->ctx.set->set_id)
+ elem->set_id = *batch->ctx.set->set_id;
+ }
+
+ add_cmd(batch, ADD_SETELEM, elem);
+}
+
+void del_elem(struct test_batch *batch, struct elem *elem)
+{
+ if (batch->ctx.table) {
+ elem->table = strdup(batch->ctx.table);
+ *elem->family = batch->ctx.family;
+ }
+ if (batch->ctx.set) {
+ if (batch->ctx.set->name)
+ elem->set = strdup(batch->ctx.set->name);
+ if (batch->ctx.set->set_id)
+ elem->set_id = *batch->ctx.set->set_id;
+ }
+
+ add_cmd(batch, DEL_SETELEM, elem);
+}
+
+static uint32_t get_nfproto(uint32_t *nfproto)
+{
+ return nfproto ? *nfproto : NFPROTO_UNSPEC;
+}
+
+static void build_table(struct test_batch *batch, struct test_batch_cmd *cmd)
+{
+ struct table *table = cmd->obj;
+ struct nlmsghdr *nlh;
+
+ nlh = nftnl_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch->batch),
+ NFT_MSG_NEWTABLE, get_nfproto(table->nfproto),
+ NLM_F_CREATE, cmd->lineno);
+
+ if (table->name)
+ mnl_attr_put_strz(nlh, NFTA_TABLE_NAME, table->name);
+ if (table->handle)
+ mnl_attr_put_u64(nlh, NFTA_TABLE_HANDLE, htobe64(*table->handle));
+ if (table->flags)
+ mnl_attr_put_u32(nlh, NFTA_TABLE_FLAGS, htonl(*table->flags));
+ if (table->userdata)
+ mnl_attr_put(nlh, NFTA_TABLE_USERDATA, strlen(table->userdata), table->userdata);
+
+ mnl_nlmsg_batch_next(batch->batch);
+}
+
+static void build_del_table(struct test_batch *batch, struct test_batch_cmd *cmd)
+{
+ struct table *table = cmd->obj;
+ struct nlmsghdr *nlh;
+
+ nlh = nftnl_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch->batch),
+ NFT_MSG_DELTABLE, get_nfproto(table->nfproto),
+ 0, cmd->lineno);
+ if (table->name)
+ mnl_attr_put_strz(nlh, NFTA_TABLE_NAME, table->name);
+ if (table->handle)
+ mnl_attr_put_u64(nlh, NFTA_TABLE_HANDLE, htobe64(*table->handle));
+ mnl_nlmsg_batch_next(batch->batch);
+}
+
+static void __build_chain(struct nlmsghdr *nlh, struct chain *chain)
+{
+ struct nlattr *nest;
+
+ if (chain->table)
+ mnl_attr_put_strz(nlh, NFTA_CHAIN_TABLE, chain->table);
+ if (chain->name)
+ mnl_attr_put_strz(nlh, NFTA_CHAIN_NAME, chain->name);
+ if (chain->flags)
+ mnl_attr_put_u32(nlh, NFTA_CHAIN_FLAGS, htonl(*chain->flags));
+ if (chain->chain_id)
+ mnl_attr_put_u32(nlh, NFTA_CHAIN_ID, htonl(*chain->chain_id));
+ if (chain->userdata)
+ mnl_attr_put(nlh, NFTA_CHAIN_USERDATA, strlen(chain->userdata), chain->userdata);
+ if (chain->handle)
+ mnl_attr_put_u64(nlh, NFTA_CHAIN_HANDLE, be64toh(*chain->handle));
+
+ if (chain->hooknum || chain->prio || chain->dev || chain->dev_array)
+ nest = mnl_attr_nest_start(nlh, NFTA_CHAIN_HOOK);
+
+ if (chain->hooknum)
+ mnl_attr_put_u32(nlh, NFTA_HOOK_HOOKNUM, htonl(*chain->hooknum));
+ if (chain->prio)
+ mnl_attr_put_u32(nlh, NFTA_HOOK_PRIORITY, htonl(*chain->prio));
+ if (chain->dev)
+ mnl_attr_put_strz(nlh, NFTA_HOOK_DEV, chain->dev);
+ if (chain->dev_array) {
+ struct nlattr *nest_dev;
+ int i;
+
+ nest_dev = mnl_attr_nest_start(nlh, NFTA_HOOK_DEVS);
+ for (i = 0; i < chain->dev_array->num; i++)
+ mnl_attr_put_strz(nlh, NFTA_DEVICE_NAME,
+ chain->dev_array->data[i]);
+ mnl_attr_nest_end(nlh, nest_dev);
+ }
+
+ if (chain->hooknum || chain->prio || chain->dev || chain->dev_array)
+ mnl_attr_nest_end(nlh, nest);
+
+ if (chain->policy)
+ mnl_attr_put_u32(nlh, NFTA_CHAIN_POLICY, htonl(*chain->policy));
+ if (chain->type)
+ mnl_attr_put_strz(nlh, NFTA_CHAIN_TYPE, chain->type);
+
+ if (chain->bytes || chain->pkts)
+ nest = mnl_attr_nest_start(nlh, NFTA_CHAIN_COUNTERS);
+
+ if (chain->bytes)
+ mnl_attr_put_u64(nlh, NFTA_COUNTER_BYTES, be64toh(*chain->bytes));
+ if (chain->pkts)
+ mnl_attr_put_u64(nlh, NFTA_COUNTER_PACKETS, be64toh(*chain->pkts));
+
+ if (chain->bytes || chain->pkts)
+ mnl_attr_nest_end(nlh, nest);
+}
+
+static void build_chain(struct test_batch *batch, struct test_batch_cmd *cmd)
+{
+ struct chain *chain = cmd->obj;
+ struct nlmsghdr *nlh;
+
+ nlh = nftnl_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch->batch),
+ NFT_MSG_NEWCHAIN, get_nfproto(chain->family),
+ NLM_F_CREATE, cmd->lineno);
+ __build_chain(nlh, chain);
+ mnl_nlmsg_batch_next(batch->batch);
+}
+
+static void build_bitwise(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct bitwise *bitwise = (struct bitwise *)expr;
+
+ if (bitwise->sreg)
+ mnl_attr_put_u32(nlh, NFTA_BITWISE_SREG, htonl(*bitwise->sreg));
+ if (bitwise->dreg)
+ mnl_attr_put_u32(nlh, NFTA_BITWISE_DREG, htonl(*bitwise->dreg));
+ if (bitwise->op)
+ mnl_attr_put_u32(nlh, NFTA_BITWISE_OP, htonl(*bitwise->op));
+ if (bitwise->len)
+ mnl_attr_put_u32(nlh, NFTA_BITWISE_LEN, htonl(*bitwise->len));
+ if (bitwise->mask_len) {
+ struct nlattr *nest;
+
+ nest = mnl_attr_nest_start(nlh, NFTA_BITWISE_MASK);
+ mnl_attr_put(nlh, NFTA_DATA_VALUE, bitwise->mask_len,
+ bitwise->mask);
+ mnl_attr_nest_end(nlh, nest);
+ }
+ if (bitwise->xor) {
+ struct nlattr *nest;
+
+ nest = mnl_attr_nest_start(nlh, NFTA_BITWISE_XOR);
+ mnl_attr_put(nlh, NFTA_DATA_VALUE, bitwise->xor_len,
+ bitwise->xor);
+ mnl_attr_nest_end(nlh, nest);
+ }
+ if (bitwise->data) {
+ struct nlattr *nest;
+
+ nest = mnl_attr_nest_start(nlh, NFTA_BITWISE_DATA);
+ mnl_attr_put(nlh, NFTA_DATA_VALUE, bitwise->data_len,
+ bitwise->data);
+ mnl_attr_nest_end(nlh, nest);
+ }
+}
+
+static void build_byteorder(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct byteorder *byteorder = (struct byteorder *)expr;
+
+ if (byteorder->sreg)
+ mnl_attr_put_u32(nlh, NFTA_BYTEORDER_SREG, htonl(*byteorder->sreg));
+ if (byteorder->dreg)
+ mnl_attr_put_u32(nlh, NFTA_BYTEORDER_DREG, htonl(*byteorder->dreg));
+ if (byteorder->op)
+ mnl_attr_put_u32(nlh, NFTA_BYTEORDER_OP, htonl(*byteorder->op));
+ if (byteorder->len)
+ mnl_attr_put_u32(nlh, NFTA_BYTEORDER_LEN, htonl(*byteorder->len));
+ if (byteorder->size)
+ mnl_attr_put_u32(nlh, NFTA_BYTEORDER_SIZE, htonl(*byteorder->size));
+}
+
+static void build_cmp(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct cmp *cmp = (struct cmp *)expr;
+
+ if (cmp->sreg)
+ mnl_attr_put_u32(nlh, NFTA_CMP_SREG, htonl(*cmp->sreg));
+ if (cmp->op)
+ mnl_attr_put_u32(nlh, NFTA_CMP_OP, htonl(*cmp->op));
+ if (cmp->data_len) {
+ struct nlattr *nest;
+
+ nest = mnl_attr_nest_start(nlh, NFTA_CMP_DATA);
+ mnl_attr_put(nlh, NFTA_DATA_VALUE, cmp->data_len, cmp->data);
+ mnl_attr_nest_end(nlh, nest);
+ }
+}
+
+static void build_connlimit(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct connlimit *connlimit = (struct connlimit *)expr;
+
+ if (connlimit->count) {
+ mnl_attr_put_u32(nlh, NFTA_CONNLIMIT_COUNT,
+ htonl(*connlimit->count));
+ }
+ if (connlimit->flags) {
+ mnl_attr_put_u32(nlh, NFTA_CONNLIMIT_FLAGS,
+ htonl(*connlimit->flags));
+ }
+}
+
+static void build_counter(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct counter *ctr = (struct counter *)expr;
+
+ if (ctr->bytes)
+ mnl_attr_put_u64(nlh, NFTA_COUNTER_BYTES, htobe64(*ctr->bytes));
+ if (ctr->pkts)
+ mnl_attr_put_u64(nlh, NFTA_COUNTER_PACKETS, htobe64(*ctr->pkts));
+}
+
+static void build_ct(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct ct *ct = (struct ct *)expr;
+
+ if (ct->key)
+ mnl_attr_put_u32(nlh, NFTA_CT_KEY, htonl(*ct->key));
+ if (ct->dreg)
+ mnl_attr_put_u32(nlh, NFTA_CT_DREG, htonl(*ct->dreg));
+ if (ct->dir)
+ mnl_attr_put_u8(nlh, NFTA_CT_DIRECTION, *ct->dir);
+ if (ct->sreg)
+ mnl_attr_put_u32(nlh, NFTA_CT_SREG, htonl(*ct->sreg));
+}
+
+static void build_dup(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct dup *dup = (struct dup *)expr;
+
+ if (dup->sreg_addr)
+ mnl_attr_put_u32(nlh, NFTA_DUP_SREG_ADDR, htonl(*dup->sreg_addr));
+ if (dup->sreg_dev)
+ mnl_attr_put_u32(nlh, NFTA_DUP_SREG_DEV, htonl(*dup->sreg_dev));
+}
+
+static void build_dynset(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct dynset *dynset = (struct dynset *)expr;
+ uint32_t num_exprs = 0;
+
+ if (dynset->sreg_key)
+ mnl_attr_put_u32(nlh, NFTA_DYNSET_SREG_KEY, htonl(*dynset->sreg_key));
+ if (dynset->sreg_data)
+ mnl_attr_put_u32(nlh, NFTA_DYNSET_SREG_DATA, htonl(*dynset->sreg_data));
+ if (dynset->op)
+ mnl_attr_put_u32(nlh, NFTA_DYNSET_OP, htonl(*dynset->op));
+ if (dynset->timeout)
+ mnl_attr_put_u64(nlh, NFTA_DYNSET_TIMEOUT, htobe64(*dynset->timeout));
+ if (dynset->set)
+ mnl_attr_put_strz(nlh, NFTA_DYNSET_SET_NAME, dynset->set);
+ if (dynset->set_id)
+ mnl_attr_put_u32(nlh, NFTA_DYNSET_SET_ID, htonl(*dynset->set_id));
+ if (dynset->flags)
+ mnl_attr_put_u32(nlh, NFTA_DYNSET_FLAGS, htonl(*dynset->flags));
+ if (!list_empty(&dynset->expr_list))
+ ; // TODO
+}
+
+static void build_exthdr(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct exthdr *exthdr = (struct exthdr *)expr;
+
+ if (exthdr->dreg)
+ mnl_attr_put_u32(nlh, NFTA_EXTHDR_DREG, htonl(*exthdr->dreg));
+ if (exthdr->sreg)
+ mnl_attr_put_u32(nlh, NFTA_EXTHDR_SREG, htonl(*exthdr->sreg));
+ if (exthdr->type)
+ mnl_attr_put_u8(nlh, NFTA_EXTHDR_TYPE, *exthdr->type);
+ if (exthdr->offset)
+ mnl_attr_put_u32(nlh, NFTA_EXTHDR_OFFSET, htonl(*exthdr->offset));
+ if (exthdr->len)
+ mnl_attr_put_u32(nlh, NFTA_EXTHDR_LEN, htonl(*exthdr->len));
+ if (exthdr->op)
+ mnl_attr_put_u32(nlh, NFTA_EXTHDR_OP, htonl(*exthdr->op));
+ if (exthdr->flags)
+ mnl_attr_put_u32(nlh, NFTA_EXTHDR_FLAGS, htonl(*exthdr->flags));
+}
+
+static void build_fib(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct fib *fib = (struct fib *)expr;
+
+ if (fib->flags)
+ mnl_attr_put_u32(nlh, NFTA_FIB_FLAGS, htonl(*fib->flags));
+ if (fib->result)
+ mnl_attr_put_u32(nlh, NFTA_FIB_RESULT, htonl(*fib->result));
+ if (fib->dreg)
+ mnl_attr_put_u32(nlh, NFTA_FIB_DREG, htonl(*fib->dreg));
+}
+
+static void build_fwd(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct fwd *fwd = (struct fwd *)expr;
+
+ if (fwd->sreg_dev)
+ mnl_attr_put_u32(nlh, NFTA_FWD_SREG_DEV, htonl(*fwd->sreg_dev));
+ if (fwd->sreg_addr)
+ mnl_attr_put_u32(nlh, NFTA_FWD_SREG_ADDR, htonl(*fwd->sreg_addr));
+ if (fwd->nfproto)
+ mnl_attr_put_u32(nlh, NFTA_FWD_NFPROTO, htonl(*fwd->nfproto));
+}
+
+static void build_hash(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct hash *hash = (struct hash *)expr;
+
+ if (hash->sreg)
+ mnl_attr_put_u32(nlh, NFTA_HASH_SREG, htonl(*hash->sreg));
+ if (hash->dreg)
+ mnl_attr_put_u32(nlh, NFTA_HASH_DREG, htonl(*hash->dreg));
+ if (hash->len)
+ mnl_attr_put_u32(nlh, NFTA_HASH_LEN, htonl(*hash->len));
+ if (hash->modulus)
+ mnl_attr_put_u32(nlh, NFTA_HASH_MODULUS, htonl(*hash->modulus));
+ if (hash->seed)
+ mnl_attr_put_u32(nlh, NFTA_HASH_SEED, htonl(*hash->seed));
+ if (hash->offset)
+ mnl_attr_put_u32(nlh, NFTA_HASH_OFFSET, htonl(*hash->offset));
+ if (hash->type)
+ mnl_attr_put_u32(nlh, NFTA_HASH_TYPE, htonl(*hash->type));
+}
+
+static void build_immediate(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct immediate *imm = (struct immediate *)expr;
+ struct nlattr *nest1, *nest2;
+
+ if (imm->dreg)
+ mnl_attr_put_u32(nlh, NFTA_IMMEDIATE_DREG, htonl(*imm->dreg));
+
+ /* Sane configurations allows you to set ONLY one of these two below */
+ if (imm->data_len) {
+ struct nlattr *nest;
+
+ nest = mnl_attr_nest_start(nlh, NFTA_IMMEDIATE_DATA);
+ mnl_attr_put(nlh, NFTA_DATA_VALUE, imm->data_len, imm->data);
+ mnl_attr_nest_end(nlh, nest);
+ }
+
+ if (imm->verdict || imm->chain) {
+ struct nlattr *nest1, *nest2;
+
+ nest1 = mnl_attr_nest_start(nlh, NFTA_IMMEDIATE_DATA);
+ nest2 = mnl_attr_nest_start(nlh, NFTA_DATA_VERDICT);
+ if (imm->verdict)
+ mnl_attr_put_u32(nlh, NFTA_VERDICT_CODE, htonl(*imm->verdict));
+ if (imm->chain)
+ mnl_attr_put_strz(nlh, NFTA_VERDICT_CHAIN, imm->chain);
+ if (imm->chain_id)
+ mnl_attr_put_u32(nlh, NFTA_VERDICT_CHAIN_ID, htonl(*imm->chain_id));
+
+ mnl_attr_nest_end(nlh, nest1);
+ mnl_attr_nest_end(nlh, nest2);
+ }
+}
+
+static void __build_expr(struct nlmsghdr *nlh, struct expr *expr);
+
+static void build_inner(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct inner *inner = (struct inner *)expr;
+ struct nlattr *nest;
+ struct expr *inner_expr;
+
+ if (inner->num)
+ mnl_attr_put_u32(nlh, NFTA_INNER_NUM, htonl(*inner->num));
+ if (inner->type)
+ mnl_attr_put_u32(nlh, NFTA_INNER_TYPE, htonl(*inner->type));
+ if (inner->flags)
+ mnl_attr_put_u32(nlh, NFTA_INNER_FLAGS, htonl(*inner->flags));
+ if (inner->hdrsize)
+ mnl_attr_put_u32(nlh, NFTA_INNER_HDRSIZE, htonl(*inner->hdrsize));
+ if (!list_empty(&inner->expr_list)) {
+ nest = mnl_attr_nest_start(nlh, NFTA_INNER_EXPR);
+ inner_expr = list_first_entry(&inner->expr_list, struct expr, list);
+ __build_expr(nlh, inner_expr);
+ mnl_attr_nest_end(nlh, nest);
+ }
+}
+
+static void build_last(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct last *last = (struct last *)expr;
+
+ if (last->msecs)
+ mnl_attr_put_u64(nlh, NFTA_LAST_MSECS, htobe64(*last->msecs));
+ if (last->set)
+ mnl_attr_put_u32(nlh, NFTA_LAST_SET, htonl(*last->set));
+}
+
+static void build_limit(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct limit *limit = (struct limit *)expr;
+
+ if (limit->rate)
+ mnl_attr_put_u64(nlh, NFTA_LIMIT_RATE, htobe64(*limit->rate));
+ if (limit->unit)
+ mnl_attr_put_u64(nlh, NFTA_LIMIT_UNIT, htobe64(*limit->unit));
+ if (limit->burst)
+ mnl_attr_put_u32(nlh, NFTA_LIMIT_BURST, htonl(*limit->burst));
+ if (limit->type)
+ mnl_attr_put_u32(nlh, NFTA_LIMIT_TYPE, htonl(*limit->type));
+ if (limit->flags)
+ mnl_attr_put_u32(nlh, NFTA_LIMIT_FLAGS, htonl(*limit->flags));
+}
+
+static void build_log(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct log *log = (struct log *)expr;
+
+ if (log->prefix)
+ mnl_attr_put_strz(nlh, NFTA_LOG_PREFIX, log->prefix);
+ if (log->group)
+ mnl_attr_put_u16(nlh, NFTA_LOG_GROUP, htons(*log->group));
+ if (log->snaplen)
+ mnl_attr_put_u32(nlh, NFTA_LOG_SNAPLEN, htonl(*log->snaplen));
+ if (log->qthreshold)
+ mnl_attr_put_u16(nlh, NFTA_LOG_QTHRESHOLD, htons(*log->qthreshold));
+ if (log->level)
+ mnl_attr_put_u32(nlh, NFTA_LOG_LEVEL, htonl(*log->level));
+ if (log->flags)
+ mnl_attr_put_u32(nlh, NFTA_LOG_FLAGS, htonl(*log->flags));
+}
+
+static void build_lookup(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct lookup *lookup = (struct lookup *)expr;
+
+ if (lookup->sreg)
+ mnl_attr_put_u32(nlh, NFTA_LOOKUP_SREG, htonl(*lookup->sreg));
+ if (lookup->dreg)
+ mnl_attr_put_u32(nlh, NFTA_LOOKUP_DREG, htonl(*lookup->dreg));
+ if (lookup->set)
+ mnl_attr_put_strz(nlh, NFTA_LOOKUP_SET, lookup->set);
+ if (lookup->set_id)
+ mnl_attr_put_u32(nlh, NFTA_LOOKUP_SET_ID, htonl(*lookup->set_id));
+ if (lookup->flags)
+ mnl_attr_put_u32(nlh, NFTA_LOOKUP_FLAGS, htonl(*lookup->flags));
+}
+
+static void build_masq(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct masq *masq = (struct masq *)expr;
+
+ if (masq->flags)
+ mnl_attr_put_u32(nlh, NFTA_MASQ_FLAGS, htobe32(*masq->flags));
+ if (masq->sreg_proto_min)
+ mnl_attr_put_u32(nlh, NFTA_MASQ_REG_PROTO_MIN,
+ htobe32(*masq->sreg_proto_min));
+ if (masq->sreg_proto_max)
+ mnl_attr_put_u32(nlh, NFTA_MASQ_REG_PROTO_MAX,
+ htobe32(*masq->sreg_proto_max));
+}
+
+static void build_match(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct match *mt = (struct match *)expr;
+
+ if (mt->name)
+ mnl_attr_put_strz(nlh, NFTA_MATCH_NAME, mt->name);
+ if (mt->rev)
+ mnl_attr_put_u32(nlh, NFTA_MATCH_REV, htonl(*mt->rev));
+ if (mt->data_len)
+ mnl_attr_put(nlh, NFTA_MATCH_INFO, mt->data_len, mt->data);
+}
+
+static void build_meta(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct meta *meta = (struct meta *)expr;
+
+ if (meta->key)
+ mnl_attr_put_u32(nlh, NFTA_META_KEY, htonl(*meta->key));
+ if (meta->dreg)
+ mnl_attr_put_u32(nlh, NFTA_META_DREG, htonl(*meta->dreg));
+ if (meta->sreg)
+ mnl_attr_put_u32(nlh, NFTA_META_SREG, htonl(*meta->sreg));
+}
+
+static void build_nat(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct nat *nat = (struct nat *)expr;
+
+ if (nat->type)
+ mnl_attr_put_u32(nlh, NFTA_NAT_TYPE, htonl(*nat->type));
+ if (nat->nfproto)
+ mnl_attr_put_u32(nlh, NFTA_NAT_FAMILY, htonl(*nat->nfproto));
+ if (nat->sreg_addr_min)
+ mnl_attr_put_u32(nlh, NFTA_NAT_REG_ADDR_MIN,
+ htonl(*nat->sreg_addr_min));
+ if (nat->sreg_addr_max)
+ mnl_attr_put_u32(nlh, NFTA_NAT_REG_ADDR_MAX,
+ htonl(*nat->sreg_addr_max));
+ if (nat->sreg_proto_min)
+ mnl_attr_put_u32(nlh, NFTA_NAT_REG_PROTO_MIN,
+ htonl(*nat->sreg_proto_min));
+ if (nat->sreg_proto_max)
+ mnl_attr_put_u32(nlh, NFTA_NAT_REG_PROTO_MAX,
+ htonl(*nat->sreg_proto_max));
+ if (nat->flags)
+ mnl_attr_put_u32(nlh, NFTA_NAT_FLAGS, htonl(*nat->flags));
+}
+
+static void build_numgen(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct numgen *ng = (struct numgen *)expr;
+
+ if (ng->dreg)
+ mnl_attr_put_u32(nlh, NFTA_NG_DREG, htonl(*ng->dreg));
+ if (ng->modulus)
+ mnl_attr_put_u32(nlh, NFTA_NG_MODULUS, htonl(*ng->modulus));
+ if (ng->type)
+ mnl_attr_put_u32(nlh, NFTA_NG_TYPE, htonl(*ng->type));
+ if (ng->offset)
+ mnl_attr_put_u32(nlh, NFTA_NG_OFFSET, htonl(*ng->offset));
+}
+
+static void build_objref(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct objref *objref = (struct objref *)expr;
+
+ if (objref->type)
+ mnl_attr_put_u32(nlh, NFTA_OBJREF_IMM_TYPE,
+ htonl(*objref->type));
+ if (objref->name)
+ mnl_attr_put_str(nlh, NFTA_OBJREF_IMM_NAME, objref->name);
+ if (objref->sreg)
+ mnl_attr_put_u32(nlh, NFTA_OBJREF_SET_SREG,
+ htonl(*objref->sreg));
+ if (objref->set_name)
+ mnl_attr_put_str(nlh, NFTA_OBJREF_SET_NAME, objref->set_name);
+ if (objref->set_id)
+ mnl_attr_put_u32(nlh, NFTA_OBJREF_SET_ID,
+ htonl(*objref->set_id));
+}
+
+static void build_osf(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct osf *osf = (struct osf *)expr;
+
+ if (osf->dreg)
+ mnl_attr_put_u32(nlh, NFTA_OSF_DREG, htonl(*osf->dreg));
+ if (osf->ttl)
+ mnl_attr_put_u8(nlh, NFTA_OSF_TTL, *osf->ttl);
+ if (osf->flags)
+ mnl_attr_put_u32(nlh, NFTA_OSF_FLAGS, htonl(*osf->flags));
+}
+
+static void build_payload(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct payload *payload = (struct payload *)expr;
+
+ if (payload->sreg)
+ mnl_attr_put_u32(nlh, NFTA_PAYLOAD_SREG, htonl(*payload->sreg));
+ if (payload->dreg)
+ mnl_attr_put_u32(nlh, NFTA_PAYLOAD_DREG, htonl(*payload->dreg));
+ if (payload->base)
+ mnl_attr_put_u32(nlh, NFTA_PAYLOAD_BASE, htonl(*payload->base));
+ if (payload->offset)
+ mnl_attr_put_u32(nlh, NFTA_PAYLOAD_OFFSET, htonl(*payload->offset));
+ if (payload->len)
+ mnl_attr_put_u32(nlh, NFTA_PAYLOAD_LEN, htonl(*payload->len));
+ if (payload->csum_type)
+ mnl_attr_put_u32(nlh, NFTA_PAYLOAD_CSUM_TYPE,
+ htonl(*payload->csum_type));
+ if (payload->csum_offset)
+ mnl_attr_put_u32(nlh, NFTA_PAYLOAD_CSUM_OFFSET,
+ htonl(*payload->csum_offset));
+ if (payload->csum_flags)
+ mnl_attr_put_u32(nlh, NFTA_PAYLOAD_CSUM_FLAGS,
+ htonl(*payload->csum_flags));
+}
+
+static void build_queue(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct queue *queue = (struct queue *)expr;
+
+ if (queue->queue_num)
+ mnl_attr_put_u16(nlh, NFTA_QUEUE_NUM, htons(*queue->queue_num));
+ if (queue->queue_total)
+ mnl_attr_put_u16(nlh, NFTA_QUEUE_TOTAL, htons(*queue->queue_total));
+ if (queue->flags)
+ mnl_attr_put_u16(nlh, NFTA_QUEUE_FLAGS, htons(*queue->flags));
+ if (queue->sreg_qnum)
+ mnl_attr_put_u32(nlh, NFTA_QUEUE_SREG_QNUM, htonl(*queue->sreg_qnum));
+}
+
+static void build_quota(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct quota *quota = (struct quota *)expr;
+
+ if (quota->bytes)
+ mnl_attr_put_u64(nlh, NFTA_QUOTA_BYTES, htobe64(*quota->bytes));
+ if (quota->consumed)
+ mnl_attr_put_u64(nlh, NFTA_QUOTA_CONSUMED, htobe64(*quota->consumed));
+ if (quota->flags)
+ mnl_attr_put_u32(nlh, NFTA_QUOTA_FLAGS, htonl(*quota->flags));
+}
+
+static void build_range(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct range *range = (struct range *)expr;
+
+ if (range->sreg)
+ mnl_attr_put_u32(nlh, NFTA_RANGE_SREG, htonl(*range->sreg));
+ if (range->op)
+ mnl_attr_put_u32(nlh, NFTA_RANGE_OP, htonl(*range->op));
+ if (range->data_from_len) {
+ struct nlattr *nest;
+
+ nest = mnl_attr_nest_start(nlh, NFTA_RANGE_FROM_DATA);
+ mnl_attr_put(nlh, NFTA_DATA_VALUE, range->data_from_len,
+ range->data_from);
+ mnl_attr_nest_end(nlh, nest);
+ }
+ if (range->data_to_len) {
+ struct nlattr *nest;
+
+ nest = mnl_attr_nest_start(nlh, NFTA_RANGE_TO_DATA);
+ mnl_attr_put(nlh, NFTA_DATA_VALUE, range->data_to_len,
+ range->data_to);
+ mnl_attr_nest_end(nlh, nest);
+ }
+}
+
+static void build_redir(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct redir *redir = (struct redir *)expr;
+
+ if (redir->sreg_proto_min)
+ mnl_attr_put_u32(nlh, NFTA_REDIR_REG_PROTO_MIN,
+ htobe32(*redir->sreg_proto_min));
+ if (redir->sreg_proto_max)
+ mnl_attr_put_u32(nlh, NFTA_REDIR_REG_PROTO_MAX,
+ htobe32(*redir->sreg_proto_max));
+ if (redir->flags)
+ mnl_attr_put_u32(nlh, NFTA_REDIR_FLAGS, htobe32(*redir->flags));
+}
+
+static void build_reject(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct reject *reject = (struct reject *)expr;
+
+ if (reject->type)
+ mnl_attr_put_u32(nlh, NFTA_REJECT_TYPE, htonl(*reject->type));
+ if (reject->icmp_code)
+ mnl_attr_put_u8(nlh, NFTA_REJECT_ICMP_CODE, *reject->icmp_code);
+}
+
+static void build_rt(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct rt *rt = (struct rt *)expr;
+
+ if (rt->key)
+ mnl_attr_put_u32(nlh, NFTA_RT_KEY, htonl(*rt->key));
+ if (rt->dreg)
+ mnl_attr_put_u32(nlh, NFTA_RT_DREG, htonl(*rt->dreg));
+}
+
+static void build_socket(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct socket *socket = (struct socket *)expr;
+
+ if (socket->key)
+ mnl_attr_put_u32(nlh, NFTA_SOCKET_KEY, htonl(*socket->key));
+ if (socket->dreg)
+ mnl_attr_put_u32(nlh, NFTA_SOCKET_DREG, htonl(*socket->dreg));
+ if (socket->level)
+ mnl_attr_put_u32(nlh, NFTA_SOCKET_LEVEL, htonl(*socket->level));
+}
+
+static void build_synproxy(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct synproxy *synproxy = (struct synproxy *)expr;
+
+ if (synproxy->mss)
+ mnl_attr_put_u16(nlh, NFTA_SYNPROXY_MSS, htons(*synproxy->mss));
+ if (synproxy->wscale)
+ mnl_attr_put_u8(nlh, NFTA_SYNPROXY_WSCALE, *synproxy->wscale);
+ if (synproxy->flags)
+ mnl_attr_put_u32(nlh, NFTA_SYNPROXY_FLAGS,
+ htonl(*synproxy->flags));
+}
+
+static void build_target(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct target *tg = (struct target *)expr;
+
+ if (tg->name)
+ mnl_attr_put_strz(nlh, NFTA_TARGET_NAME, tg->name);
+ if (tg->rev)
+ mnl_attr_put_u32(nlh, NFTA_TARGET_REV, htonl(*tg->rev));
+ if (tg->data_len)
+ mnl_attr_put(nlh, NFTA_TARGET_INFO, tg->data_len, tg->data);
+}
+
+static void build_tproxy(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct tproxy *tproxy = (struct tproxy *)expr;
+
+ if (tproxy->nfproto)
+ mnl_attr_put_u32(nlh, NFTA_TPROXY_FAMILY, htonl(*tproxy->nfproto));
+
+ if (tproxy->sreg_addr)
+ mnl_attr_put_u32(nlh, NFTA_TPROXY_REG_ADDR,
+ htonl(*tproxy->sreg_addr));
+
+ if (tproxy->sreg_port)
+ mnl_attr_put_u32(nlh, NFTA_TPROXY_REG_PORT,
+ htonl(*tproxy->sreg_port));
+}
+
+static void build_tunnel(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct tunnel *tunnel = (struct tunnel *)expr;
+
+ if (tunnel->key)
+ mnl_attr_put_u32(nlh, NFTA_TUNNEL_KEY, htonl(*tunnel->key));
+ if (tunnel->dreg)
+ mnl_attr_put_u32(nlh, NFTA_TUNNEL_DREG, htonl(*tunnel->dreg));
+}
+
+static void build_xfrm(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct xfrm *x = (struct xfrm *)expr;
+
+ if (x->key)
+ mnl_attr_put_u32(nlh, NFTA_XFRM_KEY, htonl(*x->key));
+ if (x->dir)
+ mnl_attr_put_u8(nlh, NFTA_XFRM_DIR, *x->dir);
+ if (x->spnum)
+ mnl_attr_put_u32(nlh, NFTA_XFRM_SPNUM, htonl(*x->spnum));
+ if (x->dreg)
+ mnl_attr_put_u32(nlh, NFTA_XFRM_DREG, htonl(*x->dreg));
+}
+
+struct {
+ const char *name;
+ void (*build)(struct nlmsghdr *nlh, struct expr *expr);
+} handler[] = {
+ [BITWISE] = { .name = "bitwise", .build = build_bitwise, },
+ [BYTEORDER] = { .name = "byteorder", .build = build_byteorder, },
+ [CMP] = { .name = "cmp", .build = build_cmp, },
+ [CONNLIMIT] = { .name = "connlimit", .build = build_connlimit, },
+ [COUNTER] = { .name = "counter", .build = build_counter, },
+ [CT] = { .name = "ct", .build = build_ct, },
+ [DUP] = { .name = "dup", .build = build_dup, },
+ [DYNSET] = { .name = "dynset", .build = build_dynset, },
+ [EXTHDR] = { .name = "exthdr", .build = build_exthdr, },
+ [FIB] = { .name = "fib", .build = build_fib, },
+ [FWD] = { .name = "fwd", .build = build_fwd, },
+ [HASH] = { .name = "hash", .build = build_hash, },
+ [INNER] = { .name = "inner", .build = build_inner, },
+ [IMMEDIATE] = { .name = "immediate", .build = build_immediate, },
+ [LAST] = { .name = "last", .build = build_last, },
+ [LIMIT] = { .name = "limit", .build = build_limit, },
+ [LOG] = { .name = "log", .build = build_log, },
+ [LOOKUP] = { .name = "lookup", .build = build_lookup, },
+ [MASQ] = { .name = "masq", .build = build_masq, },
+ [MATCH] = { .name = "match", .build = build_match, },
+ [META] = { .name = "meta", .build = build_meta, },
+ [NAT] = { .name = "nat", .build = build_nat, },
+ [NUMGEN] = { .name = "numgen", .build = build_numgen, },
+ [OBJREF] = { .name = "objref", .build = build_objref, },
+ [OSF] = { .name = "osf", .build = build_osf, },
+ [PAYLOAD] = { .name = "payload", .build = build_payload, },
+ [QUEUE] = { .name = "queue", .build = build_queue, },
+ [QUOTA] = { .name = "quota", .build = build_quota, },
+ [RANGE] = { .name = "range", .build = build_range, },
+ [REDIR] = { .name = "redir", .build = build_redir, },
+ [REJECT] = { .name = "reject", .build = build_reject, },
+ [RT] = { .name = "rt", .build = build_rt, },
+ [SOCKET] = { .name = "socket", .build = build_socket, },
+ [SYNPROXY] = { .name = "synproxy", .build = build_synproxy, },
+ [TARGET] = { .name = "target", .build = build_target, },
+ [TPROXY] = { .name = "tproxy", .build = build_tproxy, },
+ [TUNNEL] = { .name = "tunnel", .build = build_tunnel, },
+ [XFRM] = { .name = "xfrm", .build = build_xfrm, },
+};
+
+static void __build_expr(struct nlmsghdr *nlh, struct expr *expr)
+{
+ struct nlattr *nest;
+
+ mnl_attr_put_strz(nlh, NFTA_EXPR_NAME, handler[expr->type].name);
+
+ if (!handler[expr->type].build)
+ return;
+
+ nest = mnl_attr_nest_start(nlh, NFTA_EXPR_DATA);
+ handler[expr->type].build(nlh, expr);
+ mnl_attr_nest_end(nlh, nest);
+}
+
+static void __build_rule(struct nlmsghdr *nlh, struct rule *rule)
+{
+ struct nlattr *nest, *nest2;
+ struct expr *expr;
+
+ if (rule->table)
+ mnl_attr_put_strz(nlh, NFTA_RULE_TABLE, rule->table);
+ if (rule->chain)
+ mnl_attr_put_strz(nlh, NFTA_RULE_CHAIN, rule->chain);
+/* FIXME
+ if (rule->handle) {
+ mnl_attr_put_u64(nlh, NFTA_RULE_HANDLE, htobe64(*rule->handle));
+ } */
+ if (rule->pos)
+ mnl_attr_put_u64(nlh, NFTA_RULE_POSITION, htobe64(*rule->pos));
+ if (rule->rule_id)
+ mnl_attr_put_u32(nlh, NFTA_RULE_ID, htonl(*rule->rule_id));
+ if (rule->userdata)
+ mnl_attr_put(nlh, NFTA_RULE_USERDATA, strlen(rule->userdata),
+ rule->userdata);
+ if (rule->pos_id)
+ mnl_attr_put_u32(nlh, NFTA_RULE_POSITION_ID, htonl(*rule->pos_id));
+
+ if (!list_empty(&rule->expr_list)) {
+ nest = mnl_attr_nest_start(nlh, NFTA_RULE_EXPRESSIONS);
+ list_for_each_entry(expr, &rule->expr_list, list) {
+ nest2 = mnl_attr_nest_start(nlh, NFTA_LIST_ELEM);
+ __build_expr(nlh, expr);
+ mnl_attr_nest_end(nlh, nest2);
+ }
+ mnl_attr_nest_end(nlh, nest);
+ }
+ if (rule->compat.l4proto || rule->compat.flags) {
+ nest = mnl_attr_nest_start(nlh, NFTA_RULE_COMPAT);
+ if (rule->compat.l4proto)
+ mnl_attr_put_u32(nlh, NFTA_RULE_COMPAT_PROTO,
+ htonl(*rule->compat.l4proto));
+ if (rule->compat.flags)
+ mnl_attr_put_u32(nlh, NFTA_RULE_COMPAT_FLAGS,
+ htonl(*rule->compat.flags));
+ mnl_attr_nest_end(nlh, nest);
+ }
+}
+
+static void build_rule(struct test_batch *batch, struct test_batch_cmd *cmd)
+{
+ struct rule *rule = cmd->obj;
+ struct nlmsghdr *nlh;
+
+ nlh = nftnl_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch->batch),
+ NFT_MSG_NEWRULE, *rule->family,
+ NLM_F_APPEND | NLM_F_CREATE,
+ cmd->lineno);
+ __build_rule(nlh, rule);
+ mnl_nlmsg_batch_next(batch->batch);
+}
+
+static void __build_set_field(struct nlmsghdr *nlh, struct set *set)
+{
+ struct nlattr *nest;
+ int i;
+
+ nest = mnl_attr_nest_start(nlh, NFTA_SET_DESC_CONCAT);
+ for (i = 0; i < set->field_array->num; i++) {
+ struct nlattr *nest_elem;
+
+ nest_elem = mnl_attr_nest_start(nlh, NFTA_LIST_ELEM);
+ mnl_attr_put_u32(nlh, NFTA_SET_FIELD_LEN,
+ htonl(set->field_array->data[i]));
+ mnl_attr_nest_end(nlh, nest_elem);
+ }
+ mnl_attr_nest_end(nlh, nest);
+}
+
+static void __build_set_desc(struct nlmsghdr *nlh, struct set *set)
+{
+ struct nlattr *nest;
+
+ nest = mnl_attr_nest_start(nlh, NFTA_SET_DESC);
+ if (set->size)
+ mnl_attr_put_u32(nlh, NFTA_SET_DESC_SIZE, htonl(*set->size));
+ if (set->field_array)
+ __build_set_field(nlh, set);
+ mnl_attr_nest_end(nlh, nest);
+}
+
+static void __build_set(struct nlmsghdr *nlh, struct set *set,
+ struct test_batch *batch)
+{
+ uint32_t num_exprs = 0;
+
+ if (set->table)
+ mnl_attr_put_strz(nlh, NFTA_SET_TABLE, set->table);
+ if (set->name)
+ mnl_attr_put_strz(nlh, NFTA_SET_NAME, set->name);
+ if (set->handle)
+ mnl_attr_put_u64(nlh, NFTA_SET_HANDLE, htobe64(*set->handle));
+ if (set->flags)
+ mnl_attr_put_u32(nlh, NFTA_SET_FLAGS, htonl(*set->flags));
+ if (set->key_type)
+ mnl_attr_put_u32(nlh, NFTA_SET_KEY_TYPE, htonl(*set->key_type));
+ if (set->key_len)
+ mnl_attr_put_u32(nlh, NFTA_SET_KEY_LEN, htonl(*set->key_len));
+ if (set->data_type)
+ mnl_attr_put_u32(nlh, NFTA_SET_DATA_TYPE, htonl(*set->data_type));
+ if (set->data_len)
+ mnl_attr_put_u32(nlh, NFTA_SET_DATA_LEN, htonl(*set->data_len));
+ if (set->obj_type)
+ mnl_attr_put_u32(nlh, NFTA_SET_OBJ_TYPE, htonl(*set->obj_type));
+ if (set->set_id)
+ mnl_attr_put_u32(nlh, NFTA_SET_ID, htonl(*set->set_id));
+ if (set->policy)
+ mnl_attr_put_u32(nlh, NFTA_SET_POLICY, htonl(*set->policy));
+ if (set->field_array || set->size)
+ __build_set_desc(nlh, set);
+ if (set->timeout)
+ mnl_attr_put_u64(nlh, NFTA_SET_TIMEOUT, htobe64(*set->timeout));
+ if (set->gc_interval)
+ mnl_attr_put_u32(nlh, NFTA_SET_GC_INTERVAL, htonl(*set->gc_interval));
+ if (set->userdata)
+ mnl_attr_put(nlh, NFTA_SET_USERDATA, strlen(set->userdata), set->userdata);
+ if (!list_empty(&set->expr_list)) {
+ struct expr *expr;
+
+ list_for_each_entry(expr, &set->expr_list, list)
+ num_exprs++;
+
+ if (num_exprs == 1) {
+ struct nlattr *nest1;
+
+ nest1 = mnl_attr_nest_start(nlh, NFTA_SET_EXPR);
+ list_for_each_entry(expr, &set->expr_list, list)
+ __build_expr(nlh, expr);
+
+ mnl_attr_nest_end(nlh, nest1);
+ } else if (num_exprs > 1) {
+ struct nlattr *nest1, *nest2;
+
+ nest1 = mnl_attr_nest_start(nlh, NFTA_SET_EXPRESSIONS);
+ list_for_each_entry(expr, &set->expr_list, list) {
+ nest2 = mnl_attr_nest_start(nlh, NFTA_LIST_ELEM);
+ __build_expr(nlh, expr);
+ mnl_attr_nest_end(nlh, nest2);
+ }
+ mnl_attr_nest_end(nlh, nest1);
+ }
+ }
+}
+
+static void build_set(struct test_batch *batch, struct test_batch_cmd *cmd)
+{
+ struct set *set = cmd->obj;
+ struct nlmsghdr *nlh;
+
+ nlh = nftnl_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch->batch),
+ NFT_MSG_NEWSET, *set->family,
+ NLM_F_CREATE, cmd->lineno);
+ __build_set(nlh, set, batch);
+ mnl_nlmsg_batch_next(batch->batch);
+}
+
+static void __build_one_setelem(struct nlmsghdr *nlh, struct elem *elem,
+ struct test_batch *batch)
+{
+ uint32_t num_exprs = 0;
+ struct nlattr *nest;
+ struct expr *expr;
+
+ if (elem->flags) {
+ mnl_attr_put_u32(nlh, NFTA_SET_ELEM_FLAGS, htonl(*elem->flags));
+ }
+ if (elem->timeout) {
+ mnl_attr_put_u64(nlh, NFTA_SET_ELEM_TIMEOUT, htobe64(*elem->timeout));
+ }
+ if (elem->expiration) {
+ mnl_attr_put_u64(nlh, NFTA_SET_ELEM_EXPIRATION, htobe64(*elem->expiration));
+ }
+ if (elem->key) {
+ struct nlattr *nest1;
+
+ nest1 = mnl_attr_nest_start(nlh, NFTA_SET_ELEM_KEY);
+ mnl_attr_put(nlh, NFTA_DATA_VALUE, elem->key_len, elem->key);
+ mnl_attr_nest_end(nlh, nest1);
+ }
+ if (elem->key_end) {
+ struct nlattr *nest1;
+
+ nest1 = mnl_attr_nest_start(nlh, NFTA_SET_ELEM_KEY_END);
+ mnl_attr_put(nlh, NFTA_DATA_VALUE, elem->key_end_len,
+ elem->key_end);
+ mnl_attr_nest_end(nlh, nest1);
+ }
+ if (elem->verdict) {
+ struct nlattr *nest1, *nest2;
+
+ nest1 = mnl_attr_nest_start(nlh, NFTA_SET_ELEM_DATA);
+ nest2 = mnl_attr_nest_start(nlh, NFTA_DATA_VERDICT);
+ mnl_attr_put_u32(nlh, NFTA_VERDICT_CODE, htonl(*elem->verdict));
+ if (elem->chain)
+ mnl_attr_put_strz(nlh, NFTA_VERDICT_CHAIN, elem->chain);
+
+ mnl_attr_nest_end(nlh, nest1);
+ mnl_attr_nest_end(nlh, nest2);
+ }
+ if (elem->data_len) {
+ struct nlattr *nest1;
+
+ nest1 = mnl_attr_nest_start(nlh, NFTA_SET_ELEM_DATA);
+ mnl_attr_put(nlh, NFTA_DATA_VALUE, elem->data_len, elem->data);
+ mnl_attr_nest_end(nlh, nest1);
+ }
+ if (elem->userdata) {
+ mnl_attr_put(nlh, NFTA_SET_ELEM_USERDATA, strlen(elem->userdata), elem->userdata);
+ }
+
+ if (elem->objname)
+ mnl_attr_put_strz(nlh, NFTA_SET_ELEM_OBJREF, elem->objname);
+
+ if (!list_empty(&elem->expr_list)) {
+ list_for_each_entry(expr, &elem->expr_list, list)
+ num_exprs++;
+
+ if (num_exprs == 1) {
+ struct nlattr *nest1;
+
+ nest1 = mnl_attr_nest_start(nlh, NFTA_SET_ELEM_EXPR);
+ list_for_each_entry(expr, &elem->expr_list, list)
+ __build_expr(nlh, expr);
+
+ mnl_attr_nest_end(nlh, nest1);
+ } else if (num_exprs > 1) {
+ struct nlattr *nest1, *nest2;
+
+ nest1 = mnl_attr_nest_start(nlh, NFTA_SET_ELEM_EXPRESSIONS);
+ list_for_each_entry(expr, &elem->expr_list, list) {
+ nest2 = mnl_attr_nest_start(nlh, NFTA_LIST_ELEM);
+ __build_expr(nlh, expr);
+ mnl_attr_nest_end(nlh, nest2);
+ }
+ mnl_attr_nest_end(nlh, nest1);
+ }
+ }
+}
+
+
+static void __build_setelem(struct nlmsghdr *nlh, struct elem *elem,
+ struct test_batch *batch)
+{
+ struct nlattr *nest1, *nest2;
+
+ if (elem->set)
+ mnl_attr_put_strz(nlh, NFTA_SET_ELEM_LIST_SET, elem->set);
+ if (elem->set_id)
+ mnl_attr_put_u32(nlh, NFTA_SET_ELEM_LIST_SET_ID, htonl(elem->set_id));
+ if (elem->table)
+ mnl_attr_put_strz(nlh, NFTA_SET_ELEM_LIST_TABLE, elem->table);
+
+ nest1 = mnl_attr_nest_start(nlh, NFTA_SET_ELEM_LIST_ELEMENTS);
+ nest2 = mnl_attr_nest_start(nlh, 0);
+ __build_one_setelem(nlh, elem, batch);
+ mnl_attr_nest_end(nlh, nest2);
+ mnl_attr_nest_end(nlh, nest1);
+}
+
+static void build_setelem(struct test_batch *batch, struct test_batch_cmd *cmd)
+{
+ struct elem *elem = cmd->obj;
+ struct nlmsghdr *nlh;
+
+ nlh = nftnl_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch->batch),
+ NFT_MSG_NEWSETELEM, *elem->family,
+ 0, cmd->lineno);
+ __build_setelem(nlh, elem, batch);
+ mnl_nlmsg_batch_next(batch->batch);
+}
+
+static void build_del_setelem(struct test_batch *batch, struct test_batch_cmd *cmd)
+{
+ struct elem *elem = cmd->obj;
+ struct nlmsghdr *nlh;
+
+ nlh = nftnl_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch->batch),
+ NFT_MSG_DELSETELEM, *elem->family,
+ 0, cmd->lineno);
+ __build_setelem(nlh, elem, batch);
+ mnl_nlmsg_batch_next(batch->batch);
+}
+
+static void __build_flowtable(struct nlmsghdr *nlh, struct flowtable *ft)
+{
+ struct nlattr *nest = NULL;
+
+ if (ft->table)
+ mnl_attr_put_strz(nlh, NFTA_FLOWTABLE_TABLE, ft->table);
+ if (ft->name)
+ mnl_attr_put_strz(nlh, NFTA_FLOWTABLE_NAME, ft->name);
+
+ if (ft->hooknum || ft->prio || ft->dev_array)
+ nest = mnl_attr_nest_start(nlh, NFTA_FLOWTABLE_HOOK);
+
+ if (ft->hooknum)
+ mnl_attr_put_u32(nlh, NFTA_FLOWTABLE_HOOK_NUM, htonl(*ft->hooknum));
+ if (ft->prio)
+ mnl_attr_put_u32(nlh, NFTA_FLOWTABLE_HOOK_PRIORITY, htonl(*ft->prio));
+
+ if (ft->dev_array) {
+ struct nlattr *nest_dev;
+ unsigned int i;
+
+ nest_dev = mnl_attr_nest_start(nlh, NFTA_FLOWTABLE_HOOK_DEVS);
+ for (i = 0; i < ft->dev_array->num; i++) {
+ mnl_attr_put_strz(nlh, NFTA_DEVICE_NAME, ft->dev_array->data[i]);
+ }
+ mnl_attr_nest_end(nlh, nest_dev);
+ }
+
+ if (nest)
+ mnl_attr_nest_end(nlh, nest);
+
+/* if (ft->flags)
+ mnl_attr_put_u32(nlh, NFTA_FLOWTABLE_FLAGS, htonl(ft->flags)); */
+/* if (ft->handle)
+ mnl_attr_put_u64(nlh, NFTA_FLOWTABLE_HANDLE, htobe64(ft->handle)); */
+}
+
+static void build_add_flowtable(struct test_batch *batch, struct test_batch_cmd *cmd)
+{
+ struct flowtable *ft = cmd->obj;
+ struct nlmsghdr *nlh;
+
+ nlh = nftnl_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch->batch),
+ NFT_MSG_NEWFLOWTABLE, *ft->family,
+ NLM_F_CREATE, cmd->lineno);
+ __build_flowtable(nlh, ft);
+ mnl_nlmsg_batch_next(batch->batch);
+}
+
+static void build_del_flowtable(struct test_batch *batch, struct test_batch_cmd *cmd)
+{
+ struct flowtable *ft = cmd->obj;
+ struct nlmsghdr *nlh;
+
+ nlh = nftnl_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch->batch),
+ NFT_MSG_DELFLOWTABLE, *ft->family,
+ NLM_F_CREATE, cmd->lineno);
+ __build_flowtable(nlh, ft);
+ mnl_nlmsg_batch_next(batch->batch);
+}
+
+static void __build_obj_type(struct nlmsghdr *nlh, struct obj *obj)
+{
+ switch (*obj->type) {
+ case NFT_OBJECT_COUNTER:
+ if (obj->u.counter->bytes)
+ mnl_attr_put_u64(nlh, NFTA_COUNTER_BYTES, htobe64(*obj->u.counter->bytes));
+ if (obj->u.counter->pkts)
+ mnl_attr_put_u64(nlh, NFTA_COUNTER_PACKETS, htobe64(*obj->u.counter->pkts));
+ break;
+ case NFT_OBJECT_QUOTA:
+ if (obj->u.quota->bytes)
+ mnl_attr_put_u64(nlh, NFTA_QUOTA_BYTES, htobe64(*obj->u.quota->bytes));
+ if (obj->u.quota->consumed)
+ mnl_attr_put_u64(nlh, NFTA_QUOTA_CONSUMED, htobe64(*obj->u.quota->consumed));
+ if (obj->u.quota->flags)
+ mnl_attr_put_u32(nlh, NFTA_QUOTA_FLAGS, htonl(*obj->u.quota->flags));
+ break;
+ case NFT_OBJECT_LIMIT:
+ if (obj->u.limit->rate)
+ mnl_attr_put_u64(nlh, NFTA_LIMIT_RATE, htobe64(*obj->u.limit->rate));
+ if (obj->u.limit->unit)
+ mnl_attr_put_u64(nlh, NFTA_LIMIT_UNIT, htobe64(*obj->u.limit->unit));
+ if (obj->u.limit->burst)
+ mnl_attr_put_u32(nlh, NFTA_LIMIT_BURST, htonl(*obj->u.limit->burst));
+ if (obj->u.limit->type)
+ mnl_attr_put_u32(nlh, NFTA_LIMIT_TYPE, htonl(*obj->u.limit->type));
+ if (obj->u.limit->flags)
+ mnl_attr_put_u32(nlh, NFTA_LIMIT_FLAGS, htonl(*obj->u.limit->flags));
+ break;
+ case NFT_OBJECT_CONNLIMIT:
+ if (obj->u.connlimit->count)
+ mnl_attr_put_u32(nlh, NFTA_CONNLIMIT_COUNT,
+ htonl(*obj->u.connlimit->count));
+ if (obj->u.connlimit->flags)
+ mnl_attr_put_u32(nlh, NFTA_CONNLIMIT_FLAGS,
+ htonl(*obj->u.connlimit->flags));
+ break;
+ case NFT_OBJECT_TUNNEL:
+ if (obj->u.tun->id)
+ mnl_attr_put_u32(nlh, NFTA_TUNNEL_KEY_ID,
+ htonl(*obj->u.tun->id));
+ if (obj->u.tun->src_v4 ||
+ obj->u.tun->dst_v4) {
+ struct nlattr *nest;
+
+ nest = mnl_attr_nest_start(nlh, NFTA_TUNNEL_KEY_IP);
+ if (obj->u.tun->src_v4)
+ mnl_attr_put_u32(nlh, NFTA_TUNNEL_KEY_IP_SRC, *obj->u.tun->src_v4);
+ if (obj->u.tun->dst_v4)
+ mnl_attr_put_u32(nlh, NFTA_TUNNEL_KEY_IP_DST, *obj->u.tun->dst_v4);
+ mnl_attr_nest_end(nlh, nest);
+ }
+ if (obj->u.tun->src_v6 ||
+ obj->u.tun->dst_v6) {
+ struct nlattr *nest;
+
+ nest = mnl_attr_nest_start(nlh, NFTA_TUNNEL_KEY_IP6);
+ if (obj->u.tun->src_v6)
+ mnl_attr_put(nlh, NFTA_TUNNEL_KEY_IP6_SRC,
+ sizeof(obj->u.tun->_src_v6),
+ obj->u.tun->src_v6);
+ if (obj->u.tun->dst_v6)
+ mnl_attr_put(nlh, NFTA_TUNNEL_KEY_IP6_DST,
+ sizeof(obj->u.tun->_dst_v6),
+ obj->u.tun->dst_v6);
+ if (obj->u.tun->flowlabel)
+ mnl_attr_put_u32(nlh, NFTA_TUNNEL_KEY_IP6_FLOWLABEL,
+ htonl(*obj->u.tun->flowlabel));
+ mnl_attr_nest_end(nlh, nest);
+ }
+ if (obj->u.tun->sport)
+ mnl_attr_put_u16(nlh, NFTA_TUNNEL_KEY_SPORT, htons(*obj->u.tun->sport));
+ if (obj->u.tun->dport)
+ mnl_attr_put_u16(nlh, NFTA_TUNNEL_KEY_DPORT, htons(*obj->u.tun->dport));
+ if (obj->u.tun->tos)
+ mnl_attr_put_u8(nlh, NFTA_TUNNEL_KEY_TOS, *obj->u.tun->tos);
+ if (obj->u.tun->ttl)
+ mnl_attr_put_u8(nlh, NFTA_TUNNEL_KEY_TTL, *obj->u.tun->ttl);
+ if (obj->u.tun->flags)
+ mnl_attr_put_u32(nlh, NFTA_TUNNEL_KEY_FLAGS, htonl(*obj->u.tun->flags));
+ break;
+ case NFT_OBJECT_CT_EXPECT:
+ if (obj->u.ct_expect->l3proto)
+ mnl_attr_put_u16(nlh, NFTA_CT_EXPECT_L3PROTO, htons(*obj->u.ct_expect->l3proto));
+ if (obj->u.ct_expect->l4proto)
+ mnl_attr_put_u8(nlh, NFTA_CT_EXPECT_L4PROTO, *obj->u.ct_expect->l4proto);
+ if (obj->u.ct_expect->dport)
+ mnl_attr_put_u16(nlh, NFTA_CT_EXPECT_DPORT, htons(*obj->u.ct_expect->dport));
+ if (obj->u.ct_expect->timeout)
+ mnl_attr_put_u32(nlh, NFTA_CT_EXPECT_TIMEOUT, htonl(*obj->u.ct_expect->timeout));
+ if (obj->u.ct_expect->size)
+ mnl_attr_put_u8(nlh, NFTA_CT_EXPECT_SIZE, *obj->u.ct_expect->size);
+ break;
+ case NFT_OBJECT_SYNPROXY:
+ if (obj->u.synproxy->mss)
+ mnl_attr_put_u16(nlh, NFTA_SYNPROXY_MSS, htons(*obj->u.synproxy->mss));
+ if (obj->u.synproxy->wscale)
+ mnl_attr_put_u8(nlh, NFTA_SYNPROXY_WSCALE, *obj->u.synproxy->wscale);
+ if (obj->u.synproxy->flags)
+ mnl_attr_put_u32(nlh, NFTA_SYNPROXY_FLAGS, htonl(*obj->u.synproxy->flags));
+ break;
+ case NFT_OBJECT_CT_HELPER:
+ if (obj->u.ct_helper->name)
+ mnl_attr_put_str(nlh, NFTA_CT_HELPER_NAME, obj->u.ct_helper->name);
+ if (obj->u.ct_helper->l3proto)
+ mnl_attr_put_u16(nlh, NFTA_CT_HELPER_L3PROTO, htons(*obj->u.ct_helper->l3proto));
+ if (obj->u.ct_helper->l4proto)
+ mnl_attr_put_u8(nlh, NFTA_CT_HELPER_L4PROTO, *obj->u.ct_helper->l4proto);
+ break;
+ case NFT_OBJECT_CT_TIMEOUT:
+ if (obj->u.ct_timeout->l3proto)
+ mnl_attr_put_u16(nlh, NFTA_CT_TIMEOUT_L3PROTO, htons(*obj->u.ct_timeout->l3proto));
+ if (obj->u.ct_timeout->l4proto)
+ mnl_attr_put_u8(nlh, NFTA_CT_TIMEOUT_L4PROTO, *obj->u.ct_timeout->l4proto);
+ if (obj->u.ct_timeout->timeout_array) {
+ struct nlattr *nest;
+ int i;
+
+ nest = mnl_attr_nest_start(nlh, NFTA_CT_TIMEOUT_DATA);
+ for (i = 0; i < obj->u.ct_timeout->timeout_array->num; i++)
+ mnl_attr_put_u32(nlh, i+1, htonl(obj->u.ct_timeout->timeout_array->data[i]));
+
+ mnl_attr_nest_end(nlh, nest);
+ }
+ break;
+ case NFT_OBJECT_SECMARK:
+ if (obj->u.secmark->ctx)
+ mnl_attr_put_str(nlh, NFTA_SECMARK_CTX, obj->u.secmark->ctx);
+ break;
+ }
+}
+
+static void __build_obj_def(struct nlmsghdr *nlh, struct obj *obj)
+{
+ if (obj->table)
+ mnl_attr_put_strz(nlh, NFTA_OBJ_TABLE, obj->table);
+ if (obj->name)
+ mnl_attr_put_strz(nlh, NFTA_OBJ_NAME, obj->name);
+ if (obj->type)
+ mnl_attr_put_u32(nlh, NFTA_OBJ_TYPE, htonl(*obj->type));
+ if (obj->handle)
+ mnl_attr_put_u64(nlh, NFTA_OBJ_HANDLE, htobe64(*obj->handle));
+ if (obj->userdata)
+ mnl_attr_put(nlh, NFTA_OBJ_USERDATA, strlen(obj->userdata), obj->userdata);
+}
+
+static void __build_obj(struct nlmsghdr *nlh, struct obj *obj)
+{
+ __build_obj_def(nlh, obj);
+
+ if (obj->type &&
+ *obj->type > NFT_OBJECT_UNSPEC &&
+ *obj->type <= NFT_OBJECT_MAX) {
+ struct nlattr *nest = mnl_attr_nest_start(nlh, NFTA_OBJ_DATA);
+ __build_obj_type(nlh, obj);
+ mnl_attr_nest_end(nlh, nest);
+ }
+}
+
+static void build_obj(struct test_batch *batch, struct test_batch_cmd *cmd)
+{
+ struct obj *obj = cmd->obj;
+ struct nlmsghdr *nlh;
+
+ nlh = nftnl_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch->batch),
+ NFT_MSG_NEWOBJ, *obj->family,
+ NLM_F_CREATE, cmd->lineno);
+ __build_obj(nlh, obj);
+ mnl_nlmsg_batch_next(batch->batch);
+}
+
+static void build_del_obj(struct test_batch *batch, struct test_batch_cmd *cmd)
+{
+ struct obj *obj = cmd->obj;
+ struct nlmsghdr *nlh;
+
+ nlh = nftnl_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch->batch),
+ NFT_MSG_DELOBJ, *obj->family,
+ 0, cmd->lineno);
+ __build_obj_def(nlh, obj);
+ mnl_nlmsg_batch_next(batch->batch);
+}
+
+static void build_del_chain(struct test_batch *batch, struct test_batch_cmd *cmd)
+{
+ struct chain *chain = cmd->obj;
+ struct nlmsghdr *nlh;
+
+ nlh = nftnl_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch->batch),
+ NFT_MSG_DELCHAIN, get_nfproto(chain->family),
+ 0, cmd->lineno);
+ __build_chain(nlh, chain);
+ mnl_nlmsg_batch_next(batch->batch);
+}
+
+void del_chain(struct test_batch *batch, struct chain *chain)
+{
+ if (batch->ctx.table) {
+ chain->table = strdup(batch->ctx.table);
+ *chain->family = batch->ctx.family;
+ }
+
+ add_cmd(batch, DEL_CHAIN, chain);
+}
+
+static void build_del_rule(struct test_batch *batch, struct test_batch_cmd *cmd)
+{
+ struct rule *rule = cmd->obj;
+ struct nlmsghdr *nlh;
+
+ nlh = nftnl_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch->batch),
+ NFT_MSG_DELRULE, *rule->family,
+ 0, cmd->lineno);
+ __build_rule(nlh, rule);
+ mnl_nlmsg_batch_next(batch->batch);
+}
+
+void del_rule(struct test_batch *batch, struct rule *rule)
+{
+ if (batch->ctx.table) {
+ rule->table = strdup(batch->ctx.table);
+ *rule->family = batch->ctx.family;
+ }
+
+ add_cmd(batch, DEL_RULE, rule);
+}
+
+static void build_del_set(struct test_batch *batch, struct test_batch_cmd *cmd)
+{
+ struct set *set = cmd->obj;
+ struct nlmsghdr *nlh;
+
+ nlh = nftnl_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch->batch),
+ NFT_MSG_DELSET, *set->family,
+ 0, cmd->lineno);
+ __build_set(nlh, set, batch);
+ mnl_nlmsg_batch_next(batch->batch);
+}
+
+void del_set(struct test_batch *batch, struct set *set)
+{
+ if (batch->ctx.table) {
+ set->table = strdup(batch->ctx.table);
+ *set->family = batch->ctx.family;
+ }
+
+ add_cmd(batch, DEL_SET, set);
+}
+
+static void build_flush_set(struct test_batch *batch, struct test_batch_cmd *cmd)
+{
+ struct nlattr *nest1, *nest2;
+ struct set *set = cmd->obj;
+ struct nlmsghdr *nlh;
+
+ nlh = nftnl_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch->batch),
+ NFT_MSG_DELSETELEM, *set->family,
+ 0, cmd->lineno);
+ if (set->name)
+ mnl_attr_put_strz(nlh, NFTA_SET_ELEM_LIST_SET, set->name);
+ if (set->set_id)
+ mnl_attr_put_u32(nlh, NFTA_SET_ELEM_LIST_SET_ID, htonl(*set->set_id));
+ if (set->table)
+ mnl_attr_put_strz(nlh, NFTA_SET_ELEM_LIST_TABLE, set->table);
+ mnl_nlmsg_batch_next(batch->batch);
+}
+
+void flush_set(struct test_batch *batch, struct set *set)
+{
+ if (batch->ctx.table) {
+ set->table = strdup(batch->ctx.table);
+ *set->family = batch->ctx.family;
+ }
+
+ add_cmd(batch, FLUSH_SET, set);
+}
+
+static int mnl_batch_extack_cb(const struct nlmsghdr *nlh, void *data)
+{
+ const struct nlmsgerr *err = mnl_nlmsg_get_payload(nlh);
+ int errval;
+
+ if (nlh->nlmsg_len < mnl_nlmsg_size(sizeof(struct nlmsgerr)))
+ return MNL_CB_ERROR;
+
+ if (err->error < 0)
+ errval = -err->error;
+ else
+ errval = err->error;
+
+ if (errval) {
+ errno = errval;
+ if (errout)
+ printf("Error: %s, line %u\n", strerror(errno), nlh->nlmsg_seq);
+
+ return MNL_CB_ERROR;
+ }
+
+ return MNL_CB_OK;
+}
+
+static int nft_test_recv(struct mnl_socket *nl)
+{
+ static mnl_cb_t cb_ctl_array[NLMSG_MIN_TYPE] = {
+ [NLMSG_ERROR] = mnl_batch_extack_cb,
+ };
+ char buf[MNL_SOCKET_BUFFER_SIZE * 16];
+ bool mnl_error = false;
+ struct timeval tv;
+ fd_set readfds;
+ int ret;
+
+ FD_ZERO(&readfds);
+ FD_SET(mnl_socket_get_fd(nl), &readfds);
+
+ while (1) {
+ struct timeval tv = {};
+
+ ret = select(mnl_socket_get_fd(nl) + 1, &readfds, NULL, NULL, &tv);
+ if (ret == -1) {
+ perror("unexpected select() fails");
+ return -1;
+ }
+
+ if (!FD_ISSET(mnl_socket_get_fd(nl), &readfds))
+ break;
+
+ ret = mnl_socket_recvfrom(nl, buf, sizeof(buf));
+ if (ret == -1) {
+ /* enobufs means too many errors in batch. */
+ if (errno != ENOBUFS)
+ perror("unexpected recv() fails");
+ return -1;
+ }
+
+ ret = mnl_cb_run2(buf, ret, 0, mnl_socket_get_portid(nl), NULL, NULL,
+ cb_ctl_array, MNL_ARRAY_SIZE(cb_ctl_array));
+ if (ret < 0)
+ mnl_error = true;
+ }
+
+ return mnl_error ? -1 : 0;
+}
+
+static void setup_commit(struct mnl_nlmsg_batch *batch, unsigned int seq)
+{
+ nftnl_batch_end(mnl_nlmsg_batch_current(batch), seq);
+ mnl_nlmsg_batch_next(batch);
+}
+
+static int __send(struct mnl_socket *nl, struct mnl_nlmsg_batch *batch)
+{
+ int ret;
+
+ ret = mnl_socket_sendto(nl, mnl_nlmsg_batch_head(batch),
+ mnl_nlmsg_batch_size(batch));
+ if (ret == -1) {
+ perror("unexpected send() fails");
+ exit(EXIT_FAILURE);
+ }
+
+ return nft_test_recv(nl);
+}
+
+static void batch_cmd_free(struct test_batch_cmd *cmd)
+{
+ switch (cmd->type) {
+ case ADD_TABLE:
+ case SET_TABLE:
+ case DEL_TABLE:
+ free_table(cmd->obj);
+ break;
+ case ADD_BASECHAIN:
+ case ADD_CHAIN:
+ case DEL_BASECHAIN:
+ case DEL_CHAIN:
+ free_chain(cmd->obj);
+ break;
+ case ADD_RULE:
+ case DEL_RULE:
+ free_rule(cmd->obj);
+ break;
+ case ADD_SET:
+ case SET_SET:
+ case DEL_SET:
+ case FLUSH_SET:
+ free_set(cmd->obj);
+ break;
+ case ADD_SETELEM:
+ case DEL_SETELEM:
+ free_elem(cmd->obj);
+ break;
+ case ADD_FLOWTABLE:
+ case DEL_FLOWTABLE:
+ free_flowtable(cmd->obj);
+ break;
+ case ADD_OBJECT:
+ case DEL_OBJECT:
+ free_obj(cmd->obj);
+ break;
+ }
+
+ list_del(&cmd->list);
+ free(cmd);
+}
+
+static int __build(struct test_batch *batch)
+{
+ struct test_batch_cmd *cmd, *next;
+
+ list_for_each_entry_safe(cmd, next, &batch->cmd, list) {
+ switch (cmd->type) {
+ case ADD_TABLE:
+ build_table(batch, cmd);
+ break;
+ case DEL_TABLE:
+ build_del_table(batch, cmd);
+ break;
+ case SET_TABLE:
+ case SET_SET:
+ /* release table in context. */
+ break;
+ case ADD_BASECHAIN:
+ case ADD_CHAIN:
+ build_chain(batch, cmd);
+ break;
+ case DEL_BASECHAIN:
+ case DEL_CHAIN:
+ build_del_chain(batch, cmd);
+ break;
+ case ADD_RULE:
+ build_rule(batch, cmd);
+ break;
+ case DEL_RULE:
+ build_del_rule(batch, cmd);
+ break;
+ case ADD_SET:
+ build_set(batch, cmd);
+ break;
+ case DEL_SET:
+ build_del_set(batch, cmd);
+ break;
+ case FLUSH_SET:
+ build_flush_set(batch, cmd);
+ break;
+ case ADD_SETELEM:
+ build_setelem(batch, cmd);
+ break;
+ case DEL_SETELEM:
+ build_del_setelem(batch, cmd);
+ break;
+ case ADD_OBJECT:
+ build_obj(batch, cmd);
+ break;
+ case DEL_OBJECT:
+ build_del_obj(batch, cmd);
+ break;
+ case ADD_FLOWTABLE:
+ build_add_flowtable(batch, cmd);
+ break;
+ case DEL_FLOWTABLE:
+ build_del_flowtable(batch, cmd);
+ break;
+ }
+
+ batch_cmd_free(cmd);
+ }
+}
+
+int batch_abort(struct test_batch *batch)
+{
+ int ret;
+
+ __build(batch);
+
+ ret = __send(batch->nl, batch->batch);
+
+ batch_reset(batch);
+
+ return ret;
+}
+
+int batch_commit(struct test_batch *batch)
+{
+ int ret;
+
+ __build(batch);
+
+ setup_commit(batch->batch, UINT32_MAX);
+
+ ret = __send(batch->nl, batch->batch);
+
+ batch_reset(batch);
+
+ return ret;
+}
+
+void batch_stop(struct test_batch *batch)
+{
+ mnl_nlmsg_batch_stop(batch->batch);
+ mnl_socket_close(batch->nl);
+}
+
+void batch_reset(struct test_batch *batch)
+{
+ memset(&batch->ctx, 0, sizeof(batch->ctx));
+ mnl_nlmsg_batch_stop(batch->batch);
+ batch->batch = __setup_batch(batch->buf, sizeof(batch->buf));
+}
+
+int flush_ruleset(struct mnl_socket *nl)
+{
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+ struct mnl_nlmsg_batch *batch;
+ uint32_t seq, portid;
+ struct nlmsghdr *nlh;
+ int ret;
+
+ seq = time(NULL);
+ batch = mnl_nlmsg_batch_start(buf, sizeof(buf));
+
+ nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++);
+ mnl_nlmsg_batch_next(batch);
+
+ nlh = nftnl_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch),
+ NFT_MSG_DELTABLE, AF_UNSPEC,
+ NLM_F_ACK, seq++);
+ mnl_nlmsg_batch_next(batch);
+
+ nftnl_batch_end(mnl_nlmsg_batch_current(batch), seq++);
+ mnl_nlmsg_batch_next(batch);
+
+ portid = mnl_socket_get_portid(nl);
+
+ if (mnl_socket_sendto(nl, mnl_nlmsg_batch_head(batch),
+ mnl_nlmsg_batch_size(batch)) < 0) {
+ perror("mnl_socket_send");
+ return -1;
+ }
+
+ mnl_nlmsg_batch_stop(batch);
+
+ nft_test_recv(nl);
+
+ return 0;
+}
+
+bool kernel_is_tainted(void)
+{
+ unsigned int taint;
+ bool ret = false;
+ FILE *fp;
+
+ fp = fopen("/proc/sys/kernel/tainted", "r");
+ if (!fp)
+ return false;
+
+ if (fscanf(fp, "%u", &taint) == 1 && taint) {
+ fprintf(stderr, "kernel is tainted: 0x%x\n", taint);
+ ret = true;
+ }
+
+ fclose(fp);
+
+ return ret;
+}
+
+#define NFT_NLMSG_MAXSIZE (UINT16_MAX + getpagesize())
+
+static int
+nft_mnl_recv(struct mnl_socket *nl, uint32_t portid,
+ int (*cb)(const struct nlmsghdr *nlh, void *data), void *cb_data)
+{
+ char buf[NFT_NLMSG_MAXSIZE];
+ int ret;
+
+ ret = mnl_socket_recvfrom(nl, buf, sizeof(buf));
+ while (ret > 0) {
+ ret = mnl_cb_run(buf, ret, 0, portid, cb, cb_data);
+ if (ret == 0)
+ break;
+ if (ret < 0) {
+ if (errno == EAGAIN) {
+ ret = 0;
+ break;
+ }
+ if (errno != EINTR)
+ break;
+ }
+ ret = mnl_socket_recvfrom(nl, buf, sizeof(buf));
+ }
+
+ return ret;
+}
+
+static int
+nft_mnl_talk(struct mnl_socket *nl, const void *data, unsigned int len,
+ int (*cb)(const struct nlmsghdr *nlh, void *data), void *cb_data)
+{
+ uint32_t portid = mnl_socket_get_portid(nl);
+
+ if (mnl_socket_sendto(nl, data, len) < 0)
+ return -1;
+
+ return nft_mnl_recv(nl, portid, cb, cb_data);
+}
+
+static int dump_hooks_cb(const struct nlmsghdr *nlh, void *_data)
+{
+ /* exercise hook dump path, no real parsing in userspace. */
+ return MNL_CB_OK;
+}
+
+static int dump_hooks(struct mnl_socket *nl, uint8_t family,
+ const char *devname, uint32_t hooknum)
+{
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+ struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf);
+ struct nfgenmsg *nfg;
+
+ nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
+ nlh->nlmsg_type = NFNL_SUBSYS_HOOK << 8;
+ nlh->nlmsg_seq = time(NULL);
+
+ nfg = mnl_nlmsg_put_extra_header(nlh, sizeof(*nfg));
+ nfg->nfgen_family = family;
+ nfg->version = NFNETLINK_V0;
+
+ if (devname)
+ mnl_attr_put_strz(nlh, NFNLA_HOOK_DEV, devname);
+
+ mnl_attr_put_u32(nlh, NFNLA_HOOK_HOOKNUM, htonl(hooknum));
+
+ return nft_mnl_talk(nl, nlh, nlh->nlmsg_len, dump_hooks_cb, NULL);
+}
+
+static const char *devname[] = {
+ "dummy0", "dummy1", "dummy2", "dummy3", "lo", NULL,
+};
+
+/* detect UaF in hooks */
+int list_hooks(struct mnl_socket *nl)
+{
+ int i;
+
+ for (i = 0; i <= NF_INET_POST_ROUTING; i++) {
+ dump_hooks(nl, NFPROTO_IPV4, NULL, i);
+
+ if (kernel_is_tainted()) {
+ fprintf(stderr, "FATAL: list ip hooks taints kernel\n");
+ return -1;
+ }
+ }
+
+ for (i = 0; devname[i] != NULL; i++) {
+ dump_hooks(nl, NFPROTO_NETDEV, devname[i], NF_NETDEV_INGRESS);
+
+ if (kernel_is_tainted()) {
+ fprintf(stderr, "FATAL: list netdev hooks %s taints kernel\n", devname[i]);
+ return -1;
+ }
+ }
+
+ return 0;
+}