nf_conntrack_sip.c 21.0 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/* SIP extension for IP connection tracking.
 *
 * (C) 2005 by Christian Hentschel <chentschel@arnet.com.ar>
 * based on RR's ip_conntrack_ftp.c and other modules.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

#include <linux/module.h>
#include <linux/ctype.h>
#include <linux/skbuff.h>
#include <linux/inet.h>
#include <linux/in.h>
#include <linux/udp.h>
17
#include <linux/netfilter.h>
18 19 20 21 22 23 24 25 26 27 28 29 30

#include <net/netfilter/nf_conntrack.h>
#include <net/netfilter/nf_conntrack_expect.h>
#include <net/netfilter/nf_conntrack_helper.h>
#include <linux/netfilter/nf_conntrack_sip.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Christian Hentschel <chentschel@arnet.com.ar>");
MODULE_DESCRIPTION("SIP connection tracking helper");
MODULE_ALIAS("ip_conntrack_sip");

#define MAX_PORTS	8
static unsigned short ports[MAX_PORTS];
31
static unsigned int ports_c;
32 33 34 35 36 37 38
module_param_array(ports, ushort, &ports_c, 0400);
MODULE_PARM_DESC(ports, "port numbers of SIP servers");

static unsigned int sip_timeout __read_mostly = SIP_TIMEOUT;
module_param(sip_timeout, uint, 0600);
MODULE_PARM_DESC(sip_timeout, "timeout for the master SIP session");

39
unsigned int (*nf_nat_sip_hook)(struct sk_buff *skb,
40 41
				const char **dptr,
				unsigned int *datalen) __read_mostly;
42 43
EXPORT_SYMBOL_GPL(nf_nat_sip_hook);

44
unsigned int (*nf_nat_sdp_hook)(struct sk_buff *skb,
45
				const char **dptr,
46 47
				unsigned int *datalen,
				struct nf_conntrack_expect *exp) __read_mostly;
48 49
EXPORT_SYMBOL_GPL(nf_nat_sdp_hook);

50 51 52 53 54 55 56 57 58 59 60 61
static int string_len(const struct nf_conn *ct, const char *dptr,
		      const char *limit, int *shift)
{
	int len = 0;

	while (dptr < limit && isalpha(*dptr)) {
		dptr++;
		len++;
	}
	return len;
}

62
static int digits_len(const struct nf_conn *ct, const char *dptr,
63 64 65
		      const char *limit, int *shift)
{
	int len = 0;
66
	while (dptr < limit && isdigit(*dptr)) {
67 68 69 70 71 72
		dptr++;
		len++;
	}
	return len;
}

73 74 75
static int parse_addr(const struct nf_conn *ct, const char *cp,
                      const char **endp, union nf_inet_addr *addr,
                      const char *limit)
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
{
	const char *end;
	int family = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.l3num;
	int ret = 0;

	switch (family) {
	case AF_INET:
		ret = in4_pton(cp, limit - cp, (u8 *)&addr->ip, -1, &end);
		break;
	case AF_INET6:
		ret = in6_pton(cp, limit - cp, (u8 *)&addr->ip6, -1, &end);
		break;
	default:
		BUG();
	}

	if (ret == 0 || end == cp)
		return 0;
	if (endp)
		*endp = end;
	return 1;
}

/* skip ip address. returns its length. */
100
static int epaddr_len(const struct nf_conn *ct, const char *dptr,
101 102
		      const char *limit, int *shift)
{
103
	union nf_inet_addr addr;
104 105 106
	const char *aux = dptr;

	if (!parse_addr(ct, dptr, &dptr, &addr, limit)) {
107
		pr_debug("ip: %s parse failed.!\n", dptr);
108 109 110 111 112 113 114 115 116 117 118 119
		return 0;
	}

	/* Port number */
	if (*dptr == ':') {
		dptr++;
		dptr += digits_len(ct, dptr, limit, shift);
	}
	return dptr - aux;
}

