#include <linux/netfilter/nf_tables.h>
#include <net/netlink.h>
+#define NFT_JUMP_STACK_SIZE 16
+
struct nft_pktinfo {
struct sk_buff *skb;
const struct net_device *in;
}
/**
- * struct nft_ctx - nf_tables rule context
+ * struct nft_ctx - nf_tables rule/set context
*
+ * @skb: netlink skb
+ * @nlh: netlink message header
* @afi: address family info
* @table: the table the chain is contained in
* @chain: the chain the rule is contained in
*/
struct nft_ctx {
+ const struct sk_buff *skb;
+ const struct nlmsghdr *nlh;
const struct nft_af_info *afi;
const struct nft_table *table;
const struct nft_chain *chain;
};
-enum nft_data_types {
- NFT_DATA_VALUE,
- NFT_DATA_VERDICT,
-};
-
struct nft_data_desc {
enum nft_data_types type;
unsigned int len;
return reg == NFT_REG_VERDICT ? NFT_DATA_VERDICT : NFT_DATA_VALUE;
}
+static inline enum nft_registers nft_type_to_reg(enum nft_data_types type)
+{
+ return type == NFT_DATA_VERDICT ? NFT_REG_VERDICT : NFT_REG_1;
+}
+
extern int nft_validate_input_register(enum nft_registers reg);
extern int nft_validate_output_register(enum nft_registers reg);
extern int nft_validate_data_load(const struct nft_ctx *ctx,
const struct nft_data *data,
enum nft_data_types type);
+/**
+ * struct nft_set_elem - generic representation of set elements
+ *
+ * @cookie: implementation specific element cookie
+ * @key: element key
+ * @data: element data (maps only)
+ * @flags: element flags (end of interval)
+ *
+ * The cookie can be used to store a handle to the element for subsequent
+ * removal.
+ */
+struct nft_set_elem {
+ void *cookie;
+ struct nft_data key;
+ struct nft_data data;
+ u32 flags;
+};
+
+struct nft_set;
+struct nft_set_iter {
+ unsigned int count;
+ unsigned int skip;
+ int err;
+ int (*fn)(const struct nft_ctx *ctx,
+ const struct nft_set *set,
+ const struct nft_set_iter *iter,
+ const struct nft_set_elem *elem);
+};
+
+/**
+ * struct nft_set_ops - nf_tables set operations
+ *
+ * @lookup: look up an element within the set
+ * @insert: insert new element into set
+ * @remove: remove element from set
+ * @walk: iterate over all set elemeennts
+ * @privsize: function to return size of set private data
+ * @init: initialize private data of new set instance
+ * @destroy: destroy private data of set instance
+ * @list: nf_tables_set_ops list node
+ * @owner: module reference
+ * @features: features supported by the implementation
+ */
+struct nft_set_ops {
+ bool (*lookup)(const struct nft_set *set,
+ const struct nft_data *key,
+ struct nft_data *data);
+ int (*get)(const struct nft_set *set,
+ struct nft_set_elem *elem);
+ int (*insert)(const struct nft_set *set,
+ const struct nft_set_elem *elem);
+ void (*remove)(const struct nft_set *set,
+ const struct nft_set_elem *elem);
+ void (*walk)(const struct nft_ctx *ctx,
+ const struct nft_set *set,
+ struct nft_set_iter *iter);
+
+ unsigned int (*privsize)(const struct nlattr * const nla[]);
+ int (*init)(const struct nft_set *set,
+ const struct nlattr * const nla[]);
+ void (*destroy)(const struct nft_set *set);
+
+ struct list_head list;
+ struct module *owner;
+ u32 features;
+};
+
+extern int nft_register_set(struct nft_set_ops *ops);
+extern void nft_unregister_set(struct nft_set_ops *ops);
+
+/**
+ * struct nft_set - nf_tables set instance
+ *
+ * @list: table set list node
+ * @bindings: list of set bindings
+ * @name: name of the set
+ * @ktype: key type (numeric type defined by userspace, not used in the kernel)
+ * @dtype: data type (verdict or numeric type defined by userspace)
+ * @ops: set ops
+ * @flags: set flags
+ * @klen: key length
+ * @dlen: data length
+ * @data: private set data
+ */
+struct nft_set {
+ struct list_head list;
+ struct list_head bindings;
+ char name[IFNAMSIZ];
+ u32 ktype;
+ u32 dtype;
+ /* runtime data below here */
+ const struct nft_set_ops *ops ____cacheline_aligned;
+ u16 flags;
+ u8 klen;
+ u8 dlen;
+ unsigned char data[]
+ __attribute__((aligned(__alignof__(u64))));
+};
+
+static inline void *nft_set_priv(const struct nft_set *set)
+{
+ return (void *)set->data;
+}
+
+extern struct nft_set *nf_tables_set_lookup(const struct nft_table *table,
+ const struct nlattr *nla);
+
+/**
+ * struct nft_set_binding - nf_tables set binding
+ *
+ * @list: set bindings list node
+ * @chain: chain containing the rule bound to the set
+ *
+ * A set binding contains all information necessary for validation
+ * of new elements added to a bound set.
+ */
+struct nft_set_binding {
+ struct list_head list;
+ const struct nft_chain *chain;
+};
+
+extern int nf_tables_bind_set(const struct nft_ctx *ctx, struct nft_set *set,
+ struct nft_set_binding *binding);
+extern void nf_tables_unbind_set(const struct nft_ctx *ctx, struct nft_set *set,
+ struct nft_set_binding *binding);
+
/**
* struct nft_expr_ops - nf_tables expression operations
*
void (*destroy)(const struct nft_expr *expr);
int (*dump)(struct sk_buff *skb,
const struct nft_expr *expr);
-
+ const struct nft_data * (*get_verdict)(const struct nft_expr *expr);
struct list_head list;
const char *name;
struct module *owner;
#define MODULE_ALIAS_NFT_EXPR(name) \
MODULE_ALIAS("nft-expr-" name)
+#define MODULE_ALIAS_NFT_SET() \
+ MODULE_ALIAS("nft-set")
+
#endif /* _NET_NF_TABLES_H */
* @NFT_MSG_NEWRULE: create a new rule (enum nft_rule_attributes)
* @NFT_MSG_GETRULE: get a rule (enum nft_rule_attributes)
* @NFT_MSG_DELRULE: delete a rule (enum nft_rule_attributes)
+ * @NFT_MSG_NEWSET: create a new set (enum nft_set_attributes)
+ * @NFT_MSG_GETSET: get a set (enum nft_set_attributes)
+ * @NFT_MSG_DELSET: delete a set (enum nft_set_attributes)
+ * @NFT_MSG_NEWSETELEM: create a new set element (enum nft_set_elem_attributes)
+ * @NFT_MSG_GETSETELEM: get a set element (enum nft_set_elem_attributes)
+ * @NFT_MSG_DELSETELEM: delete a set element (enum nft_set_elem_attributes)
*/
enum nf_tables_msg_types {
NFT_MSG_NEWTABLE,
NFT_MSG_NEWRULE,
NFT_MSG_GETRULE,
NFT_MSG_DELRULE,
+ NFT_MSG_NEWSET,
+ NFT_MSG_GETSET,
+ NFT_MSG_DELSET,
+ NFT_MSG_NEWSETELEM,
+ NFT_MSG_GETSETELEM,
+ NFT_MSG_DELSETELEM,
NFT_MSG_MAX,
};
+/**
+ * enum nft_list_attributes - nf_tables generic list netlink attributes
+ *
+ * @NFTA_LIST_ELEM: list element (NLA_NESTED)
+ */
enum nft_list_attributes {
NFTA_LIST_UNPEC,
NFTA_LIST_ELEM,
};
#define NFTA_RULE_MAX (__NFTA_RULE_MAX - 1)
+/**
+ * enum nft_set_flags - nf_tables set flags
+ *
+ * @NFT_SET_ANONYMOUS: name allocation, automatic cleanup on unlink
+ * @NFT_SET_CONSTANT: set contents may not change while bound
+ * @NFT_SET_INTERVAL: set contains intervals
+ * @NFT_SET_MAP: set is used as a dictionary
+ */
+enum nft_set_flags {
+ NFT_SET_ANONYMOUS = 0x1,
+ NFT_SET_CONSTANT = 0x2,
+ NFT_SET_INTERVAL = 0x4,
+ NFT_SET_MAP = 0x8,
+};
+
+/**
+ * enum nft_set_attributes - nf_tables set netlink attributes
+ *
+ * @NFTA_SET_TABLE: table name (NLA_STRING)
+ * @NFTA_SET_NAME: set name (NLA_STRING)
+ * @NFTA_SET_FLAGS: bitmask of enum nft_set_flags (NLA_U32)
+ * @NFTA_SET_KEY_TYPE: key data type, informational purpose only (NLA_U32)
+ * @NFTA_SET_KEY_LEN: key data length (NLA_U32)
+ * @NFTA_SET_DATA_TYPE: mapping data type (NLA_U32)
+ * @NFTA_SET_DATA_LEN: mapping data length (NLA_U32)
+ */
+enum nft_set_attributes {
+ NFTA_SET_UNSPEC,
+ NFTA_SET_TABLE,
+ NFTA_SET_NAME,
+ NFTA_SET_FLAGS,
+ NFTA_SET_KEY_TYPE,
+ NFTA_SET_KEY_LEN,
+ NFTA_SET_DATA_TYPE,
+ NFTA_SET_DATA_LEN,
+ __NFTA_SET_MAX
+};
+#define NFTA_SET_MAX (__NFTA_SET_MAX - 1)
+
+/**
+ * enum nft_set_elem_flags - nf_tables set element flags
+ *
+ * @NFT_SET_ELEM_INTERVAL_END: element ends the previous interval
+ */
+enum nft_set_elem_flags {
+ NFT_SET_ELEM_INTERVAL_END = 0x1,
+};
+
+/**
+ * enum nft_set_elem_attributes - nf_tables set element netlink attributes
+ *
+ * @NFTA_SET_ELEM_KEY: key value (NLA_NESTED: nft_data)
+ * @NFTA_SET_ELEM_DATA: data value of mapping (NLA_NESTED: nft_data_attributes)
+ * @NFTA_SET_ELEM_FLAGS: bitmask of nft_set_elem_flags (NLA_U32)
+ */
+enum nft_set_elem_attributes {
+ NFTA_SET_ELEM_UNSPEC,
+ NFTA_SET_ELEM_KEY,
+ NFTA_SET_ELEM_DATA,
+ NFTA_SET_ELEM_FLAGS,
+ __NFTA_SET_ELEM_MAX
+};
+#define NFTA_SET_ELEM_MAX (__NFTA_SET_ELEM_MAX - 1)
+
+/**
+ * enum nft_set_elem_list_attributes - nf_tables set element list netlink attributes
+ *
+ * @NFTA_SET_ELEM_LIST_TABLE: table of the set to be changed (NLA_STRING)
+ * @NFTA_SET_ELEM_LIST_SET: name of the set to be changed (NLA_STRING)
+ * @NFTA_SET_ELEM_LIST_ELEMENTS: list of set elements (NLA_NESTED: nft_set_elem_attributes)
+ */
+enum nft_set_elem_list_attributes {
+ NFTA_SET_ELEM_LIST_UNSPEC,
+ NFTA_SET_ELEM_LIST_TABLE,
+ NFTA_SET_ELEM_LIST_SET,
+ NFTA_SET_ELEM_LIST_ELEMENTS,
+ __NFTA_SET_ELEM_LIST_MAX
+};
+#define NFTA_SET_ELEM_LIST_MAX (__NFTA_SET_ELEM_LIST_MAX - 1)
+
+/**
+ * enum nft_data_types - nf_tables data types
+ *
+ * @NFT_DATA_VALUE: generic data
+ * @NFT_DATA_VERDICT: netfilter verdict
+ *
+ * The type of data is usually determined by the kernel directly and is not
+ * explicitly specified by userspace. The only difference are sets, where
+ * userspace specifies the key and mapping data types.
+ *
+ * The values 0xffffff00-0xffffffff are reserved for internally used types.
+ * The remaining range can be freely used by userspace to encode types, all
+ * values are equivalent to NFT_DATA_VALUE.
+ */
+enum nft_data_types {
+ NFT_DATA_VALUE,
+ NFT_DATA_VERDICT = 0xffffff00U,
+};
+
+#define NFT_DATA_RESERVED_MASK 0xffffff00U
+
+/**
+ * enum nft_data_attributes - nf_tables data netlink attributes
+ *
+ * @NFTA_DATA_VALUE: generic data (NLA_BINARY)
+ * @NFTA_DATA_VERDICT: nf_tables verdict (NLA_NESTED: nft_verdict_attributes)
+ */
enum nft_data_attributes {
NFTA_DATA_UNSPEC,
NFTA_DATA_VALUE,
};
#define NFTA_CMP_MAX (__NFTA_CMP_MAX - 1)
-enum nft_set_elem_flags {
- NFT_SE_INTERVAL_END = 0x1,
-};
-
-enum nft_set_elem_attributes {
- NFTA_SE_UNSPEC,
- NFTA_SE_KEY,
- NFTA_SE_DATA,
- NFTA_SE_FLAGS,
- __NFTA_SE_MAX
-};
-#define NFTA_SE_MAX (__NFTA_SE_MAX - 1)
-
-enum nft_set_flags {
- NFT_SET_INTERVAL = 0x1,
- NFT_SET_MAP = 0x2,
-};
-
-enum nft_set_attributes {
- NFTA_SET_UNSPEC,
- NFTA_SET_FLAGS,
- NFTA_SET_SREG,
- NFTA_SET_DREG,
- NFTA_SET_KLEN,
- NFTA_SET_DLEN,
- NFTA_SET_ELEMENTS,
- __NFTA_SET_MAX
-};
-#define NFTA_SET_MAX (__NFTA_SET_MAX - 1)
-
-enum nft_hash_flags {
- NFT_HASH_MAP = 0x1,
-};
-
-enum nft_hash_elem_attributes {
- NFTA_HE_UNSPEC,
- NFTA_HE_KEY,
- NFTA_HE_DATA,
- __NFTA_HE_MAX
-};
-#define NFTA_HE_MAX (__NFTA_HE_MAX - 1)
-
-enum nft_hash_attributes {
- NFTA_HASH_UNSPEC,
- NFTA_HASH_FLAGS,
- NFTA_HASH_SREG,
- NFTA_HASH_DREG,
- NFTA_HASH_KLEN,
- NFTA_HASH_ELEMENTS,
- __NFTA_HASH_MAX
-};
-#define NFTA_HASH_MAX (__NFTA_HASH_MAX - 1)
+/**
+ * enum nft_lookup_attributes - nf_tables set lookup expression netlink attributes
+ *
+ * @NFTA_LOOKUP_SET: name of the set where to look for (NLA_STRING)
+ * @NFTA_LOOKUP_SREG: source register of the data to look for (NLA_U32: nft_registers)
+ * @NFTA_LOOKUP_DREG: destination register (NLA_U32: nft_registers)
+ */
+enum nft_lookup_attributes {
+ NFTA_LOOKUP_UNSPEC,
+ NFTA_LOOKUP_SET,
+ NFTA_LOOKUP_SREG,
+ NFTA_LOOKUP_DREG,
+ __NFTA_LOOKUP_MAX
+};
+#define NFTA_LOOKUP_MAX (__NFTA_LOOKUP_MAX - 1)
/**
* enum nft_payload_bases - nf_tables payload expression offset bases
depends on NF_CONNTRACK
tristate "Netfilter nf_tables conntrack module"
-config NFT_SET
+config NFT_RBTREE
depends on NF_TABLES
- tristate "Netfilter nf_tables set module"
+ tristate "Netfilter nf_tables rbtree set module"
config NFT_HASH
depends on NF_TABLES
- tristate "Netfilter nf_tables hash module"
+ tristate "Netfilter nf_tables hash set module"
config NFT_COUNTER
depends on NF_TABLES
obj-$(CONFIG_NFT_CT) += nft_ct.o
obj-$(CONFIG_NFT_LIMIT) += nft_limit.o
#nf_tables-objs += nft_meta_target.o
-obj-$(CONFIG_NFT_SET) += nft_set.o
+obj-$(CONFIG_NFT_RBTREE) += nft_rbtree.o
obj-$(CONFIG_NFT_HASH) += nft_hash.o
obj-$(CONFIG_NFT_COUNTER) += nft_counter.o
obj-$(CONFIG_NFT_LOG) += nft_log.o
/*
- * Copyright (c) 2007, 2008 Patrick McHardy <kaber@trash.net>
+ * Copyright (c) 2007-2009 Patrick McHardy <kaber@trash.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
nla_strlcpy(table->name, name, nla_len(name));
INIT_LIST_HEAD(&table->chains);
+ INIT_LIST_HEAD(&table->sets);
list_add_tail(&table->list, &afi->tables);
nf_tables_table_notify(skb, nlh, table, NFT_MSG_NEWTABLE, family);
}
table->flags |= NFT_TABLE_BUILTIN;
+ INIT_LIST_HEAD(&table->sets);
list_add_tail(&table->list, &afi->tables);
nf_tables_table_notify(NULL, NULL, table, NFT_MSG_NEWTABLE, family);
list_for_each_entry(chain, &table->chains, list)
}
static void nft_ctx_init(struct nft_ctx *ctx,
+ const struct sk_buff *skb,
+ const struct nlmsghdr *nlh,
const struct nft_af_info *afi,
const struct nft_table *table,
const struct nft_chain *chain)
{
+ ctx->skb = skb;
+ ctx->nlh = nlh;
ctx->afi = afi;
ctx->table = table;
ctx->chain = chain;
rule->handle = handle;
rule->dlen = size;
- nft_ctx_init(&ctx, afi, table, chain);
+ nft_ctx_init(&ctx, skb, nlh, afi, table, chain);
expr = nft_expr_first(rule);
for (i = 0; i < n; i++) {
err = nf_tables_newexpr(&ctx, &info[i], expr);
return 0;
}
+/*
+ * Sets
+ */
+
+static LIST_HEAD(nf_tables_set_ops);
+
+int nft_register_set(struct nft_set_ops *ops)
+{
+ nfnl_lock(NFNL_SUBSYS_NFTABLES);
+ list_add_tail(&ops->list, &nf_tables_set_ops);
+ nfnl_unlock(NFNL_SUBSYS_NFTABLES);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(nft_register_set);
+
+void nft_unregister_set(struct nft_set_ops *ops)
+{
+ nfnl_lock(NFNL_SUBSYS_NFTABLES);
+ list_del(&ops->list);
+ nfnl_unlock(NFNL_SUBSYS_NFTABLES);
+}
+EXPORT_SYMBOL_GPL(nft_unregister_set);
+
+static const struct nft_set_ops *nft_select_set_ops(const struct nlattr * const nla[])
+{
+ const struct nft_set_ops *ops;
+ u32 features;
+
+#ifdef CONFIG_MODULES
+ if (list_empty(&nf_tables_set_ops)) {
+ nfnl_unlock(NFNL_SUBSYS_NFTABLES);
+ request_module("nft-set");
+ nfnl_lock(NFNL_SUBSYS_NFTABLES);
+ if (!list_empty(&nf_tables_set_ops))
+ return ERR_PTR(-EAGAIN);
+ }
+#endif
+ features = 0;
+ if (nla[NFTA_SET_FLAGS] != NULL) {
+ features = ntohl(nla_get_be32(nla[NFTA_SET_FLAGS]));
+ features &= NFT_SET_INTERVAL | NFT_SET_MAP;
+ }
+
+ // FIXME: implement selection properly
+ list_for_each_entry(ops, &nf_tables_set_ops, list) {
+ if ((ops->features & features) != features)
+ continue;
+ if (!try_module_get(ops->owner))
+ continue;
+ return ops;
+ }
+
+ return ERR_PTR(-EOPNOTSUPP);
+}
+
+static const struct nla_policy nft_set_policy[NFTA_SET_MAX + 1] = {
+ [NFTA_SET_TABLE] = { .type = NLA_STRING },
+ [NFTA_SET_NAME] = { .type = NLA_STRING },
+ [NFTA_SET_FLAGS] = { .type = NLA_U32 },
+ [NFTA_SET_KEY_TYPE] = { .type = NLA_U32 },
+ [NFTA_SET_KEY_LEN] = { .type = NLA_U32 },
+ [NFTA_SET_DATA_TYPE] = { .type = NLA_U32 },
+ [NFTA_SET_DATA_LEN] = { .type = NLA_U32 },
+};
+
+static int nft_ctx_init_from_setattr(struct nft_ctx *ctx,
+ const struct sk_buff *skb,
+ const struct nlmsghdr *nlh,
+ const struct nlattr * const nla[])
+{
+ const struct nfgenmsg *nfmsg = nlmsg_data(nlh);
+ const struct nft_af_info *afi;
+ const struct nft_table *table = NULL;
+
+ afi = nf_tables_afinfo_lookup(nfmsg->nfgen_family, false);
+ if (IS_ERR(afi))
+ return PTR_ERR(afi);
+
+ if (nla[NFTA_SET_TABLE] != NULL) {
+ table = nf_tables_table_lookup(afi, nla[NFTA_SET_TABLE], false);
+ if (IS_ERR(table))
+ return PTR_ERR(table);
+ }
+
+ nft_ctx_init(ctx, skb, nlh, afi, table, NULL);
+ return 0;
+}
+
+struct nft_set *nf_tables_set_lookup(const struct nft_table *table,
+ const struct nlattr *nla)
+{
+ struct nft_set *set;
+
+ if (nla == NULL)
+ return ERR_PTR(-EINVAL);
+
+ list_for_each_entry(set, &table->sets, list) {
+ if (!nla_strcmp(nla, set->name))
+ return set;
+ }
+ return ERR_PTR(-ENOENT);
+}
+
+static int nf_tables_set_alloc_name(struct nft_ctx *ctx, struct nft_set *set,
+ const char *name)
+{
+ const struct nft_set *i;
+ const char *p;
+ unsigned long *inuse;
+ unsigned int n = 0;
+
+ p = strnchr(name, IFNAMSIZ, '%');
+ if (p != NULL) {
+ if (p[1] != 'd' || strchr(p + 2, '%'))
+ return -EINVAL;
+
+ inuse = (unsigned long *)get_zeroed_page(GFP_KERNEL);
+ if (inuse == NULL)
+ return -ENOMEM;
+
+ list_for_each_entry(i, &ctx->table->sets, list) {
+ if (!sscanf(i->name, name, &n))
+ continue;
+ if (n < 0 || n > BITS_PER_LONG * PAGE_SIZE)
+ continue;
+ set_bit(n, inuse);
+ }
+
+ n = find_first_zero_bit(inuse, BITS_PER_LONG * PAGE_SIZE);
+ free_page((unsigned long)inuse);
+ }
+
+ snprintf(set->name, sizeof(set->name), name, n);
+ list_for_each_entry(i, &ctx->table->sets, list) {
+ if (!strcmp(set->name, i->name))
+ return -ENFILE;
+ }
+ return 0;
+}
+
+static int nf_tables_fill_set(struct sk_buff *skb, const struct nft_ctx *ctx,
+ const struct nft_set *set, u16 event, u16 flags)
+{
+ struct nfgenmsg *nfmsg;
+ struct nlmsghdr *nlh;
+ u32 portid = NETLINK_CB(ctx->skb).portid;
+ u32 seq = ctx->nlh->nlmsg_seq;
+
+ event |= NFNL_SUBSYS_NFTABLES << 8;
+ nlh = nlmsg_put(skb, portid, seq, event, sizeof(struct nfgenmsg),
+ flags);
+ if (nlh == NULL)
+ goto nla_put_failure;
+
+ nfmsg = nlmsg_data(nlh);
+ nfmsg->nfgen_family = ctx->afi->family;
+ nfmsg->version = NFNETLINK_V0;
+ nfmsg->res_id = 0;
+
+ if (nla_put_string(skb, NFTA_SET_TABLE, ctx->table->name))
+ goto nla_put_failure;
+ if (nla_put_string(skb, NFTA_SET_NAME, set->name))
+ goto nla_put_failure;
+ if (set->flags != 0)
+ if (nla_put_be32(skb, NFTA_SET_FLAGS, htonl(set->flags)))
+ goto nla_put_failure;
+
+ if (nla_put_be32(skb, NFTA_SET_KEY_TYPE, htonl(set->ktype)))
+ goto nla_put_failure;
+ if (nla_put_be32(skb, NFTA_SET_KEY_LEN, htonl(set->klen)))
+ goto nla_put_failure;
+ if (set->flags & NFT_SET_MAP) {
+ if (nla_put_be32(skb, NFTA_SET_DATA_TYPE, htonl(set->dtype)))
+ goto nla_put_failure;
+ if (nla_put_be32(skb, NFTA_SET_DATA_LEN, htonl(set->dlen)))
+ goto nla_put_failure;
+ }
+
+ return nlmsg_end(skb, nlh);
+
+nla_put_failure:
+ nlmsg_trim(skb, nlh);
+ return -1;
+}
+
+static int nf_tables_set_notify(const struct nft_ctx *ctx,
+ const struct nft_set *set,
+ int event)
+{
+ struct sk_buff *skb;
+ u32 portid = NETLINK_CB(ctx->skb).portid;
+ struct net *net = sock_net(ctx->skb->sk);
+ bool report;
+ int err;
+
+ report = nlmsg_report(ctx->nlh);
+ if (!report && !nfnetlink_has_listeners(net, NFNLGRP_NFTABLES))
+ return 0;
+
+ err = -ENOBUFS;
+ skb = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
+ if (skb == NULL)
+ goto err;
+
+ err = nf_tables_fill_set(skb, ctx, set, event, 0);
+ if (err < 0) {
+ kfree_skb(skb);
+ goto err;
+ }
+
+ err = nfnetlink_send(skb, net, portid, NFNLGRP_NFTABLES, report,
+ GFP_KERNEL);
+err:
+ if (err < 0)
+ nfnetlink_set_err(net, portid, NFNLGRP_NFTABLES, err);
+ return err;
+}
+
+static int nf_tables_dump_sets_table(struct nft_ctx *ctx, struct sk_buff *skb,
+ struct netlink_callback *cb)
+{
+ const struct nft_set *set;
+ unsigned int idx = 0, s_idx = cb->args[0];
+
+ if (cb->args[1])
+ return skb->len;
+
+ list_for_each_entry(set, &ctx->table->sets, list) {
+ if (idx < s_idx)
+ goto cont;
+ if (nf_tables_fill_set(skb, ctx, set, NFT_MSG_NEWSET,
+ NLM_F_MULTI) < 0) {
+ cb->args[0] = idx;
+ goto done;
+ }
+cont:
+ idx++;
+ }
+ cb->args[1] = 1;
+done:
+ return skb->len;
+}
+
+static int nf_tables_dump_sets_all(struct nft_ctx *ctx, struct sk_buff *skb,
+ struct netlink_callback *cb)
+{
+ const struct nft_set *set;
+ unsigned int idx = 0, s_idx = cb->args[0];
+ struct nft_table *table, *cur_table = (struct nft_table *)cb->args[2];
+
+ if (cb->args[1])
+ return skb->len;
+
+ list_for_each_entry(table, &ctx->afi->tables, list) {
+ if (cur_table && cur_table != table)
+ continue;
+
+ ctx->table = table;
+ list_for_each_entry(set, &ctx->table->sets, list) {
+ if (idx < s_idx)
+ goto cont;
+ if (nf_tables_fill_set(skb, ctx, set, NFT_MSG_NEWSET,
+ NLM_F_MULTI) < 0) {
+ cb->args[0] = idx;
+ cb->args[2] = (unsigned long) table;
+ goto done;
+ }
+cont:
+ idx++;
+ }
+ }
+ cb->args[1] = 1;
+done:
+ return skb->len;
+}
+
+static int nf_tables_dump_sets(struct sk_buff *skb, struct netlink_callback *cb)
+{
+ const struct nfgenmsg *nfmsg = nlmsg_data(cb->nlh);
+ struct nlattr *nla[NFTA_SET_MAX + 1];
+ struct nft_ctx ctx;
+ int err, ret;
+
+ err = nlmsg_parse(cb->nlh, sizeof(*nfmsg), nla, NFTA_SET_MAX,
+ nft_set_policy);
+ if (err < 0)
+ return err;
+
+ err = nft_ctx_init_from_setattr(&ctx, cb->skb, cb->nlh, (void *)nla);
+ if (err < 0)
+ return err;
+
+ if (ctx.table == NULL)
+ ret = nf_tables_dump_sets_all(&ctx, skb, cb);
+ else
+ ret = nf_tables_dump_sets_table(&ctx, skb, cb);
+
+ return ret;
+}
+
+static int nf_tables_getset(struct sock *nlsk, struct sk_buff *skb,
+ const struct nlmsghdr *nlh,
+ const struct nlattr * const nla[])
+{
+ const struct nft_set *set;
+ struct nft_ctx ctx;
+ struct sk_buff *skb2;
+ int err;
+
+ /* Verify existance before starting dump */
+ err = nft_ctx_init_from_setattr(&ctx, skb, nlh, nla);
+ if (err < 0)
+ return err;
+
+ if (nlh->nlmsg_flags & NLM_F_DUMP) {
+ struct netlink_dump_control c = {
+ .dump = nf_tables_dump_sets,
+ };
+ return netlink_dump_start(nlsk, skb, nlh, &c);
+ }
+
+ set = nf_tables_set_lookup(ctx.table, nla[NFTA_SET_NAME]);
+ if (IS_ERR(set))
+ return PTR_ERR(set);
+
+ skb2 = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL);
+ if (skb2 == NULL)
+ return -ENOMEM;
+
+ err = nf_tables_fill_set(skb2, &ctx, set, NFT_MSG_NEWSET, 0);
+ if (err < 0)
+ goto err;
+
+ return nlmsg_unicast(nlsk, skb2, NETLINK_CB(skb).portid);
+
+err:
+ kfree_skb(skb2);
+ return err;
+}
+
+static int nf_tables_newset(struct sock *nlsk, struct sk_buff *skb,
+ const struct nlmsghdr *nlh,
+ const struct nlattr * const nla[])
+{
+ const struct nfgenmsg *nfmsg = nlmsg_data(nlh);
+ const struct nft_set_ops *ops;
+ const struct nft_af_info *afi;
+ struct nft_table *table;
+ struct nft_set *set;
+ struct nft_ctx ctx;
+ char name[IFNAMSIZ];
+ unsigned int size;
+ bool create;
+ u32 ktype, klen, dlen, dtype, flags;
+ int err;
+
+ if (nla[NFTA_SET_TABLE] == NULL ||
+ nla[NFTA_SET_NAME] == NULL ||
+ nla[NFTA_SET_KEY_LEN] == NULL)
+ return -EINVAL;
+
+ ktype = NFT_DATA_VALUE;
+ if (nla[NFTA_SET_KEY_TYPE] != NULL) {
+ ktype = ntohl(nla_get_be32(nla[NFTA_SET_KEY_TYPE]));
+ if ((ktype & NFT_DATA_RESERVED_MASK) == NFT_DATA_RESERVED_MASK)
+ return -EINVAL;
+ }
+
+ klen = ntohl(nla_get_be32(nla[NFTA_SET_KEY_LEN]));
+ if (klen == 0 || klen > FIELD_SIZEOF(struct nft_data, data))
+ return -EINVAL;
+
+ flags = 0;
+ if (nla[NFTA_SET_FLAGS] != NULL) {
+ flags = ntohl(nla_get_be32(nla[NFTA_SET_FLAGS]));
+ if (flags & ~(NFT_SET_ANONYMOUS | NFT_SET_CONSTANT |
+ NFT_SET_INTERVAL | NFT_SET_MAP))
+ return -EINVAL;
+ }
+
+ dtype = 0;
+ dlen = 0;
+ if (nla[NFTA_SET_DATA_TYPE] != NULL) {
+ if (!(flags & NFT_SET_MAP))
+ return -EINVAL;
+
+ dtype = ntohl(nla_get_be32(nla[NFTA_SET_DATA_TYPE]));
+ if ((dtype & NFT_DATA_RESERVED_MASK) == NFT_DATA_RESERVED_MASK &&
+ dtype != NFT_DATA_VERDICT)
+ return -EINVAL;
+
+ if (dtype != NFT_DATA_VERDICT) {
+ if (nla[NFTA_SET_DATA_LEN] == NULL)
+ return -EINVAL;
+ dlen = ntohl(nla_get_be32(nla[NFTA_SET_DATA_LEN]));
+ if (dlen == 0 ||
+ dlen > FIELD_SIZEOF(struct nft_data, data))
+ return -EINVAL;
+ } else
+ dlen = sizeof(struct nft_data);
+ } else if (flags & NFT_SET_MAP)
+ return -EINVAL;
+
+ create = nlh->nlmsg_flags & NLM_F_CREATE ? true : false;
+
+ afi = nf_tables_afinfo_lookup(nfmsg->nfgen_family, create);
+ if (IS_ERR(afi))
+ return PTR_ERR(afi);
+
+ table = nf_tables_table_lookup(afi, nla[NFTA_SET_TABLE], create);
+ if (IS_ERR(table))
+ return PTR_ERR(table);
+
+ nft_ctx_init(&ctx, skb, nlh, afi, table, NULL);
+
+ set = nf_tables_set_lookup(table, nla[NFTA_SET_NAME]);
+ if (IS_ERR(set)) {
+ if (PTR_ERR(set) != -ENOENT)
+ return PTR_ERR(set);
+ set = NULL;
+ }
+
+ if (set != NULL) {
+ if (nlh->nlmsg_flags & NLM_F_EXCL)
+ return -EEXIST;
+ if (nlh->nlmsg_flags & NLM_F_REPLACE)
+ return -EOPNOTSUPP;
+ return 0;
+ }
+
+ if (!(nlh->nlmsg_flags & NLM_F_CREATE))
+ return -ENOENT;
+
+ ops = nft_select_set_ops(nla);
+ if (IS_ERR(ops))
+ return PTR_ERR(ops);
+
+ size = 0;
+ if (ops->privsize != NULL)
+ size = ops->privsize(nla);
+
+ err = -ENOMEM;
+ set = kzalloc(sizeof(*set) + size, GFP_KERNEL);
+ if (set == NULL)
+ goto err1;
+
+ nla_strlcpy(name, nla[NFTA_SET_NAME], sizeof(set->name));
+ err = nf_tables_set_alloc_name(&ctx, set, name);
+ if (err < 0)
+ goto err2;
+
+ INIT_LIST_HEAD(&set->bindings);
+ set->ops = ops;
+ set->ktype = ktype;
+ set->klen = klen;
+ set->dtype = dtype;
+ set->dlen = dlen;
+ set->flags = flags;
+
+ err = ops->init(set, nla);
+ if (err < 0)
+ goto err2;
+
+ list_add_tail(&set->list, &table->sets);
+ nf_tables_set_notify(&ctx, set, NFT_MSG_NEWSET);
+ return 0;
+
+err2:
+ kfree(set);
+err1:
+ module_put(ops->owner);
+ return err;
+}
+
+static void nf_tables_set_destroy(const struct nft_ctx *ctx, struct nft_set *set)
+{
+ list_del(&set->list);
+ if (!(set->flags & NFT_SET_ANONYMOUS))
+ nf_tables_set_notify(ctx, set, NFT_MSG_DELSET);
+
+ set->ops->destroy(set);
+ module_put(set->ops->owner);
+ kfree(set);
+}
+
+static int nf_tables_delset(struct sock *nlsk, struct sk_buff *skb,
+ const struct nlmsghdr *nlh,
+ const struct nlattr * const nla[])
+{
+ struct nft_set *set;
+ struct nft_ctx ctx;
+ int err;
+
+ if (nla[NFTA_SET_TABLE] == NULL)
+ return -EINVAL;
+
+ err = nft_ctx_init_from_setattr(&ctx, skb, nlh, nla);
+ if (err < 0)
+ return err;
+
+ set = nf_tables_set_lookup(ctx.table, nla[NFTA_SET_NAME]);
+ if (IS_ERR(set))
+ return PTR_ERR(set);
+ if (!list_empty(&set->bindings))
+ return -EBUSY;
+
+ nf_tables_set_destroy(&ctx, set);
+ return 0;
+}
+
+static int nf_tables_bind_check_setelem(const struct nft_ctx *ctx,
+ const struct nft_set *set,
+ const struct nft_set_iter *iter,
+ const struct nft_set_elem *elem)
+{
+ enum nft_registers dreg;
+
+ dreg = nft_type_to_reg(set->dtype);
+ return nft_validate_data_load(ctx, dreg, &elem->data, set->dtype);
+}
+
+int nf_tables_bind_set(const struct nft_ctx *ctx, struct nft_set *set,
+ struct nft_set_binding *binding)
+{
+ struct nft_set_binding *i;
+ struct nft_set_iter iter;
+
+ if (!list_empty(&set->bindings) && set->flags & NFT_SET_ANONYMOUS)
+ return -EBUSY;
+
+ if (set->flags & NFT_SET_MAP) {
+ /* If the set is already bound to the same chain all
+ * jumps are already validated for that chain.
+ */
+ list_for_each_entry(i, &set->bindings, list) {
+ if (i->chain == binding->chain)
+ goto bind;
+ }
+
+ iter.skip = 0;
+ iter.count = 0;
+ iter.err = 0;
+ iter.fn = nf_tables_bind_check_setelem;
+
+ set->ops->walk(ctx, set, &iter);
+ if (iter.err < 0) {
+ /* Destroy anonymous sets if binding fails */
+ if (set->flags & NFT_SET_ANONYMOUS)
+ nf_tables_set_destroy(ctx, set);
+
+ return iter.err;
+ }
+ }
+bind:
+ binding->chain = ctx->chain;
+ list_add_tail(&binding->list, &set->bindings);
+ return 0;
+}
+
+void nf_tables_unbind_set(const struct nft_ctx *ctx, struct nft_set *set,
+ struct nft_set_binding *binding)
+{
+ list_del(&binding->list);
+
+ if (list_empty(&set->bindings) && set->flags & NFT_SET_ANONYMOUS)
+ nf_tables_set_destroy(ctx, set);
+}
+
+/*
+ * Set elements
+ */
+
+static const struct nla_policy nft_set_elem_policy[NFTA_SET_ELEM_MAX + 1] = {
+ [NFTA_SET_ELEM_KEY] = { .type = NLA_NESTED },
+ [NFTA_SET_ELEM_DATA] = { .type = NLA_NESTED },
+ [NFTA_SET_ELEM_FLAGS] = { .type = NLA_U32 },
+};
+
+static const struct nla_policy nft_set_elem_list_policy[NFTA_SET_ELEM_LIST_MAX + 1] = {
+ [NFTA_SET_ELEM_LIST_TABLE] = { .type = NLA_STRING },
+ [NFTA_SET_ELEM_LIST_SET] = { .type = NLA_STRING },
+ [NFTA_SET_ELEM_LIST_ELEMENTS] = { .type = NLA_NESTED },
+};
+
+static int nft_ctx_init_from_elemattr(struct nft_ctx *ctx,
+ const struct sk_buff *skb,
+ const struct nlmsghdr *nlh,
+ const struct nlattr * const nla[])
+{
+ const struct nfgenmsg *nfmsg = nlmsg_data(nlh);
+ const struct nft_af_info *afi;
+ const struct nft_table *table;
+
+ afi = nf_tables_afinfo_lookup(nfmsg->nfgen_family, false);
+ if (IS_ERR(afi))
+ return PTR_ERR(afi);
+
+ table = nf_tables_table_lookup(afi, nla[NFTA_SET_ELEM_LIST_TABLE], false);
+ if (IS_ERR(table))
+ return PTR_ERR(table);
+
+ nft_ctx_init(ctx, skb, nlh, afi, table, NULL);
+ return 0;
+}
+
+static int nf_tables_fill_setelem(struct sk_buff *skb,
+ const struct nft_set *set,
+ const struct nft_set_elem *elem)
+{
+ unsigned char *b = skb_tail_pointer(skb);
+ struct nlattr *nest;
+
+ nest = nla_nest_start(skb, NFTA_LIST_ELEM);
+ if (nest == NULL)
+ goto nla_put_failure;
+
+ if (nft_data_dump(skb, NFTA_SET_ELEM_KEY, &elem->key, NFT_DATA_VALUE,
+ set->klen) < 0)
+ goto nla_put_failure;
+
+ if (set->flags & NFT_SET_MAP &&
+ !(elem->flags & NFT_SET_ELEM_INTERVAL_END) &&
+ nft_data_dump(skb, NFTA_SET_ELEM_DATA, &elem->data,
+ set->dtype == NFT_DATA_VERDICT ? NFT_DATA_VERDICT : NFT_DATA_VALUE,
+ set->dlen) < 0)
+ goto nla_put_failure;
+
+ if (elem->flags != 0)
+ if (nla_put_be32(skb, NFTA_SET_ELEM_FLAGS, htonl(elem->flags)))
+ goto nla_put_failure;
+
+ nla_nest_end(skb, nest);
+ return 0;
+
+nla_put_failure:
+ nlmsg_trim(skb, b);
+ return -EMSGSIZE;
+}
+
+struct nft_set_dump_args {
+ const struct netlink_callback *cb;
+ struct nft_set_iter iter;
+ struct sk_buff *skb;
+};
+
+static int nf_tables_dump_setelem(const struct nft_ctx *ctx,
+ const struct nft_set *set,
+ const struct nft_set_iter *iter,
+ const struct nft_set_elem *elem)
+{
+ struct nft_set_dump_args *args;
+
+ args = container_of(iter, struct nft_set_dump_args, iter);
+ return nf_tables_fill_setelem(args->skb, set, elem);
+}
+
+static int nf_tables_dump_set(struct sk_buff *skb, struct netlink_callback *cb)
+{
+ const struct nft_set *set;
+ struct nft_set_dump_args args;
+ struct nft_ctx ctx;
+ struct nlattr *nla[NFTA_SET_ELEM_LIST_MAX + 1];
+ struct nfgenmsg *nfmsg;
+ struct nlmsghdr *nlh;
+ struct nlattr *nest;
+ u32 portid, seq;
+ int event, err;
+
+ nfmsg = nlmsg_data(cb->nlh);
+ err = nlmsg_parse(cb->nlh, sizeof(*nfmsg), nla, NFTA_SET_ELEM_LIST_MAX,
+ nft_set_elem_list_policy);
+ if (err < 0)
+ return err;
+
+ err = nft_ctx_init_from_elemattr(&ctx, cb->skb, cb->nlh, (void *)nla);
+ if (err < 0)
+ return err;
+
+ set = nf_tables_set_lookup(ctx.table, nla[NFTA_SET_ELEM_LIST_SET]);
+ if (IS_ERR(set))
+ return PTR_ERR(set);
+
+ event = NFT_MSG_NEWSETELEM;
+ event |= NFNL_SUBSYS_NFTABLES << 8;
+ portid = NETLINK_CB(cb->skb).portid;
+ seq = cb->nlh->nlmsg_seq;
+
+ nlh = nlmsg_put(skb, portid, seq, event, sizeof(struct nfgenmsg),
+ NLM_F_MULTI);
+ if (nlh == NULL)
+ goto nla_put_failure;
+
+ nfmsg = nlmsg_data(nlh);
+ nfmsg->nfgen_family = NFPROTO_UNSPEC;
+ nfmsg->version = NFNETLINK_V0;
+ nfmsg->res_id = 0;
+
+ if (nla_put_string(skb, NFTA_SET_ELEM_LIST_TABLE, ctx.table->name))
+ goto nla_put_failure;
+ if (nla_put_string(skb, NFTA_SET_ELEM_LIST_SET, set->name))
+ goto nla_put_failure;
+
+ nest = nla_nest_start(skb, NFTA_SET_ELEM_LIST_ELEMENTS);
+ if (nest == NULL)
+ goto nla_put_failure;
+
+ args.cb = cb;
+ args.skb = skb;
+ args.iter.skip = cb->args[0];
+ args.iter.count = 0;
+ args.iter.err = 0;
+ args.iter.fn = nf_tables_dump_setelem;
+ set->ops->walk(&ctx, set, &args.iter);
+
+ nla_nest_end(skb, nest);
+ nlmsg_end(skb, nlh);
+
+ if (args.iter.err && args.iter.err != -EMSGSIZE)
+ return args.iter.err;
+ if (args.iter.count == cb->args[0])
+ return 0;
+
+ cb->args[0] = args.iter.count;
+ return skb->len;
+
+nla_put_failure:
+ return -ENOSPC;
+}
+
+static int nf_tables_getsetelem(struct sock *nlsk, struct sk_buff *skb,
+ const struct nlmsghdr *nlh,
+ const struct nlattr * const nla[])
+{
+ const struct nft_set *set;
+ struct nft_ctx ctx;
+ int err;
+
+ err = nft_ctx_init_from_elemattr(&ctx, skb, nlh, nla);
+ if (err < 0)
+ return err;
+
+ set = nf_tables_set_lookup(ctx.table, nla[NFTA_SET_ELEM_LIST_SET]);
+ if (IS_ERR(set))
+ return PTR_ERR(set);
+
+ if (nlh->nlmsg_flags & NLM_F_DUMP) {
+ struct netlink_dump_control c = {
+ .dump = nf_tables_dump_set,
+ };
+ return netlink_dump_start(nlsk, skb, nlh, &c);
+ }
+ return -EOPNOTSUPP;
+}
+
+static int nft_add_set_elem(const struct nft_ctx *ctx, struct nft_set *set,
+ const struct nlattr *attr)
+{
+ struct nlattr *nla[NFTA_SET_ELEM_MAX + 1];
+ struct nft_data_desc d1, d2;
+ struct nft_set_elem elem;
+ struct nft_set_binding *binding;
+ enum nft_registers dreg;
+ int err;
+
+ err = nla_parse_nested(nla, NFTA_SET_ELEM_MAX, attr,
+ nft_set_elem_policy);
+ if (err < 0)
+ return err;
+
+ if (nla[NFTA_SET_ELEM_KEY] == NULL)
+ return -EINVAL;
+
+ elem.flags = 0;
+ if (nla[NFTA_SET_ELEM_FLAGS] != NULL) {
+ elem.flags = ntohl(nla_get_be32(nla[NFTA_SET_ELEM_FLAGS]));
+ if (elem.flags & ~NFT_SET_ELEM_INTERVAL_END)
+ return -EINVAL;
+ }
+
+ if (set->flags & NFT_SET_MAP) {
+ if (nla[NFTA_SET_ELEM_DATA] == NULL &&
+ !(elem.flags & NFT_SET_ELEM_INTERVAL_END))
+ return -EINVAL;
+ } else {
+ if (nla[NFTA_SET_ELEM_DATA] != NULL)
+ return -EINVAL;
+ }
+
+ err = nft_data_init(ctx, &elem.key, &d1, nla[NFTA_SET_ELEM_KEY]);
+ if (err < 0)
+ goto err1;
+ err = -EINVAL;
+ if (d1.type != NFT_DATA_VALUE || d1.len != set->klen)
+ goto err2;
+
+ err = -EEXIST;
+ if (set->ops->get(set, &elem) == 0)
+ goto err2;
+
+ if (nla[NFTA_SET_ELEM_DATA] != NULL) {
+ err = nft_data_init(ctx, &elem.data, &d2, nla[NFTA_SET_ELEM_DATA]);
+ if (err < 0)
+ goto err2;
+
+ err = -EINVAL;
+ if (set->dtype != NFT_DATA_VERDICT && d2.len != set->dlen)
+ goto err3;
+
+ dreg = nft_type_to_reg(set->dtype);
+ list_for_each_entry(binding, &set->bindings, list) {
+ struct nft_ctx bind_ctx = {
+ .afi = ctx->afi,
+ .table = ctx->table,
+ .chain = binding->chain,
+ };
+
+ err = nft_validate_data_load(&bind_ctx, dreg,
+ &elem.data, d2.type);
+ if (err < 0)
+ goto err3;
+ }
+ }
+
+ err = set->ops->insert(set, &elem);
+ if (err < 0)
+ goto err3;
+
+ return 0;
+
+err3:
+ if (nla[NFTA_SET_ELEM_DATA] != NULL)
+ nft_data_uninit(&elem.data, d2.type);
+err2:
+ nft_data_uninit(&elem.key, d1.type);
+err1:
+ return err;
+}
+
+static int nf_tables_newsetelem(struct sock *nlsk, struct sk_buff *skb,
+ const struct nlmsghdr *nlh,
+ const struct nlattr * const nla[])
+{
+ const struct nlattr *attr;
+ struct nft_set *set;
+ struct nft_ctx ctx;
+ int rem, err;
+
+ err = nft_ctx_init_from_elemattr(&ctx, skb, nlh, nla);
+ if (err < 0)
+ return err;
+
+ set = nf_tables_set_lookup(ctx.table, nla[NFTA_SET_ELEM_LIST_SET]);
+ if (IS_ERR(set))
+ return PTR_ERR(set);
+ if (!list_empty(&set->bindings) && set->flags & NFT_SET_CONSTANT)
+ return -EBUSY;
+
+ nla_for_each_nested(attr, nla[NFTA_SET_ELEM_LIST_ELEMENTS], rem) {
+ err = nft_add_set_elem(&ctx, set, attr);
+ if (err < 0)
+ return err;
+ }
+ return 0;
+}
+
+static int nft_del_setelem(const struct nft_ctx *ctx, struct nft_set *set,
+ const struct nlattr *attr)
+{
+ struct nlattr *nla[NFTA_SET_ELEM_MAX + 1];
+ struct nft_data_desc desc;
+ struct nft_set_elem elem;
+ int err;
+
+ err = nla_parse_nested(nla, NFTA_SET_ELEM_MAX, attr,
+ nft_set_elem_policy);
+ if (err < 0)
+ goto err1;
+
+ err = -EINVAL;
+ if (nla[NFTA_SET_ELEM_KEY] == NULL)
+ goto err1;
+
+ err = nft_data_init(ctx, &elem.key, &desc, nla[NFTA_SET_ELEM_KEY]);
+ if (err < 0)
+ goto err1;
+
+ err = -EINVAL;
+ if (desc.type != NFT_DATA_VALUE || desc.len != set->klen)
+ goto err2;
+
+ err = set->ops->get(set, &elem);
+ if (err < 0)
+ goto err2;
+
+ set->ops->remove(set, &elem);
+
+ nft_data_uninit(&elem.key, NFT_DATA_VALUE);
+ if (set->flags & NFT_SET_MAP)
+ nft_data_uninit(&elem.data, set->dtype);
+
+err2:
+ nft_data_uninit(&elem.key, desc.type);
+err1:
+ return err;
+}
+
+static int nf_tables_delsetelem(struct sock *nlsk, struct sk_buff *skb,
+ const struct nlmsghdr *nlh,
+ const struct nlattr * const nla[])
+{
+ const struct nlattr *attr;
+ struct nft_set *set;
+ struct nft_ctx ctx;
+ int rem, err;
+
+ err = nft_ctx_init_from_elemattr(&ctx, skb, nlh, nla);
+ if (err < 0)
+ return err;
+
+ set = nf_tables_set_lookup(ctx.table, nla[NFTA_SET_ELEM_LIST_SET]);
+ if (IS_ERR(set))
+ return PTR_ERR(set);
+ if (!list_empty(&set->bindings) && set->flags & NFT_SET_CONSTANT)
+ return -EBUSY;
+
+ nla_for_each_nested(attr, nla[NFTA_SET_ELEM_LIST_ELEMENTS], rem) {
+ err = nft_del_setelem(&ctx, set, attr);
+ if (err < 0)
+ return err;
+ }
+ return 0;
+}
+
static const struct nfnl_callback nf_tables_cb[NFT_MSG_MAX] = {
[NFT_MSG_NEWTABLE] = {
.call = nf_tables_newtable,
.attr_count = NFTA_RULE_MAX,
.policy = nft_rule_policy,
},
+ [NFT_MSG_NEWSET] = {
+ .call = nf_tables_newset,
+ .attr_count = NFTA_SET_MAX,
+ .policy = nft_set_policy,
+ },
+ [NFT_MSG_GETSET] = {
+ .call = nf_tables_getset,
+ .attr_count = NFTA_SET_MAX,
+ .policy = nft_set_policy,
+ },
+ [NFT_MSG_DELSET] = {
+ .call = nf_tables_delset,
+ .attr_count = NFTA_SET_MAX,
+ .policy = nft_set_policy,
+ },
+ [NFT_MSG_NEWSETELEM] = {
+ .call = nf_tables_newsetelem,
+ .attr_count = NFTA_SET_ELEM_LIST_MAX,
+ .policy = nft_set_elem_list_policy,
+ },
+ [NFT_MSG_GETSETELEM] = {
+ .call = nf_tables_getsetelem,
+ .attr_count = NFTA_SET_ELEM_LIST_MAX,
+ .policy = nft_set_elem_list_policy,
+ },
+ [NFT_MSG_DELSETELEM] = {
+ .call = nf_tables_delsetelem,
+ .attr_count = NFTA_SET_ELEM_LIST_MAX,
+ .policy = nft_set_elem_list_policy,
+ },
};
static const struct nfnetlink_subsystem nf_tables_subsys = {
.cb = nf_tables_cb,
};
+/*
+ * Loop detection - walk through the ruleset beginning at the destination chain
+ * of a new jump until either the source chain is reached (loop) or all
+ * reachable chains have been traversed.
+ *
+ * The loop check is performed whenever a new jump verdict is added to an
+ * expression or verdict map or a verdict map is bound to a new chain.
+ */
+
+static int nf_tables_check_loops(const struct nft_ctx *ctx,
+ const struct nft_chain *chain);
+
+static int nf_tables_loop_check_setelem(const struct nft_ctx *ctx,
+ const struct nft_set *set,
+ const struct nft_set_iter *iter,
+ const struct nft_set_elem *elem)
+{
+ switch (elem->data.verdict) {
+ case NFT_JUMP:
+ case NFT_GOTO:
+ return nf_tables_check_loops(ctx, elem->data.chain);
+ default:
+ return 0;
+ }
+}
+
+static int nf_tables_check_loops(const struct nft_ctx *ctx,
+ const struct nft_chain *chain)
+{
+ const struct nft_rule *rule;
+ const struct nft_expr *expr, *last;
+ const struct nft_data *data;
+ const struct nft_set *set;
+ struct nft_set_binding *binding;
+ struct nft_set_iter iter;
+ int err;
+
+ if (ctx->chain == chain)
+ return -ELOOP;
+
+ list_for_each_entry(rule, &chain->rules, list) {
+ nft_rule_for_each_expr(expr, last, rule) {
+ if (!expr->ops->get_verdict)
+ continue;
+
+ data = expr->ops->get_verdict(expr);
+ if (data == NULL)
+ break;
+
+ switch (data->verdict) {
+ case NFT_JUMP:
+ case NFT_GOTO:
+ err = nf_tables_check_loops(ctx, data->chain);
+ if (err < 0)
+ return err;
+ default:
+ break;
+ }
+ }
+ }
+
+ list_for_each_entry(set, &ctx->table->sets, list) {
+ if (!(set->flags & NFT_SET_MAP) ||
+ set->dtype != NFT_DATA_VERDICT)
+ continue;
+
+ list_for_each_entry(binding, &set->bindings, list) {
+ if (binding->chain != chain)
+ continue;
+
+ iter.skip = 0;
+ iter.count = 0;
+ iter.err = 0;
+ iter.fn = nf_tables_loop_check_setelem;
+
+ set->ops->walk(ctx, set, &iter);
+ if (iter.err < 0)
+ return iter.err;
+ }
+ }
+
+ return 0;
+}
+
/**
* nft_validate_input_register - validate an expressions' input register
*
const struct nft_data *data,
enum nft_data_types type)
{
+ int err;
+
switch (reg) {
case NFT_REG_VERDICT:
if (data == NULL || type != NFT_DATA_VERDICT)
return -EINVAL;
- // FIXME: do loop detection
+
+ if (data->verdict == NFT_GOTO || data->verdict == NFT_JUMP) {
+ err = nf_tables_check_loops(ctx, data->chain);
+ if (err < 0)
+ return err;
+
+ if (ctx->chain->level + 1 > data->chain->level) {
+ if (ctx->chain->level + 1 == NFT_JUMP_STACK_SIZE)
+ return -EMLINK;
+ data->chain->level = ctx->chain->level + 1;
+ }
+ }
+
return 0;
default:
if (data != NULL && type != NFT_DATA_VALUE)
if (chain->flags & NFT_BASE_CHAIN)
return -EOPNOTSUPP;
- if (ctx->chain->level + 1 > chain->level) {
- if (ctx->chain->level + 1 == 16)
- return -EMLINK;
- chain->level = ctx->chain->level + 1;
- }
chain->use++;
data->chain = chain;
desc->len = sizeof(data);
#include <net/netfilter/nf_tables_core.h>
#include <net/netfilter/nf_tables.h>
-#define NFT_JUMP_STACK_SIZE 16
-
unsigned int nft_do_chain(const struct nf_hook_ops *ops,
struct sk_buff *skb,
const struct net_device *in,
/*
- * Copyright (c) 2008 Patrick McHardy <kaber@trash.net>
+ * Copyright (c) 2008-2009 Patrick McHardy <kaber@trash.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
struct nft_hash {
struct hlist_head *hash;
unsigned int hsize;
- enum nft_registers sreg:8;
- enum nft_registers dreg:8;
- u8 klen;
- u8 dlen;
- u16 flags;
};
struct nft_hash_elem {
{
unsigned int h;
- // FIXME: can we reasonably guarantee the upper bits are fixed?
- h = jhash2(data->data, len >> 2, nft_hash_rnd);
+ h = jhash(data->data, len, nft_hash_rnd);
return ((u64)h * hsize) >> 32;
}
-static void nft_hash_eval(const struct nft_expr *expr,
- struct nft_data data[NFT_REG_MAX + 1],
- const struct nft_pktinfo *pkt)
+static bool nft_hash_lookup(const struct nft_set *set,
+ const struct nft_data *key,
+ struct nft_data *data)
{
- const struct nft_hash *priv = nft_expr_priv(expr);
- const struct nft_hash_elem *elem;
- const struct nft_data *key = &data[priv->sreg];
+ const struct nft_hash *priv = nft_set_priv(set);
+ const struct nft_hash_elem *he;
unsigned int h;
- h = nft_hash_data(key, priv->hsize, priv->klen);
- hlist_for_each_entry(elem, &priv->hash[h], hnode) {
- if (nft_data_cmp(&elem->key, key, priv->klen))
+ h = nft_hash_data(key, priv->hsize, set->klen);
+ hlist_for_each_entry(he, &priv->hash[h], hnode) {
+ if (nft_data_cmp(&he->key, key, set->klen))
continue;
- if (priv->flags & NFT_HASH_MAP)
- nft_data_copy(&data[priv->dreg], elem->data);
- return;
+ if (set->flags & NFT_SET_MAP)
+ nft_data_copy(data, he->data);
+ return true;
}
- data[NFT_REG_VERDICT].verdict = NFT_BREAK;
+ return false;
}
-static void nft_hash_elem_destroy(const struct nft_expr *expr,
- struct nft_hash_elem *elem)
+static void nft_hash_elem_destroy(const struct nft_set *set,
+ struct nft_hash_elem *he)
{
- const struct nft_hash *priv = nft_expr_priv(expr);
-
- nft_data_uninit(&elem->key, NFT_DATA_VALUE);
- if (priv->flags & NFT_HASH_MAP)
- nft_data_uninit(elem->data, nft_dreg_to_type(priv->dreg));
- kfree(elem);
+ nft_data_uninit(&he->key, NFT_DATA_VALUE);
+ if (set->flags & NFT_SET_MAP)
+ nft_data_uninit(he->data, set->dtype);
+ kfree(he);
}
-static const struct nla_policy nft_he_policy[NFTA_HE_MAX + 1] = {
- [NFTA_HE_KEY] = { .type = NLA_NESTED },
- [NFTA_HE_DATA] = { .type = NLA_NESTED },
-};
-
-static int nft_hash_elem_init(const struct nft_ctx *ctx,
- const struct nft_expr *expr,
- const struct nlattr *nla,
- struct nft_hash_elem **new)
+static int nft_hash_insert(const struct nft_set *set,
+ const struct nft_set_elem *elem)
{
- struct nft_hash *priv = nft_expr_priv(expr);
- struct nlattr *tb[NFTA_HE_MAX + 1];
- struct nft_hash_elem *elem;
- struct nft_data_desc d1, d2;
- unsigned int size;
- int err;
+ struct nft_hash *priv = nft_set_priv(set);
+ struct nft_hash_elem *he;
+ unsigned int size, h;
- err = nla_parse_nested(tb, NFTA_HE_MAX, nla, nft_he_policy);
- if (err < 0)
- return err;
-
- if (tb[NFTA_HE_KEY] == NULL)
+ if (elem->flags != 0)
return -EINVAL;
- size = sizeof(*elem);
-
- if (priv->flags & NFT_HASH_MAP) {
- if (tb[NFTA_HE_DATA] == NULL)
- return -EINVAL;
- size += sizeof(elem->data[0]);
- } else {
- if (tb[NFTA_HE_DATA] != NULL)
- return -EINVAL;
- }
- elem = kzalloc(size, GFP_KERNEL);
- if (elem == NULL)
+ size = sizeof(*he);
+ if (set->flags & NFT_SET_MAP)
+ size += sizeof(he->data[0]);
+
+ he = kzalloc(size, GFP_KERNEL);
+ if (he == NULL)
return -ENOMEM;
- err = nft_data_init(ctx, &elem->key, &d1, tb[NFTA_HE_KEY]);
- if (err < 0)
- goto err1;
- err = -EINVAL;
- if (d1.type != NFT_DATA_VALUE || d1.len != priv->klen)
- goto err2;
-
- if (tb[NFTA_HE_DATA] != NULL) {
- err = nft_data_init(ctx, elem->data, &d2, tb[NFTA_HE_DATA]);
- if (err < 0)
- goto err2;
- err = nft_validate_data_load(ctx, priv->dreg, elem->data, d2.type);
- if (err < 0)
- goto err3;
- }
+ nft_data_copy(&he->key, &elem->key);
+ if (set->flags & NFT_SET_MAP)
+ nft_data_copy(he->data, &elem->data);
- *new = elem;
+ h = nft_hash_data(&he->key, priv->hsize, set->klen);
+ hlist_add_head_rcu(&he->hnode, &priv->hash[h]);
return 0;
-
-err3:
- nft_data_uninit(elem->data, d2.type);
-err2:
- nft_data_uninit(&elem->key, d1.type);
-err1:
- kfree(elem);
- return err;
}
-static int nft_hash_elem_dump(struct sk_buff *skb, const struct nft_expr *expr,
- const struct nft_hash_elem *elem)
-
+static void nft_hash_remove(const struct nft_set *set,
+ const struct nft_set_elem *elem)
{
- const struct nft_hash *priv = nft_expr_priv(expr);
- struct nlattr *nest;
+ struct nft_hash_elem *he = elem->cookie;
- nest = nla_nest_start(skb, NFTA_LIST_ELEM);
- if (nest == NULL)
- goto nla_put_failure;
-
- if (nft_data_dump(skb, NFTA_HE_KEY, &elem->key,
- NFT_DATA_VALUE, priv->klen) < 0)
- goto nla_put_failure;
+ hlist_del_rcu(&he->hnode);
+ kfree(he);
+}
- if (priv->flags & NFT_HASH_MAP) {
- if (nft_data_dump(skb, NFTA_HE_DATA, elem->data,
- NFT_DATA_VALUE, priv->dlen) < 0)
- goto nla_put_failure;
- }
+static int nft_hash_get(const struct nft_set *set, struct nft_set_elem *elem)
+{
+ const struct nft_hash *priv = nft_set_priv(set);
+ struct nft_hash_elem *he;
+ unsigned int h;
- nla_nest_end(skb, nest);
- return 0;
+ h = nft_hash_data(&elem->key, priv->hsize, set->klen);
+ hlist_for_each_entry(he, &priv->hash[h], hnode) {
+ if (nft_data_cmp(&he->key, &elem->key, set->klen))
+ continue;
-nla_put_failure:
- return -1;
+ elem->cookie = he;
+ elem->flags = 0;
+ if (set->flags & NFT_SET_MAP)
+ nft_data_copy(&elem->data, he->data);
+ return 0;
+ }
+ return -ENOENT;
}
-static void nft_hash_destroy(const struct nft_ctx *ctx,
- const struct nft_expr *expr)
+static void nft_hash_walk(const struct nft_ctx *ctx, const struct nft_set *set,
+ struct nft_set_iter *iter)
{
- const struct nft_hash *priv = nft_expr_priv(expr);
- const struct hlist_node *next;
- struct nft_hash_elem *elem;
+ const struct nft_hash *priv = nft_set_priv(set);
+ const struct nft_hash_elem *he;
+ struct nft_set_elem elem;
unsigned int i;
for (i = 0; i < priv->hsize; i++) {
- hlist_for_each_entry_safe(elem, next, &priv->hash[i], hnode) {
- hlist_del(&elem->hnode);
- nft_hash_elem_destroy(expr, elem);
+ hlist_for_each_entry(he, &priv->hash[i], hnode) {
+ if (iter->count < iter->skip)
+ goto cont;
+
+ memcpy(&elem.key, &he->key, sizeof(elem.key));
+ if (set->flags & NFT_SET_MAP)
+ memcpy(&elem.data, he->data, sizeof(elem.data));
+ elem.flags = 0;
+
+ iter->err = iter->fn(ctx, set, iter, &elem);
+ if (iter->err < 0)
+ return;
+cont:
+ iter->count++;
}
}
- kfree(priv->hash);
}
-static const struct nla_policy nft_hash_policy[NFTA_HASH_MAX + 1] = {
- [NFTA_HASH_FLAGS] = { .type = NLA_U32 },
- [NFTA_HASH_SREG] = { .type = NLA_U32 },
- [NFTA_HASH_DREG] = { .type = NLA_U32 },
- [NFTA_HASH_KLEN] = { .type = NLA_U32 },
- [NFTA_HASH_ELEMENTS] = { .type = NLA_NESTED },
-};
+static unsigned int nft_hash_privsize(const struct nlattr * const nla[])
+{
+ return sizeof(struct nft_hash);
+}
-static int nft_hash_init(const struct nft_ctx *ctx, const struct nft_expr *expr,
+static int nft_hash_init(const struct nft_set *set,
const struct nlattr * const tb[])
{
- struct nft_hash *priv = nft_expr_priv(expr);
- struct nft_hash_elem *elem, *uninitialized_var(new);
- const struct nlattr *nla;
+ struct nft_hash *priv = nft_set_priv(set);
unsigned int cnt, i;
- unsigned int h;
- int err, rem;
if (unlikely(!nft_hash_rnd_initted)) {
get_random_bytes(&nft_hash_rnd, 4);
nft_hash_rnd_initted = true;
}
- if (tb[NFTA_HASH_SREG] == NULL ||
- tb[NFTA_HASH_KLEN] == NULL ||
- tb[NFTA_HASH_ELEMENTS] == NULL)
- return -EINVAL;
-
- if (tb[NFTA_HASH_FLAGS] != NULL) {
- priv->flags = ntohl(nla_get_be32(tb[NFTA_HASH_FLAGS]));
- if (priv->flags & ~NFT_HASH_MAP)
- return -EINVAL;
- }
-
- priv->sreg = ntohl(nla_get_be32(tb[NFTA_HASH_SREG]));
- err = nft_validate_input_register(priv->sreg);
- if (err < 0)
- return err;
-
- if (tb[NFTA_HASH_DREG] != NULL) {
- if (!(priv->flags & NFT_HASH_MAP))
- return -EINVAL;
- priv->dreg = ntohl(nla_get_be32(tb[NFTA_HASH_DREG]));
- err = nft_validate_output_register(priv->dreg);
- if (err < 0)
- return err;
- }
-
- priv->klen = ntohl(nla_get_be32(tb[NFTA_HASH_KLEN]));
- if (priv->klen == 0)
- return -EINVAL;
-
- cnt = 0;
- nla_for_each_nested(nla, tb[NFTA_HASH_ELEMENTS], rem) {
- if (nla_type(nla) != NFTA_LIST_ELEM)
- return -EINVAL;
- cnt++;
- }
-
/* Aim for a load factor of 0.75 */
+ // FIXME: temporarily broken until we have set descriptions
+ cnt = 100;
cnt = cnt * 4 / 3;
priv->hash = kcalloc(cnt, sizeof(struct hlist_head), GFP_KERNEL);
for (i = 0; i < cnt; i++)
INIT_HLIST_HEAD(&priv->hash[i]);
- err = -ENOMEM;
- nla_for_each_nested(nla, tb[NFTA_HASH_ELEMENTS], rem) {
- err = nft_hash_elem_init(ctx, expr, nla, &new);
- if (err < 0)
- goto err1;
-
- h = nft_hash_data(&new->key, priv->hsize, priv->klen);
- hlist_for_each_entry(elem, &priv->hash[h], hnode) {
- if (nft_data_cmp(&elem->key, &new->key, priv->klen))
- continue;
- nft_hash_elem_destroy(expr, new);
- err = -EEXIST;
- goto err1;
- }
- hlist_add_head(&new->hnode, &priv->hash[h]);
- }
return 0;
-
-err1:
- nft_hash_destroy(ctx, expr);
- return err;
}
-static int nft_hash_dump(struct sk_buff *skb, const struct nft_expr *expr)
+static void nft_hash_destroy(const struct nft_set *set)
{
- const struct nft_hash *priv = nft_expr_priv(expr);
- const struct nft_hash_elem *elem;
- struct nlattr *list;
+ const struct nft_hash *priv = nft_set_priv(set);
+ const struct hlist_node *next;
+ struct nft_hash_elem *elem;
unsigned int i;
- if (priv->flags)
- if (nla_put_be32(skb, NFTA_HASH_FLAGS, htonl(priv->flags)))
- goto nla_put_failure;
- if (nla_put_be32(skb, NFTA_HASH_SREG, htonl(priv->sreg)))
- goto nla_put_failure;
- if (priv->flags & NFT_HASH_MAP)
- if (nla_put_be32(skb, NFTA_HASH_DREG, htonl(priv->dreg)))
- goto nla_put_failure;
- if (nla_put_be32(skb, NFTA_HASH_KLEN, htonl(priv->klen)))
- goto nla_put_failure;
-
- list = nla_nest_start(skb, NFTA_HASH_ELEMENTS);
- if (list == NULL)
- goto nla_put_failure;
-
for (i = 0; i < priv->hsize; i++) {
- hlist_for_each_entry(elem, &priv->hash[i], hnode) {
- if (nft_hash_elem_dump(skb, expr, elem) < 0)
- goto nla_put_failure;
+ hlist_for_each_entry_safe(elem, next, &priv->hash[i], hnode) {
+ hlist_del(&elem->hnode);
+ nft_hash_elem_destroy(set, elem);
}
}
-
- nla_nest_end(skb, list);
- return 0;
-
-nla_put_failure:
- return -1;
+ kfree(priv->hash);
}
-static struct nft_expr_ops nft_hash_ops __read_mostly = {
- .name = "hash",
- .size = NFT_EXPR_SIZE(sizeof(struct nft_hash)),
- .owner = THIS_MODULE,
- .eval = nft_hash_eval,
+static struct nft_set_ops nft_hash_ops __read_mostly = {
+ .privsize = nft_hash_privsize,
.init = nft_hash_init,
.destroy = nft_hash_destroy,
- .dump = nft_hash_dump,
- .policy = nft_hash_policy,
- .maxattr = NFTA_HASH_MAX,
+ .get = nft_hash_get,
+ .insert = nft_hash_insert,
+ .remove = nft_hash_remove,
+ .lookup = nft_hash_lookup,
+ .walk = nft_hash_walk,
+ .features = NFT_SET_MAP,
+ .owner = THIS_MODULE,
};
static int __init nft_hash_module_init(void)
{
- return nft_register_expr(&nft_hash_ops);
+ return nft_register_set(&nft_hash_ops);
}
static void __exit nft_hash_module_exit(void)
{
- nft_unregister_expr(&nft_hash_ops);
+ nft_unregister_set(&nft_hash_ops);
}
module_init(nft_hash_module_init);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Patrick McHardy <kaber@trash.net>");
-MODULE_ALIAS_NFT_EXPR("hash");
+MODULE_ALIAS_NFT_SET();
return -1;
}
+static const struct nft_data *nft_immediate_get_verdict(const struct nft_expr *expr)
+{
+ const struct nft_immediate_expr *priv = nft_expr_priv(expr);
+
+ if (priv->dreg == NFT_REG_VERDICT)
+ return &priv->data;
+ else
+ return NULL;
+}
+
static struct nft_expr_ops nft_imm_ops __read_mostly = {
.name = "immediate",
.size = NFT_EXPR_SIZE(sizeof(struct nft_immediate_expr)),
.init = nft_immediate_init,
.destroy = nft_immediate_destroy,
.dump = nft_immediate_dump,
+ .get_verdict = nft_immediate_get_verdict,
.policy = nft_immediate_policy,
.maxattr = NFTA_IMMEDIATE_MAX,
};
--- /dev/null
+/*
+ * Copyright (c) 2009 Patrick McHardy <kaber@trash.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Development of this code funded by Astaro AG (http://www.astaro.com/)
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/rbtree.h>
+#include <linux/netlink.h>
+#include <linux/netfilter.h>
+#include <linux/netfilter/nf_tables.h>
+#include <net/netfilter/nf_tables.h>
+
+struct nft_lookup {
+ struct nft_set *set;
+ enum nft_registers sreg:8;
+ enum nft_registers dreg:8;
+ struct nft_set_binding binding;
+};
+
+static void nft_lookup_eval(const struct nft_expr *expr,
+ struct nft_data data[NFT_REG_MAX + 1],
+ const struct nft_pktinfo *pkt)
+{
+ const struct nft_lookup *priv = nft_expr_priv(expr);
+ const struct nft_set *set = priv->set;
+
+ if (set->ops->lookup(set, &data[priv->sreg], &data[priv->dreg]))
+ return;
+ data[NFT_REG_VERDICT].verdict = NFT_BREAK;
+}
+
+static const struct nla_policy nft_lookup_policy[NFTA_LOOKUP_MAX + 1] = {
+ [NFTA_LOOKUP_SET] = { .type = NLA_STRING },
+ [NFTA_LOOKUP_SREG] = { .type = NLA_U32 },
+ [NFTA_LOOKUP_DREG] = { .type = NLA_U32 },
+};
+
+static int nft_lookup_init(const struct nft_ctx *ctx,
+ const struct nft_expr *expr,
+ const struct nlattr * const tb[])
+{
+ struct nft_lookup *priv = nft_expr_priv(expr);
+ struct nft_set *set;
+ int err;
+
+ if (tb[NFTA_LOOKUP_SET] == NULL ||
+ tb[NFTA_LOOKUP_SREG] == NULL)
+ return -EINVAL;
+
+ set = nf_tables_set_lookup(ctx->table, tb[NFTA_LOOKUP_SET]);
+ if (IS_ERR(set))
+ return PTR_ERR(set);
+
+ priv->sreg = ntohl(nla_get_be32(tb[NFTA_LOOKUP_SREG]));
+ err = nft_validate_input_register(priv->sreg);
+ if (err < 0)
+ return err;
+
+ if (tb[NFTA_LOOKUP_DREG] != NULL) {
+ if (!(set->flags & NFT_SET_MAP))
+ return -EINVAL;
+
+ priv->dreg = ntohl(nla_get_be32(tb[NFTA_LOOKUP_DREG]));
+ err = nft_validate_output_register(priv->dreg);
+ if (err < 0)
+ return err;
+
+ if (priv->dreg == NFT_REG_VERDICT) {
+ if (set->dtype != NFT_DATA_VERDICT)
+ return -EINVAL;
+ } else if (set->dtype == NFT_DATA_VERDICT)
+ return -EINVAL;
+ } else if (set->flags & NFT_SET_MAP)
+ return -EINVAL;
+
+ err = nf_tables_bind_set(ctx, set, &priv->binding);
+ if (err < 0)
+ return err;
+
+ priv->set = set;
+ return 0;
+}
+
+static void nft_lookup_destroy(const struct nft_expr *expr)
+{
+ struct nft_lookup *priv = nft_expr_priv(expr);
+
+ nf_tables_unbind_set(NULL, priv->set, &priv->binding);
+}
+
+static int nft_lookup_dump(struct sk_buff *skb, const struct nft_expr *expr)
+{
+ const struct nft_lookup *priv = nft_expr_priv(expr);
+
+ if (nla_put_string(skb, NFTA_LOOKUP_SET, priv->set->name))
+ goto nla_put_failure;
+ if (nla_put_be32(skb, NFTA_LOOKUP_SREG, htonl(priv->sreg)))
+ goto nla_put_failure;
+ if (priv->set->flags & NFT_SET_MAP)
+ if (nla_put_be32(skb, NFTA_LOOKUP_DREG, htonl(priv->dreg)))
+ goto nla_put_failure;
+ return 0;
+
+nla_put_failure:
+ return -1;
+}
+
+static struct nft_expr_ops nft_lookup_ops __read_mostly = {
+ .name = "lookup",
+ .size = NFT_EXPR_SIZE(sizeof(struct nft_lookup)),
+ .owner = THIS_MODULE,
+ .eval = nft_lookup_eval,
+ .init = nft_lookup_init,
+ .destroy = nft_lookup_destroy,
+ .dump = nft_lookup_dump,
+ .policy = nft_lookup_policy,
+ .maxattr = NFTA_LOOKUP_MAX,
+};
+
+int __init nft_lookup_module_init(void)
+{
+ return nft_register_expr(&nft_lookup_ops);
+}
+
+void nft_lookup_module_exit(void)
+{
+ nft_unregister_expr(&nft_lookup_ops);
+}
--- /dev/null
+/*
+ * Copyright (c) 2008-2009 Patrick McHardy <kaber@trash.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Development of this code funded by Astaro AG (http://www.astaro.com/)
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/list.h>
+#include <linux/rbtree.h>
+#include <linux/netlink.h>
+#include <linux/netfilter.h>
+#include <linux/netfilter/nf_tables.h>
+#include <net/netfilter/nf_tables.h>
+
+struct nft_rbtree {
+ struct rb_root root;
+};
+
+struct nft_rbtree_elem {
+ struct rb_node node;
+ u16 flags;
+ struct nft_data key;
+ struct nft_data data[];
+};
+
+static bool nft_rbtree_lookup(const struct nft_set *set,
+ const struct nft_data *key,
+ struct nft_data *data)
+{
+ const struct nft_rbtree *priv = nft_set_priv(set);
+ const struct nft_rbtree_elem *rbe, *interval = NULL;
+ const struct rb_node *parent = priv->root.rb_node;
+ int d;
+
+ while (parent != NULL) {
+ rbe = rb_entry(parent, struct nft_rbtree_elem, node);
+
+ d = nft_data_cmp(&rbe->key, key, set->klen);
+ if (d < 0) {
+ parent = parent->rb_left;
+ interval = rbe;
+ } else if (d > 0)
+ parent = parent->rb_right;
+ else {
+found:
+ if (rbe->flags & NFT_SET_ELEM_INTERVAL_END)
+ goto out;
+ if (set->flags & NFT_SET_MAP)
+ nft_data_copy(data, rbe->data);
+ return true;
+ }
+ }
+
+ if (set->flags & NFT_SET_INTERVAL && interval != NULL) {
+ rbe = interval;
+ goto found;
+ }
+out:
+ return false;
+}
+
+static void nft_rbtree_elem_destroy(const struct nft_set *set,
+ struct nft_rbtree_elem *rbe)
+{
+ nft_data_uninit(&rbe->key, NFT_DATA_VALUE);
+ if (set->flags & NFT_SET_MAP)
+ nft_data_uninit(rbe->data, set->dtype);
+ kfree(rbe);
+}
+
+static int __nft_rbtree_insert(const struct nft_set *set,
+ struct nft_rbtree_elem *new)
+{
+ struct nft_rbtree *priv = nft_set_priv(set);
+ struct nft_rbtree_elem *rbe;
+ struct rb_node *parent, **p;
+ int d;
+
+ parent = NULL;
+ p = &priv->root.rb_node;
+ while (*p != NULL) {
+ parent = *p;
+ rbe = rb_entry(parent, struct nft_rbtree_elem, node);
+ d = nft_data_cmp(&rbe->key, &new->key, set->klen);
+ if (d < 0)
+ p = &parent->rb_left;
+ else if (d > 0)
+ p = &parent->rb_right;
+ else
+ return -EEXIST;
+ }
+ rb_link_node(&new->node, parent, p);
+ rb_insert_color(&new->node, &priv->root);
+ return 0;
+}
+
+static int nft_rbtree_insert(const struct nft_set *set,
+ const struct nft_set_elem *elem)
+{
+ struct nft_rbtree_elem *rbe;
+ unsigned int size;
+ int err;
+
+ size = sizeof(*rbe);
+ if (set->flags & NFT_SET_MAP)
+ size += sizeof(rbe->data[0]);
+
+ rbe = kzalloc(size, GFP_KERNEL);
+ if (rbe == NULL)
+ return -ENOMEM;
+
+ rbe->flags = elem->flags;
+ nft_data_copy(&rbe->key, &elem->key);
+ if (set->flags & NFT_SET_MAP)
+ nft_data_copy(rbe->data, &elem->data);
+
+ err = __nft_rbtree_insert(set, rbe);
+ if (err < 0)
+ kfree(rbe);
+ return err;
+}
+
+static void nft_rbtree_remove(const struct nft_set *set,
+ const struct nft_set_elem *elem)
+{
+ struct nft_rbtree *priv = nft_set_priv(set);
+ struct nft_rbtree_elem *rbe = elem->cookie;
+
+ rb_erase(&rbe->node, &priv->root);
+ kfree(rbe);
+}
+
+static int nft_rbtree_get(const struct nft_set *set, struct nft_set_elem *elem)
+{
+ const struct nft_rbtree *priv = nft_set_priv(set);
+ const struct rb_node *parent = priv->root.rb_node;
+ struct nft_rbtree_elem *rbe;
+ int d;
+
+ while (parent != NULL) {
+ rbe = rb_entry(parent, struct nft_rbtree_elem, node);
+
+ d = nft_data_cmp(&rbe->key, &elem->key, set->klen);
+ if (d < 0)
+ parent = parent->rb_left;
+ else if (d > 0)
+ parent = parent->rb_right;
+ else {
+ elem->cookie = rbe;
+ if (set->flags & NFT_SET_MAP)
+ nft_data_copy(&elem->data, rbe->data);
+ elem->flags = rbe->flags;
+ return 0;
+ }
+ }
+ return -ENOENT;
+}
+
+static void nft_rbtree_walk(const struct nft_ctx *ctx,
+ const struct nft_set *set,
+ struct nft_set_iter *iter)
+{
+ const struct nft_rbtree *priv = nft_set_priv(set);
+ const struct nft_rbtree_elem *rbe;
+ struct nft_set_elem elem;
+ struct rb_node *node;
+
+ for (node = rb_first(&priv->root); node != NULL; node = rb_next(node)) {
+ if (iter->count < iter->skip)
+ goto cont;
+
+ rbe = rb_entry(node, struct nft_rbtree_elem, node);
+ nft_data_copy(&elem.key, &rbe->key);
+ if (set->flags & NFT_SET_MAP)
+ nft_data_copy(&elem.data, rbe->data);
+ elem.flags = rbe->flags;
+
+ iter->err = iter->fn(ctx, set, iter, &elem);
+ if (iter->err < 0)
+ return;
+cont:
+ iter->count++;
+ }
+}
+
+static unsigned int nft_rbtree_privsize(const struct nlattr * const nla[])
+{
+ return sizeof(struct nft_rbtree);
+}
+
+static int nft_rbtree_init(const struct nft_set *set,
+ const struct nlattr * const nla[])
+{
+ struct nft_rbtree *priv = nft_set_priv(set);
+
+ priv->root = RB_ROOT;
+ return 0;
+}
+
+static void nft_rbtree_destroy(const struct nft_set *set)
+{
+ struct nft_rbtree *priv = nft_set_priv(set);
+ struct nft_rbtree_elem *rbe;
+ struct rb_node *node;
+
+ while ((node = priv->root.rb_node) != NULL) {
+ rb_erase(node, &priv->root);
+ rbe = rb_entry(node, struct nft_rbtree_elem, node);
+ nft_rbtree_elem_destroy(set, rbe);
+ }
+}
+
+static struct nft_set_ops nft_rbtree_ops __read_mostly = {
+ .privsize = nft_rbtree_privsize,
+ .init = nft_rbtree_init,
+ .destroy = nft_rbtree_destroy,
+ .insert = nft_rbtree_insert,
+ .remove = nft_rbtree_remove,
+ .get = nft_rbtree_get,
+ .lookup = nft_rbtree_lookup,
+ .walk = nft_rbtree_walk,
+ .features = NFT_SET_INTERVAL | NFT_SET_MAP,
+ .owner = THIS_MODULE,
+};
+
+static int __init nft_rbtree_module_init(void)
+{
+ return nft_register_set(&nft_rbtree_ops);
+}
+
+static void __exit nft_rbtree_module_exit(void)
+{
+ nft_unregister_set(&nft_rbtree_ops);
+}
+
+module_init(nft_rbtree_module_init);
+module_exit(nft_rbtree_module_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Patrick McHardy <kaber@trash.net>");
+MODULE_ALIAS_NFT_SET();
+++ /dev/null
-/*
- * Copyright (c) 2008 Patrick McHardy <kaber@trash.net>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
- *
- * Development of this code funded by Astaro AG (http://www.astaro.com/)
- */
-
-#include <linux/kernel.h>
-#include <linux/module.h>
-#include <linux/init.h>
-#include <linux/list.h>
-#include <linux/rbtree.h>
-#include <linux/netlink.h>
-#include <linux/netfilter.h>
-#include <linux/netfilter/nf_tables.h>
-#include <net/netfilter/nf_tables.h>
-
-struct nft_set {
- struct rb_root root;
- enum nft_registers sreg:8;
- enum nft_registers dreg:8;
- u8 klen;
- u8 dlen;
- u16 flags;
-};
-
-struct nft_set_elem {
- struct rb_node node;
- enum nft_set_elem_flags flags;
- struct nft_data key;
- struct nft_data data[];
-};
-
-static void nft_set_eval(const struct nft_expr *expr,
- struct nft_data data[NFT_REG_MAX + 1],
- const struct nft_pktinfo *pkt)
-{
- const struct nft_set *priv = nft_expr_priv(expr);
- const struct rb_node *parent = priv->root.rb_node;
- const struct nft_set_elem *elem, *interval = NULL;
- const struct nft_data *key = &data[priv->sreg];
- int d;
-
- while (parent != NULL) {
- elem = rb_entry(parent, struct nft_set_elem, node);
-
- d = nft_data_cmp(&elem->key, key, priv->klen);
- if (d < 0) {
- parent = parent->rb_left;
- interval = elem;
- } else if (d > 0)
- parent = parent->rb_right;
- else {
-found:
- if (elem->flags & NFT_SE_INTERVAL_END)
- goto out;
- if (priv->flags & NFT_SET_MAP)
- nft_data_copy(&data[priv->dreg], elem->data);
- return;
- }
- }
-
- if (priv->flags & NFT_SET_INTERVAL && interval != NULL) {
- elem = interval;
- goto found;
- }
-out:
- data[NFT_REG_VERDICT].verdict = NFT_BREAK;
-}
-
-static void nft_set_elem_destroy(const struct nft_expr *expr,
- struct nft_set_elem *elem)
-{
- const struct nft_set *priv = nft_expr_priv(expr);
-
- nft_data_uninit(&elem->key, NFT_DATA_VALUE);
- if (priv->flags & NFT_SET_MAP)
- nft_data_uninit(elem->data, nft_dreg_to_type(priv->dreg));
- kfree(elem);
-}
-
-static const struct nla_policy nft_se_policy[NFTA_SE_MAX + 1] = {
- [NFTA_SE_KEY] = { .type = NLA_NESTED },
- [NFTA_SE_DATA] = { .type = NLA_NESTED },
- [NFTA_SE_FLAGS] = { .type = NLA_U32 },
-};
-
-static int nft_set_elem_init(const struct nft_ctx *ctx,
- const struct nft_expr *expr,
- const struct nlattr *nla,
- struct nft_set_elem **new)
-{
- struct nft_set *priv = nft_expr_priv(expr);
- struct nlattr *tb[NFTA_SE_MAX + 1];
- struct nft_set_elem *elem;
- struct nft_data_desc d1, d2;
- enum nft_set_elem_flags flags = 0;
- unsigned int size;
- int err;
-
- err = nla_parse_nested(tb, NFTA_SE_MAX, nla, nft_se_policy);
- if (err < 0)
- return err;
-
- if (tb[NFTA_SE_KEY] == NULL)
- return -EINVAL;
-
- if (tb[NFTA_SE_FLAGS] != NULL) {
- flags = ntohl(nla_get_be32(tb[NFTA_SE_FLAGS]));
- if (flags & ~NFT_SE_INTERVAL_END)
- return -EINVAL;
- }
-
- size = sizeof(*elem);
- if (priv->flags & NFT_SET_MAP) {
- if (tb[NFTA_SE_DATA] == NULL && !(flags & NFT_SE_INTERVAL_END))
- return -EINVAL;
- size += sizeof(elem->data[0]);
- } else {
- if (tb[NFTA_SE_DATA] != NULL)
- return -EINVAL;
- }
-
- elem = kzalloc(size, GFP_KERNEL);
- if (elem == NULL)
- return -ENOMEM;
- elem->flags = flags;
-
- err = nft_data_init(ctx, &elem->key, &d1, tb[NFTA_SE_KEY]);
- if (err < 0)
- goto err1;
- err = -EINVAL;
- if (d1.type != NFT_DATA_VALUE || d1.len != priv->klen)
- goto err2;
-
- if (tb[NFTA_SE_DATA] != NULL) {
- err = nft_data_init(ctx, elem->data, &d2, tb[NFTA_SE_DATA]);
- if (err < 0)
- goto err2;
- err = -EINVAL;
- if (priv->dreg != NFT_REG_VERDICT && d2.len != priv->dlen)
- goto err2;
- err = nft_validate_data_load(ctx, priv->dreg, elem->data, d2.type);
- if (err < 0)
- goto err3;
- }
-
- *new = elem;
- return 0;
-
-err3:
- nft_data_uninit(elem->data, d2.type);
-err2:
- nft_data_uninit(&elem->key, d1.type);
-err1:
- kfree(elem);
- return err;
-}
-
-static int nft_set_elem_dump(struct sk_buff *skb, const struct nft_expr *expr,
- const struct nft_set_elem *elem)
-
-{
- const struct nft_set *priv = nft_expr_priv(expr);
- struct nlattr *nest;
-
- nest = nla_nest_start(skb, NFTA_LIST_ELEM);
- if (nest == NULL)
- goto nla_put_failure;
-
- if (nft_data_dump(skb, NFTA_SE_KEY, &elem->key,
- NFT_DATA_VALUE, priv->klen) < 0)
- goto nla_put_failure;
-
- if (priv->flags & NFT_SET_MAP && !(elem->flags & NFT_SE_INTERVAL_END)) {
- if (nft_data_dump(skb, NFTA_SE_DATA, elem->data,
- nft_dreg_to_type(priv->dreg), priv->dlen) < 0)
- goto nla_put_failure;
- }
-
- if (elem->flags){
- if (nla_put_be32(skb, NFTA_SE_FLAGS, htonl(elem->flags)))
- goto nla_put_failure;
- }
-
- nla_nest_end(skb, nest);
- return 0;
-
-nla_put_failure:
- return -1;
-}
-
-static void nft_set_destroy(const struct nft_expr *expr)
-{
- struct nft_set *priv = nft_expr_priv(expr);
- struct nft_set_elem *elem;
- struct rb_node *node;
-
- while ((node = priv->root.rb_node) != NULL) {
- rb_erase(node, &priv->root);
- elem = rb_entry(node, struct nft_set_elem, node);
- nft_set_elem_destroy(expr, elem);
- }
-}
-
-static const struct nla_policy nft_set_policy[NFTA_SET_MAX + 1] = {
- [NFTA_SET_FLAGS] = { .type = NLA_U32 },
- [NFTA_SET_SREG] = { .type = NLA_U32 },
- [NFTA_SET_DREG] = { .type = NLA_U32 },
- [NFTA_SET_KLEN] = { .type = NLA_U32 },
- [NFTA_SET_DLEN] = { .type = NLA_U32 },
- [NFTA_SET_ELEMENTS] = { .type = NLA_NESTED },
-};
-
-static int nft_set_init(const struct nft_ctx *ctx, const struct nft_expr *expr,
- const struct nlattr * const tb[])
-{
- struct nft_set *priv = nft_expr_priv(expr);
- struct nft_set_elem *elem, *uninitialized_var(new);
- struct rb_node *parent, **p;
- const struct nlattr *nla;
- int err, rem, d;
-
- if (tb[NFTA_SET_SREG] == NULL ||
- tb[NFTA_SET_KLEN] == NULL ||
- tb[NFTA_SET_ELEMENTS] == NULL)
- return -EINVAL;
-
- priv->root = RB_ROOT;
-
- if (tb[NFTA_SET_FLAGS] != NULL) {
- priv->flags = ntohl(nla_get_be32(tb[NFTA_SET_FLAGS]));
- if (priv->flags & ~(NFT_SET_INTERVAL | NFT_SET_MAP))
- return -EINVAL;
- }
-
- priv->sreg = ntohl(nla_get_be32(tb[NFTA_SET_SREG]));
- err = nft_validate_input_register(priv->sreg);
- if (err < 0)
- return err;
-
- if (tb[NFTA_SET_DREG] != NULL) {
- if (!(priv->flags & NFT_SET_MAP))
- return -EINVAL;
- if (tb[NFTA_SET_DLEN] == NULL)
- return -EINVAL;
-
- priv->dreg = ntohl(nla_get_be32(tb[NFTA_SET_DREG]));
- err = nft_validate_output_register(priv->dreg);
- if (err < 0)
- return err;
-
- if (priv->dreg == NFT_REG_VERDICT)
- priv->dlen = FIELD_SIZEOF(struct nft_data, data);
- else {
- priv->dlen = ntohl(nla_get_be32(tb[NFTA_SET_DLEN]));
- if (priv->dlen == 0 ||
- priv->dlen > FIELD_SIZEOF(struct nft_data, data))
- return -EINVAL;
- }
- } else {
- if (priv->flags & NFT_SET_MAP)
- return -EINVAL;
- if (tb[NFTA_SET_DLEN] != NULL)
- return -EINVAL;
- }
-
- priv->klen = ntohl(nla_get_be32(tb[NFTA_SET_KLEN]));
- if (priv->klen == 0 ||
- priv->klen > FIELD_SIZEOF(struct nft_data, data))
- return -EINVAL;
-
- nla_for_each_nested(nla, tb[NFTA_SET_ELEMENTS], rem) {
- err = -EINVAL;
- if (nla_type(nla) != NFTA_LIST_ELEM)
- goto err1;
-
- err = nft_set_elem_init(ctx, expr, nla, &new);
- if (err < 0)
- goto err1;
-
- parent = NULL;
- p = &priv->root.rb_node;
- while (*p != NULL) {
- parent = *p;
- elem = rb_entry(parent, struct nft_set_elem, node);
- d = nft_data_cmp(&elem->key, &new->key, priv->klen);
- if (d < 0)
- p = &parent->rb_left;
- else if (d > 0)
- p = &parent->rb_right;
- else {
- err = -EEXIST;
- goto err2;
- }
- }
- rb_link_node(&new->node, parent, p);
- rb_insert_color(&new->node, &priv->root);
- }
-
- return 0;
-
-err2:
- nft_set_elem_destroy(expr, new);
-err1:
- nft_set_destroy(expr);
- return err;
-}
-
-static int nft_set_dump(struct sk_buff *skb, const struct nft_expr *expr)
-{
- struct nft_set *priv = nft_expr_priv(expr);
- const struct nft_set_elem *elem;
- struct rb_node *node;
- struct nlattr *list;
-
- if (priv->flags) {
- if (nla_put_be32(skb, NFTA_SET_FLAGS, htonl(priv->flags)))
- goto nla_put_failure;
- }
-
- if (nla_put_be32(skb, NFTA_SET_SREG, htonl(priv->sreg)))
- goto nla_put_failure;
- if (nla_put_be32(skb, NFTA_SET_KLEN, htonl(priv->klen)))
- goto nla_put_failure;
-
- if (priv->flags & NFT_SET_MAP) {
- if (nla_put_be32(skb, NFTA_SET_DREG, htonl(priv->dreg)))
- goto nla_put_failure;
- if (nla_put_be32(skb, NFTA_SET_DLEN, htonl(priv->dlen)))
- goto nla_put_failure;
- }
-
- list = nla_nest_start(skb, NFTA_SET_ELEMENTS);
- if (list == NULL)
- goto nla_put_failure;
-
- for (node = rb_first(&priv->root); node; node = rb_next(node)) {
- elem = rb_entry(node, struct nft_set_elem, node);
- if (nft_set_elem_dump(skb, expr, elem) < 0)
- goto nla_put_failure;
- }
-
- nla_nest_end(skb, list);
- return 0;
-
-nla_put_failure:
- return -1;
-}
-
-static struct nft_expr_ops nft_set_ops __read_mostly = {
- .name = "set",
- .size = NFT_EXPR_SIZE(sizeof(struct nft_set)),
- .owner = THIS_MODULE,
- .eval = nft_set_eval,
- .init = nft_set_init,
- .destroy = nft_set_destroy,
- .dump = nft_set_dump,
- .policy = nft_set_policy,
- .maxattr = NFTA_SET_MAX,
-};
-
-static int __init nft_set_module_init(void)
-{
- return nft_register_expr(&nft_set_ops);
-}
-
-static void __exit nft_set_module_exit(void)
-{
- nft_unregister_expr(&nft_set_ops);
-}
-
-module_init(nft_set_module_init);
-module_exit(nft_set_module_exit);
-
-MODULE_LICENSE("GPL");
-MODULE_AUTHOR("Patrick McHardy <kaber@trash.net>");
-MODULE_ALIAS_NFT_EXPR("set");