Logo Search packages:      
Sourcecode: west-chamber version File versions  Download package

ip_set_iptreemap.c

/* Copyright (C) 2007 Sven Wegener <sven.wegener@stealer.net>
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 as published by
 * the Free Software Foundation.
 */

/* This modules implements the iptreemap ipset type. It uses bitmaps to
 * represent every single IPv4 address as a bit. The bitmaps are managed in a
 * tree structure, where the first three octets of an address are used as an
 * index to find the bitmap and the last octet is used as the bit number.
 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/ip.h>
#include <linux/jiffies.h>
#include <linux/skbuff.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <asm/uaccess.h>
#include <asm/bitops.h>
#include <linux/spinlock.h>
#include <linux/timer.h>

#include "ip_set.h"
#include "ip_set_bitmaps.h"
#include "ip_set_iptreemap.h"

#define IPTREEMAP_DEFAULT_GC_TIME (5 * 60)
#define IPTREEMAP_DESTROY_SLEEP (100)

static __KMEM_CACHE_T__ *cachep_b;
static __KMEM_CACHE_T__ *cachep_c;
static __KMEM_CACHE_T__ *cachep_d;

static struct ip_set_iptreemap_d *fullbitmap_d;
static struct ip_set_iptreemap_c *fullbitmap_c;
static struct ip_set_iptreemap_b *fullbitmap_b;

#if defined(__LITTLE_ENDIAN)
#define ABCD(a, b, c, d, addr) \
      do { \
            a = ((unsigned char *)addr)[3]; \
            b = ((unsigned char *)addr)[2]; \
            c = ((unsigned char *)addr)[1]; \
            d = ((unsigned char *)addr)[0]; \
      } while (0)
#elif defined(__BIG_ENDIAN)
#define ABCD(a,b,c,d,addrp) do {          \
      a = ((unsigned char *)addrp)[0];    \
      b = ((unsigned char *)addrp)[1];    \
      c = ((unsigned char *)addrp)[2];    \
      d = ((unsigned char *)addrp)[3];    \
} while (0)
#else
#error "Please fix asm/byteorder.h"
#endif /* __LITTLE_ENDIAN */

#define TESTIP_WALK(map, elem, branch, full) \
      do { \
            branch = (map)->tree[elem]; \
            if (!branch) \
                  return 0; \
            else if (branch == full) \
                  return 1; \
      } while (0)

#define ADDIP_WALK(map, elem, branch, type, cachep, full) \
      do { \
            branch = (map)->tree[elem]; \
            if (!branch) { \
                  branch = (type *) kmem_cache_alloc(cachep, GFP_ATOMIC); \
                  if (!branch) \
                        return -ENOMEM; \
                  memset(branch, 0, sizeof(*branch)); \
                  (map)->tree[elem] = branch; \
            } else if (branch == full) { \
                  return -EEXIST; \
            } \
      } while (0)

#define ADDIP_RANGE_LOOP(map, a, a1, a2, hint, branch, full, cachep, free) \
      for (a = a1; a <= a2; a++) { \
            branch = (map)->tree[a]; \
            if (branch != full) { \
                  if ((a > a1 && a < a2) || (hint)) { \
                        if (branch) \
                              free(branch); \
                        (map)->tree[a] = full; \
                        continue; \
                  } else if (!branch) { \
                        branch = kmem_cache_alloc(cachep, GFP_ATOMIC); \
                        if (!branch) \
                              return -ENOMEM; \
                        memset(branch, 0, sizeof(*branch)); \
                        (map)->tree[a] = branch; \
                  }

#define ADDIP_RANGE_LOOP_END() \
            } \
      }

#define DELIP_WALK(map, elem, branch, cachep, full, flags) \
      do { \
            branch = (map)->tree[elem]; \
            if (!branch) { \
                  return -EEXIST; \
            } else if (branch == full) { \
                  branch = kmem_cache_alloc(cachep, flags); \
                  if (!branch) \
                        return -ENOMEM; \
                  memcpy(branch, full, sizeof(*full)); \
                  (map)->tree[elem] = branch; \
            } \
      } while (0)