/* get address length, skiping user info. */
120
static int skp_epaddr_len(const struct nf_conn *ct, const char *dptr,
121 122
			  const char *limit, int *shift)
{
123
	const char *start = dptr;
124 125
	int s = *shift;

126 127 128
	/* Search for @, but stop at the end of the line.
	 * We are inside a sip: URI, so we don't need to worry about
	 * continuation lines. */
129
	while (dptr < limit &&
130
	       *dptr != '@' && *dptr != '\r' && *dptr != '\n') {
131
		(*shift)++;
132 133
		dptr++;
	}
134

135
	if (dptr < limit && *dptr == '@') {
136 137
		dptr++;
		(*shift)++;
138 139
	} else {
		dptr = start;
140
		*shift = s;
141
	}
142 143 144 145

	return epaddr_len(ct, dptr, limit, shift);
}

146 147 148 149 150 151 152 153
/* Parse a SIP request line of the form:
 *
 * Request-Line = Method SP Request-URI SP SIP-Version CRLF
 *
 * and return the offset and length of the address contained in the Request-URI.
 */
int ct_sip_parse_request(const struct nf_conn *ct,
			 const char *dptr, unsigned int datalen,
154 155
			 unsigned int *matchoff, unsigned int *matchlen,
			 union nf_inet_addr *addr, __be16 *port)
156
{
157
	const char *start = dptr, *limit = dptr + datalen, *end;
158
	unsigned int mlen;
159
	unsigned int p;
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
	int shift = 0;

	/* Skip method and following whitespace */
	mlen = string_len(ct, dptr, limit, NULL);
	if (!mlen)
		return 0;
	dptr += mlen;
	if (++dptr >= limit)
		return 0;

	/* Find SIP URI */
	limit -= strlen("sip:");
	for (; dptr < limit; dptr++) {
		if (*dptr == '\r' || *dptr == '\n')
			return -1;
		if (strnicmp(dptr, "sip:", strlen("sip:")) == 0)
			break;
	}
178
	if (!skp_epaddr_len(ct, dptr, limit, &shift))
179
		return 0;
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196
	dptr += shift;

	if (!parse_addr(ct, dptr, &end, addr, limit))
		return -1;
	if (end < limit && *end == ':') {
		end++;
		p = simple_strtoul(end, (char **)&end, 10);
		if (p < 1024 || p > 65535)
			return -1;
		*port = htons(p);
	} else
		*port = htons(SIP_PORT);

	if (end == dptr)
		return 0;
	*matchoff = dptr - start;
	*matchlen = end - dptr;
197 198 199 200
	return 1;
}
EXPORT_SYMBOL_GPL(ct_sip_parse_request);

201 202 203 204 205 206 207 208 209
/* SIP header parsing: SIP headers are located at the beginning of a line, but
 * may span several lines, in which case the continuation lines begin with a
 * whitespace character. RFC 2543 allows lines to be terminated with CR, LF or
 * CRLF, RFC 3261 allows only CRLF, we support both.
 *
 * Headers are followed by (optionally) whitespace, a colon, again (optionally)
 * whitespace and the values. Whitespace in this context means any amount of
 * tabs, spaces and continuation lines, which are treated as a single whitespace
 * character.
210 211 212
 *
 * Some headers may appear multiple times. A comma seperated list of values is
 * equivalent to multiple headers.
213 214
 */
static const struct sip_header ct_sip_hdrs[] = {
215
	[SIP_HDR_CSEQ]			= SIP_HDR("CSeq", NULL, NULL, digits_len),
216 217 218 219 220 221 222 223
	[SIP_HDR_FROM]			= SIP_HDR("From", "f", "sip:", skp_epaddr_len),
	[SIP_HDR_TO]			= SIP_HDR("To", "t", "sip:", skp_epaddr_len),
	[SIP_HDR_CONTACT]		= SIP_HDR("Contact", "m", "sip:", skp_epaddr_len),
	[SIP_HDR_VIA]			= SIP_HDR("Via", "v", "UDP ", epaddr_len),
	[SIP_HDR_CONTENT_LENGTH]	= SIP_HDR("Content-Length", "l", NULL, digits_len),
};

