diff options
author | Jozsef Kadlecsik <kadlec@blackhole.kfki.hu> | 2010-04-22 17:09:18 +0200 |
---|---|---|
committer | Jozsef Kadlecsik <kadlec@blackhole.kfki.hu> | 2010-04-22 17:09:18 +0200 |
commit | 456b1d993711eb4297012ad4a881c459c0511358 (patch) | |
tree | 518bb02b7cf25ed6f338e96969efe96b642f8bf2 /kernel/ip_set.c | |
parent | ac0e5da3166da201ea00fd7f3cd927b0a49d8fef (diff) |
Eight stage to ipset-5
Commit changed files in kernel/...
Diffstat (limited to 'kernel/ip_set.c')
-rw-r--r-- | kernel/ip_set.c | 2061 |
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); |