#define DELIP_RANGE_LOOP(map, a, a1, a2, hint, branch, full, cachep, free, flags) \
      for (a = a1; a <= a2; a++) { \
            branch = (map)->tree[a]; \
            if (branch) { \
                  if ((a > a1 && a < a2) || (hint)) { \
                        if (branch != full) \
                              free(branch); \
                        (map)->tree[a] = NULL; \
                        continue; \
                  } else if (branch == full) { \
                        branch = kmem_cache_alloc(cachep, flags); \
                        if (!branch) \
                              return -ENOMEM; \
                        memcpy(branch, full, sizeof(*branch)); \
                        (map)->tree[a] = branch; \
                  }

#define DELIP_RANGE_LOOP_END() \
            } \
      }

#define LOOP_WALK_BEGIN(map, i, branch) \
      for (i = 0; i < 256; i++) { \
            branch = (map)->tree[i]; \
            if (likely(!branch)) \
                  continue;

#define LOOP_WALK_END() \
      }

#define LOOP_WALK_BEGIN_GC(map, i, branch, full, cachep, count) \
      count = -256; \
      for (i = 0; i < 256; i++) { \
            branch = (map)->tree[i]; \
            if (likely(!branch)) \
                  continue; \
            count++; \
            if (branch == full) { \
                  count++; \
                  continue; \
            }

#define LOOP_WALK_END_GC(map, i, branch, full, cachep, count) \
            if (-256 == count) { \
                  kmem_cache_free(cachep, branch); \
                  (map)->tree[i] = NULL; \
            } else if (256 == count) { \
                  kmem_cache_free(cachep, branch); \
                  (map)->tree[i] = full; \
            } \
      }

#define LOOP_WALK_BEGIN_COUNT(map, i, branch, inrange, count) \
      for (i = 0; i < 256; i++) { \
            if (!(map)->tree[i]) { \
                  if (inrange) { \
                        count++; \
                        inrange = 0; \
                  } \
                  continue; \
            } \
            branch = (map)->tree[i];

#define LOOP_WALK_END_COUNT() \
      }

#define GETVALUE1(a, a1, b1, r) \
      (a == a1 ? b1 : r)

#define GETVALUE2(a, b, a1, b1, c1, r) \
      (a == a1 && b == b1 ? c1 : r)

#define GETVALUE3(a, b, c, a1, b1, c1, d1, r) \
      (a == a1 && b == b1 && c == c1 ? d1 : r)

#define CHECK1(a, a1, a2, b1, b2, c1, c2, d1, d2) \
      ( \
            GETVALUE1(a, a1, b1, 0) == 0 \
            && GETVALUE1(a, a2, b2, 255) == 255 \
            && c1 == 0 \
            && c2 == 255 \
            && d1 == 0 \
            && d2 == 255 \
      )

#define CHECK2(a, b, a1, a2, b1, b2, c1, c2, d1, d2) \
      ( \
            GETVALUE2(a, b, a1, b1, c1, 0) == 0 \
            && GETVALUE2(a, b, a2, b2, c2, 255) == 255 \
            && d1 == 0 \
            && d2 == 255 \
      )

#define CHECK3(a, b, c, a1, a2, b1, b2, c1, c2, d1, d2) \
      ( \
            GETVALUE3(a, b, c, a1, b1, c1, d1, 0) == 0 \
            && GETVALUE3(a, b, c, a2, b2, c2, d2, 255) == 255 \
      )


static inline void
free_d(struct ip_set_iptreemap_d *map)
{
      kmem_cache_free(cachep_d, map);
}

static inline void
free_c(struct ip_set_iptreemap_c *map)
{
      struct ip_set_iptreemap_d *dtree;
      unsigned int i;

      LOOP_WALK_BEGIN(map, i, dtree) {
            if (dtree != fullbitmap_d)
                  free_d(dtree);
      } LOOP_WALK_END();

      kmem_cache_free(cachep_c, map);
}

static inline void
free_b(struct ip_set_iptreemap_b *map)
{
      struct ip_set_iptreemap_c *ctree;
      unsigned int i;

      LOOP_WALK_BEGIN(map, i, ctree) {
            if (ctree != fullbitmap_c)
                  free_c(ctree);
      } LOOP_WALK_END();

      kmem_cache_free(cachep_b, map);
}