static const char *sip_follow_continuation(const char *dptr, const char *limit)
224
{
225 226 227 228 229 230 231 232 233
	/* Walk past newline */
	if (++dptr >= limit)
		return NULL;

	/* Skip '\n' in CR LF */
	if (*(dptr - 1) == '\r' && *dptr == '\n') {
		if (++dptr >= limit)
			return NULL;
	}
234

235 236 237
	/* Continuation line? */
	if (*dptr != ' ' && *dptr != '\t')
		return NULL;
238

239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269
	/* skip leading whitespace */
	for (; dptr < limit; dptr++) {
		if (*dptr != ' ' && *dptr != '\t')
			break;
	}
	return dptr;
}

static const char *sip_skip_whitespace(const char *dptr, const char *limit)
{
	for (; dptr < limit; dptr++) {
		if (*dptr == ' ')
			continue;
		if (*dptr != '\r' && *dptr != '\n')
			break;
		dptr = sip_follow_continuation(dptr, limit);
		if (dptr == NULL)
			return NULL;
	}
	return dptr;
}

/* Search within a SIP header value, dealing with continuation lines */
static const char *ct_sip_header_search(const char *dptr, const char *limit,
					const char *needle, unsigned int len)
{
	for (limit -= len; dptr < limit; dptr++) {
		if (*dptr == '\r' || *dptr == '\n') {
			dptr = sip_follow_continuation(dptr, limit);
			if (dptr == NULL)
				break;
270 271
			continue;
		}
272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296

		if (strnicmp(dptr, needle, len) == 0)
			return dptr;
	}
	return NULL;
}

int ct_sip_get_header(const struct nf_conn *ct, const char *dptr,
		      unsigned int dataoff, unsigned int datalen,
		      enum sip_header_types type,
		      unsigned int *matchoff, unsigned int *matchlen)
{
	const struct sip_header *hdr = &ct_sip_hdrs[type];
	const char *start = dptr, *limit = dptr + datalen;
	int shift = 0;

	for (dptr += dataoff; dptr < limit; dptr++) {
		/* Find beginning of line */
		if (*dptr != '\r' && *dptr != '\n')
			continue;
		if (++dptr >= limit)
			break;
		if (*(dptr - 1) == '\r' && *dptr == '\n') {
			if (++dptr >= limit)
				break;
297 298
		}

299 300 301
		/* Skip continuation lines */
		if (*dptr == ' ' || *dptr == '\t')
			continue;
302

303 304 305 306 307 308 309 310 311 312 313
		/* Find header. Compact headers must be followed by a
		 * non-alphabetic character to avoid mismatches. */
		if (limit - dptr >= hdr->len &&
		    strnicmp(dptr, hdr->name, hdr->len) == 0)
			dptr += hdr->len;
		else if (hdr->cname && limit - dptr >= hdr->clen + 1 &&
			 strnicmp(dptr, hdr->cname, hdr->clen) == 0 &&
			 !isalpha(*(dptr + hdr->clen + 1)))
			dptr += hdr->clen;
		else
			continue;
314

315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339
		/* Find and skip colon */
		dptr = sip_skip_whitespace(dptr, limit);
		if (dptr == NULL)
			break;
		if (*dptr != ':' || ++dptr >= limit)
			break;

		/* Skip whitespace after colon */
		dptr = sip_skip_whitespace(dptr, limit);
		if (dptr == NULL)
			break;

		*matchoff = dptr - start;
		if (hdr->search) {
			dptr = ct_sip_header_search(dptr, limit, hdr->search,
						    hdr->slen);
			if (!dptr)
				return -1;
			dptr += hdr->slen;
		}

		*matchlen = hdr->match_len(ct, dptr, limit, &shift);
		if (!*matchlen)
			return -1;
		*matchoff = dptr - start + shift;
340 341 342 343
		return 1;
	}
	return 0;
}
344
EXPORT_SYMBOL_GPL(ct_sip_get_header);
345

