]>
Commit | Line | Data |
---|---|---|
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 |
24 | MODULE_LICENSE("GPL"); |
25 | MODULE_AUTHOR("Christian Hentschel <chentschel@arnet.com.ar>"); | |
26 | MODULE_DESCRIPTION("SIP connection tracking helper"); | |
27 | MODULE_ALIAS("ip_conntrack_sip"); | |
28 | ||
29 | #define MAX_PORTS 8 | |
30 | static unsigned short ports[MAX_PORTS]; | |
2f0d2f10 | 31 | static unsigned int ports_c; |
9fafcd7b PM |
32 | module_param_array(ports, ushort, &ports_c, 0400); |
33 | MODULE_PARM_DESC(ports, "port numbers of SIP servers"); | |
34 | ||
35 | static unsigned int sip_timeout __read_mostly = SIP_TIMEOUT; | |
36 | module_param(sip_timeout, uint, 0600); | |
37 | MODULE_PARM_DESC(sip_timeout, "timeout for the master SIP session"); | |
38 | ||
3db05fea | 39 | unsigned int (*nf_nat_sip_hook)(struct sk_buff *skb, |
2a6cfb22 PM |
40 | const char **dptr, |
41 | unsigned int *datalen) __read_mostly; | |
9fafcd7b PM |
42 | EXPORT_SYMBOL_GPL(nf_nat_sip_hook); |
43 | ||
3db05fea | 44 | unsigned 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 |
48 | EXPORT_SYMBOL_GPL(nf_nat_sdp_hook); |
49 | ||
ac367740 PM |
50 | static int string_len(const struct nf_conn *ct, const char *dptr, |
51 | const char *limit, int *shift) | |
52 | { | |
53 | int len = 0; | |
54 | ||
55 | while (dptr < limit && isalpha(*dptr)) { | |
56 | dptr++; | |
57 | len++; | |
58 | } | |
59 | return len; | |
60 | } | |
61 | ||
13f7d63c | 62 | static int digits_len(const struct nf_conn *ct, const char *dptr, |
9fafcd7b PM |
63 | const char *limit, int *shift) |
64 | { | |
65 | int len = 0; | |
b1ec488b | 66 | while (dptr < limit && isdigit(*dptr)) { |
9fafcd7b PM |
67 | dptr++; |
68 | len++; | |
69 | } | |
70 | return len; | |
71 | } | |
72 | ||
13f7d63c JE |
73 | static int parse_addr(const struct nf_conn *ct, const char *cp, |
74 | const char **endp, union nf_inet_addr *addr, | |
75 | const char *limit) | |
9fafcd7b PM |
76 | { |
77 | const char *end; | |
78 | int family = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.l3num; | |
79 | int ret = 0; | |
80 | ||
81 | switch (family) { | |
82 | case AF_INET: | |
83 | ret = in4_pton(cp, limit - cp, (u8 *)&addr->ip, -1, &end); | |
84 | break; | |
85 | case AF_INET6: | |
86 | ret = in6_pton(cp, limit - cp, (u8 *)&addr->ip6, -1, &end); | |
87 | break; | |
88 | default: | |
89 | BUG(); | |
90 | } | |
91 | ||
92 | if (ret == 0 || end == cp) | |
93 | return 0; | |
94 | if (endp) | |
95 | *endp = end; | |
96 | return 1; | |
97 | } | |
98 | ||
99 | /* skip ip address. returns its length. */ | |
13f7d63c | 100 | static int epaddr_len(const struct nf_conn *ct, const char *dptr, |
9fafcd7b PM |
101 | const char *limit, int *shift) |
102 | { | |
643a2c15 | 103 | union nf_inet_addr addr; |
9fafcd7b PM |
104 | const char *aux = dptr; |
105 | ||
106 | if (!parse_addr(ct, dptr, &dptr, &addr, limit)) { | |
0d53778e | 107 | pr_debug("ip: %s parse failed.!\n", dptr); |
9fafcd7b PM |
108 | return 0; |
109 | } | |
110 | ||
111 | /* Port number */ | |
112 | if (*dptr == ':') { | |
113 | dptr++; | |
114 | dptr += digits_len(ct, dptr, limit, shift); | |
115 | } | |
116 | return dptr - aux; | |
117 | } | |
118 | ||
119 | /* get address length, skiping user info. */ | |
13f7d63c | 120 | static int skp_epaddr_len(const struct nf_conn *ct, const char *dptr, |
9fafcd7b PM |
121 | const char *limit, int *shift) |
122 | { | |
aa584eda | 123 | const char *start = dptr; |
9fafcd7b PM |
124 | int s = *shift; |
125 | ||
7da5bfbb LI |
126 | /* Search for @, but stop at the end of the line. |
127 | * We are inside a sip: URI, so we don't need to worry about | |
128 | * continuation lines. */ | |
b1ec488b | 129 | while (dptr < limit && |
7da5bfbb | 130 | *dptr != '@' && *dptr != '\r' && *dptr != '\n') { |
9fafcd7b | 131 | (*shift)++; |
7da5bfbb LI |
132 | dptr++; |
133 | } | |
9fafcd7b | 134 | |
b1ec488b | 135 | if (dptr < limit && *dptr == '@') { |
9fafcd7b PM |
136 | dptr++; |
137 | (*shift)++; | |
aa584eda PM |
138 | } else { |
139 | dptr = start; | |
9fafcd7b | 140 | *shift = s; |
aa584eda | 141 | } |
9fafcd7b PM |
142 | |
143 | return epaddr_len(ct, dptr, limit, shift); | |
144 | } | |
145 | ||
ac367740 PM |
146 | /* Parse a SIP request line of the form: |
147 | * | |
148 | * Request-Line = Method SP Request-URI SP SIP-Version CRLF | |
149 | * | |
150 | * and return the offset and length of the address contained in the Request-URI. | |
151 | */ | |
152 | int ct_sip_parse_request(const struct nf_conn *ct, | |
153 | const char *dptr, unsigned int datalen, | |
154 | unsigned int *matchoff, unsigned int *matchlen) | |
155 | { | |
156 | const char *start = dptr, *limit = dptr + datalen; | |
157 | unsigned int mlen; | |
158 | int shift = 0; | |
159 | ||
160 | /* Skip method and following whitespace */ | |
161 | mlen = string_len(ct, dptr, limit, NULL); | |
162 | if (!mlen) | |
163 | return 0; | |
164 | dptr += mlen; | |
165 | if (++dptr >= limit) | |
166 | return 0; | |
167 | ||
168 | /* Find SIP URI */ | |
169 | limit -= strlen("sip:"); | |
170 | for (; dptr < limit; dptr++) { | |
171 | if (*dptr == '\r' || *dptr == '\n') | |
172 | return -1; | |
173 | if (strnicmp(dptr, "sip:", strlen("sip:")) == 0) | |
174 | break; | |
175 | } | |
176 | *matchlen = skp_epaddr_len(ct, dptr, limit, &shift); | |
177 | if (!*matchlen) | |
178 | return 0; | |
179 | *matchoff = dptr - start + shift; | |
180 | return 1; | |
181 | } | |
182 | EXPORT_SYMBOL_GPL(ct_sip_parse_request); | |
183 | ||
ea45f12a PM |
184 | /* SIP header parsing: SIP headers are located at the beginning of a line, but |
185 | * may span several lines, in which case the continuation lines begin with a | |
186 | * whitespace character. RFC 2543 allows lines to be terminated with CR, LF or | |
187 | * CRLF, RFC 3261 allows only CRLF, we support both. | |
188 | * | |
189 | * Headers are followed by (optionally) whitespace, a colon, again (optionally) | |
190 | * whitespace and the values. Whitespace in this context means any amount of | |
191 | * tabs, spaces and continuation lines, which are treated as a single whitespace | |
192 | * character. | |
05e3ced2 PM |
193 | * |
194 | * Some headers may appear multiple times. A comma seperated list of values is | |
195 | * equivalent to multiple headers. | |
ea45f12a PM |
196 | */ |
197 | static const struct sip_header ct_sip_hdrs[] = { | |
198 | [SIP_HDR_FROM] = SIP_HDR("From", "f", "sip:", skp_epaddr_len), | |
199 | [SIP_HDR_TO] = SIP_HDR("To", "t", "sip:", skp_epaddr_len), | |
200 | [SIP_HDR_CONTACT] = SIP_HDR("Contact", "m", "sip:", skp_epaddr_len), | |
201 | [SIP_HDR_VIA] = SIP_HDR("Via", "v", "UDP ", epaddr_len), | |
202 | [SIP_HDR_CONTENT_LENGTH] = SIP_HDR("Content-Length", "l", NULL, digits_len), | |
203 | }; | |
204 | ||
205 | static const char *sip_follow_continuation(const char *dptr, const char *limit) | |
9fafcd7b | 206 | { |
ea45f12a PM |
207 | /* Walk past newline */ |
208 | if (++dptr >= limit) | |
209 | return NULL; | |
210 | ||
211 | /* Skip '\n' in CR LF */ | |
212 | if (*(dptr - 1) == '\r' && *dptr == '\n') { | |
213 | if (++dptr >= limit) | |
214 | return NULL; | |
215 | } | |
9fafcd7b | 216 | |
ea45f12a PM |
217 | /* Continuation line? */ |
218 | if (*dptr != ' ' && *dptr != '\t') | |
219 | return NULL; | |
9fafcd7b | 220 | |
ea45f12a PM |
221 | /* skip leading whitespace */ |
222 | for (; dptr < limit; dptr++) { | |
223 | if (*dptr != ' ' && *dptr != '\t') | |
224 | break; | |
225 | } | |
226 | return dptr; | |
227 | } | |
228 | ||
229 | static const char *sip_skip_whitespace(const char *dptr, const char *limit) | |
230 | { | |
231 | for (; dptr < limit; dptr++) { | |
232 | if (*dptr == ' ') | |
233 | continue; | |
234 | if (*dptr != '\r' && *dptr != '\n') | |
235 | break; | |
236 | dptr = sip_follow_continuation(dptr, limit); | |
237 | if (dptr == NULL) | |
238 | return NULL; | |
239 | } | |
240 | return dptr; | |
241 | } | |
242 | ||
243 | /* Search within a SIP header value, dealing with continuation lines */ | |
244 | static const char *ct_sip_header_search(const char *dptr, const char *limit, | |
245 | const char *needle, unsigned int len) | |
246 | { | |
247 | for (limit -= len; dptr < limit; dptr++) { | |
248 | if (*dptr == '\r' || *dptr == '\n') { | |
249 | dptr = sip_follow_continuation(dptr, limit); | |
250 | if (dptr == NULL) | |
251 | break; | |
9fafcd7b PM |
252 | continue; |
253 | } | |
ea45f12a PM |
254 | |
255 | if (strnicmp(dptr, needle, len) == 0) | |
256 | return dptr; | |
257 | } | |
258 | return NULL; | |
259 | } | |
260 | ||
261 | int ct_sip_get_header(const struct nf_conn *ct, const char *dptr, | |
262 | unsigned int dataoff, unsigned int datalen, | |
263 | enum sip_header_types type, | |
264 | unsigned int *matchoff, unsigned int *matchlen) | |
265 | { | |
266 | const struct sip_header *hdr = &ct_sip_hdrs[type]; | |
267 | const char *start = dptr, *limit = dptr + datalen; | |
268 | int shift = 0; | |
269 | ||
270 | for (dptr += dataoff; dptr < limit; dptr++) { | |
271 | /* Find beginning of line */ | |
272 | if (*dptr != '\r' && *dptr != '\n') | |
273 | continue; | |
274 | if (++dptr >= limit) | |
275 | break; | |
276 | if (*(dptr - 1) == '\r' && *dptr == '\n') { | |
277 | if (++dptr >= limit) | |
278 | break; | |
9fafcd7b | 279 | } |
9fafcd7b | 280 | |
ea45f12a PM |
281 | /* Skip continuation lines */ |
282 | if (*dptr == ' ' || *dptr == '\t') | |
283 | continue; | |
9fafcd7b | 284 | |
ea45f12a PM |
285 | /* Find header. Compact headers must be followed by a |
286 | * non-alphabetic character to avoid mismatches. */ | |
287 | if (limit - dptr >= hdr->len && | |
288 | strnicmp(dptr, hdr->name, hdr->len) == 0) | |
289 | dptr += hdr->len; | |
290 | else if (hdr->cname && limit - dptr >= hdr->clen + 1 && | |
291 | strnicmp(dptr, hdr->cname, hdr->clen) == 0 && | |
292 | !isalpha(*(dptr + hdr->clen + 1))) | |
293 | dptr += hdr->clen; | |
294 | else | |
295 | continue; | |
9fafcd7b | 296 | |
ea45f12a PM |
297 | /* Find and skip colon */ |
298 | dptr = sip_skip_whitespace(dptr, limit); | |
299 | if (dptr == NULL) | |
300 | break; | |
301 | if (*dptr != ':' || ++dptr >= limit) | |
302 | break; | |
303 | ||
304 | /* Skip whitespace after colon */ | |
305 | dptr = sip_skip_whitespace(dptr, limit); | |
306 | if (dptr == NULL) | |
307 | break; | |
308 | ||
309 | *matchoff = dptr - start; | |
310 | if (hdr->search) { | |
311 | dptr = ct_sip_header_search(dptr, limit, hdr->search, | |
312 | hdr->slen); | |
313 | if (!dptr) | |
314 | return -1; | |
315 | dptr += hdr->slen; | |
316 | } | |
317 | ||
318 | *matchlen = hdr->match_len(ct, dptr, limit, &shift); | |
319 | if (!*matchlen) | |
320 | return -1; | |
321 | *matchoff = dptr - start + shift; | |
9fafcd7b PM |
322 | return 1; |
323 | } | |
9fafcd7b PM |
324 | return 0; |
325 | } | |
ea45f12a | 326 | EXPORT_SYMBOL_GPL(ct_sip_get_header); |
9fafcd7b | 327 | |
05e3ced2 PM |
328 | /* Get next header field in a list of comma seperated values */ |
329 | static int ct_sip_next_header(const struct nf_conn *ct, const char *dptr, | |
330 | unsigned int dataoff, unsigned int datalen, | |
331 | enum sip_header_types type, | |
332 | unsigned int *matchoff, unsigned int *matchlen) | |
333 | { | |
334 | const struct sip_header *hdr = &ct_sip_hdrs[type]; | |
335 | const char *start = dptr, *limit = dptr + datalen; | |
336 | int shift = 0; | |
337 | ||
338 | dptr += dataoff; | |
339 | ||
340 | dptr = ct_sip_header_search(dptr, limit, ",", strlen(",")); | |
341 | if (!dptr) | |
342 | return 0; | |
343 | ||
344 | dptr = ct_sip_header_search(dptr, limit, hdr->search, hdr->slen); | |
345 | if (!dptr) | |
346 | return 0; | |
347 | dptr += hdr->slen; | |
348 | ||
349 | *matchoff = dptr - start; | |
350 | *matchlen = hdr->match_len(ct, dptr, limit, &shift); | |
351 | if (!*matchlen) | |
352 | return -1; | |
353 | *matchoff += shift; | |
354 | return 1; | |
355 | } | |
356 | ||
357 | /* Walk through headers until a parsable one is found or no header of the | |
358 | * given type is left. */ | |
359 | static int ct_sip_walk_headers(const struct nf_conn *ct, const char *dptr, | |
360 | unsigned int dataoff, unsigned int datalen, | |
361 | enum sip_header_types type, int *in_header, | |
362 | unsigned int *matchoff, unsigned int *matchlen) | |
363 | { | |
364 | int ret; | |
365 | ||
366 | if (in_header && *in_header) { | |
367 | while (1) { | |
368 | ret = ct_sip_next_header(ct, dptr, dataoff, datalen, | |
369 | type, matchoff, matchlen); | |
370 | if (ret > 0) | |
371 | return ret; | |
372 | if (ret == 0) | |
373 | break; | |
374 | dataoff += *matchoff; | |
375 | } | |
376 | *in_header = 0; | |
377 | } | |
378 | ||
379 | while (1) { | |
380 | ret = ct_sip_get_header(ct, dptr, dataoff, datalen, | |
381 | type, matchoff, matchlen); | |
382 | if (ret > 0) | |
383 | break; | |
384 | if (ret == 0) | |
385 | return ret; | |
386 | dataoff += *matchoff; | |
387 | } | |
388 | ||
389 | if (in_header) | |
390 | *in_header = 1; | |
391 | return 1; | |
392 | } | |
393 | ||
394 | /* Locate a SIP header, parse the URI and return the offset and length of | |
395 | * the address as well as the address and port themselves. A stream of | |
396 | * headers can be parsed by handing in a non-NULL datalen and in_header | |
397 | * pointer. | |
398 | */ | |
399 | int ct_sip_parse_header_uri(const struct nf_conn *ct, const char *dptr, | |
400 | unsigned int *dataoff, unsigned int datalen, | |
401 | enum sip_header_types type, int *in_header, | |
402 | unsigned int *matchoff, unsigned int *matchlen, | |
403 | union nf_inet_addr *addr, __be16 *port) | |
404 | { | |
405 | const char *c, *limit = dptr + datalen; | |
406 | unsigned int p; | |
407 | int ret; | |
408 | ||
409 | ret = ct_sip_walk_headers(ct, dptr, dataoff ? *dataoff : 0, datalen, | |
410 | type, in_header, matchoff, matchlen); | |
411 | WARN_ON(ret < 0); | |
412 | if (ret == 0) | |
413 | return ret; | |
414 | ||
415 | if (!parse_addr(ct, dptr + *matchoff, &c, addr, limit)) | |
416 | return -1; | |
417 | if (*c == ':') { | |
418 | c++; | |
419 | p = simple_strtoul(c, (char **)&c, 10); | |
420 | if (p < 1024 || p > 65535) | |
421 | return -1; | |
422 | *port = htons(p); | |
423 | } else | |
424 | *port = htons(SIP_PORT); | |
425 | ||
426 | if (dataoff) | |
427 | *dataoff = c - dptr; | |
428 | return 1; | |
429 | } | |
430 | EXPORT_SYMBOL_GPL(ct_sip_parse_header_uri); | |
431 | ||
3e9b4600 PM |
432 | /* SDP header parsing: a SDP session description contains an ordered set of |
433 | * headers, starting with a section containing general session parameters, | |
434 | * optionally followed by multiple media descriptions. | |
435 | * | |
436 | * SDP headers always start at the beginning of a line. According to RFC 2327: | |
437 | * "The sequence CRLF (0x0d0a) is used to end a record, although parsers should | |
438 | * be tolerant and also accept records terminated with a single newline | |
439 | * character". We handle both cases. | |
440 | */ | |
441 | static const struct sip_header ct_sdp_hdrs[] = { | |
442 | [SDP_HDR_VERSION] = SDP_HDR("v=", NULL, digits_len), | |
443 | [SDP_HDR_OWNER_IP4] = SDP_HDR("o=", "IN IP4 ", epaddr_len), | |
444 | [SDP_HDR_CONNECTION_IP4] = SDP_HDR("c=", "IN IP4 ", epaddr_len), | |
445 | [SDP_HDR_OWNER_IP6] = SDP_HDR("o=", "IN IP6 ", epaddr_len), | |
446 | [SDP_HDR_CONNECTION_IP6] = SDP_HDR("c=", "IN IP6 ", epaddr_len), | |
447 | [SDP_HDR_MEDIA] = SDP_HDR("m=", "audio ", digits_len), | |
448 | }; | |
449 | ||
450 | /* Linear string search within SDP header values */ | |
451 | static const char *ct_sdp_header_search(const char *dptr, const char *limit, | |
452 | const char *needle, unsigned int len) | |
453 | { | |
454 | for (limit -= len; dptr < limit; dptr++) { | |
455 | if (*dptr == '\r' || *dptr == '\n') | |
456 | break; | |
457 | if (strncmp(dptr, needle, len) == 0) | |
458 | return dptr; | |
459 | } | |
460 | return NULL; | |
461 | } | |
462 | ||
463 | /* Locate a SDP header (optionally a substring within the header value), | |
464 | * optionally stopping at the first occurence of the term header, parse | |
465 | * it and return the offset and length of the data we're interested in. | |
466 | */ | |
467 | int ct_sip_get_sdp_header(const struct nf_conn *ct, const char *dptr, | |
468 | unsigned int dataoff, unsigned int datalen, | |
469 | enum sdp_header_types type, | |
470 | enum sdp_header_types term, | |
471 | unsigned int *matchoff, unsigned int *matchlen) | |
472 | { | |
473 | const struct sip_header *hdr = &ct_sdp_hdrs[type]; | |
474 | const struct sip_header *thdr = &ct_sdp_hdrs[term]; | |
475 | const char *start = dptr, *limit = dptr + datalen; | |
476 | int shift = 0; | |
477 | ||
478 | for (dptr += dataoff; dptr < limit; dptr++) { | |
479 | /* Find beginning of line */ | |
480 | if (*dptr != '\r' && *dptr != '\n') | |
481 | continue; | |
482 | if (++dptr >= limit) | |
483 | break; | |
484 | if (*(dptr - 1) == '\r' && *dptr == '\n') { | |
485 | if (++dptr >= limit) | |
486 | break; | |
487 | } | |
488 | ||
489 | if (term != SDP_HDR_UNSPEC && | |
490 | limit - dptr >= thdr->len && | |
491 | strnicmp(dptr, thdr->name, thdr->len) == 0) | |
492 | break; | |
493 | else if (limit - dptr >= hdr->len && | |
494 | strnicmp(dptr, hdr->name, hdr->len) == 0) | |
495 | dptr += hdr->len; | |
496 | else | |
497 | continue; | |
498 | ||
499 | *matchoff = dptr - start; | |
500 | if (hdr->search) { | |
501 | dptr = ct_sdp_header_search(dptr, limit, hdr->search, | |
502 | hdr->slen); | |
503 | if (!dptr) | |
504 | return -1; | |
505 | dptr += hdr->slen; | |
506 | } | |
507 | ||
508 | *matchlen = hdr->match_len(ct, dptr, limit, &shift); | |
509 | if (!*matchlen) | |
510 | return -1; | |
511 | *matchoff = dptr - start + shift; | |
512 | return 1; | |
513 | } | |
514 | return 0; | |
515 | } | |
516 | EXPORT_SYMBOL_GPL(ct_sip_get_sdp_header); | |
517 | ||
3db05fea | 518 | static int set_expected_rtp(struct sk_buff *skb, |
212440a7 PM |
519 | const char **dptr, unsigned int *datalen, |
520 | union nf_inet_addr *addr, __be16 port) | |
9fafcd7b PM |
521 | { |
522 | struct nf_conntrack_expect *exp; | |
212440a7 PM |
523 | enum ip_conntrack_info ctinfo; |
524 | struct nf_conn *ct = nf_ct_get(skb, &ctinfo); | |
9fafcd7b PM |
525 | enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo); |
526 | int family = ct->tuplehash[!dir].tuple.src.l3num; | |
527 | int ret; | |
528 | typeof(nf_nat_sdp_hook) nf_nat_sdp; | |
529 | ||
6823645d | 530 | exp = nf_ct_expect_alloc(ct); |
9fafcd7b PM |
531 | if (exp == NULL) |
532 | return NF_DROP; | |
6002f266 | 533 | nf_ct_expect_init(exp, NF_CT_EXPECT_CLASS_DEFAULT, family, |
6823645d PM |
534 | &ct->tuplehash[!dir].tuple.src.u3, addr, |
535 | IPPROTO_UDP, NULL, &port); | |
9fafcd7b PM |
536 | |
537 | nf_nat_sdp = rcu_dereference(nf_nat_sdp_hook); | |
538 | if (nf_nat_sdp && ct->status & IPS_NAT_MASK) | |
212440a7 | 539 | ret = nf_nat_sdp(skb, dptr, datalen, exp); |
9fafcd7b | 540 | else { |
6823645d | 541 | if (nf_ct_expect_related(exp) != 0) |
9fafcd7b PM |
542 | ret = NF_DROP; |
543 | else | |
544 | ret = NF_ACCEPT; | |
545 | } | |
6823645d | 546 | nf_ct_expect_put(exp); |
9fafcd7b PM |
547 | |
548 | return ret; | |
549 | } | |
550 | ||
3db05fea | 551 | static int sip_help(struct sk_buff *skb, |
9fafcd7b PM |
552 | unsigned int protoff, |
553 | struct nf_conn *ct, | |
554 | enum ip_conntrack_info ctinfo) | |
555 | { | |
556 | int family = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.l3num; | |
643a2c15 | 557 | union nf_inet_addr addr; |
9fafcd7b PM |
558 | unsigned int dataoff, datalen; |
559 | const char *dptr; | |
560 | int ret = NF_ACCEPT; | |
2f0d2f10 | 561 | unsigned int matchoff, matchlen; |
9fafcd7b | 562 | u_int16_t port; |
3e9b4600 | 563 | enum sdp_header_types type; |
9fafcd7b PM |
564 | typeof(nf_nat_sip_hook) nf_nat_sip; |
565 | ||
566 | /* No Data ? */ | |
567 | dataoff = protoff + sizeof(struct udphdr); | |
3db05fea | 568 | if (dataoff >= skb->len) |
9fafcd7b PM |
569 | return NF_ACCEPT; |
570 | ||
3db05fea | 571 | nf_ct_refresh(ct, skb, sip_timeout * HZ); |
9fafcd7b | 572 | |
3db05fea HX |
573 | if (!skb_is_nonlinear(skb)) |
574 | dptr = skb->data + dataoff; | |
9fafcd7b | 575 | else { |
0d53778e | 576 | pr_debug("Copy of skbuff not supported yet.\n"); |
9fafcd7b PM |
577 | goto out; |
578 | } | |
579 | ||
580 | nf_nat_sip = rcu_dereference(nf_nat_sip_hook); | |
581 | if (nf_nat_sip && ct->status & IPS_NAT_MASK) { | |
212440a7 | 582 | if (!nf_nat_sip(skb, &dptr, &datalen)) { |
9fafcd7b PM |
583 | ret = NF_DROP; |
584 | goto out; | |
585 | } | |
586 | } | |
587 | ||
3db05fea | 588 | datalen = skb->len - dataoff; |
779382eb | 589 | if (datalen < strlen("SIP/2.0 200")) |
9fafcd7b PM |
590 | goto out; |
591 | ||
592 | /* RTP info only in some SDP pkts */ | |
779382eb PM |
593 | if (strnicmp(dptr, "INVITE", strlen("INVITE")) != 0 && |
594 | strnicmp(dptr, "UPDATE", strlen("UPDATE")) != 0 && | |
595 | strnicmp(dptr, "SIP/2.0 180", strlen("SIP/2.0 180")) != 0 && | |
596 | strnicmp(dptr, "SIP/2.0 183", strlen("SIP/2.0 183")) != 0 && | |
597 | strnicmp(dptr, "SIP/2.0 200", strlen("SIP/2.0 200")) != 0) { | |
9fafcd7b PM |
598 | goto out; |
599 | } | |
600 | /* Get address and port from SDP packet. */ | |
3e9b4600 PM |
601 | type = family == AF_INET ? SDP_HDR_CONNECTION_IP4 : |
602 | SDP_HDR_CONNECTION_IP6; | |
603 | if (ct_sip_get_sdp_header(ct, dptr, 0, datalen, type, SDP_HDR_UNSPEC, | |
604 | &matchoff, &matchlen) > 0) { | |
9fafcd7b PM |
605 | |
606 | /* We'll drop only if there are parse problems. */ | |
607 | if (!parse_addr(ct, dptr + matchoff, NULL, &addr, | |
601e68e1 | 608 | dptr + datalen)) { |
9fafcd7b PM |
609 | ret = NF_DROP; |
610 | goto out; | |
611 | } | |
3e9b4600 PM |
612 | if (ct_sip_get_sdp_header(ct, dptr, 0, datalen, |
613 | SDP_HDR_MEDIA, SDP_HDR_UNSPEC, | |
614 | &matchoff, &matchlen) > 0) { | |
9fafcd7b PM |
615 | |
616 | port = simple_strtoul(dptr + matchoff, NULL, 10); | |
617 | if (port < 1024) { | |
618 | ret = NF_DROP; | |
619 | goto out; | |
620 | } | |
212440a7 PM |
621 | ret = set_expected_rtp(skb, &dptr, &datalen, |
622 | &addr, htons(port)); | |
9fafcd7b PM |
623 | } |
624 | } | |
625 | out: | |
626 | return ret; | |
627 | } | |
628 | ||
629 | static struct nf_conntrack_helper sip[MAX_PORTS][2] __read_mostly; | |
630 | static char sip_names[MAX_PORTS][2][sizeof("sip-65535")] __read_mostly; | |
631 | ||
6002f266 PM |
632 | static const struct nf_conntrack_expect_policy sip_exp_policy = { |
633 | .max_expected = 2, | |
634 | .timeout = 3 * 60, | |
635 | }; | |
636 | ||
9fafcd7b PM |
637 | static void nf_conntrack_sip_fini(void) |
638 | { | |
639 | int i, j; | |
640 | ||
641 | for (i = 0; i < ports_c; i++) { | |
642 | for (j = 0; j < 2; j++) { | |
643 | if (sip[i][j].me == NULL) | |
644 | continue; | |
645 | nf_conntrack_helper_unregister(&sip[i][j]); | |
646 | } | |
647 | } | |
648 | } | |
649 | ||
650 | static int __init nf_conntrack_sip_init(void) | |
651 | { | |
652 | int i, j, ret; | |
653 | char *tmpname; | |
654 | ||
655 | if (ports_c == 0) | |
656 | ports[ports_c++] = SIP_PORT; | |
657 | ||
658 | for (i = 0; i < ports_c; i++) { | |
659 | memset(&sip[i], 0, sizeof(sip[i])); | |
660 | ||
661 | sip[i][0].tuple.src.l3num = AF_INET; | |
662 | sip[i][1].tuple.src.l3num = AF_INET6; | |
663 | for (j = 0; j < 2; j++) { | |
664 | sip[i][j].tuple.dst.protonum = IPPROTO_UDP; | |
665 | sip[i][j].tuple.src.u.udp.port = htons(ports[i]); | |
6002f266 | 666 | sip[i][j].expect_policy = &sip_exp_policy; |
9fafcd7b PM |
667 | sip[i][j].me = THIS_MODULE; |
668 | sip[i][j].help = sip_help; | |
669 | ||
670 | tmpname = &sip_names[i][j][0]; | |
671 | if (ports[i] == SIP_PORT) | |
672 | sprintf(tmpname, "sip"); | |
673 | else | |
674 | sprintf(tmpname, "sip-%u", i); | |
675 | sip[i][j].name = tmpname; | |
676 | ||
0d53778e | 677 | pr_debug("port #%u: %u\n", i, ports[i]); |
9fafcd7b PM |
678 | |
679 | ret = nf_conntrack_helper_register(&sip[i][j]); | |
680 | if (ret) { | |
681 | printk("nf_ct_sip: failed to register helper " | |
682 | "for pf: %u port: %u\n", | |
683 | sip[i][j].tuple.src.l3num, ports[i]); | |
684 | nf_conntrack_sip_fini(); | |
685 | return ret; | |
686 | } | |
687 | } | |
688 | } | |
689 | return 0; | |
690 | } | |
691 | ||
692 | module_init(nf_conntrack_sip_init); | |
693 | module_exit(nf_conntrack_sip_fini); |