static inline int
iptreemap_test(struct ip_set *set, ip_set_ip_t ip)
{
      struct ip_set_iptreemap *map = set->data;
      struct ip_set_iptreemap_b *btree;
      struct ip_set_iptreemap_c *ctree;
      struct ip_set_iptreemap_d *dtree;
      unsigned char a, b, c, d;

      ABCD(a, b, c, d, &ip);

      TESTIP_WALK(map, a, btree, fullbitmap_b);
      TESTIP_WALK(btree, b, ctree, fullbitmap_c);
      TESTIP_WALK(ctree, c, dtree, fullbitmap_d);

      return !!test_bit(d, (void *) dtree->bitmap);
}

#define KADT_CONDITION

UADT(iptreemap, test)
KADT(iptreemap, test, ipaddr)

static inline int
__addip_single(struct ip_set *set, ip_set_ip_t ip)
{
      struct ip_set_iptreemap *map = (struct ip_set_iptreemap *) set->data;
      struct ip_set_iptreemap_b *btree;
      struct ip_set_iptreemap_c *ctree;
      struct ip_set_iptreemap_d *dtree;
      unsigned char a, b, c, d;

      ABCD(a, b, c, d, &ip);

      ADDIP_WALK(map, a, btree, struct ip_set_iptreemap_b, cachep_b, fullbitmap_b);
      ADDIP_WALK(btree, b, ctree, struct ip_set_iptreemap_c, cachep_c, fullbitmap_c);
      ADDIP_WALK(ctree, c, dtree, struct ip_set_iptreemap_d, cachep_d, fullbitmap_d);

      if (__test_and_set_bit(d, (void *) dtree->bitmap))
            return -EEXIST;

      __set_bit(b, (void *) btree->dirty);

      return 0;
}

static inline int
iptreemap_add(struct ip_set *set, ip_set_ip_t start, ip_set_ip_t end)
{
      struct ip_set_iptreemap *map = set->data;
      struct ip_set_iptreemap_b *btree;
      struct ip_set_iptreemap_c *ctree;
      struct ip_set_iptreemap_d *dtree;
      unsigned int a, b, c, d;
      unsigned char a1, b1, c1, d1;
      unsigned char a2, b2, c2, d2;

      if (start == end)
            return __addip_single(set, start);

      ABCD(a1, b1, c1, d1, &start);
      ABCD(a2, b2, c2, d2, &end);

      /* This is sooo ugly... */
      ADDIP_RANGE_LOOP(map, a, a1, a2, CHECK1(a, a1, a2, b1, b2, c1, c2, d1, d2), btree, fullbitmap_b, cachep_b, free_b) {
            ADDIP_RANGE_LOOP(btree, b, GETVALUE1(a, a1, b1, 0), GETVALUE1(a, a2, b2, 255), CHECK2(a, b, a1, a2, b1, b2, c1, c2, d1, d2), ctree, fullbitmap_c, cachep_c, free_c) {
                  ADDIP_RANGE_LOOP(ctree, c, GETVALUE2(a, b, a1, b1, c1, 0), GETVALUE2(a, b, a2, b2, c2, 255), CHECK3(a, b, c, a1, a2, b1, b2, c1, c2, d1, d2), dtree, fullbitmap_d, cachep_d, free_d) {
                        for (d = GETVALUE3(a, b, c, a1, b1, c1, d1, 0); d <= GETVALUE3(a, b, c, a2, b2, c2, d2, 255); d++)
                              __set_bit(d, (void *) dtree->bitmap);
                        __set_bit(b, (void *) btree->dirty);
                  } ADDIP_RANGE_LOOP_END();
            } ADDIP_RANGE_LOOP_END();
      } ADDIP_RANGE_LOOP_END();

      return 0;
}

UADT0(iptreemap, add, min(req->ip, req->end), max(req->ip, req->end))
KADT(iptreemap, add, ipaddr, ip)

static inline int
__delip_single(struct ip_set *set, ip_set_ip_t ip, gfp_t flags)
{
      struct ip_set_iptreemap *map = set->data;
      struct ip_set_iptreemap_b *btree;
      struct ip_set_iptreemap_c *ctree;
      struct ip_set_iptreemap_d *dtree;
      unsigned char a,b,c,d;

      ABCD(a, b, c, d, &ip);

      DELIP_WALK(map, a, btree, cachep_b, fullbitmap_b, flags);
      DELIP_WALK(btree, b, ctree, cachep_c, fullbitmap_c, flags);
      DELIP_WALK(ctree, c, dtree, cachep_d, fullbitmap_d, flags);

      if (!__test_and_clear_bit(d, (void *) dtree->bitmap))
            return -EEXIST;

      __set_bit(b, (void *) btree->dirty);

      return 0;
}