346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449
/* Get next header field in a list of comma seperated values */
static int ct_sip_next_header(const struct nf_conn *ct, const char *dptr,
			      unsigned int dataoff, unsigned int datalen,
			      enum sip_header_types type,
			      unsigned int *matchoff, unsigned int *matchlen)
{
	const struct sip_header *hdr = &ct_sip_hdrs[type];
	const char *start = dptr, *limit = dptr + datalen;
	int shift = 0;

	dptr += dataoff;

	dptr = ct_sip_header_search(dptr, limit, ",", strlen(","));
	if (!dptr)
		return 0;

	dptr = ct_sip_header_search(dptr, limit, hdr->search, hdr->slen);
	if (!dptr)
		return 0;
	dptr += hdr->slen;

	*matchoff = dptr - start;
	*matchlen = hdr->match_len(ct, dptr, limit, &shift);
	if (!*matchlen)
		return -1;
	*matchoff += shift;
	return 1;
}

/* Walk through headers until a parsable one is found or no header of the
 * given type is left. */
static int ct_sip_walk_headers(const struct nf_conn *ct, const char *dptr,
			       unsigned int dataoff, unsigned int datalen,
			       enum sip_header_types type, int *in_header,
			       unsigned int *matchoff, unsigned int *matchlen)
{
	int ret;

	if (in_header && *in_header) {
		while (1) {
			ret = ct_sip_next_header(ct, dptr, dataoff, datalen,
						 type, matchoff, matchlen);
			if (ret > 0)
				return ret;
			if (ret == 0)
				break;
			dataoff += *matchoff;
		}
		*in_header = 0;
	}

	while (1) {
		ret = ct_sip_get_header(ct, dptr, dataoff, datalen,
					type, matchoff, matchlen);
		if (ret > 0)
			break;
		if (ret == 0)
			return ret;
		dataoff += *matchoff;
	}

	if (in_header)
		*in_header = 1;
	return 1;
}

/* Locate a SIP header, parse the URI and return the offset and length of
 * the address as well as the address and port themselves. A stream of
 * headers can be parsed by handing in a non-NULL datalen and in_header
 * pointer.
 */
int ct_sip_parse_header_uri(const struct nf_conn *ct, const char *dptr,
			    unsigned int *dataoff, unsigned int datalen,
			    enum sip_header_types type, int *in_header,
			    unsigned int *matchoff, unsigned int *matchlen,
			    union nf_inet_addr *addr, __be16 *port)
{
	const char *c, *limit = dptr + datalen;
	unsigned int p;
	int ret;

	ret = ct_sip_walk_headers(ct, dptr, dataoff ? *dataoff : 0, datalen,
				  type, in_header, matchoff, matchlen);
	WARN_ON(ret < 0);
	if (ret == 0)
		return ret;

	if (!parse_addr(ct, dptr + *matchoff, &c, addr, limit))
		return -1;
	if (*c == ':') {
		c++;
		p = simple_strtoul(c, (char **)&c, 10);
		if (p < 1024 || p > 65535)
			return -1;
		*port = htons(p);
	} else
		*port = htons(SIP_PORT);

	if (dataoff)
		*dataoff = c - dptr;
	return 1;
}
EXPORT_SYMBOL_GPL(ct_sip_parse_header_uri);

450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535
/* SDP header parsing: a SDP session description contains an ordered set of
 * headers, starting with a section containing general session parameters,
 * optionally followed by multiple media descriptions.
 *
 * SDP headers always start at the beginning of a line. According to RFC 2327:
 * "The sequence CRLF (0x0d0a) is used to end a record, although parsers should
 * be tolerant and also accept records terminated with a single newline
 * character". We handle both cases.
 */
static const struct sip_header ct_sdp_hdrs[] = {
	[SDP_HDR_VERSION]		= SDP_HDR("v=", NULL, digits_len),
	[SDP_HDR_OWNER_IP4]		= SDP_HDR("o=", "IN IP4 ", epaddr_len),
	[SDP_HDR_CONNECTION_IP4]	= SDP_HDR("c=", "IN IP4 ", epaddr_len),
	[SDP_HDR_OWNER_IP6]		= SDP_HDR("o=", "IN IP6 ", epaddr_len),
	[SDP_HDR_CONNECTION_IP6]	= SDP_HDR("c=", "IN IP6 ", epaddr_len),
	[SDP_HDR_MEDIA]			= SDP_HDR("m=", "audio ", digits_len),
};

