summaryrefslogtreecommitdiffstats
path: root/kernel/ip_set_hash_ip_src.c
diff options
context:
space:
mode:
Diffstat (limited to 'kernel/ip_set_hash_ip_src.c')
-rw-r--r--kernel/ip_set_hash_ip_src.c473
1 files changed, 473 insertions, 0 deletions
diff --git a/kernel/ip_set_hash_ip_src.c b/kernel/ip_set_hash_ip_src.c
new file mode 100644
index 0000000..ef0a8ec
--- /dev/null
+++ b/kernel/ip_set_hash_ip_src.c
@@ -0,0 +1,473 @@
+/* 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
+ * published by the Free Software Foundation.
+ */
+
+#define CONCAT(a, b, c) a##b##c
+#define TOKEN(a, b, c) CONCAT(a, b, c)
+
+/* IPv4/IPv6 dependent function prototypes for hash:ip */
+
+#if PF == 4
+#define HOST_MASK 32
+#define NLA_PUT_ADDR(skb, ip) \
+ NLA_PUT_NET32(skb, IPSET_ATTR_IP, *(ip));
+#else
+#define HOST_MASK 128
+#define NLA_PUT_ADDR(skb, ip) \
+ NLA_PUT(skb, IPSET_ATTR_IP, sizeof(struct in6_addr), ip);
+#endif
+
+#define hash_ip_pf_timeout TOKEN(hash_ip, PF, _timeout)
+#define hash_ip_pf_expired TOKEN(hash_ip, PF, _expired)
+#define hash_ip_pf_elem_test TOKEN(hash_ip, PF, _elem_test)
+#define hash_ip_pf_elem_exist TOKEN(hash_ip, PF, _elem_exist)
+#define hash_ip_pf_elem_expired TOKEN(hash_ip, PF, _elem_expired)
+#define hash_ip_pf_test TOKEN(hash_ip, PF, _test)
+#define hash_ip_pf_add TOKEN(hash_ip, PF, _add)
+#define hash_ip_pf_readd TOKEN(hash_ip, PF, _readd)
+#define hash_ip_pf_del TOKEN(hash_ip, PF, _del)
+#define hash_ip_pf_map_expired TOKEN(hash_ip, PF, _map_expired)
+#define hash_ip_pf_set_expired TOKEN(hash_ip, PF, _set_expired)
+#define hash_ip_pf_head TOKEN(hash_ip, PF, _head)
+#define hash_ip_pf_list TOKEN(hash_ip, PF, _list)
+#define hash_ip_pf_resize TOKEN(hash_ip, PF, _resize)
+#define hash_ip_pf TOKEN(hash_ip, PF , )
+#define hash_ip_pf_kadt TOKEN(hash_ip, PF, _kadt)
+#define hash_ip_pf_uadt TOKEN(hash_ip, PF, _uadt)
+#define hash_ip_pf_destroy TOKEN(hash_ip, PF, _destroy)
+#define hash_ip_pf_flush TOKEN(hash_ip, PF, _flush)
+#define hash_ip_pf_timeout_gc TOKEN(hash_ip, PF, _timeout_gc)
+#define hash_ip_pf_gc_init TOKEN(hash_ip, PF, _gc_init)
+#define ip_pf_hash TOKEN(ip, PF, _hash)
+#define ip_pf_cmp TOKEN(ip, PF, _cmp)
+#define ip_pf_null TOKEN(ip, PF, _null)
+#define ip_pf_cpy TOKEN(ip, PF, _cpy)
+#define ip_pf_zero_out TOKEN(ip, PF, _zero_out)
+#define ip_pf_elem TOKEN(ip, PF, _elem)
+#define ip_pf_elem_timeout TOKEN(ip, PF, _elem_timeout)
+#define ip_pf_get_elem_timeout TOKEN(get_ip, PF, _elem_timeout)
+
+static inline bool
+hash_ip_pf_timeout(const struct hash_ip *map, uint32_t id)
+{
+ struct ip_pf_elem_timeout *elem = hash_ip_elem(map, id);
+
+ return ip_set_timeout_test(elem->timeout);
+}
+
+static inline bool
+hash_ip_pf_expired(const struct hash_ip *map, uint32_t id)
+{
+ struct ip_pf_elem_timeout *elem = hash_ip_elem(map, id);
+
+ return ip_set_timeout_expired(elem->timeout);
+}
+
+static inline bool
+hash_ip_pf_elem_test(const struct hash_ip *map, bool with_timeout,
+ uint32_t id, struct ip_pf_elem * ip)
+{
+ struct ip_pf_elem *elem = hash_ip_elem(map, id);
+
+ return ip_pf_cmp(elem, ip)
+ && (!with_timeout || hash_ip_pf_timeout(map, id));
+}
+
+static inline bool
+hash_ip_pf_elem_exist(const struct hash_ip *map, bool with_timeout,
+ uint32_t id)
+{
+ struct ip_pf_elem *elem = hash_ip_elem(map, id);
+
+ return !(ip_pf_null(elem)
+ || (with_timeout && hash_ip_pf_expired(map, id)));
+}
+
+static inline bool
+hash_ip_pf_elem_expired(const struct hash_ip *map, bool with_timeout,
+ uint32_t id)
+{
+ struct ip_pf_elem *elem = hash_ip_elem(map, id);
+
+ return ip_pf_null(elem)
+ || (with_timeout && hash_ip_pf_expired(map, id));
+}
+
+static inline uint32_t
+hash_ip_pf_test(const struct hash_ip *map, bool with_timeout,
+ struct ip_pf_elem * ip)
+{
+ uint32_t id;
+ uint8_t i;
+
+ for (i = 0; i < map->probes; i++) {
+ id = ip_pf_hash(ip, *(map->initval + i), map->hashsize);
+ if (hash_ip_pf_elem_test(map, with_timeout, id, ip))
+ return id + 1;
+ /* No shortcut - there can be deleted entries. */
+ }
+ return 0;
+}
+
+static void
+hash_ip_pf_map_expired(struct hash_ip *map)
+{
+ struct ip_pf_elem_timeout *table = map->members;
+ uint32_t i;
+
+ /* We run parallel with other readers (test element)
+ * but adding/deleting new entries is locked out */
+ for (i = 0; i < map->hashsize; i++)
+ if (ip_set_timeout_expired(table[i].timeout)) {
+ ip_pf_zero_out((struct ip_pf_elem *)&table[i]);
+ table[i].timeout = IPSET_ELEM_UNSET;
+ map->elements--;
+ }
+}
+
+static inline void
+hash_ip_pf_set_expired(struct ip_set *set)
+{
+ /* We run parallel with other readers (test element)
+ * but adding/deleting new entries is locked out */
+ read_lock_bh(&set->lock);
+ hash_ip_pf_map_expired(set->data);
+ read_unlock_bh(&set->lock);
+}
+
+static int
+hash_ip_pf_add(struct hash_ip *map, bool with_timeout,
+ struct ip_pf_elem *ip, uint32_t timeout)
+{
+ uint32_t id, empty = 0;
+ uint8_t i;
+
+ if (map->elements >= map->maxelem) {
+ if (with_timeout) {
+ hash_ip_pf_map_expired(map);
+ if (map->elements < map->maxelem)
+ goto doit;
+ }
+ return -IPSET_ERR_HASH_FULL;
+ }
+
+doit:
+ for (i = 0; i < map->probes; i++) {
+ id = ip_pf_hash(ip, *(map->initval + i), map->hashsize);
+ if (hash_ip_pf_elem_test(map, with_timeout, id, ip))
+ return -IPSET_ERR_EXIST;
+ if (empty == 0
+ && hash_ip_pf_elem_expired(map, with_timeout, id))
+ empty = id + 1;
+ /* There can be deleted entries, must check all slots */
+ }
+ if (!empty)
+ /* Trigger rehashing */
+ return -EAGAIN;
+
+ if (with_timeout) {
+ struct ip_pf_elem_timeout *e = hash_ip_elem(map, empty - 1);
+ e->timeout = ip_set_timeout_set(timeout);
+ D("add with timeout: %u (%lu)", timeout, e->timeout);
+ ip_pf_cpy((struct ip_pf_elem *)e, ip);
+ } else {
+ struct ip_pf_elem *e = hash_ip_elem(map, empty - 1);
+ ip_pf_cpy(e, ip);
+ }
+ map->elements++;
+ return 0;
+}
+
+static int
+hash_ip_pf_readd(struct hash_ip *map, bool with_timeout, struct ip_pf_elem *ip)
+{
+ uint32_t id, empty = 0;
+ uint8_t i;
+
+ for (i = 0; empty == 0 && i < map->probes; i++) {
+ id = ip_pf_hash(ip, *(map->initval + i), map->hashsize);
+ if (ip_pf_null(hash_ip_elem(map, id)))
+ empty = id + 1;
+ }
+ if (!empty)
+ /* Trigger rehashing */
+ return -EAGAIN;
+
+ if (with_timeout) {
+ struct ip_pf_elem_timeout *e = hash_ip_elem(map, empty - 1);
+ e->timeout = ip_pf_get_elem_timeout(ip);
+ ip_pf_cpy((struct ip_pf_elem *)e, ip);
+ } else {
+ struct ip_pf_elem *e = hash_ip_elem(map, empty - 1);
+ ip_pf_cpy(e, ip);
+ }
+ map->elements++;
+ return 0;
+}
+
+static int
+hash_ip_pf_del(struct hash_ip *map, bool with_timeout, struct ip_pf_elem *ip)
+{
+ struct ip_pf_elem *e;
+ uint32_t id, found = 0;
+ uint8_t i;
+
+ for (i = 0; i < map->probes; i++) {
+ id = ip_pf_hash(ip, *(map->initval + i), map->hashsize);
+ if (hash_ip_pf_elem_test(map, with_timeout, id, ip)) {
+ found = id + 1;
+ break;
+ }
+ }
+ if (!found)
+ return -IPSET_ERR_EXIST;
+
+ e = hash_ip_elem(map, found - 1);
+ ip_pf_zero_out(e);
+ if (with_timeout)
+ ((struct ip_pf_elem_timeout *)e)->timeout = IPSET_ELEM_UNSET;
+
+ map->elements--;
+
+ return 0;
+}
+
+static int
+hash_ip_pf_head(struct ip_set *set, struct sk_buff *skb)
+{
+ const struct hash_ip *map = set->data;
+ struct nlattr *nested;
+
+ if (set->flags & IP_SET_FLAG_TIMEOUT)
+ hash_ip_pf_set_expired(set);
+
+ nested = ipset_nest_start(skb, IPSET_ATTR_DATA);
+ if (!nested)
+ goto nla_put_failure;
+ NLA_PUT_NET32(skb, IPSET_ATTR_HASHSIZE, htonl(map->hashsize));
+ NLA_PUT_NET32(skb, IPSET_ATTR_MAXELEM, htonl(map->maxelem));
+ if (map->netmask != HOST_MASK)
+ NLA_PUT_U8(skb, IPSET_ATTR_NETMASK, map->netmask);
+ NLA_PUT_U8(skb, IPSET_ATTR_PROBES, map->probes);
+ NLA_PUT_U8(skb, IPSET_ATTR_RESIZE, map->resize);
+ NLA_PUT_NET32(skb, IPSET_ATTR_ELEMENTS, htonl(map->elements));
+ NLA_PUT_NET32(skb, IPSET_ATTR_REFERENCES,
+ htonl(atomic_read(&set->ref) - 1));
+ NLA_PUT_NET32(skb, IPSET_ATTR_MEMSIZE,
+ htonl(map->hashsize * map->elem_size));
+ if (set->flags & IP_SET_FLAG_TIMEOUT)
+ NLA_PUT_NET32(skb, IPSET_ATTR_TIMEOUT, htonl(map->timeout));
+ ipset_nest_end(skb, nested);
+
+ return 0;
+nla_put_failure:
+ return -EFAULT;
+}
+
+static int
+hash_ip_pf_list(struct ip_set *set,
+ struct sk_buff *skb, struct netlink_callback *cb)
+{
+ const struct hash_ip *map = set->data;
+ struct nlattr *atd, *nested;
+ struct ip_pf_elem *elem;
+ uint32_t id, first = cb->args[2];
+ bool with_timeout = set->flags & IP_SET_FLAG_TIMEOUT;
+
+ atd = ipset_nest_start(skb, IPSET_ATTR_ADT);
+ if (!atd)
+ return -EFAULT;
+ for (; cb->args[2] < map->hashsize; cb->args[2]++) {
+ id = cb->args[2];
+ if (hash_ip_pf_elem_expired(map, with_timeout, id))
+ continue;
+ nested = ipset_nest_start(skb, IPSET_ATTR_DATA);
+ if (!nested) {
+ if (id == first) {
+ nla_nest_cancel(skb, atd);
+ return -EFAULT;
+ } else
+ goto nla_put_failure;
+ }
+ elem = hash_ip_elem(map, id);
+ NLA_PUT_ADDR(skb, &elem->ip);
+ if (map->netmask != HOST_MASK)
+ NLA_PUT_U8(skb, IPSET_ATTR_CIDR, map->netmask);
+ if (with_timeout) {
+ unsigned long timeout = ip_pf_get_elem_timeout(elem);
+ D("list with timeout: %u (%lu)",
+ ip_set_timeout_get(timeout), timeout);
+ NLA_PUT_NET32(skb, IPSET_ATTR_TIMEOUT,
+ htonl(ip_set_timeout_get(timeout)));
+ }
+ ipset_nest_end(skb, nested);
+ }
+ ipset_nest_end(skb, atd);
+ /* Set listing finished */
+ cb->args[2] = 0;
+
+ return 0;
+
+nla_put_failure:
+ nla_nest_cancel(skb, nested);
+ ipset_nest_end(skb, atd);
+ return 0;
+}
+
+static int
+hash_ip_pf_resize(struct ip_set *set, uint8_t retried)
+{
+ struct hash_ip *map = set->data, *tmp;
+ void *members;
+ uint32_t i, hashsize = map->hashsize;
+ uint8_t oflags, flags = set->flags;
+ bool with_timeout = flags & IP_SET_FLAG_TIMEOUT;
+ int ret;
+
+ if (map->resize == 0)
+ return -IPSET_ERR_HASH_FULL;
+
+ /* Try to cleanup first */
+ if (retried == 0 && with_timeout) {
+ i = map->elements;
+ hash_ip_pf_set_expired(set);
+ if (map->elements < i)
+ return 0;
+ }
+
+again:
+ ret = 0;
+
+ /* Calculate new hash size */
+ hashsize += (hashsize * map->resize)/100;
+ if (hashsize == map->hashsize)
+ hashsize++;
+ if (hashsize >= map->maxelem)
+ return -IPSET_ERR_HASH_FULL;
+
+ printk("Rehashing of set %s triggered: hash grows from %lu to %lu\n",
+ set->name,
+ (long unsigned)map->hashsize,
+ (long unsigned)hashsize);
+
+ tmp = kmalloc(sizeof(struct hash_ip)
+ + map->probes * sizeof(initval_t), GFP_ATOMIC);
+ if (!tmp)
+ return -ENOMEM;
+
+ memcpy(tmp, map, sizeof(*map) + map->probes * sizeof(initval_t));
+ tmp->elements = 0;
+ tmp->hashsize = hashsize;
+ tmp->members = ip_set_alloc(hashsize * map->elem_size,
+ GFP_ATOMIC, &flags);
+ if (!tmp->members) {
+ kfree(tmp);
+ return -ENOMEM;
+ }
+
+ write_lock_bh(&set->lock);
+ map = set->data; /* Play safe */
+ for (i = 0; i < map->hashsize && ret == 0; i++) {
+ if (hash_ip_pf_elem_exist(map, with_timeout, i))
+ ret = hash_ip_pf_readd(tmp, with_timeout,
+ hash_ip_elem(map, i));
+ }
+ if (ret) {
+ /* Failure, try again */
+ write_unlock_bh(&set->lock);
+ ip_set_free(tmp->members, flags);
+ kfree(tmp);
+ goto again;
+ }
+
+ /* Success at resizing! */
+ members = map->members;
+ oflags = set->flags;
+
+ map->hashsize = tmp->hashsize;
+ map->members = tmp->members;
+ map->elements = tmp->elements;
+ set->flags = flags;
+ write_unlock_bh(&set->lock);
+
+ ip_set_free(members, oflags);
+ kfree(tmp);
+
+ return 0;
+}
+
+static int
+hash_ip_pf_kadt(struct ip_set *set, const struct sk_buff * skb,
+ enum ipset_adt adt, uint8_t pf, const uint8_t *flags);
+static int
+hash_ip_pf_uadt(struct ip_set *set, struct nlattr *head, int len,
+ enum ipset_adt adt, uint32_t *lineno, uint32_t flags);
+
+static const struct ip_set_type_variant hash_ip_pf __read_mostly = {
+ .kadt = hash_ip_pf_kadt,
+ .uadt = hash_ip_pf_uadt,
+ .destroy = hash_ip_pf_destroy,
+ .flush = hash_ip_pf_flush,
+ .head = hash_ip_pf_head,
+ .list = hash_ip_pf_list,
+ .resize = hash_ip_pf_resize,
+};
+
+static void
+hash_ip_pf_timeout_gc(unsigned long ul_set)
+{
+ struct ip_set *set = (struct ip_set *) ul_set;
+ struct hash_ip *map = set->data;
+
+ hash_ip_pf_set_expired(set);
+
+ map->gc.expires = jiffies + IPSET_GC_PERIOD(map->timeout) * HZ;
+ add_timer(&map->gc);
+}
+
+static inline void
+hash_ip_pf_gc_init(struct ip_set *set)
+{
+ struct hash_ip *map = set->data;
+
+ init_timer(&map->gc);
+ map->gc.data = (unsigned long) set;
+ map->gc.function = hash_ip_pf_timeout_gc;
+ map->gc.expires = jiffies + IPSET_GC_PERIOD(map->timeout) * HZ;
+ add_timer(&map->gc);
+}
+
+#undef HOST_MASK
+#undef NLA_PUT_ADDR
+#undef hash_ip_pf_timeout
+#undef hash_ip_pf_expired
+#undef hash_ip_pf_elem_test
+#undef hash_ip_pf_elem_exist
+#undef hash_ip_pf_elem_expired
+#undef hash_ip_pf_test
+#undef hash_ip_pf_add
+#undef hash_ip_pf_readd
+#undef hash_ip_pf_del
+#undef hash_ip_pf_map_expired
+#undef hash_ip_pf_set_expired
+#undef hash_ip_pf_head
+#undef hash_ip_pf_list
+#undef hash_ip_pf_resize
+#undef hash_ip_pf
+#undef hash_ip_pf_kadt
+#undef hash_ip_pf_uadt
+#undef hash_ip_pf_destroy
+#undef hash_ip_pf_flush
+#undef hash_ip_pf_timeout_gc
+#undef hash_ip_pf_gc_init
+#undef ip_pf_hash
+#undef ip_pf_cmp
+#undef ip_pf_null
+#undef ip_pf_cpy
+#undef ip_pf_zero_out
+#undef ip_pf_elem
+#undef ip_pf_elem_timeout
+#undef ip_pf_get_elem_timeout