static inline int
iptreemap_del(struct ip_set *set,
            ip_set_ip_t start, ip_set_ip_t end, gfp_t flags)
{
      struct ip_set_iptreemap *map = set->data;
      struct ip_set_iptreemap_b *btree;
      struct ip_set_iptreemap_c *ctree;
      struct ip_set_iptreemap_d *dtree;
      unsigned int a, b, c, d;
      unsigned char a1, b1, c1, d1;
      unsigned char a2, b2, c2, d2;

      if (start == end)
            return __delip_single(set, start, flags);

      ABCD(a1, b1, c1, d1, &start);
      ABCD(a2, b2, c2, d2, &end);

      /* This is sooo ugly... */
      DELIP_RANGE_LOOP(map, a, a1, a2, CHECK1(a, a1, a2, b1, b2, c1, c2, d1, d2), btree, fullbitmap_b, cachep_b, free_b, flags) {
            DELIP_RANGE_LOOP(btree, b, GETVALUE1(a, a1, b1, 0), GETVALUE1(a, a2, b2, 255), CHECK2(a, b, a1, a2, b1, b2, c1, c2, d1, d2), ctree, fullbitmap_c, cachep_c, free_c, flags) {
                  DELIP_RANGE_LOOP(ctree, c, GETVALUE2(a, b, a1, b1, c1, 0), GETVALUE2(a, b, a2, b2, c2, 255), CHECK3(a, b, c, a1, a2, b1, b2, c1, c2, d1, d2), dtree, fullbitmap_d, cachep_d, free_d, flags) {
                        for (d = GETVALUE3(a, b, c, a1, b1, c1, d1, 0); d <= GETVALUE3(a, b, c, a2, b2, c2, d2, 255); d++)
                              __clear_bit(d, (void *) dtree->bitmap);
                        __set_bit(b, (void *) btree->dirty);
                  } DELIP_RANGE_LOOP_END();
            } DELIP_RANGE_LOOP_END();
      } DELIP_RANGE_LOOP_END();

      return 0;
}

UADT0(iptreemap, del, min(req->ip, req->end), max(req->ip, req->end), GFP_KERNEL)
KADT(iptreemap, del, ipaddr, ip, GFP_ATOMIC)

/* Check the status of the bitmap
 * -1 == all bits cleared
 *  1 == all bits set
 *  0 == anything else
 */
static inline int
bitmap_status(struct ip_set_iptreemap_d *dtree)
{
      unsigned char first = dtree->bitmap[0];
      int a;

      for (a = 1; a < 32; a++)
            if (dtree->bitmap[a] != first)
                  return 0;

      return (first == 0 ? -1 : (first == 255 ? 1 : 0));
}

static void
gc(unsigned long addr)
{
      struct ip_set *set = (struct ip_set *) addr;
      struct ip_set_iptreemap *map = set->data;
      struct ip_set_iptreemap_b *btree;
      struct ip_set_iptreemap_c *ctree;
      struct ip_set_iptreemap_d *dtree;
      unsigned int a, b, c;
      int i, j, k;

      write_lock_bh(&set->lock);

      LOOP_WALK_BEGIN_GC(map, a, btree, fullbitmap_b, cachep_b, i) {
            LOOP_WALK_BEGIN_GC(btree, b, ctree, fullbitmap_c, cachep_c, j) {
                  if (!__test_and_clear_bit(b, (void *) btree->dirty))
                        continue;
                  LOOP_WALK_BEGIN_GC(ctree, c, dtree, fullbitmap_d, cachep_d, k) {
                        switch (bitmap_status(dtree)) {
                              case -1:
                                    kmem_cache_free(cachep_d, dtree);
                                    ctree->tree[c] = NULL;
                                    k--;
                              break;
                              case 1:
                                    kmem_cache_free(cachep_d, dtree);
                                    ctree->tree[c] = fullbitmap_d;
                                    k++;
                              break;
                        }
                  } LOOP_WALK_END();
            } LOOP_WALK_END_GC(btree, b, ctree, fullbitmap_c, cachep_c, k);
      } LOOP_WALK_END_GC(map, a, btree, fullbitmap_b, cachep_b, j);

      write_unlock_bh(&set->lock);

      map->gc.expires = jiffies + map->gc_interval * HZ;
      add_timer(&map->gc);
}