/* Linear string search within SDP header values */
static const char *ct_sdp_header_search(const char *dptr, const char *limit,
					const char *needle, unsigned int len)
{
	for (limit -= len; dptr < limit; dptr++) {
		if (*dptr == '\r' || *dptr == '\n')
			break;
		if (strncmp(dptr, needle, len) == 0)
			return dptr;
	}
	return NULL;
}

/* Locate a SDP header (optionally a substring within the header value),
 * optionally stopping at the first occurence of the term header, parse
 * it and return the offset and length of the data we're interested in.
 */
int ct_sip_get_sdp_header(const struct nf_conn *ct, const char *dptr,
			  unsigned int dataoff, unsigned int datalen,
			  enum sdp_header_types type,
			  enum sdp_header_types term,
			  unsigned int *matchoff, unsigned int *matchlen)
{
	const struct sip_header *hdr = &ct_sdp_hdrs[type];
	const struct sip_header *thdr = &ct_sdp_hdrs[term];
	const char *start = dptr, *limit = dptr + datalen;
	int shift = 0;

	for (dptr += dataoff; dptr < limit; dptr++) {
		/* Find beginning of line */
		if (*dptr != '\r' && *dptr != '\n')
			continue;
		if (++dptr >= limit)
			break;
		if (*(dptr - 1) == '\r' && *dptr == '\n') {
			if (++dptr >= limit)
				break;
		}

		if (term != SDP_HDR_UNSPEC &&
		    limit - dptr >= thdr->len &&
		    strnicmp(dptr, thdr->name, thdr->len) == 0)
			break;
		else if (limit - dptr >= hdr->len &&
			 strnicmp(dptr, hdr->name, hdr->len) == 0)
			dptr += hdr->len;
		else
			continue;

		*matchoff = dptr - start;
		if (hdr->search) {
			dptr = ct_sdp_header_search(dptr, limit, hdr->search,
						    hdr->slen);
			if (!dptr)
				return -1;
			dptr += hdr->slen;
		}

		*matchlen = hdr->match_len(ct, dptr, limit, &shift);
		if (!*matchlen)
			return -1;
		*matchoff = dptr - start + shift;
		return 1;
	}
	return 0;
}
EXPORT_SYMBOL_GPL(ct_sip_get_sdp_header);

536
static int set_expected_rtp(struct sk_buff *skb,
537 538
			    const char **dptr, unsigned int *datalen,
			    union nf_inet_addr *addr, __be16 port)
539 540
{
	struct nf_conntrack_expect *exp;
541 542
	enum ip_conntrack_info ctinfo;
	struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
543 544 545 546 547
	enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
	int family = ct->tuplehash[!dir].tuple.src.l3num;
	int ret;
	typeof(nf_nat_sdp_hook) nf_nat_sdp;

548
	exp = nf_ct_expect_alloc(ct);
549 550
	if (exp == NULL)
		return NF_DROP;
551
	nf_ct_expect_init(exp, NF_CT_EXPECT_CLASS_DEFAULT, family,
552 553
			  &ct->tuplehash[!dir].tuple.src.u3, addr,
			  IPPROTO_UDP, NULL, &port);
554 555 556

	nf_nat_sdp = rcu_dereference(nf_nat_sdp_hook);
	if (nf_nat_sdp && ct->status & IPS_NAT_MASK)
557
		ret = nf_nat_sdp(skb, dptr, datalen, exp);
558
	else {
559
		if (nf_ct_expect_related(exp) != 0)
560 561 562 563
			ret = NF_DROP;
		else
			ret = NF_ACCEPT;
	}
564
	nf_ct_expect_put(exp);
565 566 567 568

	return ret;
}

569
static int process_sdp(struct sk_buff *skb,
570 571
		       const char **dptr, unsigned int *datalen,
		       unsigned int cseq)
