linux/net/netfilter/xt_recent.c
<<
>>
Prefs
   1/*
   2 * Copyright (c) 2006 Patrick McHardy <kaber@trash.net>
   3 * Copyright \xC2\xA9 CC Computer Consultants GmbH, 2007 - 2008
   4 *
   5 * This program is free software; you can redistribute it and/or modify
   6 * it under the terms of the GNU General Public License version 2 as
   7 * published by the Free Software Foundation.
   8 *
   9 * This is a replacement of the old ipt_recent module, which carried the
  10 * following copyright notice:
  11 *
  12 * Author: Stephen Frost <sfrost@snowman.net>
  13 * Copyright 2002-2003, Stephen Frost, 2.5.x port by laforge@netfilter.org
  14 */
  15#include <linux/init.h>
  16#include <linux/ip.h>
  17#include <linux/ipv6.h>
  18#include <linux/module.h>
  19#include <linux/moduleparam.h>
  20#include <linux/proc_fs.h>
  21#include <linux/seq_file.h>
  22#include <linux/string.h>
  23#include <linux/ctype.h>
  24#include <linux/list.h>
  25#include <linux/random.h>
  26#include <linux/jhash.h>
  27#include <linux/bitops.h>
  28#include <linux/skbuff.h>
  29#include <linux/inet.h>
  30#include <net/net_namespace.h>
  31
  32#include <linux/netfilter/x_tables.h>
  33#include <linux/netfilter/xt_recent.h>
  34
  35MODULE_AUTHOR("Patrick McHardy <kaber@trash.net>");
  36MODULE_AUTHOR("Jan Engelhardt <jengelh@computergmbh.de>");
  37MODULE_DESCRIPTION("Xtables: \"recently-seen\" host matching for IPv4");
  38MODULE_LICENSE("GPL");
  39MODULE_ALIAS("ipt_recent");
  40MODULE_ALIAS("ip6t_recent");
  41
  42static unsigned int ip_list_tot = 100;
  43static unsigned int ip_pkt_list_tot = 20;
  44static unsigned int ip_list_hash_size = 0;
  45static unsigned int ip_list_perms = 0644;
  46static unsigned int ip_list_uid = 0;
  47static unsigned int ip_list_gid = 0;
  48module_param(ip_list_tot, uint, 0400);
  49module_param(ip_pkt_list_tot, uint, 0400);
  50module_param(ip_list_hash_size, uint, 0400);
  51module_param(ip_list_perms, uint, 0400);
  52module_param(ip_list_uid, uint, 0400);
  53module_param(ip_list_gid, uint, 0400);
  54MODULE_PARM_DESC(ip_list_tot, "number of IPs to remember per list");
  55MODULE_PARM_DESC(ip_pkt_list_tot, "number of packets per IP to remember (max. 255)");
  56MODULE_PARM_DESC(ip_list_hash_size, "size of hash table used to look up IPs");
  57MODULE_PARM_DESC(ip_list_perms, "permissions on /proc/net/xt_recent/* files");
  58MODULE_PARM_DESC(ip_list_uid,"owner of /proc/net/xt_recent/* files");
  59MODULE_PARM_DESC(ip_list_gid,"owning group of /proc/net/xt_recent/* files");
  60
  61struct recent_entry {
  62        struct list_head        list;
  63        struct list_head        lru_list;
  64        union nf_inet_addr      addr;
  65        u_int16_t               family;
  66        u_int8_t                ttl;
  67        u_int8_t                index;
  68        u_int16_t               nstamps;
  69        unsigned long           stamps[0];
  70};
  71
  72struct recent_table {
  73        struct list_head        list;
  74        char                    name[XT_RECENT_NAME_LEN];
  75#ifdef CONFIG_PROC_FS
  76        struct proc_dir_entry   *proc_old, *proc;
  77#endif
  78        unsigned int            refcnt;
  79        unsigned int            entries;
  80        struct list_head        lru_list;
  81        struct list_head        iphash[0];
  82};
  83
  84static LIST_HEAD(tables);
  85static DEFINE_SPINLOCK(recent_lock);
  86static DEFINE_MUTEX(recent_mutex);
  87
  88#ifdef CONFIG_PROC_FS
  89#ifdef CONFIG_NETFILTER_XT_MATCH_RECENT_PROC_COMPAT
  90static struct proc_dir_entry *proc_old_dir;
  91#endif
  92static struct proc_dir_entry *recent_proc_dir;
  93static const struct file_operations recent_old_fops, recent_mt_fops;
  94#endif
  95
  96static u_int32_t hash_rnd;
  97static bool hash_rnd_initted;
  98
  99static unsigned int recent_entry_hash4(const union nf_inet_addr *addr)
 100{
 101        if (!hash_rnd_initted) {
 102                get_random_bytes(&hash_rnd, sizeof(hash_rnd));
 103                hash_rnd_initted = true;
 104        }
 105        return jhash_1word((__force u32)addr->ip, hash_rnd) &
 106               (ip_list_hash_size - 1);
 107}
 108
 109static unsigned int recent_entry_hash6(const union nf_inet_addr *addr)
 110{
 111        if (!hash_rnd_initted) {
 112                get_random_bytes(&hash_rnd, sizeof(hash_rnd));
 113                hash_rnd_initted = true;
 114        }
 115        return jhash2((u32 *)addr->ip6, ARRAY_SIZE(addr->ip6), hash_rnd) &
 116               (ip_list_hash_size - 1);
 117}
 118
 119static struct recent_entry *
 120recent_entry_lookup(const struct recent_table *table,
 121                    const union nf_inet_addr *addrp, u_int16_t family,
 122                    u_int8_t ttl)
 123{
 124        struct recent_entry *e;
 125        unsigned int h;
 126
 127        if (family == NFPROTO_IPV4)
 128                h = recent_entry_hash4(addrp);
 129        else
 130                h = recent_entry_hash6(addrp);
 131
 132        list_for_each_entry(e, &table->iphash[h], list)
 133                if (e->family == family &&
 134                    memcmp(&e->addr, addrp, sizeof(e->addr)) == 0 &&
 135                    (ttl == e->ttl || ttl == 0 || e->ttl == 0))
 136                        return e;
 137        return NULL;
 138}
 139
 140static void recent_entry_remove(struct recent_table *t, struct recent_entry *e)
 141{
 142        list_del(&e->list);
 143        list_del(&e->lru_list);
 144        kfree(e);
 145        t->entries--;
 146}
 147
 148static struct recent_entry *
 149recent_entry_init(struct recent_table *t, const union nf_inet_addr *addr,
 150                  u_int16_t family, u_int8_t ttl)
 151{
 152        struct recent_entry *e;
 153
 154        if (t->entries >= ip_list_tot) {
 155                e = list_entry(t->lru_list.next, struct recent_entry, lru_list);
 156                recent_entry_remove(t, e);
 157        }
 158        e = kmalloc(sizeof(*e) + sizeof(e->stamps[0]) * ip_pkt_list_tot,
 159                    GFP_ATOMIC);
 160        if (e == NULL)
 161                return NULL;
 162        memcpy(&e->addr, addr, sizeof(e->addr));
 163        e->ttl       = ttl;
 164        e->stamps[0] = jiffies;
 165        e->nstamps   = 1;
 166        e->index     = 1;
 167        e->family    = family;
 168        if (family == NFPROTO_IPV4)
 169                list_add_tail(&e->list, &t->iphash[recent_entry_hash4(addr)]);
 170        else
 171                list_add_tail(&e->list, &t->iphash[recent_entry_hash6(addr)]);
 172        list_add_tail(&e->lru_list, &t->lru_list);
 173        t->entries++;
 174        return e;
 175}
 176
 177static void recent_entry_update(struct recent_table *t, struct recent_entry *e)
 178{
 179        e->stamps[e->index++] = jiffies;
 180        if (e->index > e->nstamps)
 181                e->nstamps = e->index;
 182        e->index %= ip_pkt_list_tot;
 183        list_move_tail(&e->lru_list, &t->lru_list);
 184}
 185
 186static struct recent_table *recent_table_lookup(const char *name)
 187{
 188        struct recent_table *t;
 189
 190        list_for_each_entry(t, &tables, list)
 191                if (!strcmp(t->name, name))
 192                        return t;
 193        return NULL;
 194}
 195
 196static void recent_table_flush(struct recent_table *t)
 197{
 198        struct recent_entry *e, *next;
 199        unsigned int i;
 200
 201        for (i = 0; i < ip_list_hash_size; i++)
 202                list_for_each_entry_safe(e, next, &t->iphash[i], list)
 203                        recent_entry_remove(t, e);
 204}
 205
 206static bool
 207recent_mt(const struct sk_buff *skb, const struct xt_match_param *par)
 208{
 209        const struct xt_recent_mtinfo *info = par->matchinfo;
 210        struct recent_table *t;
 211        struct recent_entry *e;
 212        union nf_inet_addr addr = {};
 213        u_int8_t ttl;
 214        bool ret = info->invert;
 215
 216        if (par->match->family == NFPROTO_IPV4) {
 217                const struct iphdr *iph = ip_hdr(skb);
 218
 219                if (info->side == XT_RECENT_DEST)
 220                        addr.ip = iph->daddr;
 221                else
 222                        addr.ip = iph->saddr;
 223
 224                ttl = iph->ttl;
 225        } else {
 226                const struct ipv6hdr *iph = ipv6_hdr(skb);
 227
 228                if (info->side == XT_RECENT_DEST)
 229                        memcpy(&addr.in6, &iph->daddr, sizeof(addr.in6));
 230                else
 231                        memcpy(&addr.in6, &iph->saddr, sizeof(addr.in6));
 232
 233                ttl = iph->hop_limit;
 234        }
 235
 236        /* use TTL as seen before forwarding */
 237        if (par->out != NULL && skb->sk == NULL)
 238                ttl++;
 239
 240        spin_lock_bh(&recent_lock);
 241        t = recent_table_lookup(info->name);
 242        e = recent_entry_lookup(t, &addr, par->match->family,
 243                                (info->check_set & XT_RECENT_TTL) ? ttl : 0);
 244        if (e == NULL) {
 245                if (!(info->check_set & XT_RECENT_SET))
 246                        goto out;
 247                e = recent_entry_init(t, &addr, par->match->family, ttl);
 248                if (e == NULL)
 249                        *par->hotdrop = true;
 250                ret = !ret;
 251                goto out;
 252        }
 253
 254        if (info->check_set & XT_RECENT_SET)
 255                ret = !ret;
 256        else if (info->check_set & XT_RECENT_REMOVE) {
 257                recent_entry_remove(t, e);
 258                ret = !ret;
 259        } else if (info->check_set & (XT_RECENT_CHECK | XT_RECENT_UPDATE)) {
 260                unsigned long time = jiffies - info->seconds * HZ;
 261                unsigned int i, hits = 0;
 262
 263                for (i = 0; i < e->nstamps; i++) {
 264                        if (info->seconds && time_after(time, e->stamps[i]))
 265                                continue;
 266                        if (++hits >= info->hit_count) {
 267                                ret = !ret;
 268                                break;
 269                        }
 270                }
 271        }
 272
 273        if (info->check_set & XT_RECENT_SET ||
 274            (info->check_set & XT_RECENT_UPDATE && ret)) {
 275                recent_entry_update(t, e);
 276                e->ttl = ttl;
 277        }
 278out:
 279        spin_unlock_bh(&recent_lock);
 280        return ret;
 281}
 282
 283static bool recent_mt_check(const struct xt_mtchk_param *par)
 284{
 285        const struct xt_recent_mtinfo *info = par->matchinfo;
 286        struct recent_table *t;
 287        unsigned i;
 288        bool ret = false;
 289
 290        if (hweight8(info->check_set &
 291                     (XT_RECENT_SET | XT_RECENT_REMOVE |
 292                      XT_RECENT_CHECK | XT_RECENT_UPDATE)) != 1)
 293                return false;
 294        if ((info->check_set & (XT_RECENT_SET | XT_RECENT_REMOVE)) &&
 295            (info->seconds || info->hit_count))
 296                return false;
 297        if (info->hit_count > ip_pkt_list_tot)
 298                return false;
 299        if (info->name[0] == '\0' ||
 300            strnlen(info->name, XT_RECENT_NAME_LEN) == XT_RECENT_NAME_LEN)
 301                return false;
 302
 303        mutex_lock(&recent_mutex);
 304        t = recent_table_lookup(info->name);
 305        if (t != NULL) {
 306                t->refcnt++;
 307                ret = true;
 308                goto out;
 309        }
 310
 311        t = kzalloc(sizeof(*t) + sizeof(t->iphash[0]) * ip_list_hash_size,
 312                    GFP_KERNEL);
 313        if (t == NULL)
 314                goto out;
 315        t->refcnt = 1;
 316        strcpy(t->name, info->name);
 317        INIT_LIST_HEAD(&t->lru_list);
 318        for (i = 0; i < ip_list_hash_size; i++)
 319                INIT_LIST_HEAD(&t->iphash[i]);
 320#ifdef CONFIG_PROC_FS
 321        t->proc = proc_create_data(t->name, ip_list_perms, recent_proc_dir,
 322                  &recent_mt_fops, t);
 323        if (t->proc == NULL) {
 324                kfree(t);
 325                goto out;
 326        }
 327#ifdef CONFIG_NETFILTER_XT_MATCH_RECENT_PROC_COMPAT
 328        t->proc_old = proc_create_data(t->name, ip_list_perms, proc_old_dir,
 329                      &recent_old_fops, t);
 330        if (t->proc_old == NULL) {
 331                remove_proc_entry(t->name, proc_old_dir);
 332                kfree(t);
 333                goto out;
 334        }
 335        t->proc_old->uid   = ip_list_uid;
 336        t->proc_old->gid   = ip_list_gid;
 337#endif
 338        t->proc->uid       = ip_list_uid;
 339        t->proc->gid       = ip_list_gid;
 340#endif
 341        spin_lock_bh(&recent_lock);
 342        list_add_tail(&t->list, &tables);
 343        spin_unlock_bh(&recent_lock);
 344        ret = true;
 345out:
 346        mutex_unlock(&recent_mutex);
 347        return ret;
 348}
 349
 350static void recent_mt_destroy(const struct xt_mtdtor_param *par)
 351{
 352        const struct xt_recent_mtinfo *info = par->matchinfo;
 353        struct recent_table *t;
 354
 355        mutex_lock(&recent_mutex);
 356        t = recent_table_lookup(info->name);
 357        if (--t->refcnt == 0) {
 358                spin_lock_bh(&recent_lock);
 359                list_del(&t->list);
 360                spin_unlock_bh(&recent_lock);
 361#ifdef CONFIG_PROC_FS
 362#ifdef CONFIG_NETFILTER_XT_MATCH_RECENT_PROC_COMPAT
 363                remove_proc_entry(t->name, proc_old_dir);
 364#endif
 365                remove_proc_entry(t->name, recent_proc_dir);
 366#endif
 367                recent_table_flush(t);
 368                kfree(t);
 369        }
 370        mutex_unlock(&recent_mutex);
 371}
 372
 373#ifdef CONFIG_PROC_FS
 374struct recent_iter_state {
 375        const struct recent_table *table;
 376        unsigned int            bucket;
 377};
 378
 379static void *recent_seq_start(struct seq_file *seq, loff_t *pos)
 380        __acquires(recent_lock)
 381{
 382        struct recent_iter_state *st = seq->private;
 383        const struct recent_table *t = st->table;
 384        struct recent_entry *e;
 385        loff_t p = *pos;
 386
 387        spin_lock_bh(&recent_lock);
 388
 389        for (st->bucket = 0; st->bucket < ip_list_hash_size; st->bucket++)
 390                list_for_each_entry(e, &t->iphash[st->bucket], list)
 391                        if (p-- == 0)
 392                                return e;
 393        return NULL;
 394}
 395
 396static void *recent_seq_next(struct seq_file *seq, void *v, loff_t *pos)
 397{
 398        struct recent_iter_state *st = seq->private;
 399        const struct recent_table *t = st->table;
 400        const struct recent_entry *e = v;
 401        const struct list_head *head = e->list.next;
 402
 403        while (head == &t->iphash[st->bucket]) {
 404                if (++st->bucket >= ip_list_hash_size)
 405                        return NULL;
 406                head = t->iphash[st->bucket].next;
 407        }
 408        (*pos)++;
 409        return list_entry(head, struct recent_entry, list);
 410}
 411
 412static void recent_seq_stop(struct seq_file *s, void *v)
 413        __releases(recent_lock)
 414{
 415        spin_unlock_bh(&recent_lock);
 416}
 417
 418static int recent_seq_show(struct seq_file *seq, void *v)
 419{
 420        const struct recent_entry *e = v;
 421        unsigned int i;
 422
 423        i = (e->index - 1) % ip_pkt_list_tot;
 424        if (e->family == NFPROTO_IPV4)
 425                seq_printf(seq, "src=" NIPQUAD_FMT " ttl: %u last_seen: %lu "
 426                           "oldest_pkt: %u", NIPQUAD(e->addr.ip), e->ttl,
 427                           e->stamps[i], e->index);
 428        else
 429                seq_printf(seq, "src=" NIP6_FMT " ttl: %u last_seen: %lu "
 430                           "oldest_pkt: %u", NIP6(e->addr.in6), e->ttl,
 431                           e->stamps[i], e->index);
 432        for (i = 0; i < e->nstamps; i++)
 433                seq_printf(seq, "%s %lu", i ? "," : "", e->stamps[i]);
 434        seq_printf(seq, "\n");
 435        return 0;
 436}
 437
 438static const struct seq_operations recent_seq_ops = {
 439        .start          = recent_seq_start,
 440        .next           = recent_seq_next,
 441        .stop           = recent_seq_stop,
 442        .show           = recent_seq_show,
 443};
 444
 445static int recent_seq_open(struct inode *inode, struct file *file)
 446{
 447        struct proc_dir_entry *pde = PDE(inode);
 448        struct recent_iter_state *st;
 449
 450        st = __seq_open_private(file, &recent_seq_ops, sizeof(*st));
 451        if (st == NULL)
 452                return -ENOMEM;
 453
 454        st->table    = pde->data;
 455        return 0;
 456}
 457
 458#ifdef CONFIG_NETFILTER_XT_MATCH_RECENT_PROC_COMPAT
 459static int recent_old_seq_open(struct inode *inode, struct file *filp)
 460{
 461        static bool warned_of_old;
 462
 463        if (unlikely(!warned_of_old)) {
 464                printk(KERN_INFO KBUILD_MODNAME ": Use of /proc/net/ipt_recent"
 465                       " is deprecated; use /proc/net/xt_recent.\n");
 466                warned_of_old = true;
 467        }
 468        return recent_seq_open(inode, filp);
 469}
 470
 471static ssize_t recent_old_proc_write(struct file *file,
 472                                     const char __user *input,
 473                                     size_t size, loff_t *loff)
 474{
 475        const struct proc_dir_entry *pde = PDE(file->f_path.dentry->d_inode);
 476        struct recent_table *t = pde->data;
 477        struct recent_entry *e;
 478        char buf[sizeof("+255.255.255.255")], *c = buf;
 479        __be32 addr;
 480        int add;
 481
 482        if (size > sizeof(buf))
 483                size = sizeof(buf);
 484        if (copy_from_user(buf, input, size))
 485                return -EFAULT;
 486
 487        while (isspace(*c))
 488                c++;
 489
 490        if (size - (c - buf) < 5)
 491                return c - buf;
 492        if (!strncmp(c, "clear", 5)) {
 493                c += 5;
 494                spin_lock_bh(&recent_lock);
 495                recent_table_flush(t);
 496                spin_unlock_bh(&recent_lock);
 497                return c - buf;
 498        }
 499
 500        switch (*c) {
 501        case '-':
 502                add = 0;
 503                c++;
 504                break;
 505        case '+':
 506                c++;
 507        default:
 508                add = 1;
 509                break;
 510        }
 511        addr = in_aton(c);
 512
 513        spin_lock_bh(&recent_lock);
 514        e = recent_entry_lookup(t, (const void *)&addr, NFPROTO_IPV4, 0);
 515        if (e == NULL) {
 516                if (add)
 517                        recent_entry_init(t, (const void *)&addr,
 518                                          NFPROTO_IPV4, 0);
 519        } else {
 520                if (add)
 521                        recent_entry_update(t, e);
 522                else
 523                        recent_entry_remove(t, e);
 524        }
 525        spin_unlock_bh(&recent_lock);
 526        return size;
 527}
 528
 529static const struct file_operations recent_old_fops = {
 530        .open           = recent_old_seq_open,
 531        .read           = seq_read,
 532        .write          = recent_old_proc_write,
 533        .release        = seq_release_private,
 534        .owner          = THIS_MODULE,
 535};
 536#endif
 537
 538static ssize_t
 539recent_mt_proc_write(struct file *file, const char __user *input,
 540                     size_t size, loff_t *loff)
 541{
 542        const struct proc_dir_entry *pde = PDE(file->f_path.dentry->d_inode);
 543        struct recent_table *t = pde->data;
 544        struct recent_entry *e;
 545        char buf[sizeof("+b335:1d35:1e55:dead:c0de:1715:5afe:c0de")];
 546        const char *c = buf;
 547        union nf_inet_addr addr;
 548        u_int16_t family;
 549        bool add, succ;
 550
 551        if (size == 0)
 552                return 0;
 553        if (size > sizeof(buf))
 554                size = sizeof(buf);
 555        if (copy_from_user(buf, input, size) != 0)
 556                return -EFAULT;
 557
 558        /* Strict protocol! */
 559        if (*loff != 0)
 560                return -ESPIPE;
 561        switch (*c) {
 562        case '/': /* flush table */
 563                spin_lock_bh(&recent_lock);
 564                recent_table_flush(t);
 565                spin_unlock_bh(&recent_lock);
 566                return size;
 567        case '-': /* remove address */
 568                add = false;
 569                break;
 570        case '+': /* add address */
 571                add = true;
 572                break;
 573        default:
 574                printk(KERN_INFO KBUILD_MODNAME ": Need +ip, -ip or /\n");
 575                return -EINVAL;
 576        }
 577
 578        ++c;
 579        --size;
 580        if (strnchr(c, size, ':') != NULL) {
 581                family = NFPROTO_IPV6;
 582                succ   = in6_pton(c, size, (void *)&addr, '\n', NULL);
 583        } else {
 584                family = NFPROTO_IPV4;
 585                succ   = in4_pton(c, size, (void *)&addr, '\n', NULL);
 586        }
 587
 588        if (!succ) {
 589                printk(KERN_INFO KBUILD_MODNAME ": illegal address written "
 590                       "to procfs\n");
 591                return -EINVAL;
 592        }
 593
 594        spin_lock_bh(&recent_lock);
 595        e = recent_entry_lookup(t, &addr, family, 0);
 596        if (e == NULL) {
 597                if (add)
 598                        recent_entry_init(t, &addr, family, 0);
 599        } else {
 600                if (add)
 601                        recent_entry_update(t, e);
 602                else
 603                        recent_entry_remove(t, e);
 604        }
 605        spin_unlock_bh(&recent_lock);
 606        /* Note we removed one above */
 607        *loff += size + 1;
 608        return size + 1;
 609}
 610
 611static const struct file_operations recent_mt_fops = {
 612        .open    = recent_seq_open,
 613        .read    = seq_read,
 614        .write   = recent_mt_proc_write,
 615        .release = seq_release_private,
 616        .owner   = THIS_MODULE,
 617};
 618#endif /* CONFIG_PROC_FS */
 619
 620static struct xt_match recent_mt_reg[] __read_mostly = {
 621        {
 622                .name       = "recent",
 623                .revision   = 0,
 624                .family     = NFPROTO_IPV4,
 625                .match      = recent_mt,
 626                .matchsize  = sizeof(struct xt_recent_mtinfo),
 627                .checkentry = recent_mt_check,
 628                .destroy    = recent_mt_destroy,
 629                .me         = THIS_MODULE,
 630        },
 631        {
 632                .name       = "recent",
 633                .revision   = 0,
 634                .family     = NFPROTO_IPV6,
 635                .match      = recent_mt,
 636                .matchsize  = sizeof(struct xt_recent_mtinfo),
 637                .checkentry = recent_mt_check,
 638                .destroy    = recent_mt_destroy,
 639                .me         = THIS_MODULE,
 640        },
 641};
 642
 643static int __init recent_mt_init(void)
 644{
 645        int err;
 646
 647        if (!ip_list_tot || !ip_pkt_list_tot || ip_pkt_list_tot > 255)
 648                return -EINVAL;
 649        ip_list_hash_size = 1 << fls(ip_list_tot);
 650
 651        err = xt_register_matches(recent_mt_reg, ARRAY_SIZE(recent_mt_reg));
 652#ifdef CONFIG_PROC_FS
 653        if (err)
 654                return err;
 655        recent_proc_dir = proc_mkdir("xt_recent", init_net.proc_net);
 656        if (recent_proc_dir == NULL) {
 657                xt_unregister_matches(recent_mt_reg, ARRAY_SIZE(recent_mt_reg));
 658                err = -ENOMEM;
 659        }
 660#ifdef CONFIG_NETFILTER_XT_MATCH_RECENT_PROC_COMPAT
 661        if (err < 0)
 662                return err;
 663        proc_old_dir = proc_mkdir("ipt_recent", init_net.proc_net);
 664        if (proc_old_dir == NULL) {
 665                remove_proc_entry("xt_recent", init_net.proc_net);
 666                xt_unregister_matches(recent_mt_reg, ARRAY_SIZE(recent_mt_reg));
 667                err = -ENOMEM;
 668        }
 669#endif
 670#endif
 671        return err;
 672}
 673
 674static void __exit recent_mt_exit(void)
 675{
 676        BUG_ON(!list_empty(&tables));
 677        xt_unregister_matches(recent_mt_reg, ARRAY_SIZE(recent_mt_reg));
 678#ifdef CONFIG_PROC_FS
 679#ifdef CONFIG_NETFILTER_XT_MATCH_RECENT_PROC_COMPAT
 680        remove_proc_entry("ipt_recent", init_net.proc_net);
 681#endif
 682        remove_proc_entry("xt_recent", init_net.proc_net);
 683#endif
 684}
 685
 686module_init(recent_mt_init);
 687module_exit(recent_mt_exit);
 688