static inline void
init_gc_timer(struct ip_set *set)
{
      struct ip_set_iptreemap *map = set->data;

      init_timer(&map->gc);
      map->gc.data = (unsigned long) set;
      map->gc.function = gc;
      map->gc.expires = jiffies + map->gc_interval * HZ;
      add_timer(&map->gc);
}

static int
iptreemap_create(struct ip_set *set, const void *data, u_int32_t size)
{
      const struct ip_set_req_iptreemap_create *req = data;
      struct ip_set_iptreemap *map;

      map = kzalloc(sizeof(*map), GFP_KERNEL);
      if (!map)
            return -ENOMEM;

      map->gc_interval = req->gc_interval ? req->gc_interval : IPTREEMAP_DEFAULT_GC_TIME;
      set->data = map;

      init_gc_timer(set);

      return 0;
}

static inline void
__flush(struct ip_set_iptreemap *map)
{
      struct ip_set_iptreemap_b *btree;
      unsigned int a;

      LOOP_WALK_BEGIN(map, a, btree);
            if (btree != fullbitmap_b)
                  free_b(btree);
      LOOP_WALK_END();
}

static void
iptreemap_destroy(struct ip_set *set)
{
      struct ip_set_iptreemap *map = set->data;

      while (!del_timer(&map->gc))
            msleep(IPTREEMAP_DESTROY_SLEEP);

      __flush(map);
      kfree(map);

      set->data = NULL;
}

static void
iptreemap_flush(struct ip_set *set)
{
      struct ip_set_iptreemap *map = set->data;
      unsigned int gc_interval = map->gc_interval;

      while (!del_timer(&map->gc))
            msleep(IPTREEMAP_DESTROY_SLEEP);

      __flush(map);

      memset(map, 0, sizeof(*map));
      map->gc_interval = gc_interval;

      init_gc_timer(set);
}

static void
iptreemap_list_header(const struct ip_set *set, void *data)
{
      struct ip_set_iptreemap *map = set->data;
      struct ip_set_req_iptreemap_create *header = data;

      header->gc_interval = map->gc_interval;
}

static int
iptreemap_list_members_size(const struct ip_set *set, char dont_align)
{
      struct ip_set_iptreemap *map = set->data;
      struct ip_set_iptreemap_b *btree;
      struct ip_set_iptreemap_c *ctree;
      struct ip_set_iptreemap_d *dtree;
      unsigned int a, b, c, d, inrange = 0, count = 0;

      LOOP_WALK_BEGIN_COUNT(map, a, btree, inrange, count) {
            LOOP_WALK_BEGIN_COUNT(btree, b, ctree, inrange, count) {
                  LOOP_WALK_BEGIN_COUNT(ctree, c, dtree, inrange, count) {
                        for (d = 0; d < 256; d++) {
                              if (test_bit(d, (void *) dtree->bitmap)) {
                                    inrange = 1;
                              } else if (inrange) {
                                    count++;
                                    inrange = 0;
                              }
                        }
                  } LOOP_WALK_END_COUNT();
            } LOOP_WALK_END_COUNT();
      } LOOP_WALK_END_COUNT();

      if (inrange)
            count++;

      return (count * IPSET_VALIGN(sizeof(struct ip_set_req_iptreemap), dont_align));
}

static inline void
add_member(void *data, size_t offset, ip_set_ip_t start, ip_set_ip_t end)
{
      struct ip_set_req_iptreemap *entry = data + offset;

      entry->ip = start;
      entry->end = end;
}