572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604
{
	enum ip_conntrack_info ctinfo;
	struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
	int family = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.l3num;
	unsigned int matchoff, matchlen;
	union nf_inet_addr addr;
	unsigned int port;
	enum sdp_header_types type;

	/* Get address and port from SDP packet. */
	type = family == AF_INET ? SDP_HDR_CONNECTION_IP4 :
				   SDP_HDR_CONNECTION_IP6;

	if (ct_sip_get_sdp_header(ct, *dptr, 0, *datalen,
				  type, SDP_HDR_UNSPEC,
				  &matchoff, &matchlen) <= 0)
		return NF_ACCEPT;

	/* We'll drop only if there are parse problems. */
	if (!parse_addr(ct, *dptr + matchoff, NULL, &addr, *dptr + *datalen))
		return NF_DROP;

	if (ct_sip_get_sdp_header(ct, *dptr, 0, *datalen,
				  SDP_HDR_MEDIA, SDP_HDR_UNSPEC,
				  &matchoff, &matchlen) <= 0)
		return NF_ACCEPT;

	port = simple_strtoul(*dptr + matchoff, NULL, 10);
	if (port < 1024 || port > 65535)
		return NF_DROP;

	return set_expected_rtp(skb, dptr, datalen, &addr, htons(port));
}
605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694
static int process_invite_response(struct sk_buff *skb,
				   const char **dptr, unsigned int *datalen,
				   unsigned int cseq, unsigned int code)
{
	if ((code >= 100 && code <= 199) ||
	    (code >= 200 && code <= 299))
		return process_sdp(skb, dptr, datalen, cseq);

	return NF_ACCEPT;
}

static int process_update_response(struct sk_buff *skb,
				   const char **dptr, unsigned int *datalen,
				   unsigned int cseq, unsigned int code)
{
	if ((code >= 100 && code <= 199) ||
	    (code >= 200 && code <= 299))
		return process_sdp(skb, dptr, datalen, cseq);

	return NF_ACCEPT;
}

static const struct sip_handler sip_handlers[] = {
	SIP_HANDLER("INVITE", process_sdp, process_invite_response),
	SIP_HANDLER("UPDATE", process_sdp, process_update_response),
};

static int process_sip_response(struct sk_buff *skb,
				const char **dptr, unsigned int *datalen)
{
	static const struct sip_handler *handler;
	enum ip_conntrack_info ctinfo;
	struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
	unsigned int matchoff, matchlen;
	unsigned int code, cseq, dataoff, i;

	if (*datalen < strlen("SIP/2.0 200"))
		return NF_ACCEPT;
	code = simple_strtoul(*dptr + strlen("SIP/2.0 "), NULL, 10);
	if (!code)
		return NF_DROP;

	if (ct_sip_get_header(ct, *dptr, 0, *datalen, SIP_HDR_CSEQ,
			      &matchoff, &matchlen) <= 0)
		return NF_DROP;
	cseq = simple_strtoul(*dptr + matchoff, NULL, 10);
	if (!cseq)
		return NF_DROP;
	dataoff = matchoff + matchlen + 1;

	for (i = 0; i < ARRAY_SIZE(sip_handlers); i++) {
		handler = &sip_handlers[i];
		if (handler->response == NULL)
			continue;
		if (*datalen < dataoff + handler->len ||
		    strnicmp(*dptr + dataoff, handler->method, handler->len))
			continue;
		return handler->response(skb, dptr, datalen, cseq, code);
	}
	return NF_ACCEPT;
}

static int process_sip_request(struct sk_buff *skb,
			       const char **dptr, unsigned int *datalen)
{
	static const struct sip_handler *handler;
	enum ip_conntrack_info ctinfo;
	struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
	unsigned int matchoff, matchlen;
	unsigned int cseq, i;

	for (i = 0; i < ARRAY_SIZE(sip_handlers); i++) {
		handler = &sip_handlers[i];
		if (handler->request == NULL)
			continue;
		if (*datalen < handler->len ||
		    strnicmp(*dptr, handler->method, handler->len))
			continue;

		if (ct_sip_get_header(ct, *dptr, 0, *datalen, SIP_HDR_CSEQ,
				      &matchoff, &matchlen) <= 0)
			return NF_DROP;
		cseq = simple_strtoul(*dptr + matchoff, NULL, 10);
		if (!cseq)
			return NF_DROP;

		return handler->request(skb, dptr, datalen, cseq);
	}
	return NF_ACCEPT;
}
695

