summaryrefslogtreecommitdiffstats
path: root/kernel/ip_set.c
diff options
context:
space:
mode:
authorJozsef Kadlecsik <kadlec@blackhole.kfki.hu>2010-04-22 17:09:18 +0200
committerJozsef Kadlecsik <kadlec@blackhole.kfki.hu>2010-04-22 17:09:18 +0200
commit456b1d993711eb4297012ad4a881c459c0511358 (patch)
tree518bb02b7cf25ed6f338e96969efe96b642f8bf2 /kernel/ip_set.c
parentac0e5da3166da201ea00fd7f3cd927b0a49d8fef (diff)
Eight stage to ipset-5
Commit changed files in kernel/...
Diffstat (limited to 'kernel/ip_set.c')
-rw-r--r--kernel/ip_set.c2061
1 files changed, 915 insertions, 1146 deletions
diff --git a/kernel/ip_set.c b/kernel/ip_set.c
index 0ce9d3f..3af8fce 100644
--- a/kernel/ip_set.c
+++ b/kernel/ip_set.c
@@ -1,6 +1,6 @@
/* Copyright (C) 2000-2002 Joakim Axelsson <gozem@linux.nu>
* Patrick Schaaf <bof@bof.de>
- * Copyright (C) 2003-2004 Jozsef Kadlecsik <kadlec@blackhole.kfki.hu>
+ * Copyright (C) 2003-2010 Jozsef Kadlecsik <kadlec@blackhole.kfki.hu>
*
* 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
@@ -9,55 +9,65 @@
/* Kernel module for IP set management */
-#include <linux/version.h>
-#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19)
-#include <linux/config.h>
-#endif
+#include <linux/init.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
-#include <linux/kmod.h>
+#include <linux/kernel.h>
#include <linux/ip.h>
#include <linux/skbuff.h>
-#include <linux/random.h>
-#include <linux/netfilter_ipv4/ip_set_jhash.h>
-#include <linux/errno.h>
-#include <linux/capability.h>
-#include <asm/uaccess.h>
-#include <asm/bitops.h>
-#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27)
-#include <asm/semaphore.h>
-#else
-#include <linux/semaphore.h>
-#endif
#include <linux/spinlock.h>
+#include <linux/netlink.h>
+#include <net/netlink.h>
-#define ASSERT_READ_LOCK(x)
-#define ASSERT_WRITE_LOCK(x)
#include <linux/netfilter.h>
-#include <linux/netfilter_ipv4/ip_set.h>
+#include <linux/netfilter/nfnetlink.h>
+#include <linux/netfilter/ip_set.h>
+#include <linux/netfilter/ip_set_jhash.h>
-static struct list_head set_type_list; /* all registered sets */
+static struct list_head ip_set_type_list; /* all registered sets */
static struct ip_set **ip_set_list; /* all individual sets */
-static DEFINE_RWLOCK(ip_set_lock); /* protects the lists and the hash */
-static struct semaphore ip_set_app_mutex; /* serializes user access */
-static ip_set_id_t ip_set_max = CONFIG_IP_NF_SET_MAX;
-static int protocol_version = IP_SET_PROTOCOL_VERSION;
+static DEFINE_MUTEX(ip_set_type_mutex); /* protects ip_set_type_lists */
+static ip_set_id_t ip_set_max = CONFIG_IP_SET_MAX;
-#define STREQ(a,b) (strncmp(a,b,IP_SET_MAXNAMELEN) == 0)
-#define DONT_ALIGN (protocol_version == IP_SET_PROTOCOL_UNALIGNED)
-#define ALIGNED(len) IPSET_VALIGN(len, DONT_ALIGN)
+#define STREQ(a,b) (strncmp(a,b,IPSET_MAXNAMELEN) == 0)
+
+static int max_sets;
+
+module_param(max_sets, int, 0600);
+MODULE_PARM_DESC(max_sets, "maximal number of sets");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Jozsef Kadlecsik <kadlec@blackhole.kfki.hu>");
+MODULE_DESCRIPTION("core IP set support");
+MODULE_ALIAS_NFNL_SUBSYS(NFNL_SUBSYS_IPSET);
/*
- * Sets are identified either by the index in ip_set_list or by id.
- * The id never changes. The index may change by swapping and used
- * by external references (set/SET netfilter modules, etc.)
+ * The set types are implemented in modules and registered set types
+ * can be found in ip_set_type_list. Adding/deleting types is
+ * serialized by ip_set_type_list_lock/ip_set_type_list_unlock.
+ */
+
+static inline void
+ip_set_type_list_lock(void)
+{
+ mutex_lock(&ip_set_type_mutex);
+}
+
+static inline void
+ip_set_type_list_unlock(void)
+{
+ mutex_unlock(&ip_set_type_mutex);
+}
+
+/*
+ * Creating/destroying/renaming/swapping affect the existence and
+ * integrity of a set. All of these can be executed from userspace only
+ * and serialized by nfnl_lock/nfnl_unlock indirectly from nfnetlink.
+ *
+ * Sets are identified by their index in ip_set_list and the index
+ * is used by the external references (set/SET netfilter modules).
*
- * Userspace requests are serialized by ip_set_mutex and sets can
- * be deleted only from userspace. Therefore ip_set_list locking
- * must obey the following rules:
+ * The set behind an index may change by swapping.
*
- * - kernel requests: read and write locking mandatory
- * - user requests: read locking optional, write locking mandatory
*/
static inline void
@@ -75,227 +85,166 @@ __ip_set_put(ip_set_id_t index)
/* Add, del and test set entries from kernel */
int
-ip_set_testip_kernel(ip_set_id_t index,
- const struct sk_buff *skb,
- const u_int32_t *flags)
+ip_set_test(ip_set_id_t index, const struct sk_buff *skb,
+ uint8_t family, const uint8_t *flags)
{
struct ip_set *set;
- int res;
+ int ret = 0;
- read_lock_bh(&ip_set_lock);
- set = ip_set_list[index];
- IP_SET_ASSERT(set);
- DP("set %s, index %u", set->name, index);
+ rcu_read_lock();
+ set = rcu_dereference(ip_set_list[index]);
+ D("set %s, index %u", set->name, index);
read_lock_bh(&set->lock);
- res = set->type->testip_kernel(set, skb, flags);
+ ret = set->variant->kadt(set, skb, IPSET_TEST, family, flags);
read_unlock_bh(&set->lock);
- read_unlock_bh(&ip_set_lock);
+ if (ret == -EAGAIN) {
+ /* Type requests element to be re-added */
+ write_lock_bh(&set->lock);
+ set->variant->kadt(set, skb, IPSET_ADD, family, flags);
+ write_unlock_bh(&set->lock);
+ ret = 1;
+ }
+
+ rcu_read_unlock();
- return (res < 0 ? 0 : res);
+ return (ret < 0 ? 0 : ret);
}
int
-ip_set_addip_kernel(ip_set_id_t index,
- const struct sk_buff *skb,
- const u_int32_t *flags)
+ip_set_add(ip_set_id_t index, const struct sk_buff *skb,
+ uint8_t family, const uint8_t *flags)
{
struct ip_set *set;
- int res;
+ int ret = 0, retried = 0;
- retry:
- read_lock_bh(&ip_set_lock);
- set = ip_set_list[index];
- IP_SET_ASSERT(set);
- DP("set %s, index %u", set->name, index);
+retry:
+ rcu_read_lock();
+ set = rcu_dereference(ip_set_list[index]);
+ D("set %s, index %u", set->name, index);
write_lock_bh(&set->lock);
- res = set->type->addip_kernel(set, skb, flags);
+ ret = set->variant->kadt(set, skb, IPSET_ADD, family, flags);
write_unlock_bh(&set->lock);
- read_unlock_bh(&ip_set_lock);
- /* Retry function called without holding any lock */
- if (res == -EAGAIN
- && set->type->retry
- && (res = set->type->retry(set)) == 0)
+ rcu_read_unlock();
+ /* Retry function must be called without holding any lock */
+ if (ret == -EAGAIN
+ && set->variant->resize
+ && (ret = set->variant->resize(set, retried++)) == 0)
goto retry;
- return res;
+ return ret;
}
int
-ip_set_delip_kernel(ip_set_id_t index,
- const struct sk_buff *skb,
- const u_int32_t *flags)
+ip_set_del(ip_set_id_t index, const struct sk_buff *skb,
+ uint8_t family, const uint8_t *flags)
{
struct ip_set *set;
- int res;
+ int ret = 0;
- read_lock_bh(&ip_set_lock);
- set = ip_set_list[index];
- IP_SET_ASSERT(set);
- DP("set %s, index %u", set->name, index);
+ rcu_read_lock();
+ set = rcu_dereference(ip_set_list[index]);
+ D("set %s, index %u", set->name, index);
write_lock_bh(&set->lock);
- res = set->type->delip_kernel(set, skb, flags);
+ ret = set->variant->kadt(set, skb, IPSET_DEL, family, flags);
write_unlock_bh(&set->lock);
- read_unlock_bh(&ip_set_lock);
+ rcu_read_unlock();
- return res;
+ return ret;
}
/* Register and deregister settype */
+#define family_name(f) ((f) == AF_INET ? "inet" : \
+ (f) == AF_INET6 ? "inet6" : "any")
+
static inline struct ip_set_type *
-find_set_type(const char *name)
+find_set_type(const char *name, uint8_t family, uint8_t revision)
{
- struct ip_set_type *set_type;
+ struct ip_set_type *type;
- list_for_each_entry(set_type, &set_type_list, list)
- if (STREQ(set_type->typename, name))
- return set_type;
+ list_for_each_entry(type, &ip_set_type_list, list)
+ if (STREQ(type->name, name)
+ && (type->family == family || type->family == AF_UNSPEC)
+ && type->revision == revision)
+ return type;
return NULL;
}
int
-ip_set_register_set_type(struct ip_set_type *set_type)
+ip_set_type_register(struct ip_set_type *type)
{
int ret = 0;
- if (set_type->protocol_version != IP_SET_PROTOCOL_VERSION) {
- ip_set_printk("'%s' uses wrong protocol version %u (want %u)",
- set_type->typename,
- set_type->protocol_version,
- IP_SET_PROTOCOL_VERSION);
+ if (type->protocol != IPSET_PROTOCOL) {
+ printk("set type %s, family %s, revision %u uses "
+ "wrong protocol version %u (want %u)\n",
+ type->name, family_name(type->family), type->revision,
+ type->protocol, IPSET_PROTOCOL);
return -EINVAL;
}
- write_lock_bh(&ip_set_lock);
- if (find_set_type(set_type->typename)) {
+ ip_set_type_list_lock();
+ if (find_set_type(type->name, type->family, type->revision)) {
/* Duplicate! */
- ip_set_printk("'%s' already registered!",
- set_type->typename);
+ printk("type %s, family %s, revision %u already registered!\n",
+ type->name, family_name(type->family), type->revision);
ret = -EINVAL;
goto unlock;
}
- if (!try_module_get(THIS_MODULE)) {
- ret = -EFAULT;
- goto unlock;
- }
- list_add(&set_type->list, &set_type_list);
- DP("'%s' registered.", set_type->typename);
- unlock:
- write_unlock_bh(&ip_set_lock);
+ list_add(&type->list, &ip_set_type_list);
+ D("type %s, family %s, revision %u registered.",
+ type->name, family_name(type->family), type->revision);
+unlock:
+ ip_set_type_list_unlock();
return ret;
}
void
-ip_set_unregister_set_type(struct ip_set_type *set_type)
+ip_set_type_unregister(struct ip_set_type *type)
{
- write_lock_bh(&ip_set_lock);
- if (!find_set_type(set_type->typename)) {
- ip_set_printk("'%s' not registered?",
- set_type->typename);
+ ip_set_type_list_lock();
+ if (!find_set_type(type->name, type->family, type->revision)) {
+ printk("type %s, family %s, revision %u not registered\n",
+ type->name, family_name(type->family), type->revision);
goto unlock;
}
- list_del(&set_type->list);
- module_put(THIS_MODULE);
- DP("'%s' unregistered.", set_type->typename);
- unlock:
- write_unlock_bh(&ip_set_lock);
-
+ list_del(&type->list);
+ D("type %s, family %s, revision %u unregistered.",
+ type->name, family_name(type->family), type->revision);
+unlock:
+ ip_set_type_list_unlock();
}
-ip_set_id_t
-__ip_set_get_byname(const char *name, struct ip_set **set)
-{
- ip_set_id_t i, index = IP_SET_INVALID_ID;
-
- for (i = 0; i < ip_set_max; i++) {
- if (ip_set_list[i] != NULL
- && STREQ(ip_set_list[i]->name, name)) {
- __ip_set_get(i);
- index = i;
- *set = ip_set_list[i];
- break;
- }
- }
- return index;
-}
-
-void
-__ip_set_put_byindex(ip_set_id_t index)
-{
- if (ip_set_list[index])
- __ip_set_put(index);
-}
-
-/*
- * Userspace routines
- */
+/* Get/put a set with referencing */
/*
* Find set by name, reference it once. The reference makes sure the
* thing pointed to, does not go away under our feet. Drop the reference
- * later, using ip_set_put().
+ * later, using ip_set_put*().
*/
ip_set_id_t
ip_set_get_byname(const char *name)
{
- ip_set_id_t i, index = IP_SET_INVALID_ID;
+ ip_set_id_t i, index = IPSET_INVALID_ID;
- down(&ip_set_app_mutex);
- for (i = 0; i < ip_set_max; i++) {
- if (ip_set_list[i] != NULL
- && STREQ(ip_set_list[i]->name, name)) {
+ nfnl_lock();
+ for (i = 0; index == IPSET_INVALID_ID && i < ip_set_max; i++)
+ if (STREQ(ip_set_list[i]->name, name)) {
__ip_set_get(i);
index = i;
- break;
}
- }
- up(&ip_set_app_mutex);
- return index;
-}
-
-/*
- * Find set by index, reference it once. The reference makes sure the
- * thing pointed to, does not go away under our feet. Drop the reference
- * later, using ip_set_put().
- */
-ip_set_id_t
-ip_set_get_byindex(ip_set_id_t index)
-{
- down(&ip_set_app_mutex);
+ nfnl_unlock();
- if (index >= ip_set_max)
- return IP_SET_INVALID_ID;
-
- if (ip_set_list[index])
- __ip_set_get(index);
- else
- index = IP_SET_INVALID_ID;
-
- up(&ip_set_app_mutex);
return index;
}
/*
- * Find the set id belonging to the index.
- * We are protected by the mutex, so we do not need to use
- * ip_set_lock. There is no need to reference the sets either.
- */
-ip_set_id_t
-ip_set_id(ip_set_id_t index)
-{
- if (index >= ip_set_max || !ip_set_list[index])
- return IP_SET_INVALID_ID;
-
- return ip_set_list[index]->id;
-}
-
-/*
* If the given set pointer points to a valid set, decrement
* reference count by 1. The caller shall not assume the index
* to be valid, after calling this function.
@@ -303,1227 +252,1047 @@ ip_set_id(ip_set_id_t index)
void
ip_set_put_byindex(ip_set_id_t index)
{
- down(&ip_set_app_mutex);
+ nfnl_lock();
if (ip_set_list[index])
__ip_set_put(index);
- up(&ip_set_app_mutex);
+ nfnl_unlock();
}
-/* Find a set by name or index */
static ip_set_id_t
-ip_set_find_byname(const char *name)
+find_set_id(const char *name)
{
- ip_set_id_t i, index = IP_SET_INVALID_ID;
+ ip_set_id_t i, index = IPSET_INVALID_ID;
- for (i = 0; i < ip_set_max; i++) {
+ for (i = 0; index == IPSET_INVALID_ID && i < ip_set_max; i++) {
if (ip_set_list[i] != NULL
- && STREQ(ip_set_list[i]->name, name)) {
+ && STREQ(ip_set_list[i]->name, name))
index = i;
- break;
- }
}
return index;
}
static ip_set_id_t
-ip_set_find_byindex(ip_set_id_t index)
+find_set_id_rcu(const char *name)
{
- if (index >= ip_set_max || ip_set_list[index] == NULL)
- index = IP_SET_INVALID_ID;
+ ip_set_id_t i, index = IPSET_INVALID_ID;
+ struct ip_set *set;
+ for (i = 0; index == IPSET_INVALID_ID && i < ip_set_max; i++) {
+ set = rcu_dereference(ip_set_list[i]);
+ if (set != NULL && STREQ(set->name, name))
+ index = i;
+ }
return index;
}
-/*
- * Add, del and test
- */
-
-static int
-ip_set_addip(struct ip_set *set, const void *data, u_int32_t size)
+static struct ip_set *
+find_set(const char *name)
{
- int res;
-
- IP_SET_ASSERT(set);
- do {
- write_lock_bh(&set->lock);
- res = set->type->addip(set, data, size);
- write_unlock_bh(&set->lock);
- } while (res == -EAGAIN
- && set->type->retry
- && (res = set->type->retry(set)) == 0);
+ ip_set_id_t index = find_set_id(name);
- return res;
+ return index == IPSET_INVALID_ID ? NULL : ip_set_list[index];
}
-static int
-ip_set_delip(struct ip_set *set, const void *data, u_int32_t size)
-{
- int res;
-
- IP_SET_ASSERT(set);
-
- write_lock_bh(&set->lock);
- res = set->type->delip(set, data, size);
- write_unlock_bh(&set->lock);
+/* Communication protocol with userspace over netlink */
+
+/* Create a set */
+
+static const struct nla_policy
+ip_set_create_policy[IPSET_ATTR_CMD_MAX + 1] __read_mostly = {
+ [IPSET_ATTR_PROTOCOL] = { .type = NLA_U8 },
+ [IPSET_ATTR_SETNAME] = { .type = NLA_STRING,
+ .len = IPSET_MAXNAMELEN },
+ [IPSET_ATTR_TYPENAME] = { .type = NLA_STRING,
+ .len = IPSET_MAXNAMELEN },
+ [IPSET_ATTR_REVISION] = { .type = NLA_U8 },
+ [IPSET_ATTR_FAMILY] = { .type = NLA_U8 },
+ [IPSET_ATTR_LINENO] = { .type = NLA_U32 },
+ [IPSET_ATTR_DATA] = { .type = NLA_NESTED },
+};
- return res;
+static inline bool
+protocol_failed(const struct nlattr * const tb[])
+{
+ return !tb[IPSET_ATTR_PROTOCOL]
+ || nla_get_u8(tb[IPSET_ATTR_PROTOCOL]) != IPSET_PROTOCOL;
}
-static int
-ip_set_testip(struct ip_set *set, const void *data, u_int32_t size)
+static inline uint32_t
+flag_exist(const struct nlmsghdr *nlh)
{
- int res;
-
- IP_SET_ASSERT(set);
-
- read_lock_bh(&set->lock);
- res = set->type->testip(set, data, size);
- read_unlock_bh(&set->lock);
+ return nlh->nlmsg_flags & NLM_F_EXCL ? 0 : IPSET_FLAG_EXIST;
+}
- return (res > 0 ? -EEXIST : res);
+static inline bool
+flag_nested(const struct nlattr *nla)
+{
+ return nla->nla_type & NLA_F_NESTED;
}
static struct ip_set_type *
-find_set_type_rlock(const char *typename)
+find_set_type_lock(const char *name, uint8_t family, uint8_t revision)
{
struct ip_set_type *type;
- read_lock_bh(&ip_set_lock);
- type = find_set_type(typename);
+ ip_set_type_list_lock();
+ type = find_set_type(name, family, revision);
if (type == NULL)
- read_unlock_bh(&ip_set_lock);
+ ip_set_type_list_unlock();
return type;
}
static int
-find_free_id(const char *name,
- ip_set_id_t *index,
- ip_set_id_t *id)
+find_free_id(const char *name, ip_set_id_t *index, struct ip_set **set)
{
ip_set_id_t i;
- *id = IP_SET_INVALID_ID;
+ *index = IPSET_INVALID_ID;
for (i = 0; i < ip_set_max; i++) {
if (ip_set_list[i] == NULL) {
- if (*id == IP_SET_INVALID_ID)
- *id = *index = i;
- } else if (STREQ(name, ip_set_list[i]->name))
+ if (*index == IPSET_INVALID_ID)
+ *index = i;
+ } else if (STREQ(name, ip_set_list[i]->name)) {
/* Name clash */
+ *set = ip_set_list[i];
return -EEXIST;
- }
- if (*id == IP_SET_INVALID_ID)
- /* No free slot remained */
- return -ERANGE;
- /* Check that index is usable as id (swapping) */
- check:
- for (i = 0; i < ip_set_max; i++) {
- if (ip_set_list[i] != NULL
- && ip_set_list[i]->id == *id) {
- *id = i;
- goto check;
}
}
+ if (*index == IPSET_INVALID_ID)
+ /* No free slot remained */
+ return -IPSET_ERR_MAX_SETS;
return 0;
}
-/*
- * Create a set
- */
-static int
-ip_set_create(const char *name,
- const char *typename,
- ip_set_id_t restore,
- const void *data,
- u_int32_t size)
+static struct nlmsghdr *
+start_msg(struct sk_buff *skb, u32 pid, u32 seq, unsigned int flags,
+ enum ipset_cmd cmd)
{
- struct ip_set *set;
- ip_set_id_t index = 0, id;
- int res = 0;
+ struct nlmsghdr *nlh;
+ struct nfgenmsg *nfmsg;
+
+ nlh = nlmsg_put(skb, pid, seq, cmd | (NFNL_SUBSYS_IPSET << 8),
+ sizeof(*nfmsg), flags);
+ if (nlh == NULL)
+ return NULL;
+
+ nfmsg = nlmsg_data(nlh);
+ nfmsg->nfgen_family = AF_INET;
+ nfmsg->version = NFNETLINK_V0;
+ nfmsg->res_id = 0;
+
+ return nlh;
+}
+
+static inline void
+load_type_module(const char *typename)
+{
+ D("try to load ip_set_%s", typename);
+ request_module("ip_set_%s", typename);
+}
- DP("setname: %s, typename: %s, id: %u", name, typename, restore);
+static int
+ip_set_create(struct sock *ctnl, struct sk_buff *skb,
+ const struct nlmsghdr *nlh,
+ const struct nlattr * const attr[])
+{
+ struct ip_set *set, *clash;
+ ip_set_id_t index = IPSET_INVALID_ID;
+ const char *name, *typename;
+ uint8_t family, revision;
+ uint32_t flags = flag_exist(nlh);
+ int ret = 0, len;
+
+ if (unlikely(protocol_failed(attr)
+ || attr[IPSET_ATTR_SETNAME] == NULL
+ || attr[IPSET_ATTR_TYPENAME] == NULL
+ || attr[IPSET_ATTR_REVISION] == NULL
+ || attr[IPSET_ATTR_FAMILY] == NULL
+ || (attr[IPSET_ATTR_DATA] != NULL
+ && !flag_nested(attr[IPSET_ATTR_DATA]))))
+ return -IPSET_ERR_PROTOCOL;
+
+ name = nla_data(attr[IPSET_ATTR_SETNAME]);
+ typename = nla_data(attr[IPSET_ATTR_TYPENAME]);
+ family = nla_get_u8(attr[IPSET_ATTR_FAMILY]);
+ revision = nla_get_u8(attr[IPSET_ATTR_REVISION]);
+ D("setname: %s, typename: %s, family: %s, revision: %u",
+ name, typename, family_name(family), revision);
/*
* First, and without any locks, allocate and initialize
* a normal base set structure.
*/
- set = kmalloc(sizeof(struct ip_set), GFP_KERNEL);
+ set = kzalloc(sizeof(struct ip_set), GFP_KERNEL);
if (!set)
return -ENOMEM;
rwlock_init(&set->lock);
- strncpy(set->name, name, IP_SET_MAXNAMELEN);
+ strncpy(set->name, name, IPSET_MAXNAMELEN);
atomic_set(&set->ref, 0);
/*
- * Next, take the &ip_set_lock, check that we know the type,
- * and take a reference on the type, to make sure it
- * stays available while constructing our new set.
+ * Next, check that we know the type, and take
+ * a reference on the type, to make sure it stays available
+ * while constructing our new set.
*
- * After referencing the type, we drop the &ip_set_lock,
- * and let the new set construction run without locks.
+ * After referencing the type, we try to create the type
+ * specific part of the set without holding any locks.
*/
- set->type = find_set_type_rlock(typename);
+ set->type = find_set_type_lock(typename, family, revision);
if (set->type == NULL) {
/* Try loading the module */
- char modulename[IP_SET_MAXNAMELEN + strlen("ip_set_") + 1];
- strcpy(modulename, "ip_set_");
- strcat(modulename, typename);
- DP("try to load %s", modulename);
- request_module(modulename);
- set->type = find_set_type_rlock(typename);
- }
- if (set->type == NULL) {
- ip_set_printk("no set type '%s', set '%s' not created",
- typename, name);
- res = -ENOENT;
- goto out;
+ load_type_module(typename);
+ set->type = find_set_type_lock(typename, family, revision);
+ if (set->type == NULL) {
+ printk("Can't find type %s, family %s, revision %u:"
+ " set '%s' not created",
+ typename, family_name(family), revision, name);
+ ret = -IPSET_ERR_FIND_TYPE;
+ goto out;
+ }
}
if (!try_module_get(set->type->me)) {
- read_unlock_bh(&ip_set_lock);
- res = -EFAULT;
+ ip_set_type_list_unlock();
+ ret = -EFAULT;
goto out;
}
- read_unlock_bh(&ip_set_lock);
-
- /* Check request size */
- if (size != set->type->header_size) {
- ip_set_printk("data length wrong (want %lu, have %lu)",
- (long unsigned)set->type->header_size,
- (long unsigned)size);
- goto put_out;
- }
+ ip_set_type_list_unlock();
/*
* Without holding any locks, create private part.
*/
- res = set->type->create(set, data, size);
- if (res != 0)
+ len = attr[IPSET_ATTR_DATA] ? nla_len(attr[IPSET_ATTR_DATA]) : 0;
+ D("data len: %u", len);
+ ret = set->type->create(set, attr[IPSET_ATTR_DATA] ?
+ nla_data(attr[IPSET_ATTR_DATA]) : NULL, len,
+ flags);
+ if (ret != 0)
goto put_out;
- /* BTW, res==0 here. */
+ /* BTW, ret==0 here. */
/*
- * Here, we have a valid, constructed set. &ip_set_lock again,
- * find free id/index and check that it is not already in
- * ip_set_list.
+ * Here, we have a valid, constructed set and we are protected
+ * by nfnl_lock. Find the first free index in ip_set_list and
+ * check clashing.
*/
- write_lock_bh(&ip_set_lock);
- if ((res = find_free_id(set->name, &index, &id)) != 0) {
- DP("no free id!");
+ if ((ret = find_free_id(set->name, &index, &clash)) != 0) {
+ /* If this is the same set and requested, ignore error */
+ if (ret == -EEXIST
+ && (flags & IPSET_FLAG_EXIST)
+ && STREQ(set->type->name, clash->type->name)
+ && set->type->family == clash->type->family
+ && set->type->revision == clash->type->revision)
+ ret = 0;
goto cleanup;
}
- /* Make sure restore gets the same index */
- if (restore != IP_SET_INVALID_ID && index != restore) {
- DP("Can't restore, sets are screwed up");
- res = -ERANGE;
- goto cleanup;
- }
-
/*
* Finally! Add our shiny new set to the list, and be done.
*/
- DP("create: '%s' created with index %u, id %u!", set->name, index, id);
- set->id = id;
+ D("create: '%s' created with index %u!", set->name, index);
ip_set_list[index] = set;
- write_unlock_bh(&ip_set_lock);
- return res;
+
+ return ret;
- cleanup:
- write_unlock_bh(&ip_set_lock);
- set->type->destroy(set);
- put_out:
+cleanup:
+ set->variant->destroy(set);
+put_out:
module_put(set->type->me);
- out:
+out:
kfree(set);
- return res;
+ return ret;
}
-/*
- * Destroy a given existing set
- */
-static void
+/* Destroy sets */
+
+static const struct nla_policy
+ip_set_setname_policy[IPSET_ATTR_CMD_MAX + 1] __read_mostly = {
+ [IPSET_ATTR_PROTOCOL] = { .type = NLA_U8 },
+ [IPSET_ATTR_SETNAME] = { .type = NLA_STRING,
+ .len = IPSET_MAXNAMELEN },
+};
+
+static inline void
ip_set_destroy_set(ip_set_id_t index)
{
struct ip_set *set = ip_set_list[index];
- IP_SET_ASSERT(set);
- DP("set: %s", set->name);
- write_lock_bh(&ip_set_lock);
+ D("set: %s", set->name);
ip_set_list[index] = NULL;
- write_unlock_bh(&ip_set_lock);
/* Must call it without holding any lock */
- set->type->destroy(set);
+ set->variant->destroy(set);
module_put(set->type->me);
kfree(set);
}
-/*
- * Destroy a set - or all sets
- * Sets must not be referenced/used.
- */
static int
-ip_set_destroy(ip_set_id_t index)
+ip_set_destroy(struct sock *ctnl, struct sk_buff *skb,
+ const struct nlmsghdr *nlh,
+ const struct nlattr * const attr[])
{
ip_set_id_t i;
+
+ if (unlikely(protocol_failed(attr)))
+ return -IPSET_ERR_PROTOCOL;
- /* ref modification always protected by the mutex */
- if (index != IP_SET_INVALID_ID) {
- if (atomic_read(&ip_set_list[index]->ref))
- return -EBUSY;
- ip_set_destroy_set(index);
- } else {
+ /* References are protected by the nfnl mutex */
+ if (!attr[IPSET_ATTR_SETNAME]) {
for (i = 0; i < ip_set_max; i++) {
if (ip_set_list[i] != NULL
&& (atomic_read(&ip_set_list[i]->ref)))
- return -EBUSY;
+ return -IPSET_ERR_BUSY;
}
-
for (i = 0; i < ip_set_max; i++) {
if (ip_set_list[i] != NULL)
ip_set_destroy_set(i);
}
+ } else {
+ i = find_set_id(nla_data(attr[IPSET_ATTR_SETNAME]));
+ if (i == IPSET_INVALID_ID)
+ return -EEXIST;
+ else if (atomic_read(&ip_set_list[i]->ref))
+ return -IPSET_ERR_BUSY;
+
+ ip_set_destroy_set(i);
}
return 0;
}
-static void
+/* Flush sets */
+
+static inline void
ip_set_flush_set(struct ip_set *set)
{
- DP("set: %s %u", set->name, set->id);
+ D("set: %s", set->name);
write_lock_bh(&set->lock);
- set->type->flush(set);
+ set->variant->flush(set);
write_unlock_bh(&set->lock);
}
-/*
- * Flush data in a set - or in all sets
- */
static int
-ip_set_flush(ip_set_id_t index)
+ip_set_flush(struct sock *ctnl, struct sk_buff *skb,
+ const struct nlmsghdr *nlh,
+ const struct nlattr * const attr[])
{
- if (index != IP_SET_INVALID_ID) {
- IP_SET_ASSERT(ip_set_list[index]);
- ip_set_flush_set(ip_set_list[index]);
- } else {
- ip_set_id_t i;
-
+ ip_set_id_t i;
+
+ if (unlikely(protocol_failed(attr)))
+ return -EPROTO;
+
+ if (!attr[IPSET_ATTR_SETNAME]) {
for (i = 0; i < ip_set_max; i++)
if (ip_set_list[i] != NULL)
ip_set_flush_set(ip_set_list[i]);
+ } else {
+ i = find_set_id(nla_data(attr[IPSET_ATTR_SETNAME]));
+ if (i == IPSET_INVALID_ID)
+ return -EEXIST;
+
+ ip_set_flush_set(ip_set_list[i]);
}
return 0;
}
/* Rename a set */
+
+static const struct nla_policy
+ip_set_setname2_policy[IPSET_ATTR_CMD_MAX + 1] __read_mostly = {
+ [IPSET_ATTR_PROTOCOL] = { .type = NLA_U8 },
+ [IPSET_ATTR_SETNAME] = { .type = NLA_STRING,
+ .len = IPSET_MAXNAMELEN },
+ [IPSET_ATTR_SETNAME2] = { .type = NLA_STRING,
+ .len = IPSET_MAXNAMELEN },
+};
+
static int
-ip_set_rename(ip_set_id_t index, const char *name)
+ip_set_rename(struct sock *ctnl, struct sk_buff *skb,
+ const struct nlmsghdr *nlh,
+ const struct nlattr * const attr[])
{
- struct ip_set *set = ip_set_list[index];
+ struct ip_set *set;
+ const char *name2;
ip_set_id_t i;
- int res = 0;
- DP("set: %s to %s", set->name, name);
- write_lock_bh(&ip_set_lock);
+ if (unlikely(protocol_failed(attr)
+ || attr[IPSET_ATTR_SETNAME] == NULL
+ || attr[IPSET_ATTR_SETNAME2] == NULL))
+ return -IPSET_ERR_PROTOCOL;
+
+ set = find_set(nla_data(attr[IPSET_ATTR_SETNAME]));
+ if (set == NULL)
+ return -EEXIST;
+
+ name2 = nla_data(attr[IPSET_ATTR_SETNAME2]);
for (i = 0; i < ip_set_max; i++) {
if (ip_set_list[i] != NULL
- && STREQ(ip_set_list[i]->name, name)) {
- res = -EEXIST;
- goto unlock;
- }
+ && STREQ(ip_set_list[i]->name, name2))
+ return -IPSET_ERR_EXIST_SETNAME2;
}
- strncpy(set->name, name, IP_SET_MAXNAMELEN);
- unlock:
- write_unlock_bh(&ip_set_lock);
- return res;
+ strncpy(set->name, name2, IPSET_MAXNAMELEN);
+
+ return 0;
}
-/*
- * Swap two sets so that name/index points to the other.
- * References are also swapped.
- */
+/* Swap two sets so that name/index points to the other.
+ * References are also swapped. */
+
static int
-ip_set_swap(ip_set_id_t from_index, ip_set_id_t to_index)
+ip_set_swap(struct sock *ctnl, struct sk_buff *skb,
+ const struct nlmsghdr *nlh,
+ const struct nlattr * const attr[])
{
- struct ip_set *from = ip_set_list[from_index];
- struct ip_set *to = ip_set_list[to_index];
- char from_name[IP_SET_MAXNAMELEN];
- u_int32_t from_ref;
+ struct ip_set *from, *to;
+ ip_set_id_t from_id, to_id;
+ char from_name[IPSET_MAXNAMELEN];
+ uint32_t from_ref;
+
+ if (unlikely(protocol_failed(attr)
+ || attr[IPSET_ATTR_SETNAME] == NULL
+ || attr[IPSET_ATTR_SETNAME2] == NULL))
+ return -IPSET_ERR_PROTOCOL;
- DP("set: %s to %s", from->name, to->name);
- /* Features must not change.
+ from_id = find_set_id(nla_data(attr[IPSET_ATTR_SETNAME]));
+ if (from_id == IPSET_INVALID_ID)
+ return -EEXIST;
+
+ to_id = find_set_id(nla_data(attr[IPSET_ATTR_SETNAME2]));
+ if (to_id == IPSET_INVALID_ID)
+ return -IPSET_ERR_EXIST_SETNAME2;
+
+ from = ip_set_list[from_id];
+ to = ip_set_list[to_id];
+
+ /* Features must not change.
* Not an artifical restriction anymore, as we must prevent
* possible loops created by swapping in setlist type of sets. */
- if (from->type->features != to->type->features)
- return -ENOEXEC;
+ if (!(from->type->features == to->type->features
+ && from->type->family == to->type->family))
+ return -IPSET_ERR_TYPE_MISMATCH;
/* No magic here: ref munging protected by the mutex */
- write_lock_bh(&ip_set_lock);
- strncpy(from_name, from->name, IP_SET_MAXNAMELEN);
+ strncpy(from_name, from->name, IPSET_MAXNAMELEN);
from_ref = atomic_read(&from->ref);
- strncpy(from->name, to->name, IP_SET_MAXNAMELEN);
+ strncpy(from->name, to->name, IPSET_MAXNAMELEN);
atomic_set(&from->ref, atomic_read(&to->ref));
- strncpy(to->name, from_name, IP_SET_MAXNAMELEN);
+ strncpy(to->name, from_name, IPSET_MAXNAMELEN);
atomic_set(&to->ref, from_ref);
- ip_set_list[from_index] = to;
- ip_set_list[to_index] = from;
-
- write_unlock_bh(&ip_set_lock);
+ rcu_assign_pointer(ip_set_list[from_id], to);
+ rcu_assign_pointer(ip_set_list[to_id], from);
+ synchronize_rcu();
+
return 0;
}
-/*
- * List set data
- */
+/* List/save set data */
static int
-ip_set_list_set(ip_set_id_t index, void *data, int *used, int len)
+ip_set_dump_done(struct netlink_callback *cb)
{
- struct ip_set *set = ip_set_list[index];
- struct ip_set_list *set_list;
-
- /* Pointer to our header */
- set_list = data + *used;
-
- DP("set: %s, used: %d len %u %p %p", set->name, *used, len, data, data + *used);
-
- /* Get and ensure header size */
- if (*used + ALIGNED(sizeof(struct ip_set_list)) > len)
- goto not_enough_mem;
- *used += ALIGNED(sizeof(struct ip_set_list));
-
- read_lock_bh(&set->lock);
- /* Get and ensure set specific header size */
- set_list->header_size = ALIGNED(set->type->header_size);
- if (*used + set_list->header_size > len)
- goto unlock_set;
-
- /* Fill in the header */
- set_list->index = index;
- set_list->binding = IP_SET_INVALID_ID;
- set_list->ref = atomic_read(&set->ref);
-
- /* Fill in set spefific header data */
- set->type->list_header(set, data + *used);
- *used += set_list->header_size;
-
- /* Get and ensure set specific members size */
- set_list->members_size = set->type->list_members_size(set, DONT_ALIGN);
- if (*used + set_list->members_size > len)
- goto unlock_set;
-
- /* Fill in set spefific members data */
- set->type->list_members(set, data + *used, DONT_ALIGN);
- *used += set_list->members_size;
- read_unlock_bh(&set->lock);
-
- /* Bindings */
- set_list->bindings_size = 0;
-
+ if (cb->args[2])
+ __ip_set_put((ip_set_id_t) cb->args[1]);
return 0;
+}
- unlock_set:
- read_unlock_bh(&set->lock);
- not_enough_mem:
- DP("not enough mem, try again");
- return -EAGAIN;
+static inline void
+dump_attrs(struct nlmsghdr *nlh)
+{
+ struct nlattr *attr;
+ int rem;
+
+ D("dump nlmsg");
+ nlmsg_for_each_attr(attr, nlh, sizeof(struct nfgenmsg), rem) {
+ D("type: %u, len %u", nla_type(attr), attr->nla_len);
+ }
}
-/*
- * Save sets
- */
-static inline int
-ip_set_save_marker(void *data, int *used, int len)
+static int
+ip_set_dump_start(struct sk_buff *skb, struct netlink_callback *cb)
{
- struct ip_set_save *set_save;
+ ip_set_id_t index = IPSET_INVALID_ID, max;
+ struct ip_set *set = NULL;
+ struct nlmsghdr *nlh = NULL;
+ unsigned int flags = NETLINK_CB(cb->skb).pid ? NLM_F_MULTI : 0;
+ int ret = 0;
- DP("used %u, len %u", *used, len);
- /* Get and ensure header size */
- if (*used + ALIGNED(sizeof(struct ip_set_save)) > len)
- return -ENOMEM;
+ max = cb->args[0] ? cb->args[1] + 1 : ip_set_max;
+ rcu_read_lock();
+ for (; cb->args[1] < max; cb->args[1]++) {
+ index = (ip_set_id_t) cb->args[1];
+ set = rcu_dereference(ip_set_list[index]);
+ if (set == NULL) {
+ if (cb->args[0]) {
+ ret = -EEXIST;
+ goto unlock;
+ }
+ continue;
+ }
+ D("List set: %s", set->name);
+ if (!cb->args[2]) {
+ /* Start listing: make sure set won't be destroyed */
+ D("reference set");
+ __ip_set_get(index);
+ }
+ nlh = start_msg(skb, NETLINK_CB(cb->skb).pid,
+ cb->nlh->nlmsg_seq, flags,
+ IPSET_CMD_LIST);
+ if (!nlh) {
+ ret = -EFAULT;
+ goto release_refcount;
+ }
+ NLA_PUT_U8(skb, IPSET_ATTR_PROTOCOL, IPSET_PROTOCOL);
+ NLA_PUT_STRING(skb, IPSET_ATTR_SETNAME, set->name);
+ switch (cb->args[2]) {
+ case 0:
+ /* Core header data */
+ NLA_PUT_STRING(skb, IPSET_ATTR_TYPENAME,
+ set->type->name);
+ NLA_PUT_U8(skb, IPSET_ATTR_FAMILY,
+ set->type->family);
+ NLA_PUT_U8(skb, IPSET_ATTR_REVISION,
+ set->type->revision);
+ ret = set->variant->head(set, skb);
+ if (ret < 0)
+ goto release_refcount;
+ /* Fall through and add elements */
+ default:
+ read_lock_bh(&set->lock);
+ ret = set->variant->list(set, skb, cb);
+ read_unlock_bh(&set->lock);
+ if (!cb->args[2])
+ /* Set is done, proceed with next one */
+ cb->args[1]++;
+ goto release_refcount;
+ }
+ }
+ goto unlock;
+
+nla_put_failure:
+ ret = -EFAULT;
+release_refcount:
+ /* If there was an error or set is done, release set */
+ if (ret || !cb->args[2]) {
+ D("release set");
+ __ip_set_put(index);
+ }
+unlock:
+ rcu_read_unlock();
- /* Marker: just for backward compatibility */
- set_save = data + *used;
- set_save->index = IP_SET_INVALID_ID;
- set_save->header_size = 0;
- set_save->members_size = 0;
- *used += ALIGNED(sizeof(struct ip_set_save));
+ if (nlh) {
+ nlmsg_end(skb, nlh);
+ D("nlmsg_len: %u", nlh->nlmsg_len);
+ dump_attrs(nlh);
+ }
- return 0;
+ return ret < 0 ? ret : skb->len;
}
static int
-ip_set_save_set(ip_set_id_t index, void *data, int *used, int len)
+ip_set_dump(struct sock *ctnl, struct sk_buff *skb,
+ const struct nlmsghdr *nlh,
+ const struct nlattr * const attr[])
{
- struct ip_set *set;
- struct ip_set_save *set_save;
-
- /* Pointer to our header */
- set_save = data + *used;
+ ip_set_id_t index;
+
+ if (unlikely(protocol_failed(attr)))
+ return -IPSET_ERR_PROTOCOL;
+
+ if (!attr[IPSET_ATTR_SETNAME])
+ return netlink_dump_start(ctnl, skb, nlh,
+ ip_set_dump_start,
+ ip_set_dump_done);
+
+ rcu_read_lock();
+ index = find_set_id_rcu(nla_data(attr[IPSET_ATTR_SETNAME]));
+ if (index == IPSET_INVALID_ID) {
+ rcu_read_unlock();
+ return -EEXIST;
+ }
+ rcu_read_unlock();
- /* Get and ensure header size */
- if (*used + ALIGNED(sizeof(struct ip_set_save)) > len)
- goto not_enough_mem;
- *used += ALIGNED(sizeof(struct ip_set_save));
+ /* cb->args[0] : 1 => dump single set,
+ * : 0 => dump all sets
+ * [1] : set index
+ * [..]: type specific
+ */
+ return netlink_dump_init(ctnl, skb, nlh,
+ ip_set_dump_start,
+ ip_set_dump_done,
+ 2, 1, index);
+}
- set = ip_set_list[index];
- DP("set: %s, used: %d(%d) %p %p", set->name, *used, len,
- data, data + *used);
+/* Add, del and test */
- read_lock_bh(&set->lock);
- /* Get and ensure set specific header size */
- set_save->header_size = ALIGNED(set->type->header_size);
- if (*used + set_save->header_size > len)
- goto unlock_set;
-
- /* Fill in the header */
- set_save->index = index;
- set_save->binding = IP_SET_INVALID_ID;
-
- /* Fill in set spefific header data */
- set->type->list_header(set, data + *used);
- *used += set_save->header_size;
-
- DP("set header filled: %s, used: %d(%lu) %p %p", set->name, *used,
- (unsigned long)set_save->header_size, data, data + *used);
- /* Get and ensure set specific members size */
- set_save->members_size = set->type->list_members_size(set, DONT_ALIGN);
- if (*used + set_save->members_size > len)
- goto unlock_set;
-
- /* Fill in set spefific members data */
- set->type->list_members(set, data + *used, DONT_ALIGN);
- *used += set_save->members_size;
- read_unlock_bh(&set->lock);
- DP("set members filled: %s, used: %d(%lu) %p %p", set->name, *used,
- (unsigned long)set_save->members_size, data, data + *used);
- return 0;
+static const struct nla_policy
+ip_set_adt_policy[IPSET_ATTR_CMD_MAX + 1] __read_mostly = {
+ [IPSET_ATTR_PROTOCOL] = { .type = NLA_U8 },
+ [IPSET_ATTR_SETNAME] = { .type = NLA_STRING,
+ .len = IPSET_MAXNAMELEN },
+ [IPSET_ATTR_LINENO] = { .type = NLA_U32 },
+ [IPSET_ATTR_DATA] = { .type = NLA_NESTED },
+ [IPSET_ATTR_ADT] = { .type = NLA_NESTED },
+};
- unlock_set:
- read_unlock_bh(&set->lock);
- not_enough_mem:
- DP("not enough mem, try again");
- return -EAGAIN;
+static int
+call_ad(struct sock *ctnl, struct sk_buff *skb,
+ const struct nlattr * const attr[],
+ struct ip_set *set, const struct nlattr *nla,
+ enum ipset_adt adt, uint32_t flags)
+{
+ struct nlattr *head = nla_data(nla);
+ int ret, len = nla_len(nla), retried = 0;
+ uint32_t lineno = 0;
+ bool eexist = flags & IPSET_FLAG_EXIST;
+
+ do {
+ write_lock_bh(&set->lock);
+ ret = set->variant->uadt(set, head, len, adt,
+ &lineno, flags);
+ write_unlock_bh(&set->lock);
+ } while (ret == -EAGAIN
+ && set->variant->resize
+ && (ret = set->variant->resize(set, retried++)) == 0);
+
+ if (!ret || (ret == -IPSET_ERR_EXIST && eexist))
+ return 0;
+ if (lineno && attr[IPSET_ATTR_LINENO]) {
+ /* Error in restore/batch mode: send back lineno */
+ uint32_t *errline = nla_data(attr[IPSET_ATTR_LINENO]);
+
+ *errline = lineno;
+ }
+
+ return ret;
}
-/*
- * Restore sets
- */
static int
-ip_set_restore(void *data, int len)
+ip_set_uadd(struct sock *ctnl, struct sk_buff *skb,
+ const struct nlmsghdr *nlh,
+ const struct nlattr * const attr[])
{
- int res = 0;
- int line = 0, used = 0, members_size;
struct ip_set *set;
- struct ip_set_restore *set_restore;
- ip_set_id_t index;
+ const struct nlattr *nla;
+ uint32_t flags = flag_exist(nlh);
+ int ret = 0;
- /* Loop to restore sets */
- while (1) {
- line++;
-
- DP("%d %zu %d", used, ALIGNED(sizeof(struct ip_set_restore)), len);
- /* Get and ensure header size */
- if (used + ALIGNED(sizeof(struct ip_set_restore)) > len)
- return line;
- set_restore = data + used;
- used += ALIGNED(sizeof(struct ip_set_restore));
-
- /* Ensure data size */
- if (used
- + set_restore->header_size
- + set_restore->members_size > len)
- return line;
-
- /* Check marker */
- if (set_restore->index == IP_SET_INVALID_ID) {
- line--;
- goto finish;
- }
-
- /* Try to create the set */
- DP("restore %s %s", set_restore->name, set_restore->typename);
- res = ip_set_create(set_restore->name,
- set_restore->typename,
- set_restore->index,
- data + used,
- set_restore->header_size);
+ if (unlikely(protocol_failed(attr)
+ || attr[IPSET_ATTR_SETNAME] == NULL
+ || !((attr[IPSET_ATTR_DATA] != NULL) ^
+ (attr[IPSET_ATTR_ADT] != NULL))
+ || (attr[IPSET_ATTR_DATA] != NULL
+ && !flag_nested(attr[IPSET_ATTR_DATA]))
+ || (attr[IPSET_ATTR_ADT] != NULL
+ && (!flag_nested(attr[IPSET_ATTR_ADT])
+ || attr[IPSET_ATTR_LINENO] == NULL))))
+ return -IPSET_ERR_PROTOCOL;
+
+ set = find_set(nla_data(attr[IPSET_ATTR_SETNAME]));
+ if (set == NULL)
+ return -EEXIST;
+
+ if (attr[IPSET_ATTR_DATA]) {
+ ret = call_ad(ctnl, skb, attr,
+ set, attr[IPSET_ATTR_DATA], IPSET_ADD, flags);
+ } else {
+ int nla_rem;
- if (res != 0)
- return line;
- used += ALIGNED(set_restore->header_size);
-
- index = ip_set_find_byindex(set_restore->index);
- DP("index %u, restore_index %u", index, set_restore->index);
- if (index != set_restore->index)
- return line;
- /* Try to restore members data */
- set = ip_set_list[index];
- members_size = 0;
- DP("members_size %lu reqsize %lu",
- (unsigned long)set_restore->members_size,
- (unsigned long)set->type->reqsize);
- while (members_size + ALIGNED(set->type->reqsize) <=
- set_restore->members_size) {
- line++;
- DP("members: %d, line %d", members_size, line);
- res = ip_set_addip(set,
- data + used + members_size,
- set->type->reqsize);
- if (!(res == 0 || res == -EEXIST))
- return line;
- members_size += ALIGNED(set->type->reqsize);
+ nla_for_each_nested(nla, attr[IPSET_ATTR_ADT], nla_rem) {
+ if (nla_type(nla) != IPSET_ATTR_DATA
+ || !flag_nested(nla))
+ return -IPSET_ERR_PROTOCOL;
+ ret = call_ad(ctnl, skb, attr,
+ set, nla, IPSET_ADD, flags);
+ if (ret < 0)
+ return ret;
}
-
- DP("members_size %lu %d",
- (unsigned long)set_restore->members_size, members_size);
- if (members_size != set_restore->members_size)
- return line++;
- used += set_restore->members_size;
}
-
- finish:
- if (used != len)
- return line;
-
- return 0;
+ return ret;
}
static int
-ip_set_sockfn_set(struct sock *sk, int optval, void *user, unsigned int len)
+ip_set_udel(struct sock *ctnl, struct sk_buff *skb,
+ const struct nlmsghdr *nlh,
+ const struct nlattr * const attr[])
{
- void *data;
- int res = 0; /* Assume OK */
- size_t offset;
- unsigned *op;
- struct ip_set_req_adt *req_adt;
- ip_set_id_t index = IP_SET_INVALID_ID;
- int (*adtfn)(struct ip_set *set,
- const void *data, u_int32_t size);
- struct fn_table {
- int (*fn)(struct ip_set *set,
- const void *data, u_int32_t size);
- } adtfn_table[] =
- { { ip_set_addip }, { ip_set_delip }, { ip_set_testip},
- };
-
- DP("optval=%d, user=%p, len=%d", optval, user, len);
- if (!capable(CAP_NET_ADMIN))
- return -EPERM;
- if (optval != SO_IP_SET)
- return -EBADF;
- if (len <= sizeof(unsigned)) {
- ip_set_printk("short userdata (want >%zu, got %u)",
- sizeof(unsigned), len);
- return -EINVAL;
- }
- data = vmalloc(len);
- if (!data) {
- DP("out of mem for %u bytes", len);
- return -ENOMEM;
- }
- if (copy_from_user(data, user, len) != 0) {
- res = -EFAULT;
- goto done;
- }
- if (down_interruptible(&ip_set_app_mutex)) {
- res = -EINTR;
- goto done;
- }
+ struct ip_set *set;
+ const struct nlattr *nla;
+ uint32_t flags = flag_exist(nlh);
+ int ret = 0;
- op = (unsigned *)data;
- DP("op=%x", *op);
+ if (unlikely(protocol_failed(attr)
+ || attr[IPSET_ATTR_SETNAME] == NULL
+ || !((attr[IPSET_ATTR_DATA] != NULL) ^
+ (attr[IPSET_ATTR_ADT] != NULL))
+ || (attr[IPSET_ATTR_DATA] != NULL
+ && !flag_nested(attr[IPSET_ATTR_DATA]))
+ || (attr[IPSET_ATTR_ADT] != NULL
+ && (!flag_nested(attr[IPSET_ATTR_ADT])
+ || attr[IPSET_ATTR_LINENO] == NULL))))
+ return -IPSET_ERR_PROTOCOL;
- if (*op < IP_SET_OP_VERSION) {
- /* Check the version at the beginning of operations */
- struct ip_set_req_version *req_version = data;
- if (!(req_version->version == IP_SET_PROTOCOL_UNALIGNED
- || req_version->version == IP_SET_PROTOCOL_VERSION)) {
- res = -EPROTO;
- goto done;
- }
- protocol_version = req_version->version;
- }
-
- switch (*op) {
- case IP_SET_OP_CREATE:{
- struct ip_set_req_create *req_create = data;
- offset = ALIGNED(sizeof(struct ip_set_req_create));
-
- if (len < offset) {
- ip_set_printk("short CREATE data (want >=%zu, got %u)",
- offset, len);
- res = -EINVAL;
- goto done;
- }
- req_create->name[IP_SET_MAXNAMELEN - 1] = '\0';
- req_create->typename[IP_SET_MAXNAMELEN - 1] = '\0';
- res = ip_set_create(req_create->name,
- req_create->typename,
- IP_SET_INVALID_ID,
- data + offset,
- len - offset);
- goto done;
- }
- case IP_SET_OP_DESTROY:{
- struct ip_set_req_std *req_destroy = data;
+ set = find_set(nla_data(attr[IPSET_ATTR_SETNAME]));
+ if (set == NULL)
+ return -EEXIST;
+
+ if (attr[IPSET_ATTR_DATA]) {
+ ret = call_ad(ctnl, skb, attr,
+ set, attr[IPSET_ATTR_DATA], IPSET_DEL, flags);
+ } else {
+ int nla_rem;
- if (len != sizeof(struct ip_set_req_std)) {
- ip_set_printk("invalid DESTROY data (want %zu, got %u)",
- sizeof(struct ip_set_req_std), len);
- res = -EINVAL;
- goto done;
- }
- if (STREQ(req_destroy->name, IPSET_TOKEN_ALL)) {
- /* Destroy all sets */
- index = IP_SET_INVALID_ID;
- } else {
- req_destroy->name[IP_SET_MAXNAMELEN - 1] = '\0';
- index = ip_set_find_byname(req_destroy->name);
-
- if (index == IP_SET_INVALID_ID) {
- res = -ENOENT;
- goto done;
- }
- }
-
- res = ip_set_destroy(index);
- goto done;
- }
- case IP_SET_OP_FLUSH:{
- struct ip_set_req_std *req_flush = data;
-
- if (len != sizeof(struct ip_set_req_std)) {
- ip_set_printk("invalid FLUSH data (want %zu, got %u)",
- sizeof(struct ip_set_req_std), len);
- res = -EINVAL;
- goto done;
+ nla_for_each_nested(nla, attr[IPSET_ATTR_ADT], nla_rem) {
+ if (nla_type(nla) != IPSET_ATTR_DATA
+ || !flag_nested(nla))
+ return -IPSET_ERR_PROTOCOL;
+ ret = call_ad(ctnl, skb, attr,
+ set, nla, IPSET_DEL, flags);
+ if (ret < 0)
+ return ret;
}
- if (STREQ(req_flush->name, IPSET_TOKEN_ALL)) {
- /* Flush all sets */
- index = IP_SET_INVALID_ID;
- } else {
- req_flush->name[IP_SET_MAXNAMELEN - 1] = '\0';
- index = ip_set_find_byname(req_flush->name);
-
- if (index == IP_SET_INVALID_ID) {
- res = -ENOENT;
- goto done;
- }
- }
- res = ip_set_flush(index);
- goto done;
}
- case IP_SET_OP_RENAME:{
- struct ip_set_req_create *req_rename = data;
-
- if (len != sizeof(struct ip_set_req_create)) {
- ip_set_printk("invalid RENAME data (want %zu, got %u)",
- sizeof(struct ip_set_req_create), len);
- res = -EINVAL;
- goto done;
- }
+ return ret;
+}
- req_rename->name[IP_SET_MAXNAMELEN - 1] = '\0';
- req_rename->typename[IP_SET_MAXNAMELEN - 1] = '\0';
-
- index = ip_set_find_byname(req_rename->name);
- if (index == IP_SET_INVALID_ID) {
- res = -ENOENT;
- goto done;
- }
- res = ip_set_rename(index, req_rename->typename);
- goto done;
- }
- case IP_SET_OP_SWAP:{
- struct ip_set_req_create *req_swap = data;
- ip_set_id_t to_index;
-
- if (len != sizeof(struct ip_set_req_create)) {
- ip_set_printk("invalid SWAP data (want %zu, got %u)",
- sizeof(struct ip_set_req_create), len);
- res = -EINVAL;
- goto done;
- }
+static int
+ip_set_utest(struct sock *ctnl, struct sk_buff *skb,
+ const struct nlmsghdr *nlh,
+ const struct nlattr * const attr[])
+{
+ struct ip_set *set;
+ int ret = 0;
- req_swap->name[IP_SET_MAXNAMELEN - 1] = '\0';
- req_swap->typename[IP_SET_MAXNAMELEN - 1] = '\0';
+ if (unlikely(protocol_failed(attr)
+ || attr[IPSET_ATTR_SETNAME] == NULL
+ || attr[IPSET_ATTR_DATA] == NULL
+ || !flag_nested(attr[IPSET_ATTR_DATA])))
+ return -IPSET_ERR_PROTOCOL;
+
+ set = find_set(nla_data(attr[IPSET_ATTR_SETNAME]));
+ if (set == NULL)
+ return -EEXIST;
+
+ read_lock_bh(&set->lock);
+ ret = set->variant->uadt(set,
+ nla_data(attr[IPSET_ATTR_DATA]),
+ nla_len(attr[IPSET_ATTR_DATA]),
+ IPSET_TEST, NULL, 0);
+ read_unlock_bh(&set->lock);
+ /* Userspace can't trigger element to be re-added */
+ if (ret == -EAGAIN)
+ ret = 1;
+
+ return ret < 0 ? ret : ret > 0 ? 0 : -IPSET_ERR_EXIST;
+}
- index = ip_set_find_byname(req_swap->name);
- if (index == IP_SET_INVALID_ID) {
- res = -ENOENT;
- goto done;
- }
- to_index = ip_set_find_byname(req_swap->typename);
- if (to_index == IP_SET_INVALID_ID) {
- res = -ENOENT;
- goto done;
- }
- res = ip_set_swap(index, to_index);
- goto done;
- }
- default:
- break; /* Set identified by id */
- }
+/* Get headed data of a set */
+
+static int
+ip_set_header(struct sock *ctnl, struct sk_buff *skb,
+ const struct nlmsghdr *nlh,
+ const struct nlattr * const attr[])
+{
+ struct ip_set *set;
+ struct sk_buff *skb2;
+ struct nlmsghdr *nlh2;
+ ip_set_id_t index;
+ int ret = 0;
+
+ if (unlikely(protocol_failed(attr)
+ || attr[IPSET_ATTR_SETNAME] == NULL))
+ return -IPSET_ERR_PROTOCOL;
- /* There we may have add/del/test/bind/unbind/test_bind operations */
- if (*op < IP_SET_OP_ADD_IP || *op > IP_SET_OP_TEST_IP) {
- res = -EBADMSG;
- goto done;
- }
- adtfn = adtfn_table[*op - IP_SET_OP_ADD_IP].fn;
+ index = find_set_id(nla_data(attr[IPSET_ATTR_SETNAME]));
+ if (index == IPSET_INVALID_ID)
+ return -EEXIST;
+ set = ip_set_list[index];
- if (len < ALIGNED(sizeof(struct ip_set_req_adt))) {
- ip_set_printk("short data in adt request (want >=%zu, got %u)",
- ALIGNED(sizeof(struct ip_set_req_adt)), len);
- res = -EINVAL;
- goto done;
- }
- req_adt = data;
+ skb2 = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ if (skb2 == NULL)
+ return -ENOMEM;
+
+ nlh2 = start_msg(skb2, NETLINK_CB(skb).pid, nlh->nlmsg_seq, 0,
+ IPSET_CMD_HEADER);
+ if (!nlh2)
+ goto nlmsg_failure;
+ NLA_PUT_U8(skb2, IPSET_ATTR_PROTOCOL, IPSET_PROTOCOL);
+ NLA_PUT_STRING(skb2, IPSET_ATTR_SETNAME, set->name);
+ NLA_PUT_STRING(skb2, IPSET_ATTR_TYPENAME, set->type->name);
+ NLA_PUT_U8(skb2, IPSET_ATTR_FAMILY, set->type->family);
+ NLA_PUT_U8(skb2, IPSET_ATTR_REVISION, set->type->revision);
+ nlmsg_end(skb2, nlh2);
+
+ ret = netlink_unicast(ctnl, skb2, NETLINK_CB(skb).pid, MSG_DONTWAIT);
+ if (ret < 0)
+ return -EFAULT;
+
+ return 0;
- index = ip_set_find_byindex(req_adt->index);
- if (index == IP_SET_INVALID_ID) {
- res = -ENOENT;
- goto done;
- }
- do {
- struct ip_set *set = ip_set_list[index];
- size_t offset = ALIGNED(sizeof(struct ip_set_req_adt));
+nla_put_failure:
+ nlmsg_cancel(skb2, nlh2);
+nlmsg_failure:
+ kfree_skb(skb2);
+ return -EFAULT;
+}
- IP_SET_ASSERT(set);
+/* Get type data */
- if (len - offset != set->type->reqsize) {
- ip_set_printk("data length wrong (want %lu, have %zu)",
- (long unsigned)set->type->reqsize,
- len - offset);
- res = -EINVAL;
- goto done;
+static const struct nla_policy
+ip_set_type_policy[IPSET_ATTR_CMD_MAX + 1] __read_mostly = {
+ [IPSET_ATTR_PROTOCOL] = { .type = NLA_U8 },
+ [IPSET_ATTR_TYPENAME] = { .type = NLA_STRING,
+ .len = IPSET_MAXNAMELEN },
+ [IPSET_ATTR_FAMILY] = { .type = NLA_U8 },
+};
+
+static bool
+find_set_type_minmax(const char *name, uint8_t family,
+ uint8_t *min, uint8_t *max)
+{
+ struct ip_set_type *type;
+ bool ret = false;
+
+ *min = *max = 0;
+ ip_set_type_list_lock();
+ list_for_each_entry(type, &ip_set_type_list, list)
+ if (STREQ(type->name, name)
+ && (type->family == family || type->family == AF_UNSPEC)) {
+ ret = true;
+ if (type->revision < *min)
+ *min = type->revision;
+ else if (type->revision > *max)
+ *max = type->revision;
}
- res = adtfn(set, data + offset, len - offset);
- } while (0);
-
- done:
- up(&ip_set_app_mutex);
- vfree(data);
- if (res > 0)
- res = 0;
- DP("final result %d", res);
- return res;
+ ip_set_type_list_unlock();
+
+ return ret;
}
static int
-ip_set_sockfn_get(struct sock *sk, int optval, void *user, int *len)
+ip_set_type(struct sock *ctnl, struct sk_buff *skb,
+ const struct nlmsghdr *nlh,
+ const struct nlattr * const attr[])
{
- int res = 0;
- unsigned *op;
- ip_set_id_t index = IP_SET_INVALID_ID;
- void *data;
- int copylen = *len;
-
- DP("optval=%d, user=%p, len=%d", optval, user, *len);
- if (!capable(CAP_NET_ADMIN))
- return -EPERM;
- if (optval != SO_IP_SET)
- return -EBADF;
- if (*len < sizeof(unsigned)) {
- ip_set_printk("short userdata (want >=%zu, got %d)",
- sizeof(unsigned), *len);
- return -EINVAL;
- }
- data = vmalloc(*len);
- if (!data) {
- DP("out of mem for %d bytes", *len);
- return -ENOMEM;
- }
- if (copy_from_user(data, user, *len) != 0) {
- res = -EFAULT;
- goto done;
- }
- if (down_interruptible(&ip_set_app_mutex)) {
- res = -EINTR;
- goto done;
- }
-
- op = (unsigned *) data;
- DP("op=%x", *op);
+ struct sk_buff *skb2;
+ struct nlmsghdr *nlh2;
+ uint8_t family, min, max;
+ const char *typename;
+ int ret = 0;
- if (*op < IP_SET_OP_VERSION) {
- /* Check the version at the beginning of operations */
- struct ip_set_req_version *req_version = data;
- if (!(req_version->version == IP_SET_PROTOCOL_UNALIGNED
- || req_version->version == IP_SET_PROTOCOL_VERSION)) {
- res = -EPROTO;
- goto done;
+ if (unlikely(protocol_failed(attr)
+ || attr[IPSET_ATTR_TYPENAME] == NULL
+ || attr[IPSET_ATTR_FAMILY] == NULL))
+ return -IPSET_ERR_PROTOCOL;
+
+ family = nla_get_u8(attr[IPSET_ATTR_FAMILY]);
+ typename = nla_data(attr[IPSET_ATTR_TYPENAME]);
+ if (!find_set_type_minmax(typename, family, &min, &max)) {
+ /* Try to load in the type module */
+ load_type_module(typename);
+ if (!find_set_type_minmax(typename, family, &min, &max)) {
+ D("can't find: %s, family: %u", typename, family);
+ return -EEXIST;
}
- protocol_version = req_version->version;
}
- switch (*op) {
- case IP_SET_OP_VERSION: {
- struct ip_set_req_version *req_version = data;
+ skb2 = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ if (skb2 == NULL)
+ return -ENOMEM;
+
+ nlh2 = start_msg(skb2, NETLINK_CB(skb).pid, nlh->nlmsg_seq, 0,
+ IPSET_CMD_TYPE);
+ if (!nlh2)
+ goto nlmsg_failure;
+ NLA_PUT_U8(skb2, IPSET_ATTR_PROTOCOL, IPSET_PROTOCOL);
+ NLA_PUT_STRING(skb2, IPSET_ATTR_TYPENAME, typename);
+ NLA_PUT_U8(skb2, IPSET_ATTR_FAMILY, family);
+ NLA_PUT_U8(skb2, IPSET_ATTR_REVISION, max);
+ NLA_PUT_U8(skb2, IPSET_ATTR_REVISION_MIN, min);
+ nlmsg_end(skb2, nlh2);
+
+ D("Send TYPE, nlmsg_len: %u", nlh2->nlmsg_len);
+ ret = netlink_unicast(ctnl, skb2, NETLINK_CB(skb).pid, MSG_DONTWAIT);
+ if (ret < 0)
+ return -EFAULT;
+
+ return 0;
- if (*len != sizeof(struct ip_set_req_version)) {
- ip_set_printk("invalid VERSION (want %zu, got %d)",
- sizeof(struct ip_set_req_version),
- *len);
- res = -EINVAL;
- goto done;
- }
+nla_put_failure:
+ nlmsg_cancel(skb2, nlh2);
+nlmsg_failure:
+ kfree_skb(skb2);
+ return -EFAULT;
+}
- req_version->version = IP_SET_PROTOCOL_VERSION;
- res = copy_to_user(user, req_version,
- sizeof(struct ip_set_req_version));
- goto done;
- }
- case IP_SET_OP_GET_BYNAME: {
- struct ip_set_req_get_set *req_get = data;
-
- if (*len != sizeof(struct ip_set_req_get_set)) {
- ip_set_printk("invalid GET_BYNAME (want %zu, got %d)",
- sizeof(struct ip_set_req_get_set), *len);
- res = -EINVAL;
- goto done;
- }
- req_get->set.name[IP_SET_MAXNAMELEN - 1] = '\0';
- index = ip_set_find_byname(req_get->set.name);
- req_get->set.index = index;
- goto copy;
- }
- case IP_SET_OP_GET_BYINDEX: {
- struct ip_set_req_get_set *req_get = data;
-
- if (*len != sizeof(struct ip_set_req_get_set)) {
- ip_set_printk("invalid GET_BYINDEX (want %zu, got %d)",
- sizeof(struct ip_set_req_get_set), *len);
- res = -EINVAL;
- goto done;
- }
- req_get->set.name[IP_SET_MAXNAMELEN - 1] = '\0';
- index = ip_set_find_byindex(req_get->set.index);
- strncpy(req_get->set.name,
- index == IP_SET_INVALID_ID ? ""
- : ip_set_list[index]->name, IP_SET_MAXNAMELEN);
- goto copy;
- }
- case IP_SET_OP_ADT_GET: {
- struct ip_set_req_adt_get *req_get = data;
-
- if (*len != sizeof(struct ip_set_req_adt_get)) {
- ip_set_printk("invalid ADT_GET (want %zu, got %d)",
- sizeof(struct ip_set_req_adt_get), *len);
- res = -EINVAL;
- goto done;
- }
- req_get->set.name[IP_SET_MAXNAMELEN - 1] = '\0';
- index = ip_set_find_byname(req_get->set.name);
- if (index != IP_SET_INVALID_ID) {
- req_get->set.index = index;
- strncpy(req_get->typename,
- ip_set_list[index]->type->typename,
- IP_SET_MAXNAMELEN - 1);
- } else {
- res = -ENOENT;
- goto done;
- }
- goto copy;
- }
- case IP_SET_OP_MAX_SETS: {
- struct ip_set_req_max_sets *req_max_sets = data;
- ip_set_id_t i;
-
- if (*len != sizeof(struct ip_set_req_max_sets)) {
- ip_set_printk("invalid MAX_SETS (want %zu, got %d)",
- sizeof(struct ip_set_req_max_sets), *len);
- res = -EINVAL;
- goto done;
- }
+/* Get protocol version */
- if (STREQ(req_max_sets->set.name, IPSET_TOKEN_ALL)) {
- req_max_sets->set.index = IP_SET_INVALID_ID;
- } else {
- req_max_sets->set.name[IP_SET_MAXNAMELEN - 1] = '\0';
- req_max_sets->set.index =
- ip_set_find_byname(req_max_sets->set.name);
- if (req_max_sets->set.index == IP_SET_INVALID_ID) {
- res = -ENOENT;
- goto done;
- }
- }
- req_max_sets->max_sets = ip_set_max;
- req_max_sets->sets = 0;
- for (i = 0; i < ip_set_max; i++) {
- if (ip_set_list[i] != NULL)
- req_max_sets->sets++;
- }
- goto copy;
- }
- case IP_SET_OP_LIST_SIZE:
- case IP_SET_OP_SAVE_SIZE: {
- struct ip_set_req_setnames *req_setnames = data;
- struct ip_set_name_list *name_list;
- struct ip_set *set;
- ip_set_id_t i;
- int used;
-
- if (*len < ALIGNED(sizeof(struct ip_set_req_setnames))) {
- ip_set_printk("short LIST_SIZE (want >=%zu, got %d)",
- ALIGNED(sizeof(struct ip_set_req_setnames)),
- *len);
- res = -EINVAL;
- goto done;
- }
+static const struct nla_policy
+ip_set_protocol_policy[IPSET_ATTR_CMD_MAX + 1] __read_mostly = {
+ [IPSET_ATTR_PROTOCOL] = { .type = NLA_U8 },
+};
- req_setnames->size = 0;
- used = ALIGNED(sizeof(struct ip_set_req_setnames));
- for (i = 0; i < ip_set_max; i++) {
- if (ip_set_list[i] == NULL)
- continue;
- name_list = data + used;
- used += ALIGNED(sizeof(struct ip_set_name_list));
- if (used > copylen) {
- res = -EAGAIN;
- goto done;
- }
- set = ip_set_list[i];
- /* Fill in index, name, etc. */
- name_list->index = i;
- name_list->id = set->id;
- strncpy(name_list->name,
- set->name,
- IP_SET_MAXNAMELEN - 1);
- strncpy(name_list->typename,
- set->type->typename,
- IP_SET_MAXNAMELEN - 1);
- DP("filled %s of type %s, index %u\n",
- name_list->name, name_list->typename,
- name_list->index);
- if (!(req_setnames->index == IP_SET_INVALID_ID
- || req_setnames->index == i))
- continue;
- /* Update size */
- req_setnames->size +=
- (*op == IP_SET_OP_LIST_SIZE ?
- ALIGNED(sizeof(struct ip_set_list)) :
- ALIGNED(sizeof(struct ip_set_save)))
- + ALIGNED(set->type->header_size)
- + set->type->list_members_size(set, DONT_ALIGN);
- }
- if (copylen != used) {
- res = -EAGAIN;
- goto done;
- }
- goto copy;
- }
- case IP_SET_OP_LIST: {
- struct ip_set_req_list *req_list = data;
- ip_set_id_t i;
- int used;
-
- if (*len < sizeof(struct ip_set_req_list)) {
- ip_set_printk("short LIST (want >=%zu, got %d)",
- sizeof(struct ip_set_req_list), *len);
- res = -EINVAL;
- goto done;
- }
- index = req_list->index;
- if (index != IP_SET_INVALID_ID
- && ip_set_find_byindex(index) != index) {
- res = -ENOENT;
- goto done;
- }
- used = 0;
- if (index == IP_SET_INVALID_ID) {
- /* List all sets */
- for (i = 0; i < ip_set_max && res == 0; i++) {
- if (ip_set_list[i] != NULL)
- res = ip_set_list_set(i, data, &used, *len);
- }
- } else {
- /* List an individual set */
- res = ip_set_list_set(index, data, &used, *len);
- }
- if (res != 0)
- goto done;
- else if (copylen != used) {
- res = -EAGAIN;
- goto done;
- }
- goto copy;
- }
- case IP_SET_OP_SAVE: {
- struct ip_set_req_list *req_save = data;
- ip_set_id_t i;
- int used;
-
- if (*len < sizeof(struct ip_set_req_list)) {
- ip_set_printk("short SAVE (want >=%zu, got %d)",
- sizeof(struct ip_set_req_list), *len);
- res = -EINVAL;
- goto done;
- }
- index = req_save->index;
- if (index != IP_SET_INVALID_ID
- && ip_set_find_byindex(index) != index) {
- res = -ENOENT;
- goto done;
- }
+static int
+ip_set_protocol(struct sock *ctnl, struct sk_buff *skb,
+ const struct nlmsghdr *nlh,
+ const struct nlattr * const attr[])
+{
+ struct sk_buff *skb2;
+ struct nlmsghdr *nlh2;
+ int ret = 0;
-#define SETLIST(set) (strcmp(set->type->typename, "setlist") == 0)
-
- used = 0;
- if (index == IP_SET_INVALID_ID) {
- /* Save all sets: ugly setlist type dependency */
- int setlist = 0;
- setlists:
- for (i = 0; i < ip_set_max && res == 0; i++) {
- if (ip_set_list[i] != NULL
- && !(setlist ^ SETLIST(ip_set_list[i])))
- res = ip_set_save_set(i, data, &used, *len);
- }
- if (!setlist) {
- setlist = 1;
- goto setlists;
- }
- } else {
- /* Save an individual set */
- res = ip_set_save_set(index, data, &used, *len);
- }
- if (res == 0)
- res = ip_set_save_marker(data, &used, *len);
-
- if (res != 0)
- goto done;
- else if (copylen != used) {
- res = -EAGAIN;
- goto done;
- }
- goto copy;
- }
- case IP_SET_OP_RESTORE: {
- struct ip_set_req_setnames *req_restore = data;
- size_t offset = ALIGNED(sizeof(struct ip_set_req_setnames));
- int line;
-
- if (*len < offset || *len != req_restore->size) {
- ip_set_printk("invalid RESTORE (want =%lu, got %d)",
- (long unsigned)req_restore->size, *len);
- res = -EINVAL;
- goto done;
- }
- line = ip_set_restore(data + offset, req_restore->size - offset);
- DP("ip_set_restore: %d", line);
- if (line != 0) {
- res = -EAGAIN;
- req_restore->size = line;
- copylen = sizeof(struct ip_set_req_setnames);
- goto copy;
- }
- goto done;
- }
- default:
- res = -EBADMSG;
- goto done;
- } /* end of switch(op) */
-
- copy:
- DP("set %s, copylen %d", index != IP_SET_INVALID_ID
- && ip_set_list[index]
- ? ip_set_list[index]->name
- : ":all:", copylen);
- res = copy_to_user(user, data, copylen);
-
- done:
- up(&ip_set_app_mutex);
- vfree(data);
- if (res > 0)
- res = 0;
- DP("final result %d", res);
- return res;
+ if (unlikely(attr[IPSET_ATTR_PROTOCOL] == NULL))
+ return -IPSET_ERR_PROTOCOL;
+
+ skb2 = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ if (skb2 == NULL)
+ return -ENOMEM;
+
+ nlh2 = start_msg(skb2, NETLINK_CB(skb).pid, nlh->nlmsg_seq, 0,
+ IPSET_CMD_PROTOCOL);
+ if (!nlh2)
+ goto nlmsg_failure;
+ NLA_PUT_U8(skb2, IPSET_ATTR_PROTOCOL, IPSET_PROTOCOL);
+ nlmsg_end(skb2, nlh2);
+
+ ret = netlink_unicast(ctnl, skb2, NETLINK_CB(skb).pid, MSG_DONTWAIT);
+ if (ret < 0)
+ return -EFAULT;
+
+ return 0;
+
+nla_put_failure:
+ nlmsg_cancel(skb2, nlh2);
+nlmsg_failure:
+ kfree_skb(skb2);
+ return -EFAULT;
}
-static struct nf_sockopt_ops so_set = {
- .pf = PF_INET,
- .set_optmin = SO_IP_SET,
- .set_optmax = SO_IP_SET + 1,
- .set = &ip_set_sockfn_set,
- .get_optmin = SO_IP_SET,
- .get_optmax = SO_IP_SET + 1,
- .get = &ip_set_sockfn_get,
-#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,23)
- .use = 0,
-#else
- .owner = THIS_MODULE,
-#endif
+static const struct nfnl_callback ip_set_netlink_subsys_cb[IPSET_MSG_MAX] = {
+ [IPSET_CMD_CREATE] = {
+ .call = ip_set_create,
+ .attr_count = IPSET_ATTR_CMD_MAX,
+ .policy = ip_set_create_policy,
+ },
+ [IPSET_CMD_DESTROY] = {
+ .call = ip_set_destroy,
+ .attr_count = IPSET_ATTR_CMD_MAX,
+ .policy = ip_set_setname_policy,
+ },
+ [IPSET_CMD_FLUSH] = {
+ .call = ip_set_flush,
+ .attr_count = IPSET_ATTR_CMD_MAX,
+ .policy = ip_set_setname_policy,
+ },
+ [IPSET_CMD_RENAME] = {
+ .call = ip_set_rename,
+ .attr_count = IPSET_ATTR_CMD_MAX,
+ .policy = ip_set_setname2_policy,
+ },
+ [IPSET_CMD_SWAP] = {
+ .call = ip_set_swap,
+ .attr_count = IPSET_ATTR_CMD_MAX,
+ .policy = ip_set_setname2_policy,
+ },
+ [IPSET_CMD_LIST] = {
+ .call = ip_set_dump,
+ .attr_count = IPSET_ATTR_CMD_MAX,
+ .policy = ip_set_setname_policy,
+ },
+ [IPSET_CMD_SAVE] = {
+ .call = ip_set_dump,
+ .attr_count = IPSET_ATTR_CMD_MAX,
+ .policy = ip_set_setname_policy,
+ },
+ [IPSET_CMD_ADD] = {
+ .call = ip_set_uadd,
+ .attr_count = IPSET_ATTR_CMD_MAX,
+ .policy = ip_set_adt_policy,
+ },
+ [IPSET_CMD_DEL] = {
+ .call = ip_set_udel,
+ .attr_count = IPSET_ATTR_CMD_MAX,
+ .policy = ip_set_adt_policy,
+ },
+ [IPSET_CMD_TEST] = {
+ .call = ip_set_utest,
+ .attr_count = IPSET_ATTR_CMD_MAX,
+ .policy = ip_set_adt_policy,
+ },
+ [IPSET_CMD_HEADER] = {
+ .call = ip_set_header,
+ .attr_count = IPSET_ATTR_CMD_MAX,
+ .policy = ip_set_setname_policy,
+ },
+ [IPSET_CMD_TYPE] = {
+ .call = ip_set_type,
+ .attr_count = IPSET_ATTR_CMD_MAX,
+ .policy = ip_set_type_policy,
+ },
+ [IPSET_CMD_PROTOCOL] = {
+ .call = ip_set_protocol,
+ .attr_count = IPSET_ATTR_CMD_MAX,
+ .policy = ip_set_protocol_policy,
+ },
};
-static int max_sets;
-
-module_param(max_sets, int, 0600);
-MODULE_PARM_DESC(max_sets, "maximal number of sets");
-MODULE_LICENSE("GPL");
-MODULE_AUTHOR("Jozsef Kadlecsik <kadlec@blackhole.kfki.hu>");
-MODULE_DESCRIPTION("module implementing core IP set support");
+static struct nfnetlink_subsystem ip_set_netlink_subsys = {
+ .name = "ip_set",
+ .subsys_id = NFNL_SUBSYS_IPSET,
+ .cb_count = IPSET_MSG_MAX,
+ .cb = ip_set_netlink_subsys_cb,
+};
static int __init
ip_set_init(void)
{
- int res;
-
- /* For the -rt branch, DECLARE_MUTEX/init_MUTEX avoided */
- sema_init(&ip_set_app_mutex, 1);
+ int ret;
if (max_sets)
ip_set_max = max_sets;
- if (ip_set_max >= IP_SET_INVALID_ID)
- ip_set_max = IP_SET_INVALID_ID - 1;
+ if (ip_set_max >= IPSET_INVALID_ID)
+ ip_set_max = IPSET_INVALID_ID - 1;
- ip_set_list = vmalloc(sizeof(struct ip_set *) * ip_set_max);
+ ip_set_list = kzalloc(sizeof(struct ip_set *) * ip_set_max, GFP_KERNEL);
if (!ip_set_list) {
printk(KERN_ERR "Unable to create ip_set_list\n");
return -ENOMEM;
}
- memset(ip_set_list, 0, sizeof(struct ip_set *) * ip_set_max);
- INIT_LIST_HEAD(&set_type_list);
+ INIT_LIST_HEAD(&ip_set_type_list);
- res = nf_register_sockopt(&so_set);
- if (res != 0) {
- ip_set_printk("SO_SET registry failed: %d", res);
- vfree(ip_set_list);
- return res;
+ ret = nfnetlink_subsys_register(&ip_set_netlink_subsys);
+ if (ret != 0) {
+ printk("ip_set_init: cannot register with nfnetlink.\n");
+ kfree(ip_set_list);
+ return ret;
}
- printk("ip_set version %u loaded\n", IP_SET_PROTOCOL_VERSION);
+ printk("ip_set with protocol version %u loaded\n", IPSET_PROTOCOL);
return 0;
}
static void __exit
ip_set_fini(void)
{
- /* There can't be any existing set or binding */
- nf_unregister_sockopt(&so_set);
- vfree(ip_set_list);
- DP("these are the famous last words");
+ /* There can't be any existing set */
+ nfnetlink_subsys_unregister(&ip_set_netlink_subsys);
+ kfree(ip_set_list);
+ D("these are the famous last words");
}
-EXPORT_SYMBOL(ip_set_register_set_type);
-EXPORT_SYMBOL(ip_set_unregister_set_type);
+EXPORT_SYMBOL(ip_set_type_register);
+EXPORT_SYMBOL(ip_set_type_unregister);
EXPORT_SYMBOL(ip_set_get_byname);
-EXPORT_SYMBOL(ip_set_get_byindex);
EXPORT_SYMBOL(ip_set_put_byindex);
-EXPORT_SYMBOL(ip_set_id);
-EXPORT_SYMBOL(__ip_set_get_byname);
-EXPORT_SYMBOL(__ip_set_put_byindex);
-EXPORT_SYMBOL(ip_set_addip_kernel);
-EXPORT_SYMBOL(ip_set_delip_kernel);
-EXPORT_SYMBOL(ip_set_testip_kernel);
+EXPORT_SYMBOL(ip_set_add);
+EXPORT_SYMBOL(ip_set_del);
+EXPORT_SYMBOL(ip_set_test);
module_init(ip_set_init);
module_exit(ip_set_fini);