]> git.ipfire.org Git - people/arne_f/kernel.git/blame - net/netfilter/xt_layer7.c
netfilter: layer7: ct memory optimization
[people/arne_f/kernel.git] / net / netfilter / xt_layer7.c
CommitLineData
9697c820
AF
1/*
2 Kernel module to match application layer (OSI layer 7) data in connections.
3
4 http://l7-filter.sf.net
5
6 (C) 2003-2009 Matthew Strait and Ethan Sommer.
7
8 This program is free software; you can redistribute it and/or
9 modify it under the terms of the GNU General Public License
10 as published by the Free Software Foundation; either version
11 2 of the License, or (at your option) any later version.
12 http://www.gnu.org/licenses/gpl.txt
13
14 Based on ipt_string.c (C) 2000 Emmanuel Roger <winfield@freegates.be>,
15 xt_helper.c (C) 2002 Harald Welte and cls_layer7.c (C) 2003 Matthew Strait,
16 Ethan Sommer, Justin Levandoski.
17*/
18
19#include <linux/spinlock.h>
20#include <linux/version.h>
21#include <net/ip.h>
22#include <net/tcp.h>
23#include <linux/module.h>
24#include <linux/skbuff.h>
25#include <linux/netfilter.h>
26#include <net/netfilter/nf_conntrack.h>
27#include <net/netfilter/nf_conntrack_core.h>
28#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27)
29#include <net/netfilter/nf_conntrack_extend.h>
30#include <net/netfilter/nf_conntrack_acct.h>
31#endif
32#include <linux/netfilter/x_tables.h>
33#include <linux/netfilter/xt_layer7.h>
34#include <linux/ctype.h>
35#include <linux/proc_fs.h>
36
37#include "regexp/regexp.c"
38
39MODULE_LICENSE("GPL");
e6cddf5d 40MODULE_AUTHOR("Matthew Strait <quadong@users.sf.net>, Ethan Sommer <sommere@users.sf.net>, Arne Fitzenreiter <arne_f@ipfire.org>");
9697c820
AF
41MODULE_DESCRIPTION("iptables application layer match module");
42MODULE_ALIAS("ipt_layer7");
e6cddf5d 43MODULE_VERSION("2.30");
9697c820
AF
44
45static int maxdatalen = 2048; // this is the default
46module_param(maxdatalen, int, 0444);
47MODULE_PARM_DESC(maxdatalen, "maximum bytes of data looked at by l7-filter");
48#ifdef CONFIG_NETFILTER_XT_MATCH_LAYER7_DEBUG
49 #define DPRINTK(format,args...) printk(format,##args)
50#else
51 #define DPRINTK(format,args...)
52#endif
53
54/* Number of packets whose data we look at.
55This can be modified through /proc/net/layer7_numpackets */
56static int num_packets = 10;
57
58static struct pattern_cache {
59 char * regex_string;
60 regexp * pattern;
61 struct pattern_cache * next;
62} * first_pattern_cache = NULL;
63
e6cddf5d
AF
64static struct proto_cache {
65 char * proto_string;
66 struct proto_cache * next;
67} * first_proto_cache = NULL;
68
9697c820
AF
69DEFINE_SPINLOCK(l7_lock);
70
71static int total_acct_packets(struct nf_conn *ct)
72{
73#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 26)
74 BUG_ON(ct == NULL);
75 return (ct->counters[IP_CT_DIR_ORIGINAL].packets + ct->counters[IP_CT_DIR_REPLY].packets);
76#else
77 struct nf_conn_counter *acct;
78
79 BUG_ON(ct == NULL);
80 acct = nf_conn_acct_find(ct);
81 if (!acct)
82 return 0;
83 return (atomic64_read(&acct[IP_CT_DIR_ORIGINAL].packets) + atomic64_read(&acct[IP_CT_DIR_REPLY].packets));
84#endif
85}
86
87#ifdef CONFIG_IP_NF_MATCH_LAYER7_DEBUG
88/* Converts an unfriendly string into a friendly one by
89replacing unprintables with periods and all whitespace with " ". */
90static char * friendly_print(unsigned char * s)
91{
92 char * f = kmalloc(strlen(s) + 1, GFP_ATOMIC);
93 int i;
94
95 if(!f) {
96 if (net_ratelimit())
97 printk(KERN_ERR "layer7: out of memory in "
98 "friendly_print, bailing.\n");
99 return NULL;
100 }
101
102 for(i = 0; i < strlen(s); i++){
103 if(isprint(s[i]) && s[i] < 128) f[i] = s[i];
104 else if(isspace(s[i])) f[i] = ' ';
105 else f[i] = '.';
106 }
107 f[i] = '\0';
108 return f;
109}
110
111static char dec2hex(int i)
112{
113 switch (i) {
114 case 0 ... 9:
115 return (i + '0');
116 break;
117 case 10 ... 15:
118 return (i - 10 + 'a');
119 break;
120 default:
121 if (net_ratelimit())
122 printk("layer7: Problem in dec2hex\n");
123 return '\0';
124 }
125}
126
127static char * hex_print(unsigned char * s)
128{
129 char * g = kmalloc(strlen(s)*3 + 1, GFP_ATOMIC);
130 int i;
131
132 if(!g) {
133 if (net_ratelimit())
134 printk(KERN_ERR "layer7: out of memory in hex_print, "
135 "bailing.\n");
136 return NULL;
137 }
138
139 for(i = 0; i < strlen(s); i++) {
140 g[i*3 ] = dec2hex(s[i]/16);
141 g[i*3 + 1] = dec2hex(s[i]%16);
142 g[i*3 + 2] = ' ';
143 }
144 g[i*3] = '\0';
145
146 return g;
147}
148#endif // DEBUG
149
150/* Use instead of regcomp. As we expect to be seeing the same regexps over and
151over again, it make sense to cache the results. */
152static regexp * compile_and_cache(const char * regex_string,
153 const char * protocol)
154{
155 struct pattern_cache * node = first_pattern_cache;
156 struct pattern_cache * last_pattern_cache = first_pattern_cache;
157 struct pattern_cache * tmp;
158 unsigned int len;
159
160 while (node != NULL) {
161 if (!strcmp(node->regex_string, regex_string))
162 return node->pattern;
163
164 last_pattern_cache = node;/* points at the last non-NULL node */
165 node = node->next;
166 }
167
168 /* If we reach the end of the list, then we have not yet cached
169 the pattern for this regex. Let's do that now.
170 Be paranoid about running out of memory to avoid list corruption. */
171 tmp = kmalloc(sizeof(struct pattern_cache), GFP_ATOMIC);
172
173 if(!tmp) {
174 if (net_ratelimit())
175 printk(KERN_ERR "layer7: out of memory in "
176 "compile_and_cache, bailing.\n");
177 return NULL;
178 }
179
180 tmp->regex_string = kmalloc(strlen(regex_string) + 1, GFP_ATOMIC);
181 tmp->pattern = kmalloc(sizeof(struct regexp), GFP_ATOMIC);
182 tmp->next = NULL;
183
184 if(!tmp->regex_string || !tmp->pattern) {
185 if (net_ratelimit())
186 printk(KERN_ERR "layer7: out of memory in "
187 "compile_and_cache, bailing.\n");
188 kfree(tmp->regex_string);
189 kfree(tmp->pattern);
190 kfree(tmp);
191 return NULL;
192 }
193
194 /* Ok. The new node is all ready now. */
195 node = tmp;
196
197 if(first_pattern_cache == NULL) /* list is empty */
198 first_pattern_cache = node; /* make node the beginning */
199 else
200 last_pattern_cache->next = node; /* attach node to the end */
201
202 /* copy the string and compile the regex */
203 len = strlen(regex_string);
204 DPRINTK("About to compile this: \"%s\"\n", regex_string);
205 node->pattern = regcomp((char *)regex_string, &len);
206 if ( !node->pattern ) {
207 if (net_ratelimit())
208 printk(KERN_ERR "layer7: Error compiling regexp "
209 "\"%s\" (%s)\n",
210 regex_string, protocol);
211 /* pattern is now cached as NULL, so we won't try again. */
212 }
213
214 strcpy(node->regex_string, regex_string);
215 return node->pattern;
216}
217
e6cddf5d
AF
218static char * get_protostr_ptr(const char * protocol)
219{
220 struct proto_cache * node = first_proto_cache;
221 struct proto_cache * last_proto_cache = first_proto_cache;
222 struct proto_cache * tmp;
223
224 while (node != NULL) {
225 if (!strcmp(node->proto_string, protocol))
226 return node->proto_string;
227
228 last_proto_cache = node;/* points at the last non-NULL node */
229 node = node->next;
230 }
231
232 /* If we reach the end of the list, then we have not yet cached protocol
233 Be paranoid about running out of memory to avoid list corruption. */
234 tmp = kmalloc(sizeof(struct proto_cache), GFP_ATOMIC);
235
236 if(!tmp) {
237 if (net_ratelimit())
238 printk(KERN_ERR "layer7: out of memory in "
239 "proto_cache add, bailing.\n");
240 return NULL;
241 }
242
243 tmp->proto_string = kmalloc(strlen(protocol) + 1 , GFP_ATOMIC);
244 tmp->next = NULL;
245
246 if(!tmp->proto_string) {
247 if (net_ratelimit())
248 printk(KERN_ERR "layer7: out of memory in "
249 "proto_cache add, bailing.\n");
250 kfree(tmp->proto_string);
251 kfree(tmp);
252 return NULL;
253 }
254
255 /* Ok. The new node is all ready now. */
256 node = tmp;
257
258 if(first_proto_cache == NULL) /* list is empty */
259 first_proto_cache = node; /* make node the beginning */
260 else
261 last_proto_cache->next = node; /* attach node to the end */
262
263 strcpy(node->proto_string, protocol);
264 return node->proto_string;
265}
266
9697c820
AF
267static int can_handle(const struct sk_buff *skb)
268{
269 if(!ip_hdr(skb)) /* not IP */
270 return 0;
271 if(ip_hdr(skb)->protocol != IPPROTO_TCP &&
272 ip_hdr(skb)->protocol != IPPROTO_UDP &&
273 ip_hdr(skb)->protocol != IPPROTO_ICMP)
274 return 0;
275 return 1;
276}
277
278/* Returns offset the into the skb->data that the application data starts */
279static int app_data_offset(const struct sk_buff *skb)
280{
281 /* In case we are ported somewhere (ebtables?) where ip_hdr(skb)
282 isn't set, this can be gotten from 4*(skb->data[0] & 0x0f) as well. */
283 int ip_hl = 4*ip_hdr(skb)->ihl;
284
285 if( ip_hdr(skb)->protocol == IPPROTO_TCP ) {
286 /* 12 == offset into TCP header for the header length field.
287 Can't get this with skb->h.th->doff because the tcphdr
288 struct doesn't get set when routing (this is confirmed to be
289 true in Netfilter as well as QoS.) */
290 int tcp_hl = 4*(skb->data[ip_hl + 12] >> 4);
291
292 return ip_hl + tcp_hl;
293 } else if( ip_hdr(skb)->protocol == IPPROTO_UDP ) {
294 return ip_hl + 8; /* UDP header is always 8 bytes */
295 } else if( ip_hdr(skb)->protocol == IPPROTO_ICMP ) {
296 return ip_hl + 8; /* ICMP header is 8 bytes */
297 } else {
298 if (net_ratelimit())
299 printk(KERN_ERR "layer7: tried to handle unknown "
300 "protocol!\n");
301 return ip_hl + 8; /* something reasonable */
302 }
303}
304
305/* handles whether there's a match when we aren't appending data anymore */
306static int match_no_append(struct nf_conn * conntrack,
307 struct nf_conn * master_conntrack,
308 enum ip_conntrack_info ctinfo,
309 enum ip_conntrack_info master_ctinfo,
310 const struct xt_layer7_info * info)
311{
312 /* If we're in here, throw the app data away */
313 if(master_conntrack->layer7.app_data != NULL) {
314
315 #ifdef CONFIG_IP_NF_MATCH_LAYER7_DEBUG
316 if(!master_conntrack->layer7.app_proto) {
317 char * f =
318 friendly_print(master_conntrack->layer7.app_data);
319 char * g =
320 hex_print(master_conntrack->layer7.app_data);
321 DPRINTK("\nl7-filter gave up after %d bytes "
322 "(%d packets):\n%s\n",
323 strlen(f), total_acct_packets(master_conntrack), f);
324 kfree(f);
325 DPRINTK("In hex: %s\n", g);
326 kfree(g);
327 }
328 #endif
329
330 kfree(master_conntrack->layer7.app_data);
331 master_conntrack->layer7.app_data = NULL; /* don't free again */
332 }
333
334 if(master_conntrack->layer7.app_proto){
335 /* Here child connections set their .app_proto (for /proc) */
336 if(!conntrack->layer7.app_proto) {
e6cddf5d 337 conntrack->layer7.app_proto = master_conntrack->layer7.app_proto;
9697c820
AF
338 }
339
340 return (!strcmp(master_conntrack->layer7.app_proto,
341 info->protocol));
342 }
343 else {
344 /* If not classified, set to "unknown" to distinguish from
345 connections that are still being tested. */
e6cddf5d 346 master_conntrack->layer7.app_proto = get_protostr_ptr("unknown");
9697c820
AF
347 return 0;
348 }
349}
350
351/* add the new app data to the conntrack. Return number of bytes added. */
352static int add_data(struct nf_conn * master_conntrack,
353 char * app_data, int appdatalen)
354{
355 int length = 0, i;
356 int oldlength = master_conntrack->layer7.app_data_len;
357
358 /* This is a fix for a race condition by Deti Fliegl. However, I'm not
359 clear on whether the race condition exists or whether this really
360 fixes it. I might just be being dense... Anyway, if it's not really
361 a fix, all it does is waste a very small amount of time. */
362 if(!master_conntrack->layer7.app_data) return 0;
363
364 /* Strip nulls. Make everything lower case (our regex lib doesn't
365 do case insensitivity). Add it to the end of the current data. */
366 for(i = 0; i < maxdatalen-oldlength-1 &&
367 i < appdatalen; i++) {
368 if(app_data[i] != '\0') {
369 /* the kernel version of tolower mungs 'upper ascii' */
370 master_conntrack->layer7.app_data[length+oldlength] =
371 isascii(app_data[i])?
372 tolower(app_data[i]) : app_data[i];
373 length++;
374 }
375 }
376
377 master_conntrack->layer7.app_data[length+oldlength] = '\0';
378 master_conntrack->layer7.app_data_len = length + oldlength;
379
380 return length;
381}
382
383/* taken from drivers/video/modedb.c */
384static int my_atoi(const char *s)
385{
386 int val = 0;
387
388 for (;; s++) {
389 switch (*s) {
390 case '0'...'9':
391 val = 10*val+(*s-'0');
392 break;
393 default:
394 return val;
395 }
396 }
397}
398
399static int layer7_numpackets_proc_show(struct seq_file *s, void *p) {
400 seq_printf(s, "%d\n", num_packets);
401
402 return 0;
403}
404
405static int layer7_numpackets_proc_open(struct inode *inode, struct file *file) {
406 return single_open(file, layer7_numpackets_proc_show, NULL);
407}
408
409/* Read in num_packets from userland */
410static ssize_t layer7_numpackets_write_proc(struct file* file, const char __user *buffer,
411 size_t count, loff_t *data) {
412 char value[1024];
413 int new_num_packets;
414
415 if (copy_from_user(&value, buffer, sizeof(value)))
416 return -EFAULT;
417
418 new_num_packets = my_atoi(value);
419
420 if ((new_num_packets < 1) || (new_num_packets > 99)) {
421 printk(KERN_WARNING "layer7: numpackets must be between 1 and 99\n");
422 return -EFAULT;
423 }
424
425 num_packets = new_num_packets;
426
427 return count;
428}
429
430static bool
431#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 35)
432match(const struct sk_buff *skbin, struct xt_action_param *par)
433#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 28)
434match(const struct sk_buff *skbin, const struct xt_match_param *par)
435#else
436match(const struct sk_buff *skbin,
437 const struct net_device *in,
438 const struct net_device *out,
439 const struct xt_match *match,
440 const void *matchinfo,
441 int offset,
442 unsigned int protoff,
443 bool *hotdrop)
444#endif
445{
446 /* sidestep const without getting a compiler warning... */
447 struct sk_buff * skb = (struct sk_buff *)skbin;
448
449 const struct xt_layer7_info * info =
450 #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 28)
451 par->matchinfo;
452 #else
453 matchinfo;
454 #endif
455
456 enum ip_conntrack_info master_ctinfo, ctinfo;
457 struct nf_conn *master_conntrack, *conntrack;
458 unsigned char * app_data;
459 unsigned int pattern_result, appdatalen;
460 regexp * comppattern;
461
462 /* Be paranoid/incompetent - lock the entire match function. */
463 spin_lock_bh(&l7_lock);
464
465 if(!can_handle(skb)){
466 DPRINTK("layer7: This is some protocol I can't handle.\n");
467 spin_unlock_bh(&l7_lock);
468 return info->invert;
469 }
470
471 /* Treat parent & all its children together as one connection, except
472 for the purpose of setting conntrack->layer7.app_proto in the actual
473 connection. This makes /proc/net/ip_conntrack more satisfying. */
474 if(!(conntrack = nf_ct_get(skb, &ctinfo)) ||
475 !(master_conntrack=nf_ct_get(skb,&master_ctinfo))){
476 DPRINTK("layer7: couldn't get conntrack.\n");
477 spin_unlock_bh(&l7_lock);
478 return info->invert;
479 }
480
481 /* Try to get a master conntrack (and its master etc) for FTP, etc. */
482 while (master_ct(master_conntrack) != NULL)
483 master_conntrack = master_ct(master_conntrack);
484
485 /* if we've classified it or seen too many packets */
486 if(total_acct_packets(master_conntrack) > num_packets ||
487 master_conntrack->layer7.app_proto) {
488
489 pattern_result = match_no_append(conntrack, master_conntrack,
490 ctinfo, master_ctinfo, info);
491
492 /* skb->cb[0] == seen. Don't do things twice if there are
493 multiple l7 rules. I'm not sure that using cb for this purpose
494 is correct, even though it says "put your private variables
495 there". But it doesn't look like it is being used for anything
496 else in the skbs that make it here. */
497 skb->cb[0] = 1; /* marking it seen here's probably irrelevant */
498
499 spin_unlock_bh(&l7_lock);
500 return (pattern_result ^ info->invert);
501 }
502
503 if(skb_is_nonlinear(skb)){
504 if(skb_linearize(skb) != 0){
505 if (net_ratelimit())
506 printk(KERN_ERR "layer7: failed to linearize "
507 "packet, bailing.\n");
508 spin_unlock_bh(&l7_lock);
509 return info->invert;
510 }
511 }
512
513 /* now that the skb is linearized, it's safe to set these. */
514 app_data = skb->data + app_data_offset(skb);
515 appdatalen = skb_tail_pointer(skb) - app_data;
516
517 /* the return value gets checked later, when we're ready to use it */
518 comppattern = compile_and_cache(info->pattern, info->protocol);
519
520 /* On the first packet of a connection, allocate space for app data */
521 if(total_acct_packets(master_conntrack) == 1 && !skb->cb[0] &&
522 !master_conntrack->layer7.app_data){
523 master_conntrack->layer7.app_data =
524 kmalloc(maxdatalen, GFP_ATOMIC);
525 if(!master_conntrack->layer7.app_data){
526 if (net_ratelimit())
527 printk(KERN_ERR "layer7: out of memory in "
528 "match, bailing.\n");
529 spin_unlock_bh(&l7_lock);
530 return info->invert;
531 }
532
533 master_conntrack->layer7.app_data[0] = '\0';
534 }
535
536 /* Can be here, but unallocated, if numpackets is increased near
537 the beginning of a connection */
538 if(master_conntrack->layer7.app_data == NULL){
539 spin_unlock_bh(&l7_lock);
540 return info->invert; /* unmatched */
541 }
542
543 if(!skb->cb[0]){
544 int newbytes;
545 newbytes = add_data(master_conntrack, app_data, appdatalen);
9697c820
AF
546 if(newbytes == 0) { /* didn't add any data */
547 skb->cb[0] = 1;
548 /* Didn't match before, not going to match now */
549 spin_unlock_bh(&l7_lock);
550 return info->invert;
551 }
552 }
553
554 /* If looking for "unknown", then never match. "Unknown" means that
555 we've given up; we're still trying with these packets. */
556 if(!strcmp(info->protocol, "unknown")) {
557 pattern_result = 0;
558 /* If looking for "unset", then always match. "Unset" means that we
559 haven't yet classified the connection. */
560 } else if(!strcmp(info->protocol, "unset")) {
561 pattern_result = 2;
562 DPRINTK("layer7: matched unset: not yet classified "
563 "(%d/%d packets)\n",
564 total_acct_packets(master_conntrack), num_packets);
565 /* If the regexp failed to compile, don't bother running it */
566 } else if(comppattern &&
567 regexec(comppattern, master_conntrack->layer7.app_data)){
568 DPRINTK("layer7: matched %s\n", info->protocol);
569 pattern_result = 1;
570 } else pattern_result = 0;
571
572 if(pattern_result == 1) {
e6cddf5d 573 master_conntrack->layer7.app_proto=get_protostr_ptr(info->protocol);
9697c820
AF
574 } else if(pattern_result > 1) { /* cleanup from "unset" */
575 pattern_result = 1;
576 }
577
578 /* mark the packet seen */
579 skb->cb[0] = 1;
580
581 spin_unlock_bh(&l7_lock);
582 return (pattern_result ^ info->invert);
583}
584
585// load nf_conntrack_ipv4
586#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 35)
587static int
588#else
589static bool
590#endif
591#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 28)
592check(const struct xt_mtchk_param *par)
593{
594 if (nf_ct_l3proto_try_module_get(par->match->family) < 0) {
595 printk(KERN_WARNING "can't load conntrack support for "
596 "proto=%d\n", par->match->family);
597#else
598check(const char *tablename, const void *inf,
599 const struct xt_match *match, void *matchinfo,
600 unsigned int hook_mask)
601{
602 if (nf_ct_l3proto_try_module_get(match->family) < 0) {
603 printk(KERN_WARNING "can't load conntrack support for "
604 "proto=%d\n", match->family);
605#endif
606#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 35)
607 return -EINVAL;
608 }
609 return 0;
610#else
611 return 0;
612 }
613 return 1;
614#endif
615}
616
617
618#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 28)
619 static void destroy(const struct xt_mtdtor_param *par)
620 {
621 nf_ct_l3proto_module_put(par->match->family);
622 }
623#else
624 static void destroy(const struct xt_match *match, void *matchinfo)
625 {
626 nf_ct_l3proto_module_put(match->family);
627 }
628#endif
629
630static struct xt_match xt_layer7_match[] __read_mostly = {
631{
632 .name = "layer7",
c59158ee 633 .family = NFPROTO_IPV4,
9697c820
AF
634 .checkentry = check,
635 .match = match,
636 .destroy = destroy,
637 .matchsize = sizeof(struct xt_layer7_info),
638 .me = THIS_MODULE
639}
640};
641
642static const struct file_operations layer7_numpackets_proc_fops = {
643 .owner = THIS_MODULE,
644 .open = layer7_numpackets_proc_open,
645 .read = seq_read,
646 .llseek = seq_lseek,
647 .release = single_release,
648 .write = layer7_numpackets_write_proc,
649};
650
651static int __init xt_layer7_init(void)
652{
653 need_conntrack();
654
655 // Register proc interface
656 proc_create_data("layer7_numpackets", 0644,
657 init_net.proc_net, &layer7_numpackets_proc_fops, NULL);
658
659 if(maxdatalen < 1) {
660 printk(KERN_WARNING "layer7: maxdatalen can't be < 1, "
661 "using 1\n");
662 maxdatalen = 1;
663 }
664 /* This is not a hard limit. It's just here to prevent people from
665 bringing their slow machines to a grinding halt. */
666 else if(maxdatalen > 65536) {
667 printk(KERN_WARNING "layer7: maxdatalen can't be > 65536, "
668 "using 65536\n");
669 maxdatalen = 65536;
670 }
671 return xt_register_matches(xt_layer7_match,
672 ARRAY_SIZE(xt_layer7_match));
673}
674
675static void __exit xt_layer7_fini(void)
676{
677 remove_proc_entry("layer7_numpackets", init_net.proc_net);
678 xt_unregister_matches(xt_layer7_match, ARRAY_SIZE(xt_layer7_match));
679}
680
681module_init(xt_layer7_init);
682module_exit(xt_layer7_fini);
683