static void
iptreemap_list_members(const struct ip_set *set, void *data, char dont_align)
{
      struct ip_set_iptreemap *map = set->data;
      struct ip_set_iptreemap_b *btree;
      struct ip_set_iptreemap_c *ctree;
      struct ip_set_iptreemap_d *dtree;
      unsigned int a, b, c, d, inrange = 0;
      size_t offset = 0, datasize;
      ip_set_ip_t start = 0, end = 0, ip;

      datasize = IPSET_VALIGN(sizeof(struct ip_set_req_iptreemap), dont_align);
      LOOP_WALK_BEGIN(map, a, btree) {
            LOOP_WALK_BEGIN(btree, b, ctree) {
                  LOOP_WALK_BEGIN(ctree, c, dtree) {
                        for (d = 0; d < 256; d++) {
                              if (test_bit(d, (void *) dtree->bitmap)) {
                                    ip = ((a << 24) | (b << 16) | (c << 8) | d);
                                    if (!inrange) {
                                          inrange = 1;
                                          start = ip;
                                    } else if (end < ip - 1) {
                                          add_member(data, offset, start, end);
                                          offset += datasize;
                                          start = ip;
                                    }
                                    end = ip;
                              } else if (inrange) {
                                    add_member(data, offset, start, end);
                                    offset += datasize;
                                    inrange = 0;
                              }
                        }
                  } LOOP_WALK_END();
            } LOOP_WALK_END();
      } LOOP_WALK_END();

      if (inrange)
            add_member(data, offset, start, end);
}

IP_SET_TYPE(iptreemap, IPSET_TYPE_IP | IPSET_DATA_SINGLE)

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Sven Wegener <sven.wegener@stealer.net>");
MODULE_DESCRIPTION("iptreemap type of IP sets");

static int __init ip_set_iptreemap_init(void)
{
      int ret = -ENOMEM;
      int a;

      cachep_b = KMEM_CACHE_CREATE("ip_set_iptreemap_b",
                             sizeof(struct ip_set_iptreemap_b));
      if (!cachep_b) {
            ip_set_printk("Unable to create ip_set_iptreemap_b slab cache");
            goto out;
      }

      cachep_c = KMEM_CACHE_CREATE("ip_set_iptreemap_c",
                             sizeof(struct ip_set_iptreemap_c));
      if (!cachep_c) {
            ip_set_printk("Unable to create ip_set_iptreemap_c slab cache");
            goto outb;
      }

      cachep_d = KMEM_CACHE_CREATE("ip_set_iptreemap_d",
                             sizeof(struct ip_set_iptreemap_d));
      if (!cachep_d) {
            ip_set_printk("Unable to create ip_set_iptreemap_d slab cache");
            goto outc;
      }

      fullbitmap_d = kmem_cache_alloc(cachep_d, GFP_KERNEL);
      if (!fullbitmap_d)
            goto outd;

      fullbitmap_c = kmem_cache_alloc(cachep_c, GFP_KERNEL);
      if (!fullbitmap_c)
            goto outbitmapd;

      fullbitmap_b = kmem_cache_alloc(cachep_b, GFP_KERNEL);
      if (!fullbitmap_b)
            goto outbitmapc;

      ret = ip_set_register_set_type(&ip_set_iptreemap);
      if (0 > ret)
            goto outbitmapb;

      /* Now init our global bitmaps */
      memset(fullbitmap_d->bitmap, 0xff, sizeof(fullbitmap_d->bitmap));

      for (a = 0; a < 256; a++)
            fullbitmap_c->tree[a] = fullbitmap_d;

      for (a = 0; a < 256; a++)
            fullbitmap_b->tree[a] = fullbitmap_c;
      memset(fullbitmap_b->dirty, 0, sizeof(fullbitmap_b->dirty));

      return 0;

outbitmapb:
      kmem_cache_free(cachep_b, fullbitmap_b);
outbitmapc:
      kmem_cache_free(cachep_c, fullbitmap_c);
outbitmapd:
      kmem_cache_free(cachep_d, fullbitmap_d);
outd:
      kmem_cache_destroy(cachep_d);
outc:
      kmem_cache_destroy(cachep_c);
outb:
      kmem_cache_destroy(cachep_b);
out:

      return ret;
}

static void __exit ip_set_iptreemap_fini(void)
{
      ip_set_unregister_set_type(&ip_set_iptreemap);
      kmem_cache_free(cachep_d, fullbitmap_d);
      kmem_cache_free(cachep_c, fullbitmap_c);
      kmem_cache_free(cachep_b, fullbitmap_b);
      kmem_cache_destroy(cachep_d);
      kmem_cache_destroy(cachep_c);
      kmem_cache_destroy(cachep_b);
}

module_init(ip_set_iptreemap_init);
module_exit(ip_set_iptreemap_fini);

Generated by  Doxygen 1.6.0   Back to index