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