]> bbs.cooldavid.org Git - net-next-2.6.git/blame - net/netfilter/nf_conntrack_sip.c
[NETFILTER]: nf_conntrack_sip: kill request URI "header" definitions
[net-next-2.6.git] / net / netfilter / nf_conntrack_sip.c
CommitLineData
9fafcd7b
PM
1/* SIP extension for IP connection tracking.
2 *
3 * (C) 2005 by Christian Hentschel <chentschel@arnet.com.ar>
4 * based on RR's ip_conntrack_ftp.c and other modules.
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 2 as
8 * published by the Free Software Foundation.
9 */
10
11#include <linux/module.h>
12#include <linux/ctype.h>
13#include <linux/skbuff.h>
14#include <linux/inet.h>
15#include <linux/in.h>
16#include <linux/udp.h>
1863f096 17#include <linux/netfilter.h>
9fafcd7b
PM
18
19#include <net/netfilter/nf_conntrack.h>
20#include <net/netfilter/nf_conntrack_expect.h>
21#include <net/netfilter/nf_conntrack_helper.h>
22#include <linux/netfilter/nf_conntrack_sip.h>
23
9fafcd7b
PM
24MODULE_LICENSE("GPL");
25MODULE_AUTHOR("Christian Hentschel <chentschel@arnet.com.ar>");
26MODULE_DESCRIPTION("SIP connection tracking helper");
27MODULE_ALIAS("ip_conntrack_sip");
28
29#define MAX_PORTS 8
30static unsigned short ports[MAX_PORTS];
2f0d2f10 31static unsigned int ports_c;
9fafcd7b
PM
32module_param_array(ports, ushort, &ports_c, 0400);
33MODULE_PARM_DESC(ports, "port numbers of SIP servers");
34
35static unsigned int sip_timeout __read_mostly = SIP_TIMEOUT;
36module_param(sip_timeout, uint, 0600);
37MODULE_PARM_DESC(sip_timeout, "timeout for the master SIP session");
38
3db05fea 39unsigned int (*nf_nat_sip_hook)(struct sk_buff *skb,
2a6cfb22
PM
40 const char **dptr,
41 unsigned int *datalen) __read_mostly;
9fafcd7b
PM
42EXPORT_SYMBOL_GPL(nf_nat_sip_hook);
43
3db05fea 44unsigned int (*nf_nat_sdp_hook)(struct sk_buff *skb,
2a6cfb22 45 const char **dptr,
212440a7
PM
46 unsigned int *datalen,
47 struct nf_conntrack_expect *exp) __read_mostly;
9fafcd7b
PM
48EXPORT_SYMBOL_GPL(nf_nat_sdp_hook);
49
13f7d63c
JE
50static int digits_len(const struct nf_conn *, const char *, const char *, int *);
51static int epaddr_len(const struct nf_conn *, const char *, const char *, int *);
52static int skp_digits_len(const struct nf_conn *, const char *, const char *, int *);
53static int skp_epaddr_len(const struct nf_conn *, const char *, const char *, int *);
9fafcd7b
PM
54
55struct sip_header_nfo {
56 const char *lname;
57 const char *sname;
58 const char *ln_str;
59 size_t lnlen;
60 size_t snlen;
61 size_t ln_strlen;
62 int case_sensitive;
13f7d63c 63 int (*match_len)(const struct nf_conn *, const char *,
9fafcd7b
PM
64 const char *, int *);
65};
66
67static const struct sip_header_nfo ct_sip_hdrs[] = {
9fafcd7b
PM
68 [POS_FROM] = { /* SIP From header */
69 .lname = "From:",
70 .lnlen = sizeof("From:") - 1,
71 .sname = "\r\nf:",
72 .snlen = sizeof("\r\nf:") - 1,
73 .ln_str = "sip:",
74 .ln_strlen = sizeof("sip:") - 1,
75 .match_len = skp_epaddr_len,
76 },
77 [POS_TO] = { /* SIP To header */
78 .lname = "To:",
79 .lnlen = sizeof("To:") - 1,
80 .sname = "\r\nt:",
81 .snlen = sizeof("\r\nt:") - 1,
82 .ln_str = "sip:",
83 .ln_strlen = sizeof("sip:") - 1,
84 .match_len = skp_epaddr_len
85 },
86 [POS_VIA] = { /* SIP Via header */
87 .lname = "Via:",
88 .lnlen = sizeof("Via:") - 1,
89 .sname = "\r\nv:",
90 .snlen = sizeof("\r\nv:") - 1, /* rfc3261 "\r\n" */
91 .ln_str = "UDP ",
92 .ln_strlen = sizeof("UDP ") - 1,
93 .match_len = epaddr_len,
94 },
95 [POS_CONTACT] = { /* SIP Contact header */
96 .lname = "Contact:",
97 .lnlen = sizeof("Contact:") - 1,
98 .sname = "\r\nm:",
99 .snlen = sizeof("\r\nm:") - 1,
100 .ln_str = "sip:",
101 .ln_strlen = sizeof("sip:") - 1,
102 .match_len = skp_epaddr_len
103 },
104 [POS_CONTENT] = { /* SIP Content length header */
105 .lname = "Content-Length:",
106 .lnlen = sizeof("Content-Length:") - 1,
107 .sname = "\r\nl:",
108 .snlen = sizeof("\r\nl:") - 1,
109 .ln_str = ":",
110 .ln_strlen = sizeof(":") - 1,
111 .match_len = skp_digits_len
112 },
9fafcd7b
PM
113};
114
c8238177 115/* get line length until first CR or LF seen. */
9fafcd7b
PM
116int ct_sip_lnlen(const char *line, const char *limit)
117{
118 const char *k = line;
119
b1ec488b 120 while ((line < limit) && (*line == '\r' || *line == '\n'))
9fafcd7b
PM
121 line++;
122
b1ec488b 123 while (line < limit) {
9fafcd7b
PM
124 if (*line == '\r' || *line == '\n')
125 break;
126 line++;
127 }
128 return line - k;
129}
130EXPORT_SYMBOL_GPL(ct_sip_lnlen);
131
132/* Linear string search, case sensitive. */
133const char *ct_sip_search(const char *needle, const char *haystack,
134 size_t needle_len, size_t haystack_len,
135 int case_sensitive)
136{
137 const char *limit = haystack + (haystack_len - needle_len);
138
b1ec488b 139 while (haystack < limit) {
9fafcd7b
PM
140 if (case_sensitive) {
141 if (strncmp(haystack, needle, needle_len) == 0)
142 return haystack;
143 } else {
144 if (strnicmp(haystack, needle, needle_len) == 0)
145 return haystack;
146 }
147 haystack++;
148 }
149 return NULL;
150}
151EXPORT_SYMBOL_GPL(ct_sip_search);
152
ac367740
PM
153static int string_len(const struct nf_conn *ct, const char *dptr,
154 const char *limit, int *shift)
155{
156 int len = 0;
157
158 while (dptr < limit && isalpha(*dptr)) {
159 dptr++;
160 len++;
161 }
162 return len;
163}
164
13f7d63c 165static int digits_len(const struct nf_conn *ct, const char *dptr,
9fafcd7b
PM
166 const char *limit, int *shift)
167{
168 int len = 0;
b1ec488b 169 while (dptr < limit && isdigit(*dptr)) {
9fafcd7b
PM
170 dptr++;
171 len++;
172 }
173 return len;
174}
175
c8238177 176/* get digits length, skipping blank spaces. */
13f7d63c 177static int skp_digits_len(const struct nf_conn *ct, const char *dptr,
9fafcd7b
PM
178 const char *limit, int *shift)
179{
b1ec488b 180 for (; dptr < limit && *dptr == ' '; dptr++)
9fafcd7b
PM
181 (*shift)++;
182
183 return digits_len(ct, dptr, limit, shift);
184}
185
13f7d63c
JE
186static int parse_addr(const struct nf_conn *ct, const char *cp,
187 const char **endp, union nf_inet_addr *addr,
188 const char *limit)
9fafcd7b
PM
189{
190 const char *end;
191 int family = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.l3num;
192 int ret = 0;
193
194 switch (family) {
195 case AF_INET:
196 ret = in4_pton(cp, limit - cp, (u8 *)&addr->ip, -1, &end);
197 break;
198 case AF_INET6:
199 ret = in6_pton(cp, limit - cp, (u8 *)&addr->ip6, -1, &end);
200 break;
201 default:
202 BUG();
203 }
204
205 if (ret == 0 || end == cp)
206 return 0;
207 if (endp)
208 *endp = end;
209 return 1;
210}
211
212/* skip ip address. returns its length. */
13f7d63c 213static int epaddr_len(const struct nf_conn *ct, const char *dptr,
9fafcd7b
PM
214 const char *limit, int *shift)
215{
643a2c15 216 union nf_inet_addr addr;
9fafcd7b
PM
217 const char *aux = dptr;
218
219 if (!parse_addr(ct, dptr, &dptr, &addr, limit)) {
0d53778e 220 pr_debug("ip: %s parse failed.!\n", dptr);
9fafcd7b
PM
221 return 0;
222 }
223
224 /* Port number */
225 if (*dptr == ':') {
226 dptr++;
227 dptr += digits_len(ct, dptr, limit, shift);
228 }
229 return dptr - aux;
230}
231
232/* get address length, skiping user info. */
13f7d63c 233static int skp_epaddr_len(const struct nf_conn *ct, const char *dptr,
9fafcd7b
PM
234 const char *limit, int *shift)
235{
aa584eda 236 const char *start = dptr;
9fafcd7b
PM
237 int s = *shift;
238
7da5bfbb
LI
239 /* Search for @, but stop at the end of the line.
240 * We are inside a sip: URI, so we don't need to worry about
241 * continuation lines. */
b1ec488b 242 while (dptr < limit &&
7da5bfbb 243 *dptr != '@' && *dptr != '\r' && *dptr != '\n') {
9fafcd7b 244 (*shift)++;
7da5bfbb
LI
245 dptr++;
246 }
9fafcd7b 247
b1ec488b 248 if (dptr < limit && *dptr == '@') {
9fafcd7b
PM
249 dptr++;
250 (*shift)++;
aa584eda
PM
251 } else {
252 dptr = start;
9fafcd7b 253 *shift = s;
aa584eda 254 }
9fafcd7b
PM
255
256 return epaddr_len(ct, dptr, limit, shift);
257}
258
ac367740
PM
259/* Parse a SIP request line of the form:
260 *
261 * Request-Line = Method SP Request-URI SP SIP-Version CRLF
262 *
263 * and return the offset and length of the address contained in the Request-URI.
264 */
265int ct_sip_parse_request(const struct nf_conn *ct,
266 const char *dptr, unsigned int datalen,
267 unsigned int *matchoff, unsigned int *matchlen)
268{
269 const char *start = dptr, *limit = dptr + datalen;
270 unsigned int mlen;
271 int shift = 0;
272
273 /* Skip method and following whitespace */
274 mlen = string_len(ct, dptr, limit, NULL);
275 if (!mlen)
276 return 0;
277 dptr += mlen;
278 if (++dptr >= limit)
279 return 0;
280
281 /* Find SIP URI */
282 limit -= strlen("sip:");
283 for (; dptr < limit; dptr++) {
284 if (*dptr == '\r' || *dptr == '\n')
285 return -1;
286 if (strnicmp(dptr, "sip:", strlen("sip:")) == 0)
287 break;
288 }
289 *matchlen = skp_epaddr_len(ct, dptr, limit, &shift);
290 if (!*matchlen)
291 return 0;
292 *matchoff = dptr - start + shift;
293 return 1;
294}
295EXPORT_SYMBOL_GPL(ct_sip_parse_request);
296
9fafcd7b 297/* Returns 0 if not found, -1 error parsing. */
13f7d63c 298int ct_sip_get_info(const struct nf_conn *ct,
9fafcd7b
PM
299 const char *dptr, size_t dlen,
300 unsigned int *matchoff,
301 unsigned int *matchlen,
302 enum sip_header_pos pos)
303{
304 const struct sip_header_nfo *hnfo = &ct_sip_hdrs[pos];
305 const char *limit, *aux, *k = dptr;
306 int shift = 0;
307
308 limit = dptr + (dlen - hnfo->lnlen);
309
b1ec488b 310 while (dptr < limit) {
9fafcd7b 311 if ((strncmp(dptr, hnfo->lname, hnfo->lnlen) != 0) &&
465f90a4
PM
312 (hnfo->sname == NULL ||
313 strncmp(dptr, hnfo->sname, hnfo->snlen) != 0)) {
9fafcd7b
PM
314 dptr++;
315 continue;
316 }
317 aux = ct_sip_search(hnfo->ln_str, dptr, hnfo->ln_strlen,
601e68e1 318 ct_sip_lnlen(dptr, limit),
9fafcd7b
PM
319 hnfo->case_sensitive);
320 if (!aux) {
0d53778e
PM
321 pr_debug("'%s' not found in '%s'.\n", hnfo->ln_str,
322 hnfo->lname);
9fafcd7b
PM
323 return -1;
324 }
325 aux += hnfo->ln_strlen;
326
327 *matchlen = hnfo->match_len(ct, aux, limit, &shift);
328 if (!*matchlen)
329 return -1;
330
331 *matchoff = (aux - k) + shift;
332
0d53778e
PM
333 pr_debug("%s match succeeded! - len: %u\n", hnfo->lname,
334 *matchlen);
9fafcd7b
PM
335 return 1;
336 }
0d53778e 337 pr_debug("%s header not found.\n", hnfo->lname);
9fafcd7b
PM
338 return 0;
339}
340EXPORT_SYMBOL_GPL(ct_sip_get_info);
341
3e9b4600
PM
342/* SDP header parsing: a SDP session description contains an ordered set of
343 * headers, starting with a section containing general session parameters,
344 * optionally followed by multiple media descriptions.
345 *
346 * SDP headers always start at the beginning of a line. According to RFC 2327:
347 * "The sequence CRLF (0x0d0a) is used to end a record, although parsers should
348 * be tolerant and also accept records terminated with a single newline
349 * character". We handle both cases.
350 */
351static const struct sip_header ct_sdp_hdrs[] = {
352 [SDP_HDR_VERSION] = SDP_HDR("v=", NULL, digits_len),
353 [SDP_HDR_OWNER_IP4] = SDP_HDR("o=", "IN IP4 ", epaddr_len),
354 [SDP_HDR_CONNECTION_IP4] = SDP_HDR("c=", "IN IP4 ", epaddr_len),
355 [SDP_HDR_OWNER_IP6] = SDP_HDR("o=", "IN IP6 ", epaddr_len),
356 [SDP_HDR_CONNECTION_IP6] = SDP_HDR("c=", "IN IP6 ", epaddr_len),
357 [SDP_HDR_MEDIA] = SDP_HDR("m=", "audio ", digits_len),
358};
359
360/* Linear string search within SDP header values */
361static const char *ct_sdp_header_search(const char *dptr, const char *limit,
362 const char *needle, unsigned int len)
363{
364 for (limit -= len; dptr < limit; dptr++) {
365 if (*dptr == '\r' || *dptr == '\n')
366 break;
367 if (strncmp(dptr, needle, len) == 0)
368 return dptr;
369 }
370 return NULL;
371}
372
373/* Locate a SDP header (optionally a substring within the header value),
374 * optionally stopping at the first occurence of the term header, parse
375 * it and return the offset and length of the data we're interested in.
376 */
377int ct_sip_get_sdp_header(const struct nf_conn *ct, const char *dptr,
378 unsigned int dataoff, unsigned int datalen,
379 enum sdp_header_types type,
380 enum sdp_header_types term,
381 unsigned int *matchoff, unsigned int *matchlen)
382{
383 const struct sip_header *hdr = &ct_sdp_hdrs[type];
384 const struct sip_header *thdr = &ct_sdp_hdrs[term];
385 const char *start = dptr, *limit = dptr + datalen;
386 int shift = 0;
387
388 for (dptr += dataoff; dptr < limit; dptr++) {
389 /* Find beginning of line */
390 if (*dptr != '\r' && *dptr != '\n')
391 continue;
392 if (++dptr >= limit)
393 break;
394 if (*(dptr - 1) == '\r' && *dptr == '\n') {
395 if (++dptr >= limit)
396 break;
397 }
398
399 if (term != SDP_HDR_UNSPEC &&
400 limit - dptr >= thdr->len &&
401 strnicmp(dptr, thdr->name, thdr->len) == 0)
402 break;
403 else if (limit - dptr >= hdr->len &&
404 strnicmp(dptr, hdr->name, hdr->len) == 0)
405 dptr += hdr->len;
406 else
407 continue;
408
409 *matchoff = dptr - start;
410 if (hdr->search) {
411 dptr = ct_sdp_header_search(dptr, limit, hdr->search,
412 hdr->slen);
413 if (!dptr)
414 return -1;
415 dptr += hdr->slen;
416 }
417
418 *matchlen = hdr->match_len(ct, dptr, limit, &shift);
419 if (!*matchlen)
420 return -1;
421 *matchoff = dptr - start + shift;
422 return 1;
423 }
424 return 0;
425}
426EXPORT_SYMBOL_GPL(ct_sip_get_sdp_header);
427
3db05fea 428static int set_expected_rtp(struct sk_buff *skb,
212440a7
PM
429 const char **dptr, unsigned int *datalen,
430 union nf_inet_addr *addr, __be16 port)
9fafcd7b
PM
431{
432 struct nf_conntrack_expect *exp;
212440a7
PM
433 enum ip_conntrack_info ctinfo;
434 struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
9fafcd7b
PM
435 enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
436 int family = ct->tuplehash[!dir].tuple.src.l3num;
437 int ret;
438 typeof(nf_nat_sdp_hook) nf_nat_sdp;
439
6823645d 440 exp = nf_ct_expect_alloc(ct);
9fafcd7b
PM
441 if (exp == NULL)
442 return NF_DROP;
6002f266 443 nf_ct_expect_init(exp, NF_CT_EXPECT_CLASS_DEFAULT, family,
6823645d
PM
444 &ct->tuplehash[!dir].tuple.src.u3, addr,
445 IPPROTO_UDP, NULL, &port);
9fafcd7b
PM
446
447 nf_nat_sdp = rcu_dereference(nf_nat_sdp_hook);
448 if (nf_nat_sdp && ct->status & IPS_NAT_MASK)
212440a7 449 ret = nf_nat_sdp(skb, dptr, datalen, exp);
9fafcd7b 450 else {
6823645d 451 if (nf_ct_expect_related(exp) != 0)
9fafcd7b
PM
452 ret = NF_DROP;
453 else
454 ret = NF_ACCEPT;
455 }
6823645d 456 nf_ct_expect_put(exp);
9fafcd7b
PM
457
458 return ret;
459}
460
3db05fea 461static int sip_help(struct sk_buff *skb,
9fafcd7b
PM
462 unsigned int protoff,
463 struct nf_conn *ct,
464 enum ip_conntrack_info ctinfo)
465{
466 int family = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.l3num;
643a2c15 467 union nf_inet_addr addr;
9fafcd7b
PM
468 unsigned int dataoff, datalen;
469 const char *dptr;
470 int ret = NF_ACCEPT;
2f0d2f10 471 unsigned int matchoff, matchlen;
9fafcd7b 472 u_int16_t port;
3e9b4600 473 enum sdp_header_types type;
9fafcd7b
PM
474 typeof(nf_nat_sip_hook) nf_nat_sip;
475
476 /* No Data ? */
477 dataoff = protoff + sizeof(struct udphdr);
3db05fea 478 if (dataoff >= skb->len)
9fafcd7b
PM
479 return NF_ACCEPT;
480
3db05fea 481 nf_ct_refresh(ct, skb, sip_timeout * HZ);
9fafcd7b 482
3db05fea
HX
483 if (!skb_is_nonlinear(skb))
484 dptr = skb->data + dataoff;
9fafcd7b 485 else {
0d53778e 486 pr_debug("Copy of skbuff not supported yet.\n");
9fafcd7b
PM
487 goto out;
488 }
489
490 nf_nat_sip = rcu_dereference(nf_nat_sip_hook);
491 if (nf_nat_sip && ct->status & IPS_NAT_MASK) {
212440a7 492 if (!nf_nat_sip(skb, &dptr, &datalen)) {
9fafcd7b
PM
493 ret = NF_DROP;
494 goto out;
495 }
496 }
497
3db05fea 498 datalen = skb->len - dataoff;
779382eb 499 if (datalen < strlen("SIP/2.0 200"))
9fafcd7b
PM
500 goto out;
501
502 /* RTP info only in some SDP pkts */
779382eb
PM
503 if (strnicmp(dptr, "INVITE", strlen("INVITE")) != 0 &&
504 strnicmp(dptr, "UPDATE", strlen("UPDATE")) != 0 &&
505 strnicmp(dptr, "SIP/2.0 180", strlen("SIP/2.0 180")) != 0 &&
506 strnicmp(dptr, "SIP/2.0 183", strlen("SIP/2.0 183")) != 0 &&
507 strnicmp(dptr, "SIP/2.0 200", strlen("SIP/2.0 200")) != 0) {
9fafcd7b
PM
508 goto out;
509 }
510 /* Get address and port from SDP packet. */
3e9b4600
PM
511 type = family == AF_INET ? SDP_HDR_CONNECTION_IP4 :
512 SDP_HDR_CONNECTION_IP6;
513 if (ct_sip_get_sdp_header(ct, dptr, 0, datalen, type, SDP_HDR_UNSPEC,
514 &matchoff, &matchlen) > 0) {
9fafcd7b
PM
515
516 /* We'll drop only if there are parse problems. */
517 if (!parse_addr(ct, dptr + matchoff, NULL, &addr,
601e68e1 518 dptr + datalen)) {
9fafcd7b
PM
519 ret = NF_DROP;
520 goto out;
521 }
3e9b4600
PM
522 if (ct_sip_get_sdp_header(ct, dptr, 0, datalen,
523 SDP_HDR_MEDIA, SDP_HDR_UNSPEC,
524 &matchoff, &matchlen) > 0) {
9fafcd7b
PM
525
526 port = simple_strtoul(dptr + matchoff, NULL, 10);
527 if (port < 1024) {
528 ret = NF_DROP;
529 goto out;
530 }
212440a7
PM
531 ret = set_expected_rtp(skb, &dptr, &datalen,
532 &addr, htons(port));
9fafcd7b
PM
533 }
534 }
535out:
536 return ret;
537}
538
539static struct nf_conntrack_helper sip[MAX_PORTS][2] __read_mostly;
540static char sip_names[MAX_PORTS][2][sizeof("sip-65535")] __read_mostly;
541
6002f266
PM
542static const struct nf_conntrack_expect_policy sip_exp_policy = {
543 .max_expected = 2,
544 .timeout = 3 * 60,
545};
546
9fafcd7b
PM
547static void nf_conntrack_sip_fini(void)
548{
549 int i, j;
550
551 for (i = 0; i < ports_c; i++) {
552 for (j = 0; j < 2; j++) {
553 if (sip[i][j].me == NULL)
554 continue;
555 nf_conntrack_helper_unregister(&sip[i][j]);
556 }
557 }
558}
559
560static int __init nf_conntrack_sip_init(void)
561{
562 int i, j, ret;
563 char *tmpname;
564
565 if (ports_c == 0)
566 ports[ports_c++] = SIP_PORT;
567
568 for (i = 0; i < ports_c; i++) {
569 memset(&sip[i], 0, sizeof(sip[i]));
570
571 sip[i][0].tuple.src.l3num = AF_INET;
572 sip[i][1].tuple.src.l3num = AF_INET6;
573 for (j = 0; j < 2; j++) {
574 sip[i][j].tuple.dst.protonum = IPPROTO_UDP;
575 sip[i][j].tuple.src.u.udp.port = htons(ports[i]);
6002f266 576 sip[i][j].expect_policy = &sip_exp_policy;
9fafcd7b
PM
577 sip[i][j].me = THIS_MODULE;
578 sip[i][j].help = sip_help;
579
580 tmpname = &sip_names[i][j][0];
581 if (ports[i] == SIP_PORT)
582 sprintf(tmpname, "sip");
583 else
584 sprintf(tmpname, "sip-%u", i);
585 sip[i][j].name = tmpname;
586
0d53778e 587 pr_debug("port #%u: %u\n", i, ports[i]);
9fafcd7b
PM
588
589 ret = nf_conntrack_helper_register(&sip[i][j]);
590 if (ret) {
591 printk("nf_ct_sip: failed to register helper "
592 "for pf: %u port: %u\n",
593 sip[i][j].tuple.src.l3num, ports[i]);
594 nf_conntrack_sip_fini();
595 return ret;
596 }
597 }
598 }
599 return 0;
600}
601
602module_init(nf_conntrack_sip_init);
603module_exit(nf_conntrack_sip_fini);