696
static int sip_help(struct sk_buff *skb,
697 698 699 700 701 702
		    unsigned int protoff,
		    struct nf_conn *ct,
		    enum ip_conntrack_info ctinfo)
{
	unsigned int dataoff, datalen;
	const char *dptr;
703
	int ret;
704 705 706 707
	typeof(nf_nat_sip_hook) nf_nat_sip;

	/* No Data ? */
	dataoff = protoff + sizeof(struct udphdr);
708
	if (dataoff >= skb->len)
709 710
		return NF_ACCEPT;

711
	nf_ct_refresh(ct, skb, sip_timeout * HZ);
712

713 714
	if (!skb_is_nonlinear(skb))
		dptr = skb->data + dataoff;
715
	else {
716
		pr_debug("Copy of skbuff not supported yet.\n");
717
		return NF_ACCEPT;
718 719
	}

720
	datalen = skb->len - dataoff;
721
	if (datalen < strlen("SIP/2.0 200"))
722
		return NF_ACCEPT;
723

724
	if (strnicmp(dptr, "SIP/2.0 ", strlen("SIP/2.0 ")) != 0)
725
		ret = process_sip_request(skb, &dptr, &datalen);
726
	else
727 728 729 730 731 732 733 734 735
		ret = process_sip_response(skb, &dptr, &datalen);

	if (ret == NF_ACCEPT && ct->status & IPS_NAT_MASK) {
		nf_nat_sip = rcu_dereference(nf_nat_sip_hook);
		if (nf_nat_sip && !nf_nat_sip(skb, &dptr, &datalen))
			ret = NF_DROP;
	}

	return ret;
736 737 738 739 740
}

static struct nf_conntrack_helper sip[MAX_PORTS][2] __read_mostly;
static char sip_names[MAX_PORTS][2][sizeof("sip-65535")] __read_mostly;

741 742 743 744 745
static const struct nf_conntrack_expect_policy sip_exp_policy = {
	.max_expected	= 2,
	.timeout	= 3 * 60,
};

746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774
static void nf_conntrack_sip_fini(void)
{
	int i, j;

	for (i = 0; i < ports_c; i++) {
		for (j = 0; j < 2; j++) {
			if (sip[i][j].me == NULL)
				continue;
			nf_conntrack_helper_unregister(&sip[i][j]);
		}
	}
}

static int __init nf_conntrack_sip_init(void)
{
	int i, j, ret;
	char *tmpname;

	if (ports_c == 0)
		ports[ports_c++] = SIP_PORT;

	for (i = 0; i < ports_c; i++) {
		memset(&sip[i], 0, sizeof(sip[i]));

		sip[i][0].tuple.src.l3num = AF_INET;
		sip[i][1].tuple.src.l3num = AF_INET6;
		for (j = 0; j < 2; j++) {
			sip[i][j].tuple.dst.protonum = IPPROTO_UDP;
			sip[i][j].tuple.src.u.udp.port = htons(ports[i]);
775
			sip[i][j].expect_policy = &sip_exp_policy;
776 777 778 779 780 781 782 783 784 785
			sip[i][j].me = THIS_MODULE;
			sip[i][j].help = sip_help;

			tmpname = &sip_names[i][j][0];
			if (ports[i] == SIP_PORT)
				sprintf(tmpname, "sip");
			else
				sprintf(tmpname, "sip-%u", i);
			sip[i][j].name = tmpname;

786
			pr_debug("port #%u: %u\n", i, ports[i]);
787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802

			ret = nf_conntrack_helper_register(&sip[i][j]);
			if (ret) {
				printk("nf_ct_sip: failed to register helper "
				       "for pf: %u port: %u\n",
				       sip[i][j].tuple.src.l3num, ports[i]);
				nf_conntrack_sip_fini();
				return ret;
			}
		}
	}
	return 0;
}

module_init(nf_conntrack_sip_init);
module_exit(nf_conntrack_sip_fini);