linux/net/netfilter/xt_recent.c
<<
>>
Prefs
   1/*
   2 * Copyright (c) 2006 Patrick McHardy <kaber@trash.net>
   3 * Copyright © 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        unsigned int            refcnt;
  76        unsigned int            entries;
  77        struct list_head        lru_list;
  78        struct list_head        iphash[0];
  79};
  80
  81static LIST_HEAD(tables);
  82static DEFINE_SPINLOCK(recent_lock);
  83static DEFINE_MUTEX(recent_mutex);
  84
  85#ifdef CONFIG_PROC_FS
  86#ifdef CONFIG_NETFILTER_XT_MATCH_RECENT_PROC_COMPAT
  87static struct proc_dir_entry *proc_old_dir;
  88#endif
  89static struct proc_dir_entry *recent_proc_dir;
  90static const struct file_operations recent_old_fops, recent_mt_fops;
  91#endif
  92
  93static u_int32_t hash_rnd;
  94static bool hash_rnd_initted;
  95
  96static unsigned int recent_entry_hash4(const union nf_inet_addr *addr)
  97{
  98        if (!hash_rnd_initted) {
  99                get_random_bytes(&hash_rnd, sizeof(hash_rnd));
 100                hash_rnd_initted = true;
 101        }
 102        return jhash_1word((__force u32)addr->ip, hash_rnd) &
 103               (ip_list_hash_size - 1);
 104}
 105
 106static unsigned int recent_entry_hash6(const union nf_inet_addr *addr)
 107{
 108        if (!hash_rnd_initted) {
 109                get_random_bytes(&hash_rnd, sizeof(hash_rnd));
 110                hash_rnd_initted = true;
 111        }
 112        return jhash2((u32 *)addr->ip6, ARRAY_SIZE(addr->ip6), hash_rnd) &
 113               (ip_list_hash_size - 1);
 114}
 115
 116static struct recent_entry *
 117recent_entry_lookup(const struct recent_table *table,
 118                    const union nf_inet_addr *addrp, u_int16_t family,
 119                    u_int8_t ttl)
 120{
 121        struct recent_entry *e;
 122        unsigned int h;
 123
 124        if (family == NFPROTO_IPV4)
 125                h = recent_entry_hash4(addrp);
 126        else
 127                h = recent_entry_hash6(addrp);
 128
 129        list_for_each_entry(e, &table->iphash[h], list)
 130                if (e->family == family &&
 131                    memcmp(&e->addr, addrp, sizeof(e->addr)) == 0 &&
 132                    (ttl == e->ttl || ttl == 0 || e->ttl == 0))
 133                        return e;
 134        return NULL;
 135}
 136
 137static void recent_entry_remove(struct recent_table *t, struct recent_entry *e)
 138{
 139        list_del(&e->list);
 140        list_del(&e->lru_list);
 141        kfree(e);
 142        t->entries--;
 143}
 144
 145static struct recent_entry *
 146recent_entry_init(struct recent_table *t, const union nf_inet_addr *addr,
 147                  u_int16_t family, u_int8_t ttl)
 148{
 149        struct recent_entry *e;
 150
 151        if (t->entries >= ip_list_tot) {
 152                e = list_entry(t->lru_list.next, struct recent_entry, lru_list);
 153                recent_entry_remove(t, e);
 154        }
 155        e = kmalloc(sizeof(*e) + sizeof(e->stamps[0]) * ip_pkt_list_tot,
 156                    GFP_ATOMIC);
 157        if (e == NULL)
 158                return NULL;
 159        memcpy(&e->addr, addr, sizeof(e->addr));
 160        e->ttl       = ttl;
 161        e->stamps[0] = jiffies;
 162        e->nstamps   = 1;
 163        e->index     = 1;
 164        e->family    = family;
 165        if (family == NFPROTO_IPV4)
 166                list_add_tail(&e->list, &t->iphash[recent_entry_hash4(addr)]);
 167        else
 168                list_add_tail(&e->list, &t->iphash[recent_entry_hash6(addr)]);
 169        list_add_tail(&e->lru_list, &t->lru_list);
 170        t->entries++;
 171        return e;
 172}
 173
 174static void recent_entry_update(struct recent_table *t, struct recent_entry *e)
 175{
 176        e->stamps[e->index++] = jiffies;
 177        if (e->index > e->nstamps)
 178                e->nstamps = e->index;
 179        e->index %= ip_pkt_list_tot;
 180        list_move_tail(&e->lru_list, &t->lru_list);
 181}
 182
 183static struct recent_table *recent_table_lookup(const char *name)
 184{
 185        struct recent_table *t;
 186
 187        list_for_each_entry(t, &tables, list)
 188                if (!strcmp(t->name, name))
 189                        return t;
 190        return NULL;
 191}
 192
 193static void recent_table_flush(struct recent_table *t)
 194{
 195        struct recent_entry *e, *next;
 196        unsigned int i;
 197
 198        for (i = 0; i < ip_list_hash_size; i++)
 199                list_for_each_entry_safe(e, next, &t->iphash[i], list)
 200                        recent_entry_remove(t, e);
 201}
 202
 203static bool
 204recent_mt(const struct sk_buff *skb, const struct xt_match_param *par)
 205{
 206        const struct xt_recent_mtinfo *info = par->matchinfo;
 207        struct recent_table *t;
 208        struct recent_entry *e;
 209        union nf_inet_addr addr = {};
 210        u_int8_t ttl;
 211        bool ret = info->invert;
 212
 213        if (par->match->family == NFPROTO_IPV4) {
 214                const struct iphdr *iph = ip_hdr(skb);
 215
 216                if (info->side == XT_RECENT_DEST)
 217                        addr.ip = iph->daddr;
 218                else
 219                        addr.ip = iph->saddr;
 220
 221                ttl = iph->ttl;
 222        } else {
 223                const struct ipv6hdr *iph = ipv6_hdr(skb);
 224
 225                if (info->side == XT_RECENT_DEST)
 226                        memcpy(&addr.in6, &iph->daddr, sizeof(addr.in6));
 227                else
 228                        memcpy(&addr.in6, &iph->saddr, sizeof(addr.in6));
 229
 230                ttl = iph->hop_limit;
 231        }
 232
 233        /* use TTL as seen before forwarding */
 234        if (par->out != NULL && skb->sk == NULL)
 235                ttl++;
 236
 237        spin_lock_bh(&recent_lock);
 238        t = recent_table_lookup(info->name);
 239        e = recent_entry_lookup(t, &addr, par->match->family,
 240                                (info->check_set & XT_RECENT_TTL) ? ttl : 0);
 241        if (e == NULL) {
 242                if (!(info->check_set & XT_RECENT_SET))
 243                        goto out;
 244                e = recent_entry_init(t, &addr, par->match->family, ttl);
 245                if (e == NULL)
 246                        *par->hotdrop = true;
 247                ret = !ret;
 248                goto out;
 249        }
 250
 251        if (info->check_set & XT_RECENT_SET)
 252                ret = !ret;
 253        else if (info->check_set & XT_RECENT_REMOVE) {
 254                recent_entry_remove(t, e);
 255                ret = !ret;
 256        } else if (info->check_set & (XT_RECENT_CHECK | XT_RECENT_UPDATE)) {
 257                unsigned long time = jiffies - info->seconds * HZ;
 258                unsigned int i, hits = 0;
 259
 260                for (i = 0; i < e->nstamps; i++) {
 261                        if (info->seconds && time_after(time, e->stamps[i]))
 262                                continue;
 263                        if (++hits >= info->hit_count) {
 264                                ret = !ret;
 265                                break;
 266                        }
 267                }
 268        }
 269
 270        if (info->check_set & XT_RECENT_SET ||
 271            (info->check_set & XT_RECENT_UPDATE && ret)) {
 272                recent_entry_update(t, e);
 273                e->ttl = ttl;
 274        }
 275out:
 276        spin_unlock_bh(&recent_lock);
 277        return ret;
 278}
 279
 280static bool recent_mt_check(const struct xt_mtchk_param *par)
 281{
 282        const struct xt_recent_mtinfo *info = par->matchinfo;
 283        struct recent_table *t;
 284#ifdef CONFIG_PROC_FS
 285        struct proc_dir_entry *pde;
 286#endif
 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        pde = proc_create_data(t->name, ip_list_perms, recent_proc_dir,
 322                  &recent_mt_fops, t);
 323        if (pde == NULL) {
 324                kfree(t);
 325                goto out;
 326        }
 327        pde->uid = ip_list_uid;
 328        pde->gid = ip_list_gid;
 329#ifdef CONFIG_NETFILTER_XT_MATCH_RECENT_PROC_COMPAT
 330        pde = proc_create_data(t->name, ip_list_perms, proc_old_dir,
 331                      &recent_old_fops, t);
 332        if (pde == NULL) {
 333                remove_proc_entry(t->name, proc_old_dir);
 334                kfree(t);
 335                goto out;
 336        }
 337        pde->uid = ip_list_uid;
 338        pde->gid = ip_list_gid;
 339#endif
 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=%pI4 ttl: %u last_seen: %lu oldest_pkt: %u",
 426                           &e->addr.ip, e->ttl, e->stamps[i], e->index);
 427        else
 428                seq_printf(seq, "src=%pI6 ttl: %u last_seen: %lu oldest_pkt: %u",
 429                           &e->addr.in6, e->ttl, e->stamps[i], e->index);
 430        for (i = 0; i < e->nstamps; i++)
 431                seq_printf(seq, "%s %lu", i ? "," : "", e->stamps[i]);
 432        seq_printf(seq, "\n");
 433        return 0;
 434}
 435
 436static const struct seq_operations recent_seq_ops = {
 437        .start          = recent_seq_start,
 438        .next           = recent_seq_next,
 439        .stop           = recent_seq_stop,
 440        .show           = recent_seq_show,
 441};
 442
 443static int recent_seq_open(struct inode *inode, struct file *file)
 444{
 445        struct proc_dir_entry *pde = PDE(inode);
 446        struct recent_iter_state *st;
 447
 448        st = __seq_open_private(file, &recent_seq_ops, sizeof(*st));
 449        if (st == NULL)
 450                return -ENOMEM;
 451
 452        st->table    = pde->data;
 453        return 0;
 454}
 455
 456#ifdef CONFIG_NETFILTER_XT_MATCH_RECENT_PROC_COMPAT
 457static int recent_old_seq_open(struct inode *inode, struct file *filp)
 458{
 459        static bool warned_of_old;
 460
 461        if (unlikely(!warned_of_old)) {
 462                printk(KERN_INFO KBUILD_MODNAME ": Use of /proc/net/ipt_recent"
 463                       " is deprecated; use /proc/net/xt_recent.\n");
 464                warned_of_old = true;
 465        }
 466        return recent_seq_open(inode, filp);
 467}
 468
 469static ssize_t recent_old_proc_write(struct file *file,
 470                                     const char __user *input,
 471                                     size_t size, loff_t *loff)
 472{
 473        const struct proc_dir_entry *pde = PDE(file->f_path.dentry->d_inode);
 474        struct recent_table *t = pde->data;
 475        struct recent_entry *e;
 476        char buf[sizeof("+255.255.255.255")], *c = buf;
 477        union nf_inet_addr addr = {};
 478        int add;
 479
 480        if (size > sizeof(buf))
 481                size = sizeof(buf);
 482        if (copy_from_user(buf, input, size))
 483                return -EFAULT;
 484
 485        while (isspace(*c))
 486                c++;
 487
 488        if (size - (c - buf) < 5)
 489                return c - buf;
 490        if (!strncmp(c, "clear", 5)) {
 491                c += 5;
 492                spin_lock_bh(&recent_lock);
 493                recent_table_flush(t);
 494                spin_unlock_bh(&recent_lock);
 495                return c - buf;
 496        }
 497
 498        switch (*c) {
 499        case '-':
 500                add = 0;
 501                c++;
 502                break;
 503        case '+':
 504                c++;
 505        default:
 506                add = 1;
 507                break;
 508        }
 509        addr.ip = in_aton(c);
 510
 511        spin_lock_bh(&recent_lock);
 512        e = recent_entry_lookup(t, &addr, NFPROTO_IPV4, 0);
 513        if (e == NULL) {
 514                if (add)
 515                        recent_entry_init(t, &addr, NFPROTO_IPV4, 0);
 516        } else {
 517                if (add)
 518                        recent_entry_update(t, e);
 519                else
 520                        recent_entry_remove(t, e);
 521        }
 522        spin_unlock_bh(&recent_lock);
 523        return size;
 524}
 525
 526static const struct file_operations recent_old_fops = {
 527        .open           = recent_old_seq_open,
 528        .read           = seq_read,
 529        .write          = recent_old_proc_write,
 530        .release        = seq_release_private,
 531        .owner          = THIS_MODULE,
 532};
 533#endif
 534
 535static ssize_t
 536recent_mt_proc_write(struct file *file, const char __user *input,
 537                     size_t size, loff_t *loff)
 538{
 539        const struct proc_dir_entry *pde = PDE(file->f_path.dentry->d_inode);
 540        struct recent_table *t = pde->data;
 541        struct recent_entry *e;
 542        char buf[sizeof("+b335:1d35:1e55:dead:c0de:1715:5afe:c0de")];
 543        const char *c = buf;
 544        union nf_inet_addr addr = {};
 545        u_int16_t family;
 546        bool add, succ;
 547
 548        if (size == 0)
 549                return 0;
 550        if (size > sizeof(buf))
 551                size = sizeof(buf);
 552        if (copy_from_user(buf, input, size) != 0)
 553                return -EFAULT;
 554
 555        /* Strict protocol! */
 556        if (*loff != 0)
 557                return -ESPIPE;
 558        switch (*c) {
 559        case '/': /* flush table */
 560                spin_lock_bh(&recent_lock);
 561                recent_table_flush(t);
 562                spin_unlock_bh(&recent_lock);
 563                return size;
 564        case '-': /* remove address */
 565                add = false;
 566                break;
 567        case '+': /* add address */
 568                add = true;
 569                break;
 570        default:
 571                printk(KERN_INFO KBUILD_MODNAME ": Need +ip, -ip or /\n");
 572                return -EINVAL;
 573        }
 574
 575        ++c;
 576        --size;
 577        if (strnchr(c, size, ':') != NULL) {
 578                family = NFPROTO_IPV6;
 579                succ   = in6_pton(c, size, (void *)&addr, '\n', NULL);
 580        } else {
 581                family = NFPROTO_IPV4;
 582                succ   = in4_pton(c, size, (void *)&addr, '\n', NULL);
 583        }
 584
 585        if (!succ) {
 586                printk(KERN_INFO KBUILD_MODNAME ": illegal address written "
 587                       "to procfs\n");
 588                return -EINVAL;
 589        }
 590
 591        spin_lock_bh(&recent_lock);
 592        e = recent_entry_lookup(t, &addr, family, 0);
 593        if (e == NULL) {
 594                if (add)
 595                        recent_entry_init(t, &addr, family, 0);
 596        } else {
 597                if (add)
 598                        recent_entry_update(t, e);
 599                else
 600                        recent_entry_remove(t, e);
 601        }
 602        spin_unlock_bh(&recent_lock);
 603        /* Note we removed one above */
 604        *loff += size + 1;
 605        return size + 1;
 606}
 607
 608static const struct file_operations recent_mt_fops = {
 609        .open    = recent_seq_open,
 610        .read    = seq_read,
 611        .write   = recent_mt_proc_write,
 612        .release = seq_release_private,
 613        .owner   = THIS_MODULE,
 614};
 615#endif /* CONFIG_PROC_FS */
 616
 617static struct xt_match recent_mt_reg[] __read_mostly = {
 618        {
 619                .name       = "recent",
 620                .revision   = 0,
 621                .family     = NFPROTO_IPV4,
 622                .match      = recent_mt,
 623                .matchsize  = sizeof(struct xt_recent_mtinfo),
 624                .checkentry = recent_mt_check,
 625                .destroy    = recent_mt_destroy,
 626                .me         = THIS_MODULE,
 627        },
 628        {
 629                .name       = "recent",
 630                .revision   = 0,
 631                .family     = NFPROTO_IPV6,
 632                .match      = recent_mt,
 633                .matchsize  = sizeof(struct xt_recent_mtinfo),
 634                .checkentry = recent_mt_check,
 635                .destroy    = recent_mt_destroy,
 636                .me         = THIS_MODULE,
 637        },
 638};
 639
 640static int __init recent_mt_init(void)
 641{
 642        int err;
 643
 644        if (!ip_list_tot || !ip_pkt_list_tot || ip_pkt_list_tot > 255)
 645                return -EINVAL;
 646        ip_list_hash_size = 1 << fls(ip_list_tot);
 647
 648        err = xt_register_matches(recent_mt_reg, ARRAY_SIZE(recent_mt_reg));
 649#ifdef CONFIG_PROC_FS
 650        if (err)
 651                return err;
 652        recent_proc_dir = proc_mkdir("xt_recent", init_net.proc_net);
 653        if (recent_proc_dir == NULL) {
 654                xt_unregister_matches(recent_mt_reg, ARRAY_SIZE(recent_mt_reg));
 655                err = -ENOMEM;
 656        }
 657#ifdef CONFIG_NETFILTER_XT_MATCH_RECENT_PROC_COMPAT
 658        if (err < 0)
 659                return err;
 660        proc_old_dir = proc_mkdir("ipt_recent", init_net.proc_net);
 661        if (proc_old_dir == NULL) {
 662                remove_proc_entry("xt_recent", init_net.proc_net);
 663                xt_unregister_matches(recent_mt_reg, ARRAY_SIZE(recent_mt_reg));
 664                err = -ENOMEM;
 665        }
 666#endif
 667#endif
 668        return err;
 669}
 670
 671static void __exit recent_mt_exit(void)
 672{
 673        BUG_ON(!list_empty(&tables));
 674        xt_unregister_matches(recent_mt_reg, ARRAY_SIZE(recent_mt_reg));
 675#ifdef CONFIG_PROC_FS
 676#ifdef CONFIG_NETFILTER_XT_MATCH_RECENT_PROC_COMPAT
 677        remove_proc_entry("ipt_recent", init_net.proc_net);
 678#endif
 679        remove_proc_entry("xt_recent", init_net.proc_net);
 680#endif
 681}
 682
 683module_init(recent_mt_init);
 684module_exit(recent_mt_exit);
 685