summaryrefslogtreecommitdiffstats
path: root/kernel/ip_set.c
diff options
context:
space:
mode:
Diffstat (limited to 'kernel/ip_set.c')
-rw-r--r--kernel/ip_set.c1981
1 files changed, 1981 insertions, 0 deletions
diff --git a/kernel/ip_set.c b/kernel/ip_set.c
new file mode 100644
index 0000000..52741b1
--- /dev/null
+++ b/kernel/ip_set.c
@@ -0,0 +1,1981 @@
+/* Copyright (C) 2000-2002 Joakim Axelsson <gozem@linux.nu>
+ * Patrick Schaaf <bof@bof.de>
+ * Copyright (C) 2003-2004 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
+ * published by the Free Software Foundation.
+ */
+
+/* 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/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kmod.h>
+#include <linux/ip.h>
+#include <linux/skbuff.h>
+#include <linux/random.h>
+#include <linux/jhash.h>
+#include <linux/netfilter_ipv4/ip_tables.h>
+#include <linux/errno.h>
+#include <asm/uaccess.h>
+#include <asm/bitops.h>
+#include <asm/semaphore.h>
+#include <linux/spinlock.h>
+#include <linux/vmalloc.h>
+
+#define ASSERT_READ_LOCK(x)
+#define ASSERT_WRITE_LOCK(x)
+#include <linux/netfilter_ipv4/ip_set.h>
+
+static struct list_head 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 DECLARE_MUTEX(ip_set_app_mutex); /* serializes user access */
+static ip_set_id_t ip_set_max = CONFIG_IP_NF_SET_MAX;
+static ip_set_id_t ip_set_bindings_hash_size = CONFIG_IP_NF_SET_HASHSIZE;
+static struct list_head *ip_set_hash; /* hash of bindings */
+static unsigned int ip_set_hash_random; /* random seed */
+
+#define SETNAME_EQ(a,b) (strncmp(a,b,IP_SET_MAXNAMELEN) == 0)
+
+/*
+ * Sets are identified either by the index in ip_set_list or by id.
+ * The id never changes and is used to find a key in the hash.
+ * The index may change by swapping and used at all other places
+ * (set/SET netfilter modules, binding value, etc.)
+ *
+ * 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:
+ *
+ * - kernel requests: read and write locking mandatory
+ * - user requests: read locking optional, write locking mandatory
+ */
+
+static inline void
+__ip_set_get(ip_set_id_t index)
+{
+ atomic_inc(&ip_set_list[index]->ref);
+}
+
+static inline void
+__ip_set_put(ip_set_id_t index)
+{
+ atomic_dec(&ip_set_list[index]->ref);
+}
+
+/*
+ * Binding routines
+ */
+
+static inline struct ip_set_hash *
+__ip_set_find(u_int32_t key, ip_set_id_t id, ip_set_ip_t ip)
+{
+ struct ip_set_hash *set_hash;
+
+ list_for_each_entry(set_hash, &ip_set_hash[key], list)
+ if (set_hash->id == id && set_hash->ip == ip)
+ return set_hash;
+
+ return NULL;
+}
+
+static ip_set_id_t
+ip_set_find_in_hash(ip_set_id_t id, ip_set_ip_t ip)
+{
+ u_int32_t key = jhash_2words(id, ip, ip_set_hash_random)
+ % ip_set_bindings_hash_size;
+ struct ip_set_hash *set_hash;
+
+ ASSERT_READ_LOCK(&ip_set_lock);
+ IP_SET_ASSERT(ip_set_list[id]);
+ DP("set: %s, ip: %u.%u.%u.%u", ip_set_list[id]->name, HIPQUAD(ip));
+
+ set_hash = __ip_set_find(key, id, ip);
+
+ DP("set: %s, ip: %u.%u.%u.%u, binding: %s", ip_set_list[id]->name,
+ HIPQUAD(ip),
+ set_hash != NULL ? ip_set_list[set_hash->binding]->name : "");
+
+ return (set_hash != NULL ? set_hash->binding : IP_SET_INVALID_ID);
+}
+
+static inline void
+__set_hash_del(struct ip_set_hash *set_hash)
+{
+ ASSERT_WRITE_LOCK(&ip_set_lock);
+ IP_SET_ASSERT(ip_set_list[set_hash->binding]);
+
+ __ip_set_put(set_hash->binding);
+ list_del(&set_hash->list);
+ kfree(set_hash);
+}
+
+static int
+ip_set_hash_del(ip_set_id_t id, ip_set_ip_t ip)
+{
+ u_int32_t key = jhash_2words(id, ip, ip_set_hash_random)
+ % ip_set_bindings_hash_size;
+ struct ip_set_hash *set_hash;
+
+ IP_SET_ASSERT(ip_set_list[id]);
+ DP("set: %s, ip: %u.%u.%u.%u", ip_set_list[id]->name, HIPQUAD(ip));
+ write_lock_bh(&ip_set_lock);
+ set_hash = __ip_set_find(key, id, ip);
+ DP("set: %s, ip: %u.%u.%u.%u, binding: %s", ip_set_list[id]->name,
+ HIPQUAD(ip),
+ set_hash != NULL ? ip_set_list[set_hash->binding]->name : "");
+
+ if (set_hash != NULL)
+ __set_hash_del(set_hash);
+ write_unlock_bh(&ip_set_lock);
+ return 0;
+}
+
+static int
+ip_set_hash_add(ip_set_id_t id, ip_set_ip_t ip, ip_set_id_t binding)
+{
+ u_int32_t key = jhash_2words(id, ip, ip_set_hash_random)
+ % ip_set_bindings_hash_size;
+ struct ip_set_hash *set_hash;
+ int ret = 0;
+
+ IP_SET_ASSERT(ip_set_list[id]);
+ IP_SET_ASSERT(ip_set_list[binding]);
+ DP("set: %s, ip: %u.%u.%u.%u, binding: %s", ip_set_list[id]->name,
+ HIPQUAD(ip), ip_set_list[binding]->name);
+ write_lock_bh(&ip_set_lock);
+ set_hash = __ip_set_find(key, id, ip);
+ if (!set_hash) {
+ set_hash = kmalloc(sizeof(struct ip_set_hash), GFP_ATOMIC);
+ if (!set_hash) {
+ ret = -ENOMEM;
+ goto unlock;
+ }
+ INIT_LIST_HEAD(&set_hash->list);
+ set_hash->id = id;
+ set_hash->ip = ip;
+ list_add(&set_hash->list, &ip_set_hash[key]);
+ } else {
+ IP_SET_ASSERT(ip_set_list[set_hash->binding]);
+ DP("overwrite binding: %s",
+ ip_set_list[set_hash->binding]->name);
+ __ip_set_put(set_hash->binding);
+ }
+ set_hash->binding = binding;
+ __ip_set_get(set_hash->binding);
+ DP("stored: key %u, id %u (%s), ip %u.%u.%u.%u, binding %u (%s)",
+ key, id, ip_set_list[id]->name,
+ HIPQUAD(ip), binding, ip_set_list[binding]->name);
+ unlock:
+ write_unlock_bh(&ip_set_lock);
+ return ret;
+}
+
+#define FOREACH_HASH_DO(fn, args...) \
+({ \
+ ip_set_id_t __key; \
+ struct ip_set_hash *__set_hash; \
+ \
+ for (__key = 0; __key < ip_set_bindings_hash_size; __key++) { \
+ list_for_each_entry(__set_hash, &ip_set_hash[__key], list) \
+ fn(__set_hash , ## args); \
+ } \
+})
+
+#define FOREACH_HASH_RW_DO(fn, args...) \
+({ \
+ ip_set_id_t __key; \
+ struct ip_set_hash *__set_hash, *__n; \
+ \
+ ASSERT_WRITE_LOCK(&ip_set_lock); \
+ for (__key = 0; __key < ip_set_bindings_hash_size; __key++) { \
+ list_for_each_entry_safe(__set_hash, __n, &ip_set_hash[__key], list)\
+ fn(__set_hash , ## args); \
+ } \
+})
+
+/* Add, del and test set entries from kernel */
+
+#define follow_bindings(index, set, ip) \
+((index = ip_set_find_in_hash((set)->id, ip)) != IP_SET_INVALID_ID \
+ || (index = (set)->binding) != IP_SET_INVALID_ID)
+
+int
+ip_set_testip_kernel(ip_set_id_t index,
+ const struct sk_buff *skb,
+ const u_int32_t *flags)
+{
+ struct ip_set *set;
+ ip_set_ip_t ip;
+ int res;
+ unsigned char i = 0;
+
+ IP_SET_ASSERT(flags[i]);
+ read_lock_bh(&ip_set_lock);
+ do {
+ set = ip_set_list[index];
+ IP_SET_ASSERT(set);
+ DP("set %s, index %u", set->name, index);
+ read_lock_bh(&set->lock);
+ res = set->type->testip_kernel(set, skb, &ip, flags, i++);
+ read_unlock_bh(&set->lock);
+ i += !!(set->type->features & IPSET_DATA_DOUBLE);
+ } while (res > 0
+ && flags[i]
+ && follow_bindings(index, set, ip));
+ read_unlock_bh(&ip_set_lock);
+
+ return res;
+}
+
+void
+ip_set_addip_kernel(ip_set_id_t index,
+ const struct sk_buff *skb,
+ const u_int32_t *flags)
+{
+ struct ip_set *set;
+ ip_set_ip_t ip;
+ int res;
+ unsigned char i = 0;
+
+ IP_SET_ASSERT(flags[i]);
+ retry:
+ read_lock_bh(&ip_set_lock);
+ do {
+ set = ip_set_list[index];
+ IP_SET_ASSERT(set);
+ DP("set %s, index %u", set->name, index);
+ write_lock_bh(&set->lock);
+ res = set->type->addip_kernel(set, skb, &ip, flags, i++);
+ write_unlock_bh(&set->lock);
+ i += !!(set->type->features & IPSET_DATA_DOUBLE);
+ } while ((res == 0 || res == -EEXIST)
+ && flags[i]
+ && follow_bindings(index, set, ip));
+ read_unlock_bh(&ip_set_lock);
+
+ if (res == -EAGAIN
+ && set->type->retry
+ && (res = set->type->retry(set)) == 0)
+ goto retry;
+}
+
+void
+ip_set_delip_kernel(ip_set_id_t index,
+ const struct sk_buff *skb,
+ const u_int32_t *flags)
+{
+ struct ip_set *set;
+ ip_set_ip_t ip;
+ int res;
+ unsigned char i = 0;
+
+ IP_SET_ASSERT(flags[i]);
+ read_lock_bh(&ip_set_lock);
+ do {
+ set = ip_set_list[index];
+ IP_SET_ASSERT(set);
+ DP("set %s, index %u", set->name, index);
+ write_lock_bh(&set->lock);
+ res = set->type->delip_kernel(set, skb, &ip, flags, i++);
+ write_unlock_bh(&set->lock);
+ i += !!(set->type->features & IPSET_DATA_DOUBLE);
+ } while ((res == 0 || res == -EEXIST)
+ && flags[i]
+ && follow_bindings(index, set, ip));
+ read_unlock_bh(&ip_set_lock);
+}
+
+/* Register and deregister settype */
+
+static inline struct ip_set_type *
+find_set_type(const char *name)
+{
+ struct ip_set_type *set_type;
+
+ list_for_each_entry(set_type, &set_type_list, list)
+ if (!strncmp(set_type->typename, name, IP_SET_MAXNAMELEN - 1))
+ return set_type;
+ return NULL;
+}
+
+int
+ip_set_register_set_type(struct ip_set_type *set_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);
+ return -EINVAL;
+ }
+
+ write_lock_bh(&ip_set_lock);
+ if (find_set_type(set_type->typename)) {
+ /* Duplicate! */
+ ip_set_printk("'%s' already registered!",
+ set_type->typename);
+ 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);
+ return ret;
+}
+
+void
+ip_set_unregister_set_type(struct ip_set_type *set_type)
+{
+ write_lock_bh(&ip_set_lock);
+ if (!find_set_type(set_type->typename)) {
+ ip_set_printk("'%s' not registered?",
+ set_type->typename);
+ goto unlock;
+ }
+ list_del(&set_type->list);
+ module_put(THIS_MODULE);
+ DP("'%s' unregistered.", set_type->typename);
+ unlock:
+ write_unlock_bh(&ip_set_lock);
+
+}
+
+/*
+ * Userspace routines
+ */
+
+/*
+ * 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().
+ */
+ip_set_id_t
+ip_set_get_byname(const char *name)
+{
+ ip_set_id_t i, index = IP_SET_INVALID_ID;
+
+ down(&ip_set_app_mutex);
+ for (i = 0; i < ip_set_max; i++) {
+ if (ip_set_list[i] != NULL
+ && SETNAME_EQ(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);
+
+ 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;
+}
+
+/*
+ * 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.
+ */
+void ip_set_put(ip_set_id_t index)
+{
+ down(&ip_set_app_mutex);
+ if (ip_set_list[index])
+ __ip_set_put(index);
+ up(&ip_set_app_mutex);
+}
+
+/* Find a set by name or index */
+static ip_set_id_t
+ip_set_find_byname(const char *name)
+{
+ ip_set_id_t i, index = IP_SET_INVALID_ID;
+
+ for (i = 0; i < ip_set_max; i++) {
+ if (ip_set_list[i] != NULL
+ && SETNAME_EQ(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)
+{
+ if (index >= ip_set_max || ip_set_list[index] == NULL)
+ index = IP_SET_INVALID_ID;
+
+ return index;
+}
+
+/*
+ * Add, del, test, bind and unbind
+ */
+
+static inline int
+__ip_set_testip(struct ip_set *set,
+ const void *data,
+ size_t size,
+ ip_set_ip_t *ip)
+{
+ int res;
+
+ read_lock_bh(&set->lock);
+ res = set->type->testip(set, data, size, ip);
+ read_unlock_bh(&set->lock);
+
+ return res;
+}
+
+static int
+__ip_set_addip(ip_set_id_t index,
+ const void *data,
+ size_t size)
+{
+ struct ip_set *set = ip_set_list[index];
+ ip_set_ip_t ip;
+ int res;
+
+ IP_SET_ASSERT(set);
+ do {
+ write_lock_bh(&set->lock);
+ res = set->type->addip(set, data, size, &ip);
+ write_unlock_bh(&set->lock);
+ } while (res == -EAGAIN
+ && set->type->retry
+ && (res = set->type->retry(set)) == 0);
+
+ return res;
+}
+
+static int
+ip_set_addip(ip_set_id_t index,
+ const void *data,
+ size_t size)
+{
+
+ return __ip_set_addip(index,
+ data + sizeof(struct ip_set_req_adt),
+ size - sizeof(struct ip_set_req_adt));
+}
+
+static int
+ip_set_delip(ip_set_id_t index,
+ const void *data,
+ size_t size)
+{
+ struct ip_set *set = ip_set_list[index];
+ ip_set_ip_t ip;
+ int res;
+
+ IP_SET_ASSERT(set);
+ write_lock_bh(&set->lock);
+ res = set->type->delip(set,
+ data + sizeof(struct ip_set_req_adt),
+ size - sizeof(struct ip_set_req_adt),
+ &ip);
+ write_unlock_bh(&set->lock);
+
+ return res;
+}
+
+static int
+ip_set_testip(ip_set_id_t index,
+ const void *data,
+ size_t size)
+{
+ struct ip_set *set = ip_set_list[index];
+ ip_set_ip_t ip;
+ int res;
+
+ IP_SET_ASSERT(set);
+ res = __ip_set_testip(set,
+ data + sizeof(struct ip_set_req_adt),
+ size - sizeof(struct ip_set_req_adt),
+ &ip);
+
+ return (res > 0 ? -EEXIST : res);
+}
+
+static int
+ip_set_bindip(ip_set_id_t index,
+ const void *data,
+ size_t size)
+{
+ struct ip_set *set = ip_set_list[index];
+ const struct ip_set_req_bind *req_bind;
+ ip_set_id_t binding;
+ ip_set_ip_t ip;
+ int res;
+
+ IP_SET_ASSERT(set);
+ if (size < sizeof(struct ip_set_req_bind))
+ return -EINVAL;
+
+ req_bind = data;
+
+ if (SETNAME_EQ(req_bind->binding, IPSET_TOKEN_DEFAULT)) {
+ /* Default binding of a set */
+ const char *binding_name;
+
+ if (size != sizeof(struct ip_set_req_bind) + IP_SET_MAXNAMELEN)
+ return -EINVAL;
+
+ binding_name = data + sizeof(struct ip_set_req_bind);
+
+ binding = ip_set_find_byname(binding_name);
+ if (binding == IP_SET_INVALID_ID)
+ return -ENOENT;
+
+ write_lock_bh(&ip_set_lock);
+ /* Sets as binding values are referenced */
+ if (set->binding != IP_SET_INVALID_ID)
+ __ip_set_put(set->binding);
+ set->binding = binding;
+ __ip_set_get(set->binding);
+ write_unlock_bh(&ip_set_lock);
+
+ return 0;
+ }
+ binding = ip_set_find_byname(req_bind->binding);
+ if (binding == IP_SET_INVALID_ID)
+ return -ENOENT;
+
+ res = __ip_set_testip(set,
+ data + sizeof(struct ip_set_req_bind),
+ size - sizeof(struct ip_set_req_bind),
+ &ip);
+ DP("set %s, ip: %u.%u.%u.%u, binding %s",
+ set->name, HIPQUAD(ip), ip_set_list[binding]->name);
+
+ if (res >= 0)
+ res = ip_set_hash_add(set->id, ip, binding);
+
+ return res;
+}
+
+#define FOREACH_SET_DO(fn, args...) \
+({ \
+ ip_set_id_t __i; \
+ struct ip_set *__set; \
+ \
+ for (__i = 0; __i < ip_set_max; __i++) { \
+ __set = ip_set_list[__i]; \
+ if (__set != NULL) \
+ fn(__set , ##args); \
+ } \
+})
+
+static inline void
+__set_hash_del_byid(struct ip_set_hash *set_hash, ip_set_id_t id)
+{
+ if (set_hash->id == id)
+ __set_hash_del(set_hash);
+}
+
+static inline void
+__unbind_default(struct ip_set *set)
+{
+ if (set->binding != IP_SET_INVALID_ID) {
+ /* Sets as binding values are referenced */
+ __ip_set_put(set->binding);
+ set->binding = IP_SET_INVALID_ID;
+ }
+}
+
+static int
+ip_set_unbindip(ip_set_id_t index,
+ const void *data,
+ size_t size)
+{
+ struct ip_set *set;
+ const struct ip_set_req_bind *req_bind;
+ ip_set_ip_t ip;
+ int res;
+
+ DP("");
+ if (size < sizeof(struct ip_set_req_bind))
+ return -EINVAL;
+
+ req_bind = data;
+
+ DP("%u %s", index, req_bind->binding);
+ if (index == IP_SET_INVALID_ID) {
+ /* unbind :all: */
+ if (SETNAME_EQ(req_bind->binding, IPSET_TOKEN_DEFAULT)) {
+ /* Default binding of sets */
+ write_lock_bh(&ip_set_lock);
+ FOREACH_SET_DO(__unbind_default);
+ write_unlock_bh(&ip_set_lock);
+ return 0;
+ } else if (SETNAME_EQ(req_bind->binding, IPSET_TOKEN_ALL)) {
+ /* Flush all bindings of all sets*/
+ write_lock_bh(&ip_set_lock);
+ FOREACH_HASH_RW_DO(__set_hash_del);
+ write_unlock_bh(&ip_set_lock);
+ return 0;
+ }
+ DP("unreachable reached!");
+ return -EINVAL;
+ }
+
+ set = ip_set_list[index];
+ IP_SET_ASSERT(set);
+ if (SETNAME_EQ(req_bind->binding, IPSET_TOKEN_DEFAULT)) {
+ /* Default binding of set */
+ ip_set_id_t binding = ip_set_find_byindex(set->binding);
+
+ if (binding == IP_SET_INVALID_ID)
+ return -ENOENT;
+
+ write_lock_bh(&ip_set_lock);
+ /* Sets in hash values are referenced */
+ __ip_set_put(set->binding);
+ set->binding = IP_SET_INVALID_ID;
+ write_unlock_bh(&ip_set_lock);
+
+ return 0;
+ } else if (SETNAME_EQ(req_bind->binding, IPSET_TOKEN_ALL)) {
+ /* Flush all bindings */
+
+ write_lock_bh(&ip_set_lock);
+ FOREACH_HASH_RW_DO(__set_hash_del_byid, set->id);
+ write_unlock_bh(&ip_set_lock);
+ return 0;
+ }
+
+ res = __ip_set_testip(set,
+ data + sizeof(struct ip_set_req_bind),
+ size - sizeof(struct ip_set_req_bind),
+ &ip);
+
+ DP("set %s, ip: %u.%u.%u.%u", set->name, HIPQUAD(ip));
+ if (res >= 0)
+ res = ip_set_hash_del(set->id, ip);
+
+ return res;
+}
+
+static int
+ip_set_testbind(ip_set_id_t index,
+ const void *data,
+ size_t size)
+{
+ struct ip_set *set = ip_set_list[index];
+ const struct ip_set_req_bind *req_bind;
+ ip_set_id_t binding;
+ ip_set_ip_t ip;
+ int res;
+
+ IP_SET_ASSERT(set);
+ if (size < sizeof(struct ip_set_req_bind))
+ return -EINVAL;
+
+ req_bind = data;
+
+ if (SETNAME_EQ(req_bind->binding, IPSET_TOKEN_DEFAULT)) {
+ /* Default binding of set */
+ const char *binding_name;
+
+ if (size != sizeof(struct ip_set_req_bind) + IP_SET_MAXNAMELEN)
+ return -EINVAL;
+
+ binding_name = data + sizeof(struct ip_set_req_bind);
+
+ binding = ip_set_find_byname(binding_name);
+ if (binding == IP_SET_INVALID_ID)
+ return -ENOENT;
+
+ res = (set->binding == binding) ? -EEXIST : 0;
+
+ return res;
+ }
+ binding = ip_set_find_byname(req_bind->binding);
+ if (binding == IP_SET_INVALID_ID)
+ return -ENOENT;
+
+
+ res = __ip_set_testip(set,
+ data + sizeof(struct ip_set_req_bind),
+ size - sizeof(struct ip_set_req_bind),
+ &ip);
+ DP("set %s, ip: %u.%u.%u.%u, binding %s",
+ set->name, HIPQUAD(ip), ip_set_list[binding]->name);
+
+ if (res >= 0)
+ res = (ip_set_find_in_hash(set->id, ip) == binding)
+ ? -EEXIST : 0;
+
+ return res;
+}
+
+static struct ip_set_type *
+find_set_type_rlock(const char *typename)
+{
+ struct ip_set_type *type;
+
+ read_lock_bh(&ip_set_lock);
+ type = find_set_type(typename);
+ if (type == NULL)
+ read_unlock_bh(&ip_set_lock);
+
+ return type;
+}
+
+static int
+find_free_id(const char *name,
+ ip_set_id_t *index,
+ ip_set_id_t *id)
+{
+ ip_set_id_t i;
+
+ *id = IP_SET_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 (SETNAME_EQ(name, ip_set_list[i]->name))
+ /* Name clash */
+ 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;
+ }
+ }
+ return 0;
+}
+
+/*
+ * Create a set
+ */
+static int
+ip_set_create(const char *name,
+ const char *typename,
+ ip_set_id_t restore,
+ const void *data,
+ size_t size)
+{
+ struct ip_set *set;
+ ip_set_id_t index = 0, id;
+ int res = 0;
+
+ DP("setname: %s, typename: %s, id: %u", name, typename, restore);
+ /*
+ * First, and without any locks, allocate and initialize
+ * a normal base set structure.
+ */
+ set = kmalloc(sizeof(struct ip_set), GFP_KERNEL);
+ if (!set)
+ return -ENOMEM;
+ set->lock = RW_LOCK_UNLOCKED;
+ strncpy(set->name, name, IP_SET_MAXNAMELEN);
+ set->binding = IP_SET_INVALID_ID;
+ 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.
+ *
+ * After referencing the type, we drop the &ip_set_lock,
+ * and let the new set construction run without locks.
+ */
+ set->type = find_set_type_rlock(typename);
+ 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;
+ }
+ if (!try_module_get(set->type->me)) {
+ read_unlock_bh(&ip_set_lock);
+ res = -EFAULT;
+ goto out;
+ }
+ read_unlock_bh(&ip_set_lock);
+
+ /*
+ * Without holding any locks, create private part.
+ */
+ res = set->type->create(set, data, size);
+ if (res != 0)
+ goto put_out;
+
+ /* BTW, res==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.
+ */
+ write_lock_bh(&ip_set_lock);
+ if ((res = find_free_id(set->name, &index, &id)) != 0) {
+ DP("no free id!");
+ 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;
+ ip_set_list[index] = set;
+ write_unlock_bh(&ip_set_lock);
+ return res;
+
+ cleanup:
+ write_unlock_bh(&ip_set_lock);
+ set->type->destroy(set);
+ put_out:
+ module_put(set->type->me);
+ out:
+ kfree(set);
+ return res;
+}
+
+/*
+ * Destroy a given existing set
+ */
+static 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);
+ FOREACH_HASH_RW_DO(__set_hash_del_byid, set->id);
+ if (set->binding != IP_SET_INVALID_ID)
+ __ip_set_put(set->binding);
+ ip_set_list[index] = NULL;
+ write_unlock_bh(&ip_set_lock);
+
+ /* Must call it without holding any lock */
+ set->type->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_id_t i;
+
+ /* 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 {
+ for (i = 0; i < ip_set_max; i++) {
+ if (ip_set_list[i] != NULL
+ && (atomic_read(&ip_set_list[i]->ref)))
+ return -EBUSY;
+ }
+
+ for (i = 0; i < ip_set_max; i++) {
+ if (ip_set_list[i] != NULL)
+ ip_set_destroy_set(i);
+ }
+ }
+ return 0;
+}
+
+static void
+ip_set_flush_set(struct ip_set *set)
+{
+ DP("set: %s %u", set->name, set->id);
+
+ write_lock_bh(&set->lock);
+ set->type->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)
+{
+ if (index != IP_SET_INVALID_ID) {
+ IP_SET_ASSERT(ip_set_list[index]);
+ ip_set_flush_set(ip_set_list[index]);
+ } else
+ FOREACH_SET_DO(ip_set_flush_set);
+
+ return 0;
+}
+
+/* Rename a set */
+static int
+ip_set_rename(ip_set_id_t index, const char *name)
+{
+ struct ip_set *set = ip_set_list[index];
+ ip_set_id_t i;
+ int res = 0;
+
+ DP("set: %s to %s", set->name, name);
+ write_lock_bh(&ip_set_lock);
+ for (i = 0; i < ip_set_max; i++) {
+ if (ip_set_list[i] != NULL
+ && SETNAME_EQ(ip_set_list[i]->name, name)) {
+ res = -EEXIST;
+ goto unlock;
+ }
+ }
+ strncpy(set->name, name, IP_SET_MAXNAMELEN);
+ unlock:
+ write_unlock_bh(&ip_set_lock);
+ return res;
+}
+
+/*
+ * 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)
+{
+ 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;
+
+ DP("set: %s to %s", from->name, to->name);
+ /* Features must not change. Artifical restriction. */
+ if (from->type->features != to->type->features)
+ return -ENOEXEC;
+
+ /* No magic here: ref munging protected by the mutex */
+ write_lock_bh(&ip_set_lock);
+ strncpy(from_name, from->name, IP_SET_MAXNAMELEN);
+ from_ref = atomic_read(&from->ref);
+
+ strncpy(from->name, to->name, IP_SET_MAXNAMELEN);
+ atomic_set(&from->ref, atomic_read(&to->ref));
+ strncpy(to->name, from_name, IP_SET_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);
+ return 0;
+}
+
+/*
+ * List set data
+ */
+
+static inline void
+__set_hash_bindings_size_list(struct ip_set_hash *set_hash,
+ ip_set_id_t id, size_t *size)
+{
+ if (set_hash->id == id)
+ *size += sizeof(struct ip_set_hash_list);
+}
+
+static inline void
+__set_hash_bindings_size_save(struct ip_set_hash *set_hash,
+ ip_set_id_t id, size_t *size)
+{
+ if (set_hash->id == id)
+ *size += sizeof(struct ip_set_hash_save);
+}
+
+static inline void
+__set_hash_bindings(struct ip_set_hash *set_hash,
+ ip_set_id_t id, void *data, int *used)
+{
+ if (set_hash->id == id) {
+ struct ip_set_hash_list *hash_list = data + *used;
+
+ hash_list->ip = set_hash->ip;
+ hash_list->binding = set_hash->binding;
+ *used += sizeof(struct ip_set_hash_list);
+ }
+}
+
+static int ip_set_list_set(ip_set_id_t index,
+ void *data,
+ int *used,
+ int len)
+{
+ 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 %p %p", set->name, *used, data, data + *used);
+
+ /* Get and ensure header size */
+ if (*used + sizeof(struct ip_set_list) > len)
+ goto not_enough_mem;
+ *used += sizeof(struct ip_set_list);
+
+ read_lock_bh(&set->lock);
+ /* Get and ensure set specific header size */
+ set_list->header_size = 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 = set->binding;
+ 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);
+ if (*used + set_list->members_size > len)
+ goto unlock_set;
+
+ /* Fill in set spefific members data */
+ set->type->list_members(set, data + *used);
+ *used += set_list->members_size;
+ read_unlock_bh(&set->lock);
+
+ /* Bindings */
+
+ /* Get and ensure set specific bindings size */
+ set_list->bindings_size = 0;
+ FOREACH_HASH_DO(__set_hash_bindings_size_list,
+ set->id, &set_list->bindings_size);
+ if (*used + set_list->bindings_size > len)
+ goto not_enough_mem;
+
+ /* Fill in set spefific bindings data */
+ FOREACH_HASH_DO(__set_hash_bindings, set->id, data, used);
+
+ return 0;
+
+ unlock_set:
+ read_unlock_bh(&set->lock);
+ not_enough_mem:
+ DP("not enough mem, try again");
+ return -EAGAIN;
+}
+
+/*
+ * Save sets
+ */
+static int ip_set_save_set(ip_set_id_t index,
+ void *data,
+ int *used,
+ int len)
+{
+ struct ip_set *set;
+ struct ip_set_save *set_save;
+
+ /* Pointer to our header */
+ set_save = data + *used;
+
+ /* Get and ensure header size */
+ if (*used + sizeof(struct ip_set_save) > len)
+ goto not_enough_mem;
+ *used += sizeof(struct ip_set_save);
+
+ set = ip_set_list[index];
+ DP("set: %s, used: %u(%u) %p %p", set->name, *used, len,
+ data, data + *used);
+
+ read_lock_bh(&set->lock);
+ /* Get and ensure set specific header size */
+ set_save->header_size = 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 = set->binding;
+
+ /* Fill in set spefific header data */
+ set->type->list_header(set, data + *used);
+ *used += set_save->header_size;
+
+ DP("set header filled: %s, used: %u(%u) %p %p", set->name, *used,
+ set_save->header_size, data, data + *used);
+ /* Get and ensure set specific members size */
+ set_save->members_size = set->type->list_members_size(set);
+ if (*used + set_save->members_size > len)
+ goto unlock_set;
+
+ /* Fill in set spefific members data */
+ set->type->list_members(set, data + *used);
+ *used += set_save->members_size;
+ read_unlock_bh(&set->lock);
+ DP("set members filled: %s, used: %u(%u) %p %p", set->name, *used,
+ set_save->members_size, data, data + *used);
+ return 0;
+
+ unlock_set:
+ read_unlock_bh(&set->lock);
+ not_enough_mem:
+ DP("not enough mem, try again");
+ return -EAGAIN;
+}
+
+static inline void
+__set_hash_save_bindings(struct ip_set_hash *set_hash,
+ ip_set_id_t id,
+ void *data,
+ int *used,
+ int len,
+ int *res)
+{
+ if (*res == 0
+ && (id == IP_SET_INVALID_ID || set_hash->id == id)) {
+ struct ip_set_hash_save *hash_save = data + *used;
+ /* Ensure bindings size */
+ if (*used + sizeof(struct ip_set_hash_save) > len) {
+ *res = -ENOMEM;
+ return;
+ }
+ hash_save->id = set_hash->id;
+ hash_save->ip = set_hash->ip;
+ hash_save->binding = set_hash->binding;
+ *used += sizeof(struct ip_set_hash_save);
+ }
+}
+
+static int ip_set_save_bindings(ip_set_id_t index,
+ void *data,
+ int *used,
+ int len)
+{
+ int res = 0;
+ struct ip_set_save *set_save;
+
+ DP("used %u, len %u", *used, len);
+ /* Get and ensure header size */
+ if (*used + sizeof(struct ip_set_save) > len)
+ return -ENOMEM;
+
+ /* Marker */
+ set_save = data + *used;
+ set_save->index = IP_SET_INVALID_ID;
+ set_save->header_size = 0;
+ set_save->members_size = 0;
+ *used += sizeof(struct ip_set_save);
+
+ DP("marker added used %u, len %u", *used, len);
+ /* Fill in bindings data */
+ if (index != IP_SET_INVALID_ID)
+ /* Sets are identified by id in hash */
+ index = ip_set_list[index]->id;
+ FOREACH_HASH_DO(__set_hash_save_bindings, index, data, used, len, &res);
+
+ return res;
+}
+
+/*
+ * Restore sets
+ */
+static int ip_set_restore(void *data,
+ int len)
+{
+ int res = 0;
+ int line = 0, used = 0, members_size;
+ struct ip_set *set;
+ struct ip_set_hash_save *hash_save;
+ struct ip_set_restore *set_restore;
+ ip_set_id_t index;
+
+ /* Loop to restore sets */
+ while (1) {
+ line++;
+
+ DP("%u %u %u", used, sizeof(struct ip_set_restore), len);
+ /* Get and ensure header size */
+ if (used + sizeof(struct ip_set_restore) > len)
+ return line;
+ set_restore = data + used;
+ used += 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 bindings;
+ }
+
+ /* 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 (res != 0)
+ return line;
+ used += 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 %u reqsize %u",
+ set_restore->members_size, set->type->reqsize);
+ while (members_size + set->type->reqsize <=
+ set_restore->members_size) {
+ line++;
+ DP("members: %u, line %u", members_size, line);
+ res = __ip_set_addip(index,
+ data + used + members_size,
+ set->type->reqsize);
+ if (!(res == 0 || res == -EEXIST))
+ return line;
+ members_size += set->type->reqsize;
+ }
+
+ DP("members_size %u %u",
+ set_restore->members_size, members_size);
+ if (members_size != set_restore->members_size)
+ return line++;
+ used += set_restore->members_size;
+ }
+
+ bindings:
+ /* Loop to restore bindings */
+ while (used < len) {
+ line++;
+
+ DP("restore binding, line %u", line);
+ /* Get and ensure size */
+ if (used + sizeof(struct ip_set_hash_save) > len)
+ return line;
+ hash_save = data + used;
+ used += sizeof(struct ip_set_hash_save);
+
+ /* hash_save->id is used to store the index */
+ index = ip_set_find_byindex(hash_save->id);
+ DP("restore binding index %u, id %u, %u -> %u",
+ index, hash_save->id, hash_save->ip, hash_save->binding);
+ if (index != hash_save->id)
+ return line;
+ if (ip_set_find_byindex(hash_save->binding) == IP_SET_INVALID_ID) {
+ DP("corrupt binding set index %u", hash_save->binding);
+ return line;
+ }
+ set = ip_set_list[hash_save->id];
+ /* Null valued IP means default binding */
+ if (hash_save->ip)
+ res = ip_set_hash_add(set->id,
+ hash_save->ip,
+ hash_save->binding);
+ else {
+ IP_SET_ASSERT(set->binding == IP_SET_INVALID_ID);
+ write_lock_bh(&ip_set_lock);
+ set->binding = hash_save->binding;
+ __ip_set_get(set->binding);
+ write_unlock_bh(&ip_set_lock);
+ DP("default binding: %u", set->binding);
+ }
+ if (res != 0)
+ return line;
+ }
+ if (used != len)
+ return line;
+
+ return 0;
+}
+
+static int
+ip_set_sockfn_set(struct sock *sk, int optval, void *user, unsigned int len)
+{
+ void *data;
+ int res = 0; /* Assume OK */
+ unsigned *op;
+ struct ip_set_req_adt *req_adt;
+ ip_set_id_t index = IP_SET_INVALID_ID;
+ int (*adtfn)(ip_set_id_t index,
+ const void *data, size_t size);
+ struct fn_table {
+ int (*fn)(ip_set_id_t index,
+ const void *data, size_t size);
+ } adtfn_table[] =
+ { { ip_set_addip }, { ip_set_delip }, { ip_set_testip},
+ { ip_set_bindip}, { ip_set_unbindip }, { ip_set_testbind },
+ };
+
+ 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;
+ }
+
+ op = (unsigned *)data;
+ DP("op=%x", *op);
+
+ 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_VERSION) {
+ res = -EPROTO;
+ goto done;
+ }
+ }
+
+ switch (*op) {
+ case IP_SET_OP_CREATE:{
+ struct ip_set_req_create *req_create = data;
+
+ if (len < sizeof(struct ip_set_req_create)) {
+ ip_set_printk("short CREATE data (want >=%zu, got %u)",
+ sizeof(struct ip_set_req_create), 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 + sizeof(struct ip_set_req_create),
+ len - sizeof(struct ip_set_req_create));
+ goto done;
+ }
+ case IP_SET_OP_DESTROY:{
+ struct ip_set_req_std *req_destroy = data;
+
+ 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 (SETNAME_EQ(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;
+ }
+ if (SETNAME_EQ(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;
+ }
+
+ 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;
+ }
+
+ req_swap->name[IP_SET_MAXNAMELEN - 1] = '\0';
+ req_swap->typename[IP_SET_MAXNAMELEN - 1] = '\0';
+
+ 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 */
+ }
+
+ /* There we may have add/del/test/bind/unbind/test_bind operations */
+ if (*op < IP_SET_OP_ADD_IP || *op > IP_SET_OP_TEST_BIND_SET) {
+ res = -EBADMSG;
+ goto done;
+ }
+ adtfn = adtfn_table[*op - IP_SET_OP_ADD_IP].fn;
+
+ if (len < sizeof(struct ip_set_req_adt)) {
+ ip_set_printk("short data in adt request (want >=%zu, got %u)",
+ sizeof(struct ip_set_req_adt), len);
+ res = -EINVAL;
+ goto done;
+ }
+ req_adt = data;
+
+ /* -U :all: :all:|:default: uses IP_SET_INVALID_ID */
+ if (!(*op == IP_SET_OP_UNBIND_SET
+ && req_adt->index == IP_SET_INVALID_ID)) {
+ index = ip_set_find_byindex(req_adt->index);
+ if (index == IP_SET_INVALID_ID) {
+ res = -ENOENT;
+ goto done;
+ }
+ }
+ res = adtfn(index, data, len);
+
+ done:
+ up(&ip_set_app_mutex);
+ vfree(data);
+ if (res > 0)
+ res = 0;
+ DP("final result %d", res);
+ return res;
+}
+
+static int
+ip_set_sockfn_get(struct sock *sk, int optval, void *user, int *len)
+{
+ 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);
+
+ 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_VERSION) {
+ res = -EPROTO;
+ goto done;
+ }
+ }
+
+ switch (*op) {
+ case IP_SET_OP_VERSION: {
+ struct ip_set_req_version *req_version = data;
+
+ 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;
+ }
+
+ 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;
+ }
+
+ if (SETNAME_EQ(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 < sizeof(struct ip_set_req_setnames)) {
+ ip_set_printk("short LIST_SIZE (want >=%zu, got %d)",
+ sizeof(struct ip_set_req_setnames), *len);
+ res = -EINVAL;
+ goto done;
+ }
+
+ req_setnames->size = 0;
+ used = 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 += 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 */
+ switch (*op) {
+ case IP_SET_OP_LIST_SIZE: {
+ req_setnames->size += sizeof(struct ip_set_list)
+ + set->type->header_size
+ + set->type->list_members_size(set);
+ /* Sets are identified by id in the hash */
+ FOREACH_HASH_DO(__set_hash_bindings_size_list,
+ set->id, &req_setnames->size);
+ break;
+ }
+ case IP_SET_OP_SAVE_SIZE: {
+ req_setnames->size += sizeof(struct ip_set_save)
+ + set->type->header_size
+ + set->type->list_members_size(set);
+ FOREACH_HASH_DO(__set_hash_bindings_size_save,
+ set->id, &req_setnames->size);
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ 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;
+ }
+ used = 0;
+ if (index == IP_SET_INVALID_ID) {
+ /* Save all sets */
+ for (i = 0; i < ip_set_max && res == 0; i++) {
+ if (ip_set_list[i] != NULL)
+ res = ip_set_save_set(i, data, &used, *len);
+ }
+ } else {
+ /* Save an individual set */
+ res = ip_set_save_set(index, data, &used, *len);
+ }
+ if (res == 0)
+ res = ip_set_save_bindings(index, 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;
+ int line;
+
+ if (*len < sizeof(struct ip_set_req_setnames)
+ || *len != req_restore->size) {
+ ip_set_printk("invalid RESTORE (want =%zu, got %d)",
+ req_restore->size, *len);
+ res = -EINVAL;
+ goto done;
+ }
+ line = ip_set_restore(data + sizeof(struct ip_set_req_setnames),
+ req_restore->size - sizeof(struct ip_set_req_setnames));
+ DP("ip_set_restore: %u", 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 %u", 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;
+}
+
+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 int max_sets, hash_size;
+module_param(max_sets, int, 0600);
+MODULE_PARM_DESC(max_sets, "maximal number of sets");
+module_param(hash_size, int, 0600);
+MODULE_PARM_DESC(hash_size, "hash size for bindings");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Jozsef Kadlecsik <kadlec@blackhole.kfki.hu>");
+MODULE_DESCRIPTION("module implementing core IP set support");
+
+static int __init ip_set_init(void)
+{
+ int res;
+ ip_set_id_t i;
+
+ get_random_bytes(&ip_set_hash_random, 4);
+ if (max_sets)
+ ip_set_max = max_sets;
+ ip_set_list = vmalloc(sizeof(struct ip_set *) * ip_set_max);
+ 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);
+ if (hash_size)
+ ip_set_bindings_hash_size = hash_size;
+ ip_set_hash = vmalloc(sizeof(struct list_head) * ip_set_bindings_hash_size);
+ if (!ip_set_hash) {
+ printk(KERN_ERR "Unable to create ip_set_hash\n");
+ vfree(ip_set_list);
+ return -ENOMEM;
+ }
+ for (i = 0; i < ip_set_bindings_hash_size; i++)
+ INIT_LIST_HEAD(&ip_set_hash[i]);
+
+ INIT_LIST_HEAD(&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);
+ vfree(ip_set_hash);
+ return res;
+ }
+ 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);
+ vfree(ip_set_hash);
+ DP("these are the famous last words");
+}
+
+EXPORT_SYMBOL(ip_set_register_set_type);
+EXPORT_SYMBOL(ip_set_unregister_set_type);
+
+EXPORT_SYMBOL(ip_set_get_byname);
+EXPORT_SYMBOL(ip_set_get_byindex);
+EXPORT_SYMBOL(ip_set_put);
+
+EXPORT_SYMBOL(ip_set_addip_kernel);
+EXPORT_SYMBOL(ip_set_delip_kernel);
+EXPORT_SYMBOL(ip_set_testip_kernel);
+
+module_init(ip_set_init);
+module_exit(ip_set_fini);